@snail-js/api 0.1.28 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -4871
- package/dist/snail-api.umd.cjs +0 -4875
- 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":"use-download-FAjtdJ58.js","names":[],"sources":["../../src/adapter/plain.ts","../../src/adapter/registry.ts","../../src/strategies/shared/adapter.ts","../../src/strategies/shared/error.ts","../../src/strategies/shared/state.ts","../../src/strategies/shared/method.ts","../../src/strategies/use-request.ts","../../src/strategies/shared/timing.ts","../../src/strategies/shared/watcher.ts","../../src/strategies/use-watcher.ts","../../src/strategies/use-fetcher.ts","../../src/strategies/use-pagination.ts","../../src/strategies/shared/dom.ts","../../src/strategies/use-auto-request.ts","../../src/strategies/shared/retry.ts","../../src/strategies/use-retriable-request.ts","../../src/strategies/shared/queue.ts","../../src/strategies/use-uploader.ts","../../src/strategies/use-token-auth.ts","../../src/strategies/use-sse.ts","../../src/strategies/use-download.ts"],"sourcesContent":["import type { SnailStateAdapter, SnailStateRef } from \"../typings/adapter\";\n\n/**\n * Framework-free state adapter.\n *\n * A plain mutable box. Values update correctly, they just do not *trigger*\n * anything — which is exactly right for a test, an SSR pass or a script, and is\n * the safe default when no UI framework is present.\n *\n * A framework adapter registered with `setStateAdapter` replaces it.\n */\nexport const plainStateAdapter: SnailStateAdapter = {\n name: \"plain\",\n\n create<T>(initial: T): SnailStateRef<T> {\n return { value: initial };\n },\n\n read<T>(ref: SnailStateRef<T>): T {\n return ref.value;\n },\n\n write<T>(ref: SnailStateRef<T>, value: T): void {\n ref.value = value;\n }\n};\n","import type { SnailStateAdapter, SnailStateRef } from \"../typings/adapter\";\nimport { plainStateAdapter } from \"./plain\";\n\n/**\n * The active state adapter.\n *\n * Strategies never touch `ref()` or `useState()` directly — they ask this\n * registry. That is what lets one `useRequest` implementation serve Vue, React\n * and framework-free code without the core importing any of them.\n */\nlet active: SnailStateAdapter = plainStateAdapter;\n\n/** Replace the active adapter. Called once by a framework entry point. */\nexport function setStateAdapter(adapter: SnailStateAdapter): void {\n if (!adapter || typeof adapter.create !== \"function\") {\n throw new TypeError(\n \"[snail] setStateAdapter() expects an object with a create() method\"\n );\n }\n active = adapter;\n}\n\n/** The active adapter. */\nexport function getStateAdapter(): SnailStateAdapter {\n return active;\n}\n\n/** `true` when an adapter other than the framework-free default is installed. */\nexport function hasFrameworkAdapter(): boolean {\n return active !== plainStateAdapter;\n}\n\n/** Create a tracked value with the active adapter. */\nexport function createState<T>(initial: T): SnailStateRef<T> {\n return active.create(initial);\n}\n\n/** Read a tracked value. */\nexport function readState<T>(ref: SnailStateRef<T>): T {\n return active.read(ref);\n}\n\n/** Write a tracked value. */\nexport function writeState<T>(ref: SnailStateRef<T>, value: T): void {\n active.write(ref, value);\n}\n\n/** Release a tracked value, if the adapter allocated anything. */\nexport function disposeState<T>(ref: SnailStateRef<T>): void {\n active.dispose?.(ref);\n}\n","import { getStateAdapter } from \"../../adapter/registry\";\nimport type { SnailStateAdapter, SnailStateRef } from \"../../typings/adapter\";\n\n/**\n * Resolve the state adapter one strategy instance should use.\n *\n * The adapter is resolved **once per hook** rather than per read. A hook that\n * asked the registry on every write could observe two different adapters if an\n * application called `setStateAdapter()` between two requests, and would then mix\n * Vue refs with plain boxes in one set of handles.\n */\nexport function resolveStateAdapter(options: { adapter?: SnailStateAdapter } = {}): SnailStateAdapter {\n return options.adapter ?? getStateAdapter();\n}\n\n/**\n * Read a handle for the current render.\n *\n * `useBind` exists for frameworks that only re-render on an explicit\n * subscription (React's `useSyncExternalStore`); Vue tracks the `.value` access\n * itself, so its adapter omits the method and the read is the whole story. The\n * `??` fallback — rather than a truthiness check — keeps a legitimate `false`,\n * `0` or `\"\"` from being replaced by a second read.\n */\nexport function bindRef<T>(adapter: SnailStateAdapter, ref: SnailStateRef<T>): T {\n return adapter.useBind?.(ref) ?? adapter.read(ref);\n}\n","import { isCancel } from \"axios\";\nimport { SnailCancelledError } from \"../../error/request\";\nimport { SnailHttpError, SnailResponseError } from \"../../error/response\";\n\n/**\n * `true` when an error means \"this request was deliberately stopped\".\n *\n * Cancellation is **expected control flow**, not a failure: `method.abort()` and\n * a strategy discarding a stale response both produce it. Every strategy must ask\n * this question before writing `error` state or firing `onError`, otherwise a\n * user aborting a request would see a spurious error toast.\n *\n * The duck-typed `code` check is what catches an `AbortError` produced by a\n * caller-supplied `AbortSignal`, which axios does not always wrap in its own\n * cancel class.\n */\nexport function isCancellation(error: unknown): boolean {\n if (error instanceof SnailCancelledError) return true;\n if (isCancel(error)) return true;\n const code = (error as { code?: unknown } | null | undefined)?.code;\n return code === \"ERR_CANCELED\" || code === \"ABORT_ERR\";\n}\n\n/**\n * Best-effort business/HTTP code of a failure, for the `code` state handle.\n *\n * Reading it from the error rather than from a response keeps `code` meaningful\n * on the failure path too — a `401` should be visible to the UI even though no\n * envelope ever passed validation.\n */\nexport function readErrorCode(error: unknown): number | string | undefined {\n if (error instanceof SnailResponseError) return error.businessCode;\n if (error instanceof SnailHttpError) return error.status;\n\n const candidate = error as\n | { status?: unknown; response?: { status?: unknown } }\n | null\n | undefined;\n const value = candidate?.response?.status ?? candidate?.status;\n return typeof value === \"number\" || typeof value === \"string\" ? value : undefined;\n}\n\n/**\n * Best-effort human readable message of a failure, for the `message` handle.\n *\n * The backend envelope is preferred over the `Error.message` because an axios\n * transport message (\"Request failed with status code 401\") is useless to show a\n * user while `{ message: \"token expired\" }` is not.\n */\nexport function readErrorMessage(error: unknown): string | undefined {\n if (error instanceof SnailResponseError) {\n const payload = error.payload as { message?: unknown } | null | undefined;\n if (payload && typeof payload.message === \"string\") return payload.message;\n }\n\n const message = (error as { message?: unknown } | null | undefined)?.message;\n return typeof message === \"string\" ? message : undefined;\n}\n","import type { SnailStateAdapter, SnailStateRef } from \"../../typings/adapter\";\nimport { Emitter } from \"../../utils/emitter\";\nimport { bindRef, resolveStateAdapter } from \"./adapter\";\nimport { readErrorCode, readErrorMessage } from \"./error\";\n\n/** Partial write accepted by {@link StrategyState.update}. */\nexport interface StrategyStatePatch<TData> {\n data?: TData;\n loading?: boolean;\n error?: unknown;\n code?: number | string;\n message?: string;\n}\n\n/** The resolved snapshot {@link StrategyState.bind} returns. */\nexport interface StrategyBoundState<TData> {\n loading: boolean;\n data: TData | undefined;\n error: unknown;\n code: number | string | undefined;\n message: string | undefined;\n}\n\n/**\n * The state surface every request strategy exposes.\n *\n * ## Why handles and not values\n *\n * A strategy cannot know whether it is being read by a Vue render effect, a React\n * render or a plain script. Handing back a `SnailStateRef` keeps the strategy\n * framework-free: a Vue `Ref` already satisfies the interface, React goes through\n * `bind()`, and a script may read `.value` directly.\n *\n * ## Why `update()` exists\n *\n * An optimistic update has to write the cache entry into the state *before* the\n * server confirms it, and has to roll it back when the request fails. Without\n * `update()` every caller would reach into the refs and bypass the adapter.\n */\nexport interface StrategyState<TData> {\n /** `true` from the start of a send until it settles. */\n readonly loading: SnailStateRef<boolean>;\n\n /** Payload of the most recent successful send. */\n readonly data: SnailStateRef<TData | undefined>;\n\n /** Failure of the most recent send. Never set for a cancellation. */\n readonly error: SnailStateRef<unknown>;\n\n /** Business/HTTP code of the most recent send. */\n readonly code: SnailStateRef<number | string | undefined>;\n\n /** Business message of the most recent send. */\n readonly message: SnailStateRef<string | undefined>;\n\n /** Abort the in-flight request, if any. */\n abort(): void;\n\n /** Patch state directly — e.g. after an optimistic update. */\n update(patch: StrategyStatePatch<TData>): void;\n\n /** Resolved values, subscribing the current component when the adapter supports it. */\n bind(): StrategyBoundState<TData>;\n\n /** Called after a successful send. Returns an unsubscribe function. */\n onSuccess(callback: (data: TData) => void): () => void;\n\n /** Called after a failed send. Cancellations are not failures. */\n onError(callback: (error: unknown) => void): () => void;\n\n /** Called once a send settles, successfully or not. */\n onFinish(callback: () => void): () => void;\n}\n\n/**\n * Events a state controller emits.\n *\n * A type alias rather than an interface so it satisfies the `Record<string,\n * unknown>` constraint `Emitter` requires — interfaces do not get implicit index\n * signatures.\n */\nexport type StrategyStateEvents<TData> = {\n success: TData;\n error: unknown;\n finish: undefined;\n};\n\n/** Options accepted by {@link createStrategyState}. */\nexport interface StrategyStateOptions<TData> {\n adapter?: SnailStateAdapter;\n /** Value `data` starts at. Defaults to `undefined`. */\n initialData?: TData;\n /** Invoked by `state.abort()`. The hook owns what \"abort\" means. */\n onAbort?: () => void;\n}\n\n/**\n * The {@link StrategyState} plus the write side only the hook should use.\n *\n * Keeping the writers off the caller-visible object is what stops application code\n * from poking `data` mid-flight: the handle is readable, the transitions are not.\n */\nexport interface StrategyStateController<TData> {\n readonly adapter: SnailStateAdapter;\n readonly state: StrategyState<TData>;\n\n setLoading(value: boolean): void;\n setData(value: TData | undefined): void;\n setError(value: unknown): void;\n setCode(value: number | string | undefined): void;\n setMessage(value: string | undefined): void;\n\n /** Clear the failure fields at the start of a send. `data` is left alone. */\n resetForSend(): void;\n\n /** Write `data`/`code`/`message` from a successful result and clear `error`. */\n applySuccess(result: {\n data: TData;\n code?: number | string | undefined;\n message?: string | undefined;\n }): TData;\n\n /** Write `error` — plus the code/message the error carries. */\n applyFailure(error: unknown): void;\n\n emitSuccess(data: TData): void;\n emitError(error: unknown): void;\n emitFinish(): void;\n\n /** Release adapter resources and drop every listener. */\n dispose(): void;\n}\n\n/** `true` when `key` was explicitly provided, even with an `undefined` value. */\nfunction hasKey(source: object, key: string): boolean {\n return Object.prototype.hasOwnProperty.call(source, key);\n}\n\n/**\n * Build the state handles, the listeners and the writers for one hook.\n *\n * All five refs are created here, even for a hook that will not use them: a\n * `useFetcher({ withState: false })` still has to answer `abort()` and\n * `onFinish()`, and creating state lazily would hand the UI handles that appear\n * only after the first request.\n */\nexport function createStrategyState<TData>(\n options: StrategyStateOptions<TData> = {}\n): StrategyStateController<TData> {\n const adapter = resolveStateAdapter(options);\n const loading = adapter.create<boolean>(false);\n const data = adapter.create<TData | undefined>(options.initialData);\n const error = adapter.create<unknown>(undefined);\n const code = adapter.create<number | string | undefined>(undefined);\n const message = adapter.create<string | undefined>(undefined);\n const events = new Emitter<StrategyStateEvents<TData>>();\n\n const state: StrategyState<TData> = {\n loading,\n data,\n error,\n code,\n message,\n\n abort(): void {\n options.onAbort?.();\n },\n\n update(patch: StrategyStatePatch<TData>): void {\n // Presence, not truthiness: `update({ error: undefined })` is how a caller\n // clears a failure, and `update({ loading: false })` must not be skipped.\n if (hasKey(patch, \"data\")) adapter.write(data, patch.data);\n if (hasKey(patch, \"loading\")) adapter.write(loading, patch.loading as boolean);\n if (hasKey(patch, \"error\")) adapter.write(error, patch.error);\n if (hasKey(patch, \"code\")) adapter.write(code, patch.code);\n if (hasKey(patch, \"message\")) adapter.write(message, patch.message);\n },\n\n bind(): StrategyBoundState<TData> {\n return {\n loading: bindRef(adapter, loading),\n data: bindRef(adapter, data),\n error: bindRef(adapter, error),\n code: bindRef(adapter, code),\n message: bindRef(adapter, message)\n };\n },\n\n onSuccess(callback: (value: TData) => void): () => void {\n return events.on(\"success\", callback);\n },\n\n onError(callback: (value: unknown) => void): () => void {\n return events.on(\"error\", callback);\n },\n\n onFinish(callback: () => void): () => void {\n return events.on(\"finish\", callback);\n }\n };\n\n return {\n adapter,\n state,\n\n setLoading(value: boolean): void {\n adapter.write(loading, value);\n },\n\n setData(value: TData | undefined): void {\n adapter.write(data, value);\n },\n\n setError(value: unknown): void {\n adapter.write(error, value);\n },\n\n setCode(value: number | string | undefined): void {\n adapter.write(code, value);\n },\n\n setMessage(value: string | undefined): void {\n adapter.write(message, value);\n },\n\n resetForSend(): void {\n adapter.write(error, undefined);\n adapter.write(code, undefined);\n adapter.write(message, undefined);\n },\n\n applySuccess(result: {\n data: TData;\n code?: number | string | undefined;\n message?: string | undefined;\n }): TData {\n adapter.write(data, result.data);\n adapter.write(code, result.code);\n adapter.write(message, result.message);\n adapter.write(error, undefined);\n return result.data;\n },\n\n applyFailure(failure: unknown): void {\n adapter.write(error, failure);\n adapter.write(code, readErrorCode(failure));\n adapter.write(message, readErrorMessage(failure));\n },\n\n emitSuccess(value: TData): void {\n events.emit(\"success\", value);\n },\n\n emitError(failure: unknown): void {\n events.emit(\"error\", failure);\n },\n\n emitFinish(): void {\n events.emit(\"finish\", undefined);\n },\n\n dispose(): void {\n events.clear();\n adapter.dispose?.(loading);\n adapter.dispose?.(data);\n adapter.dispose?.(error);\n adapter.dispose?.(code);\n adapter.dispose?.(message);\n }\n };\n}\n","import type { AxiosProgressEvent } from \"axios\";\nimport type { SnailMethod } from \"../../core/method\";\n\n/**\n * A proxied api method — exactly what `Service.createApi(UserApi).getUser` is.\n *\n * Calling it **builds** a request (no network); `send()` performs it. A strategy\n * needs the callable rather than an already-built `SnailMethod` because it must\n * be able to re-send with different arguments, and because a strategy that\n * forwards the argument tuple keeps the api class's own signature intact.\n */\nexport type StrategyMethod<\n TArgs extends readonly unknown[] = readonly unknown[],\n TData = unknown\n> = (...args: TArgs) => SnailMethod<any, TData, any, any, any>;\n\n/** The `SnailMethod` a strategy drives, with its payload type narrowed. */\nexport type SnailRequest<TData = unknown> = SnailMethod<any, TData, any, any, any>;\n\n/**\n * Holds the single `SnailMethod` a hook reuses across sends.\n *\n * `docs/guide/plugin-lifecycle.md` §4.3 is explicit that `initMeta` runs once per method, not\n * once per send, so the reactive handles a UI captured stay identical across\n * re-sends. Calling `method(...args)` again would build a *second* context with a\n * second set of refs — the UI would keep rendering the first, frozen one. This\n * holder is what makes \"one instance, many sends\" the default instead of a rule\n * every hook author has to remember.\n */\nexport interface MethodHolder<TData = unknown> {\n /** The instance driving every send, or `undefined` before the first one. */\n readonly instance: SnailRequest<TData> | undefined;\n\n /** `true` while this holder's method has a request in flight. */\n readonly pending: boolean;\n\n /** Get-or-create the instance. Only the first call's arguments are captured. */\n resolve(args: readonly unknown[]): SnailRequest<TData>;\n\n /** Cancel the in-flight request, if any. A no-op before the first send. */\n abort(): void;\n}\n\n/** Create a {@link MethodHolder} around a proxied api method. */\nexport function createMethodHolder<\n TArgs extends readonly unknown[],\n TData\n>(method: StrategyMethod<TArgs, TData>): MethodHolder<TData> {\n let instance: SnailRequest<TData> | undefined;\n\n return {\n get instance(): SnailRequest<TData> | undefined {\n return instance;\n },\n get pending(): boolean {\n // `SnailMethod.pending` is the authoritative flag: it is set inside\n // `send()` and cleared in its `finally`, so it also covers a rejected send.\n return instance?.pending ?? false;\n },\n resolve(args: readonly unknown[]): SnailRequest<TData> {\n instance ??= method(...(args as unknown as TArgs));\n return instance;\n },\n abort(): void {\n instance?.abort();\n }\n };\n}\n\n/**\n * Attach a per-request upload progress callback.\n *\n * The core resolves `onUploadProgress` from `@UploadProgress(...)` metadata when\n * the `SnailMethod` is built, which is once per method — not once per file. To\n * vary it per request this writes onto the **live** axios config instead.\n *\n * The timing is load-bearing: `send()` calls `begin()` synchronously before its\n * first `await`, so the config read here is the one axios will send. Calling this\n * before `send()` would be silently overwritten by `begin()`, and calling it\n * after an `await` would be too late.\n *\n * @returns `false` when there was no live config to attach to.\n */\nexport function attachUploadProgress<TData>(\n snail: SnailRequest<TData>,\n listener: (event: AxiosProgressEvent) => void\n): boolean {\n const config = snail.context?.request;\n if (!config) return false;\n\n // Chain rather than replace: a `@UploadProgress()` decorator on the method must\n // keep receiving events even though the strategy owns the callback now.\n const previous = config.onUploadProgress;\n config.onUploadProgress = (event: AxiosProgressEvent): void => {\n if (typeof previous === \"function\") previous(event);\n listener(event);\n };\n return true;\n}\n","import type { SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport { noop } from \"../utils/object\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder } from \"./shared/method\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport { createStrategyState } from \"./shared/state\";\nimport type { StrategyState } from \"./shared/state\";\n\n/** Options accepted by {@link useRequest}. */\nexport interface UseRequestOptions<TData> extends SnailStrategyCommonOptions {\n /** Value `data` starts at, before the first successful send. */\n initialData?: TData;\n\n /**\n * Reset `data` back to `initialData` before every send.\n *\n * Off by default because the previous payload is usually still worth rendering\n * while the next one is in flight (no flicker on refresh). Turn it on for a\n * detail pane that must not show the *previous* record's data while loading the\n * next one.\n */\n resetOnSend?: boolean;\n}\n\n/**\n * What {@link useRequest} returns.\n *\n * `send` resolves with the **unwrapped payload** (`result.data`) rather than the\n * whole `SnailResult`: the envelope's `code`/`message` are already on the state\n * handles, and every caller that wants more can still reach the method's own\n * `result`.\n */\nexport interface UseRequestResult<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> extends StrategyState<TData> {\n send(...args: TArgs): Promise<TData>;\n}\n\n/**\n * Drive one api method from component state.\n *\n * ```ts\n * const user = useRequest(userApi.getUser);\n * await user.send(\"1\"); // → payload\n * user.data.value; // → the same payload\n * ```\n *\n * The hook owns exactly one `SnailMethod`, built from the arguments of the first\n * send and reused afterwards. That is what keeps the reactive handles stable\n * across re-sends — see `shared/method.ts` for why a second instance would break\n * the UI — while `send(\"2\")` still overrides the arguments per call.\n *\n * One instance also means one request at a time: `SnailMethod` resets its context\n * at the start of every `send()`, so a second `send()` issued while the first is\n * still in flight would leave the first reading a context that belongs to the\n * second. Call `abort()` first, or use `useWatcher`/`useAutoRequest`, which\n * collapse bursts for exactly this reason.\n */\nexport function useRequest<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseRequestOptions<TData> = {}\n): UseRequestResult<TData, TArgs> {\n const holder = createMethodHolder<TArgs, TData>(method);\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n initialData: options.initialData,\n onAbort: () => holder.abort()\n });\n\n const { state } = controller;\n\n // The common callbacks are registered as plain listeners rather than called\n // inline, so a caller can also subscribe later with `onSuccess`/`onError` and\n // both paths behave identically.\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n async function send(...args: TArgs): Promise<TData> {\n const snail = holder.resolve(args);\n\n // Cleared before the request: a stale error left in place during the next\n // send would make the UI show \"failed\" while a fresh attempt is in flight.\n controller.resetForSend();\n if (options.resetOnSend) controller.setData(options.initialData);\n controller.setLoading(true);\n\n try {\n const result = await snail.send(...args);\n const payload = controller.applySuccess(result);\n controller.emitSuccess(payload);\n return payload;\n } catch (error) {\n // Cancellation is expected control flow: reporting it would mean every\n // `abort()` raised an error toast. It still settles, so `onFinish` runs.\n if (isCancellation(error)) throw error;\n controller.applyFailure(error);\n controller.emitError(error);\n throw error;\n } finally {\n controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n const result: UseRequestResult<TData, TArgs> = { ...state, send };\n\n if (options.immediate) {\n // The rejection is deliberately not propagated: no caller holds this promise,\n // so a failed immediate send would surface as an unhandled rejection instead\n // of as `state.error` (plus whatever `onError` the caller registered).\n void send(...([] as unknown as TArgs)).catch(noop);\n }\n\n return result;\n}\n","import { SnailCancelledError } from \"../../error/request\";\n\n/**\n * Sleep for `ms`, rejecting with a {@link SnailCancelledError} the moment the\n * signal aborts.\n *\n * A plain `setTimeout` promise is the wrong primitive for a backoff: `abort()`\n * during a 30 second retry delay would leave the loop asleep and the caller's\n * promise pending long after it cancelled. The listener is `{ once: true }` and\n * removed on either outcome so a long-lived signal cannot accumulate handlers.\n */\nexport function cancellableDelay(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new SnailCancelledError(\"wait cancelled before it started\"));\n return;\n }\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n const onAbort = (): void => {\n if (timer !== undefined) clearTimeout(timer);\n signal?.removeEventListener(\"abort\", onAbort);\n reject(new SnailCancelledError(\"wait cancelled\"));\n };\n\n timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort);\n resolve();\n }, Math.max(0, ms));\n\n signal?.addEventListener(\"abort\", onAbort, { once: true });\n });\n}\n\n/**\n * Detach a Node timer from the event loop, when it has an `unref`.\n *\n * A polling strategy that is never stopped would otherwise keep a Node process\n * (or a test worker) alive forever. In a browser `setTimeout` returns a number,\n * which has no `unref` — hence the optional call rather than a feature test.\n */\nexport function unrefTimer(timer: unknown): void {\n (timer as { unref?: () => void } | undefined)?.unref?.();\n}\n\n/** Scheduler contract used by `useWatcher`. */\nexport interface RequestScheduler {\n /** Queue `task` for the next allowed run. The latest task always wins. */\n schedule(task: () => void): void;\n\n /** `true` while a run is still pending on a timer. */\n readonly pending: boolean;\n\n /** Drop a pending run without executing it. */\n cancel(): void;\n}\n\n/** Timing options accepted by {@link createRequestScheduler}. */\nexport interface SchedulerOptions {\n /** Wait for quiet before running. Wins over `throttle` when both are set. */\n debounce?: number;\n /** Run at most once per window, on the leading edge. */\n throttle?: number;\n}\n\n/**\n * Collapse a burst of `schedule()` calls into at most one request.\n *\n * Two modes, one rule — **the most recent task wins**, because a request must be\n * sent with the latest arguments, never with the ones captured when the burst\n * started:\n *\n * - `debounce` waits for `debounce` ms of quiet. Every new call pushes the run\n * back, so ten keystrokes produce one request, not ten.\n * - `throttle` runs on the leading edge and then books exactly one trailing run\n * per window. Without the trailing run the last change in a burst would be\n * silently dropped — a search box that ignores the final character.\n *\n * `debounce` is preferred when both are configured (see `useWatcher`): the two\n * express contradictory intents, and picking one deterministically beats a\n * half-debounced, half-throttled hybrid nobody can reason about.\n */\nexport function createRequestScheduler(options: SchedulerOptions = {}): RequestScheduler {\n const debounce = options.debounce && options.debounce > 0 ? options.debounce : 0;\n const throttle = options.throttle && options.throttle > 0 ? options.throttle : 0;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let latest: (() => void) | undefined;\n let lastRunAt = 0;\n // A flag rather than `lastRunAt === 0`: a fake clock can legitimately report\n // time zero, and then every call would look like the free first one.\n let ran = false;\n\n const clear = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n\n const runNow = (): void => {\n const task = latest;\n latest = undefined;\n clear();\n lastRunAt = Date.now();\n ran = true;\n task?.();\n };\n\n return {\n get pending(): boolean {\n return latest !== undefined;\n },\n\n schedule(task: () => void): void {\n latest = task;\n\n if (debounce > 0) {\n clear();\n timer = setTimeout(runNow, debounce);\n return;\n }\n\n if (throttle > 0) {\n // A trailing run is already booked; replacing `latest` is enough.\n if (timer !== undefined) return;\n\n const elapsed = Date.now() - lastRunAt;\n if (!ran || elapsed >= throttle) {\n runNow();\n return;\n }\n timer = setTimeout(runNow, throttle - elapsed);\n return;\n }\n\n runNow();\n },\n\n cancel(): void {\n clear();\n latest = undefined;\n }\n };\n}\n","import type { SnailStateAdapter, SnailStateRef } from \"../../typings/adapter\";\n\n/**\n * Read one entry of a `watching()` result.\n *\n * `docs/guide/plugin-lifecycle.md`'s adapter contract exposes no way to ask \"is this a state\n * handle?\" portably: the Vue adapter has `isState`, React's has none, and the\n * plain adapter's boxes are bare `{ value }` objects. So three cases are handled,\n * in order of confidence:\n *\n * 1. `adapter.isState(value)` says yes — unwrap it (Vue).\n * 2. the value is a plain object whose *only* own key is `value` — the exact shape\n * the plain and React adapters allocate — unwrap it.\n * 3. anything else is the value itself.\n *\n * Case 2 is a heuristic, and it is the reason `watching: () => [{ value: 1 }]`\n * should be written as `() => [{ value: 1 }.value]` if the object is genuine\n * data rather than a handle. Without it, `() => [pageRef]` — the natural thing to\n * write — would compare the ref object itself and never detect a change.\n */\nexport function unwrapWatchedValue(adapter: SnailStateAdapter, value: unknown): unknown {\n if (adapter.isState?.(value)) {\n return adapter.read(value as SnailStateRef<unknown>);\n }\n\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n const keys = Object.keys(value);\n if (keys.length === 1 && keys[0] === \"value\") {\n return (value as SnailStateRef<unknown>).value;\n }\n }\n\n return value;\n}\n\n/**\n * Evaluate a `watching()` function into the plain values that get compared.\n *\n * A watcher is user code, so a non-array return is normalised to a one-element\n * list rather than iterated blindly — spreading a string would compare its\n * characters, and spreading `undefined` would throw inside the hook.\n */\nexport function readWatchedValues(\n adapter: SnailStateAdapter,\n watching: () => readonly unknown[]\n): unknown[] {\n const produced = watching() as unknown;\n const list = Array.isArray(produced) ? produced : [produced];\n return list.map((value) => unwrapWatchedValue(adapter, value));\n}\n\n/**\n * Compare two watched snapshots with `Object.is`.\n *\n * `Object.is` rather than `===` so `NaN` does not look like a change on every\n * render — a watcher over a numeric field that happens to be `NaN` would\n * otherwise re-send forever.\n */\nexport function shallowEqual(a: readonly unknown[], b: readonly unknown[]): boolean {\n if (a === b) return true;\n if (a.length !== b.length) return false;\n for (let index = 0; index < a.length; index += 1) {\n if (!Object.is(a[index], b[index])) return false;\n }\n return true;\n}\n","import { SnailCancelledError } from \"../error/request\";\nimport type { SnailStateRef } from \"../typings/adapter\";\nimport { noop } from \"../utils/object\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder } from \"./shared/method\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport { createStrategyState } from \"./shared/state\";\nimport { createRequestScheduler } from \"./shared/timing\";\nimport { readWatchedValues, shallowEqual } from \"./shared/watcher\";\nimport type { UseRequestOptions, UseRequestResult } from \"./use-request\";\n\n/** Options accepted by {@link useWatcher}. */\nexport interface UseWatcherOptions<TData> extends UseRequestOptions<TData> {\n /**\n * Values that trigger a re-send when they change.\n *\n * Must be a function returning **plain values** — `() => [page.value]`. State\n * handles are unwrapped too when the adapter can identify them (see\n * `shared/watcher.ts`), but the handle form is a heuristic: only a `{ value }`\n * object is recognised, so a genuine data object with a lone `value` key is\n * indistinguishable from a handle.\n */\n watching: () => readonly unknown[];\n\n /**\n * Wait for this many milliseconds of quiet, then send once.\n *\n * Takes precedence over `throttle` when both are set: the two express opposite\n * intents (\"wait until it settles\" vs \"send immediately, then at most once per\n * window\"), and silently honouring one of them is better than a hybrid whose\n * behaviour depends on the call order.\n */\n debounce?: number;\n\n /** Send on the leading edge, then at most once per window. */\n throttle?: number;\n}\n\n/** What {@link useWatcher} returns. */\nexport interface UseWatcherResult<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> extends UseRequestResult<TData, TArgs> {\n /**\n * Whether `send()` collapses unchanged watched values.\n *\n * `true` (default) is the watching behaviour. Setting it to `false` turns every\n * `send()` into an unconditional request, which is the escape hatch for a manual\n * \"refresh\" button while the same hook keeps watching fields.\n */\n readonly watching: SnailStateRef<boolean>;\n}\n\n/**\n * Re-send a request when watched values move.\n *\n * ```ts\n * const keyword = createState(\"\");\n * const search = useWatcher(searchApi.find, {\n * watching: () => [keyword.value],\n * debounce: 200\n * });\n *\n * keyword.value = \"a\"; await search.send(); // sends\n * await search.send(); // same values → no request\n * keyword.value = \"ab\"; await search.send(); // sends again\n * ```\n *\n * ## Why `send()` is the evaluation trigger\n *\n * `SnailStateAdapter` exposes `create`/`read`/`write` and an optional\n * `subscribe`; it has no `watch` or `effect`, and the core deliberately keeps\n * framework reactivity out of the strategy layer. So there is nothing to fire at\n * the *moment* a value changes. The hook therefore evaluates `watching()` when\n * `send()` is called and treats an unchanged snapshot as a no-op, which keeps the\n * behaviour deterministic and framework-free: a Vue/React integration can call\n * `send()` from its own reactive effect, and a script calls it directly.\n *\n * `debounce`/`throttle` then collapse the *bursts* of `send()` calls a reactive\n * effect produces. Every collapsed caller's promise settles with the single\n * request's outcome, so no `await` is ever left hanging.\n */\nexport function useWatcher<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseWatcherOptions<TData>\n): UseWatcherResult<TData, TArgs> {\n const holder = createMethodHolder<TArgs, TData>(method);\n const scheduler = createRequestScheduler({\n debounce: options.debounce,\n throttle: options.throttle\n });\n\n let settleWaiters: Array<{\n resolve: (value: TData) => void;\n reject: (error: unknown) => void;\n }> = [];\n\n const rejectWaiters = (error: unknown): void => {\n const waiters = settleWaiters;\n settleWaiters = [];\n for (const waiter of waiters) waiter.reject(error);\n };\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n initialData: options.initialData,\n onAbort: () => {\n // Cancelling a *scheduled* run has no method to abort yet, so the queued\n // promises are rejected here instead — otherwise `abort()` would leave\n // every waiting `await send()` pending forever.\n scheduler.cancel();\n rejectWaiters(new SnailCancelledError(\"watched request cancelled before it was sent\"));\n holder.abort();\n }\n });\n\n const { state } = controller;\n const watching = controller.adapter.create<boolean>(true);\n\n let snapshot: unknown[] | undefined;\n let queuedArgs: TArgs | undefined;\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n async function perform(): Promise<void> {\n const waiters = settleWaiters;\n settleWaiters = [];\n if (waiters.length === 0) return;\n\n const args = (queuedArgs ?? []) as unknown as TArgs;\n const snail = holder.resolve(args);\n\n controller.resetForSend();\n if (options.resetOnSend) controller.setData(options.initialData);\n controller.setLoading(true);\n\n try {\n const result = await snail.send(...args);\n const payload = controller.applySuccess(result);\n controller.emitSuccess(payload);\n for (const waiter of waiters) waiter.resolve(payload);\n } catch (error) {\n if (!isCancellation(error)) {\n controller.applyFailure(error);\n controller.emitError(error);\n }\n for (const waiter of waiters) waiter.reject(error);\n } finally {\n controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n function send(...args: TArgs): Promise<TData> {\n let values: unknown[];\n\n try {\n values = readWatchedValues(controller.adapter, options.watching);\n } catch (error) {\n // A throwing watcher is user code: report it through `error` and reject this\n // one call instead of letting it escape the hook (which would break the\n // component that merely called `send()`).\n controller.setError(error);\n controller.emitError(error);\n return Promise.reject(error);\n }\n\n const changed = snapshot === undefined || !shallowEqual(values, snapshot);\n snapshot = values;\n queuedArgs = args;\n\n if (controller.adapter.read(watching) && !changed) {\n return Promise.resolve(controller.adapter.read(state.data) as TData);\n }\n\n const promise = new Promise<TData>((resolve, reject) => {\n settleWaiters.push({ resolve, reject });\n });\n scheduler.schedule(() => {\n void perform();\n });\n return promise;\n }\n\n const result: UseWatcherResult<TData, TArgs> = { ...state, watching, send };\n\n if (options.immediate) {\n void send(...([] as unknown as TArgs)).catch(noop);\n }\n\n return result;\n}\n","import type { SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport { noop } from \"../utils/object\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder } from \"./shared/method\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport { createStrategyState } from \"./shared/state\";\nimport type { StrategyState } from \"./shared/state\";\n\n/** Options accepted by {@link useFetcher}. */\nexport interface UseFetcherOptions<TData> extends SnailStrategyCommonOptions {\n /**\n * Mirror each request into `loading`/`data`/`error`/`code`/`message`.\n *\n * Off by default: a fetcher is usually a *background* request — a prefetch, an\n * SSR pass, a silent refresh — and writing to the caller's state would make a\n * spinner appear for work nobody asked to watch. Turn it on when the fetcher is\n * the only thing driving a visible view.\n */\n withState?: boolean;\n}\n\n/**\n * The always-present surface of {@link useFetcher}.\n *\n * Returned whether or not state is enabled, because `abort()` and the lifecycle\n * callbacks have nothing to do with rendering.\n */\nexport interface UseFetcherCore<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> {\n /** Run the request. Resolves with the unwrapped payload. */\n fetch(...args: TArgs): Promise<TData>;\n\n /** Abort the in-flight request, if any. */\n abort(): void;\n\n onSuccess(callback: (data: TData) => void): () => void;\n onError(callback: (error: unknown) => void): () => void;\n onFinish(callback: () => void): () => void;\n}\n\n/** {@link useFetcher} with `withState: true`. */\nexport interface UseFetcherResult<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> extends UseFetcherCore<TData, TArgs>,\n StrategyState<TData> {}\n\n/** Options accepted by {@link useFetcher} when state is requested. */\nexport type UseFetcherStateOptions<TData> = UseFetcherOptions<TData> & { withState: true };\n\n/**\n * Run an api method without a view.\n *\n * ```ts\n * const prefetch = useFetcher(userApi.getUser);\n * await prefetch.fetch(\"1\"); // warms the cache, touches no state\n * ```\n *\n * This is the hook for prefetching, SSR and background refresh: it deliberately\n * has no `loading`/`data`/`error` handles unless `withState` asks for them, so\n * calling it can never make an unrelated spinner appear.\n */\nexport function useFetcher<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseFetcherStateOptions<TData>\n): UseFetcherResult<TData, TArgs>;\nexport function useFetcher<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options?: UseFetcherOptions<TData>\n): UseFetcherCore<TData, TArgs>;\nexport function useFetcher<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseFetcherOptions<TData> = {}\n): UseFetcherCore<TData, TArgs> | UseFetcherResult<TData, TArgs> {\n const track = options.withState === true;\n const holder = createMethodHolder<TArgs, TData>(method);\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n onAbort: () => holder.abort()\n });\n\n const { state } = controller;\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n async function fetch(...args: TArgs): Promise<TData> {\n const snail = holder.resolve(args);\n\n if (track) {\n controller.resetForSend();\n controller.setLoading(true);\n }\n\n try {\n const result = await snail.send(...args);\n const payload = result.data;\n if (track) controller.applySuccess(result);\n controller.emitSuccess(payload);\n return payload;\n } catch (error) {\n // A silent fetcher is silent in every direction: no state write, and a\n // cancellation is not reported as a failure either.\n if (!isCancellation(error)) {\n if (track) controller.applyFailure(error);\n controller.emitError(error);\n }\n throw error;\n } finally {\n if (track) controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n const core: UseFetcherCore<TData, TArgs> = {\n fetch,\n abort: () => state.abort(),\n onSuccess: (callback) => state.onSuccess(callback),\n onError: (callback) => state.onError(callback),\n onFinish: (callback) => state.onFinish(callback)\n };\n\n if (options.immediate) {\n void fetch(...([] as unknown as TArgs)).catch(noop);\n }\n\n if (!track) return core;\n\n return { ...core, ...state };\n}\n","import type { SnailStateRef, SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder } from \"./shared/method\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport { createStrategyState } from \"./shared/state\";\nimport type { StrategyState } from \"./shared/state\";\n\n/**\n * The request descriptor a paginated method receives as its single argument.\n *\n * ```ts\n * @Get(\"/users\")\n * list(@Query() query: PageRequest): Promise<Page<User>> { return null!; }\n * ```\n *\n * The hook owns paging, so it — not the caller — supplies these two values. A\n * method that declares extra query fields (`PageRequest & { keyword: string }`)\n * still matches, because the hook's argument is assignable to the wider shape.\n */\nexport interface PageRequest {\n /** 1-based page number. */\n page: number;\n /** Items per page. */\n pageSize: number;\n}\n\n/** Options accepted by {@link usePagination}. */\nexport interface UsePaginationOptions<TData> extends SnailStrategyCommonOptions {\n /** Page the hook starts on. Defaults to `1`. */\n initialPage?: number;\n\n /** Items per page the hook starts with. Defaults to `10`. */\n initialPageSize?: number;\n\n /**\n * Read the total row count out of the payload.\n *\n * Defaults to `payload.total ?? payload.count ?? payload.length` — the three\n * shapes backends actually return. Without a trustworthy total the hook falls\n * back to \"a short page is the last page\", which cannot detect the final page\n * when it happens to be exactly full.\n */\n total?: (payload: TData) => number;\n\n /**\n * Read the page's rows out of the payload.\n *\n * Defaults to the payload itself when it is an array, otherwise\n * `payload.list ?? payload.items`.\n */\n list?: (payload: TData) => unknown[];\n\n /**\n * Append each page to `list` instead of replacing it.\n *\n * This is the infinite-scroll mode. It is off by default because the *replacing*\n * behaviour is what a table with page controls needs, and appending silently\n * grows the array forever.\n */\n append?: boolean;\n\n /**\n * Fetch the next page in the background and serve it instantly on `next()`.\n *\n * Costs one extra request per page, so it is off by default; useful for a\n * \"next\" button a user is likely to press. A preload never touches\n * `loading`/`data`: it is speculative work and must not flash a spinner.\n */\n preloadNext?: boolean;\n}\n\n/** What {@link usePagination} returns. */\nexport interface UsePaginationResult<TData> extends StrategyState<TData> {\n /** Current page, 1-based. */\n readonly page: SnailStateRef<number>;\n\n /** Items per page. */\n readonly pageSize: SnailStateRef<number>;\n\n /** Total row count, as read by the `total` extractor. */\n readonly total: SnailStateRef<number>;\n\n /** Rows, accumulated or replaced according to `append`. */\n readonly list: SnailStateRef<unknown[]>;\n\n /** `true` once the last page has been loaded. */\n readonly isLastPage: SnailStateRef<boolean>;\n\n /** Load the next page. A no-op at the last page. */\n next(): Promise<TData | undefined>;\n\n /** Load the previous page. A no-op at the first page. */\n prev(): Promise<TData | undefined>;\n\n /** Jump to a page, clamped into range. A no-op on the current page. */\n goTo(page: number): Promise<TData | undefined>;\n\n /** Go back to the first page and re-fetch it. */\n reload(): Promise<TData | undefined>;\n\n /** Change the page size, reset to page 1 and re-fetch. */\n changePageSize(pageSize: number): Promise<TData | undefined>;\n}\n\n/** Default row extractor — array payloads, then the two common wrapper keys. */\nfunction defaultList<TData>(payload: TData): unknown[] {\n if (Array.isArray(payload)) return payload;\n const candidate = payload as { list?: unknown; items?: unknown } | null | undefined;\n if (Array.isArray(candidate?.list)) return candidate.list;\n if (Array.isArray(candidate?.items)) return candidate.items;\n return [];\n}\n\n/** Default total extractor — `total`, then `count`, then the current page size. */\nfunction defaultTotal<TData>(payload: TData): number {\n if (Array.isArray(payload)) return payload.length;\n const candidate = payload as { total?: unknown; count?: unknown } | null | undefined;\n if (typeof candidate?.total === \"number\") return candidate.total;\n if (typeof candidate?.count === \"number\") return candidate.count;\n return defaultList(payload).length;\n}\n\n/**\n * Page through one api method.\n *\n * ```ts\n * const users = usePagination(userApi.list, {\n * total: (payload) => payload.total,\n * list: (payload) => payload.rows\n * });\n * await users.reload();\n * await users.next();\n * ```\n *\n * `next()`/`prev()` are **no-ops at the bounds and fire no request**: a user\n * holding down the \"next\" button at the last page must not hammer the server, and\n * the promise still resolves (with `undefined`) so an `await` never hangs.\n */\nexport function usePagination<TData>(\n method: StrategyMethod<[PageRequest], TData>,\n options: UsePaginationOptions<TData> = {}\n): UsePaginationResult<TData> {\n const initialPage = Number.isFinite(options.initialPage)\n ? Math.max(1, Math.floor(options.initialPage as number))\n : 1;\n const initialPageSize = Number.isFinite(options.initialPageSize)\n ? Math.max(1, Math.floor(options.initialPageSize as number))\n : 10;\n const append = options.append === true;\n const extractList = options.list ?? defaultList;\n const extractTotal = options.total ?? defaultTotal;\n\n /** The preloaded page, ready to be served without a request. */\n let buffered: { page: number; payload: TData } | undefined;\n let preloading = false;\n\n const holder = createMethodHolder<[PageRequest], TData>(method);\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n onAbort: () => holder.abort()\n });\n\n const { state } = controller;\n const adapter = controller.adapter;\n const page = adapter.create<number>(initialPage);\n const pageSize = adapter.create<number>(initialPageSize);\n const total = adapter.create<number>(0);\n const list = adapter.create<unknown[]>([]);\n const isLastPage = adapter.create<boolean>(false);\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n /** Last page reachable, or `Infinity` while the total is still unknown. */\n function lastPage(): number {\n const size = Math.max(1, adapter.read(pageSize));\n const known = adapter.read(total);\n if (known > 0) return Math.max(1, Math.ceil(known / size));\n return adapter.read(isLastPage) ? adapter.read(page) : Number.POSITIVE_INFINITY;\n }\n\n /** Write the paging derived from one payload. */\n function applyPage(payload: TData, target: number, size: number, accumulate: boolean): void {\n const rows = extractList(payload);\n const known = extractTotal(payload);\n\n adapter.write(page, target);\n adapter.write(total, known);\n adapter.write(list, accumulate ? [...adapter.read(list), ...rows] : rows);\n // A short page means the end even when the backend reports no total; a full\n // page with no total is \"maybe more\", never a false \"this is the last page\".\n adapter.write(\n isLastPage,\n known > 0 ? target * size >= known : rows.length < size\n );\n }\n\n async function request(\n target: number,\n size: number,\n accumulate: boolean\n ): Promise<TData | undefined> {\n const snail = holder.resolve([{ page: target, pageSize: size }]);\n\n controller.resetForSend();\n controller.setLoading(true);\n\n try {\n const result = await snail.send({ page: target, pageSize: size });\n const payload = controller.applySuccess(result);\n applyPage(payload, target, size, accumulate);\n controller.emitSuccess(payload);\n if (options.preloadNext) void startPreload(target + 1);\n return payload;\n } catch (error) {\n if (!isCancellation(error)) {\n controller.applyFailure(error);\n controller.emitError(error);\n }\n throw error;\n } finally {\n controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n /**\n * Warm the buffer with `target`, using a throwaway `SnailMethod`.\n *\n * A second instance is what keeps the preload invisible: it has its own context,\n * so its `loading`/`data` never reach the caller's handles. A failed preload is\n * swallowed on purpose — `next()` simply falls back to a real request.\n */\n async function startPreload(target: number): Promise<void> {\n if (preloading) return;\n if (target > lastPage()) return;\n\n preloading = true;\n try {\n const size = Math.max(1, adapter.read(pageSize));\n const result = await method({ page: target, pageSize: size }).send({\n page: target,\n pageSize: size\n });\n buffered = { page: target, payload: result.data };\n } catch {\n buffered = undefined;\n } finally {\n preloading = false;\n }\n }\n\n function clamp(target: number): number {\n const bound = lastPage();\n const value = Number.isFinite(target) ? Math.floor(target) : 1;\n return Math.min(Math.max(1, value), bound);\n }\n\n async function next(): Promise<TData | undefined> {\n const current = adapter.read(page);\n if (current >= lastPage()) return undefined;\n\n const target = current + 1;\n const size = Math.max(1, adapter.read(pageSize));\n\n // A buffered page is served without any request — that is the whole point of\n // `preloadNext`.\n if (buffered && buffered.page === target) {\n const ready = buffered;\n buffered = undefined;\n applyPage(ready.payload, target, size, append);\n if (options.preloadNext) void startPreload(target + 1);\n return ready.payload;\n }\n\n return request(target, size, append);\n }\n\n async function prev(): Promise<TData | undefined> {\n const current = adapter.read(page);\n if (current <= 1) return undefined;\n return request(current - 1, Math.max(1, adapter.read(pageSize)), append);\n }\n\n async function goTo(target: number): Promise<TData | undefined> {\n const resolved = clamp(target);\n if (resolved === adapter.read(page)) return undefined;\n // A jump replaces the list even in append mode: concatenating page 7 after\n // page 3 would interleave two unrelated ranges.\n return request(resolved, Math.max(1, adapter.read(pageSize)), false);\n }\n\n async function reload(): Promise<TData | undefined> {\n const size = Math.max(1, adapter.read(pageSize));\n adapter.write(list, []);\n adapter.write(isLastPage, false);\n buffered = undefined;\n return request(initialPage, size, append);\n }\n\n async function changePageSize(size: number): Promise<TData | undefined> {\n if (!Number.isFinite(size) || size < 1) return undefined;\n const resolved = Math.max(1, Math.floor(size));\n adapter.write(pageSize, resolved);\n adapter.write(list, []);\n adapter.write(isLastPage, false);\n buffered = undefined;\n return request(initialPage, resolved, false);\n }\n\n return { ...state, page, pageSize, total, list, isLastPage, next, prev, goTo, reload, changePageSize };\n}\n","/**\n * Guarded access to the browser globals a strategy listens to.\n *\n * The tests for this layer run in Node, where `window` and `document` do not\n * exist at all. Reading them lazily — inside a function, never at module scope —\n * is what lets the same module be imported in both environments, and it is the\n * only reason `useAutoRequest` can be created in a server-side render without\n * throwing.\n */\n\n/**\n * The subset of `EventTarget` this layer uses.\n *\n * `(...args: any[]) => void` rather than `() => void` on purpose: `window`'s own\n * `addEventListener` expects an `EventListener`, and a zero-argument listener type\n * would make the real object structurally incompatible.\n */\nexport interface DomEventTargetLike {\n addEventListener(type: string, listener: (...args: any[]) => void, options?: unknown): void;\n removeEventListener(type: string, listener: (...args: any[]) => void, options?: unknown): void;\n}\n\n/** The global `window`, or `undefined` outside a DOM environment. */\nexport function getWindowTarget(): DomEventTargetLike | undefined {\n if (typeof window === \"undefined\") return undefined;\n return window as unknown as DomEventTargetLike;\n}\n\n/** The global `document`, or `undefined` outside a DOM environment. */\nexport function getDocumentTarget(): DomEventTargetLike | undefined {\n if (typeof document === \"undefined\") return undefined;\n return document as unknown as DomEventTargetLike;\n}\n\n/**\n * `true` when the page is on screen.\n *\n * Node reports `true` so a `visibilitychange`-driven strategy still works in a\n * script; there is no hidden page to miss.\n */\nexport function isDocumentVisible(): boolean {\n if (typeof document === \"undefined\") return true;\n return document.visibilityState !== \"hidden\";\n}\n\n/** A collection of registered listeners that can be removed as one unit. */\nexport interface ListenerScope {\n /**\n * Register `listener`. Returns `false` when the target does not exist — the\n * Node case — so the caller can tell \"not attached\" from \"attached\".\n */\n add(\n target: DomEventTargetLike | undefined,\n type: string,\n listener: (...args: any[]) => void\n ): boolean;\n\n /** Remove every listener registered through this scope. Safe to call twice. */\n removeAll(): void;\n\n /** Number of listeners currently attached. */\n readonly size: number;\n}\n\n/**\n * Create a listener bookkeeping scope.\n *\n * `stop()` and `dispose()` are called from event handlers, from `finally` blocks\n * and sometimes twice; without one central registry it is far too easy to remove a\n * listener with a *different* function reference than the one added, which leaks\n * silently and keeps the component from being collected.\n */\nexport function createListenerScope(): ListenerScope {\n const registered: Array<{\n target: DomEventTargetLike;\n type: string;\n listener: (...args: any[]) => void;\n }> = [];\n\n return {\n get size(): number {\n return registered.length;\n },\n\n add(\n target: DomEventTargetLike | undefined,\n type: string,\n listener: (...args: any[]) => void\n ): boolean {\n if (!target) return false;\n target.addEventListener(type, listener);\n registered.push({ target, type, listener });\n return true;\n },\n\n removeAll(): void {\n while (registered.length > 0) {\n const entry = registered.pop()!;\n entry.target.removeEventListener(entry.type, entry.listener);\n }\n }\n };\n}\n","import type { SnailStateRef } from \"../typings/adapter\";\nimport { resolveStateAdapter } from \"./shared/adapter\";\nimport { createListenerScope, getDocumentTarget, getWindowTarget, isDocumentVisible } from \"./shared/dom\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport { unrefTimer } from \"./shared/timing\";\nimport { useRequest } from \"./use-request\";\nimport type { UseRequestOptions, UseRequestResult } from \"./use-request\";\n\n/** Options accepted by {@link useAutoRequest}. */\nexport interface UseAutoRequestOptions<TData> extends UseRequestOptions<TData> {\n /**\n * Poll every `pollingInterval` ms.\n *\n * The next tick is scheduled only **after** the previous request settles, so a\n * backend slower than the interval produces a queue of one, not an unbounded\n * pile of overlapping requests.\n */\n pollingInterval?: number;\n\n /** Refresh when the window regains focus. */\n enableFocusRefresh?: boolean;\n\n /** Refresh when the browser reports the network is back. */\n enableReconnectRefresh?: boolean;\n\n /** Refresh when the tab becomes visible again. */\n refreshOnVisible?: boolean;\n}\n\n/** What {@link useAutoRequest} returns. */\nexport interface UseAutoRequestResult<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> extends UseRequestResult<TData, TArgs> {\n /** `true` between `start()` and `stop()`. */\n readonly running: SnailStateRef<boolean>;\n\n /**\n * Arm polling and the refresh listeners.\n *\n * Also fires the first request immediately — waiting a whole interval before the\n * first call would leave the view empty for no reason. Because that first request\n * uses {@link refresh}, it carries no arguments unless a `send()` already\n * provided some.\n */\n start(): void;\n\n /** Disarm polling and remove every listener this hook registered. */\n stop(): void;\n\n /**\n * Run one request now, reusing the arguments of the previous send.\n *\n * Before any `send()`, that means no arguments at all — a method whose `@Params`\n * cannot be satisfied would reject, which is the same contract `immediate` has.\n */\n refresh(): Promise<TData>;\n\n /** `stop()` plus a permanent flag: a disposed hook ignores `start()`. */\n dispose(): void;\n}\n\n/**\n * Keep a request fresh by itself: polling plus the three \"the user is back\"\n * signals a dashboard actually needs.\n *\n * ```ts\n * const stats = useAutoRequest(statsApi.get, {\n * pollingInterval: 5000,\n * refreshOnVisible: true\n * });\n * stats.start();\n * stats.dispose(); // on unmount\n * ```\n *\n * ## Lifecycle\n *\n * `stop()` is the single release point: it halts the polling timer **and** removes\n * every listener, so an unmounted view cannot be kept alive by a focus handler.\n * `start()` re-arms both. The refresh listeners are attached at creation rather\n * than in `start()` — a hook whose only job is \"refresh when the user comes back\"\n * should not need an extra call — while polling itself only runs between `start()`\n * and `stop()`.\n *\n * ## Node safety\n *\n * `window`/`document` are looked up lazily and may be absent, so creating the hook\n * during SSR is harmless. Every listener goes through one listener scope, which is\n * what makes `stop()` remove exactly what was added; a listener removed with a\n * fresh closure reference would leak silently. The polling timer is `unref`'d on\n * Node so it never holds the process (or a test worker) open.\n */\nexport function useAutoRequest<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseAutoRequestOptions<TData> = {}\n): UseAutoRequestResult<TData, TArgs> {\n // Delegated rather than re-implemented: the state machine, the single-method\n // rule and the cancellation handling are identical to `useRequest`; only *when*\n // to send differs here.\n const request = useRequest<TArgs, TData>(method, { ...options, immediate: false });\n\n // Same adapter the state handles use — resolved from the same options object, so\n // `running` cannot end up tracking a different reactivity system than `loading`.\n const adapter = resolveStateAdapter(options);\n const running = adapter.create<boolean>(false);\n const scope = createListenerScope();\n\n const interval =\n Number.isFinite(options.pollingInterval) ? Math.max(0, options.pollingInterval as number) : 0;\n\n let timer: ReturnType<typeof setTimeout> | undefined;\n let disposed = false;\n\n function clearTimer(): void {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n }\n\n function refresh(): Promise<TData> {\n return request.send(...([] as unknown as TArgs));\n }\n\n /** Run one refresh without letting a rejection escape into a timer/listener. */\n async function refreshQuietly(): Promise<void> {\n try {\n await refresh();\n } catch {\n // The failure is already on `error` and in `onError`; rethrowing it here\n // would turn a poll into an unhandled rejection.\n }\n }\n\n function schedule(): void {\n if (disposed || !running.value || interval <= 0) return;\n timer = setTimeout(() => {\n timer = undefined;\n void loop();\n }, interval);\n unrefTimer(timer);\n }\n\n async function loop(): Promise<void> {\n await refreshQuietly();\n schedule();\n }\n\n function attach(): void {\n // Remove first: `start()` after `stop()` must not double-register, and a second\n // `start()` while already running is a no-op anyway.\n scope.removeAll();\n\n if (options.enableFocusRefresh) {\n scope.add(getWindowTarget(), \"focus\", () => {\n void refreshQuietly();\n });\n }\n if (options.enableReconnectRefresh) {\n scope.add(getWindowTarget(), \"online\", () => {\n void refreshQuietly();\n });\n }\n if (options.refreshOnVisible) {\n scope.add(getDocumentTarget(), \"visibilitychange\", () => {\n if (isDocumentVisible()) void refreshQuietly();\n });\n }\n }\n\n function start(): void {\n if (disposed || running.value) return;\n running.value = true;\n attach();\n void loop();\n }\n\n function stop(): void {\n running.value = false;\n clearTimer();\n scope.removeAll();\n }\n\n function dispose(): void {\n stop();\n disposed = true;\n }\n\n attach();\n\n if (options.immediate) start();\n\n return { ...request, running, start, stop, refresh, dispose };\n}\n","import { isCancellation } from \"./error\";\n\n/** Retry knobs shared by `useRetriableRequest` and the reconnect transports. */\nexport interface RetryOptions {\n /** Extra attempts after the first failure. Defaults to `3`. */\n retries?: number;\n /** First delay in milliseconds. Defaults to `1000`. */\n delayMs?: number;\n /** Upper bound for one delay. Defaults to `30000`. */\n maxDelayMs?: number;\n /** Multiplier applied after each failure. Defaults to `2`. */\n factor?: number;\n /** Randomise the delay to avoid a thundering herd. Defaults to `true`. */\n jitter?: boolean;\n}\n\n/** Fully defaulted retry configuration. */\nexport interface RetryPolicy {\n retries: number;\n delayMs: number;\n maxDelayMs: number;\n factor: number;\n jitter: boolean;\n}\n\n/** Decides whether one failed attempt deserves another. */\nexport type RetryPredicate = (error: unknown, attempt: number) => boolean;\n\n/** Defaults are deliberately conservative: three tries, exponential, jittered. */\nexport const DEFAULT_RETRY_POLICY: RetryPolicy = {\n retries: 3,\n delayMs: 1000,\n maxDelayMs: 30000,\n factor: 2,\n jitter: true\n};\n\n/**\n * Fill in the retry defaults.\n *\n * `retries` is floored and clamped to `>= 0`: a negative value would make the\n * attempt loop run zero times and resolve without ever sending, which is a\n * silent no-op rather than a validation error the caller can see.\n */\nexport function resolveRetryPolicy(options: RetryOptions = {}): RetryPolicy {\n const retries = Number.isFinite(options.retries) ? Math.max(0, Math.floor(options.retries as number)) : DEFAULT_RETRY_POLICY.retries;\n const delayMs = Number.isFinite(options.delayMs)\n ? Math.max(0, options.delayMs as number)\n : DEFAULT_RETRY_POLICY.delayMs;\n const maxDelayMs = Number.isFinite(options.maxDelayMs)\n ? Math.max(0, options.maxDelayMs as number)\n : DEFAULT_RETRY_POLICY.maxDelayMs;\n const factor = Number.isFinite(options.factor) ? Math.max(1, options.factor as number) : DEFAULT_RETRY_POLICY.factor;\n\n return {\n retries,\n delayMs,\n maxDelayMs,\n factor,\n jitter: options.jitter ?? DEFAULT_RETRY_POLICY.jitter\n };\n}\n\n/**\n * Delay before attempt `attempt` (1-based), with exponential growth and an\n * optional jitter.\n *\n * Jitter is *half* the computed delay plus a random share, not a fresh random\n * pick: full jitter can produce a near-zero delay that turns a retry storm into a\n * hammering loop, while half jitter still spreads a fleet of clients out.\n */\nexport function computeBackoffDelay(attempt: number, policy: RetryPolicy): number {\n const exponent = Math.max(0, attempt - 1);\n const base = Math.min(policy.delayMs * policy.factor ** exponent, policy.maxDelayMs);\n if (!policy.jitter) return base;\n return base / 2 + Math.random() * (base / 2);\n}\n\n/**\n * The default {@link RetryPredicate}: retry everything except a cancellation.\n *\n * Retrying a cancellation is the one case that is always wrong — the caller asked\n * for the request to stop, so a retry ignores an explicit instruction and keeps\n * the socket busy.\n */\nexport const defaultRetryPredicate: RetryPredicate = (error: unknown): boolean =>\n !isCancellation(error);\n","import { SnailCancelledError } from \"../error/request\";\nimport type { SnailStateRef } from \"../typings/adapter\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder } from \"./shared/method\";\nimport type { StrategyMethod } from \"./shared/method\";\nimport {\n computeBackoffDelay,\n defaultRetryPredicate,\n resolveRetryPolicy\n} from \"./shared/retry\";\nimport type { RetryOptions, RetryPredicate } from \"./shared/retry\";\nimport { createStrategyState } from \"./shared/state\";\nimport type { StrategyState } from \"./shared/state\";\nimport { cancellableDelay } from \"./shared/timing\";\nimport type { UseRequestOptions } from \"./use-request\";\n\n/** Options accepted by {@link useRetriableRequest}. */\nexport interface UseRetriableRequestOptions<TData>\n extends UseRequestOptions<TData>,\n RetryOptions {\n /**\n * Decide whether one failed attempt deserves another.\n *\n * Receives the attempted number of the *next* try (1-based), so a caller can\n * cap retries by reason as well as by count. The default retries everything\n * except a cancellation, which is never retried: the caller asked for the\n * request to stop, and a retry would ignore that instruction.\n */\n retryOn?: RetryPredicate;\n}\n\n/** What {@link useRetriableRequest} returns. */\nexport interface UseRetriableRequestResult<\n TData,\n TArgs extends readonly unknown[] = readonly unknown[]\n> extends StrategyState<TData> {\n send(...args: TArgs): Promise<TData>;\n\n /** Attempts made by the most recent `send()`. Starts at `0`. */\n readonly attempts: SnailStateRef<number>;\n}\n\n/**\n * A request that heals itself.\n *\n * ```ts\n * const save = useRetriableRequest(api.save, { retries: 3, delayMs: 200 });\n * await save.send(payload); // may hit the network up to 4 times\n * save.attempts.value; // how many tries it actually took\n * ```\n *\n * ## Cancellation is the hard part\n *\n * The hook keeps its own `AbortController` for the backoff *between* attempts,\n * because at that moment there is no in-flight request to cancel. `abort()` trips\n * both, so a `abort()` during a 30 second delay rejects immediately instead of\n * leaving the caller's promise pending until the timer fires. A cancellation is\n * never counted as a failed attempt and never written to `error`.\n *\n * `attempts` is exposed because it is genuinely useful for telemetry (\"this call\n * needed three tries\") and because it makes the retry loop testable without\n * counting requests.\n */\nexport function useRetriableRequest<TArgs extends readonly unknown[], TData>(\n method: StrategyMethod<TArgs, TData>,\n options: UseRetriableRequestOptions<TData> = {}\n): UseRetriableRequestResult<TData, TArgs> {\n const policy = resolveRetryPolicy(options);\n const retryAllowed = options.retryOn ?? defaultRetryPredicate;\n const holder = createMethodHolder<TArgs, TData>(method);\n\n let backoff: AbortController | undefined;\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n initialData: options.initialData,\n onAbort: () => {\n backoff?.abort();\n holder.abort();\n }\n });\n\n const { state } = controller;\n const attempts = controller.adapter.create<number>(0);\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n async function send(...args: TArgs): Promise<TData> {\n const snail = holder.resolve(args);\n const signal = new AbortController();\n backoff = signal;\n\n controller.resetForSend();\n if (options.resetOnSend) controller.setData(options.initialData);\n controller.setLoading(true);\n controller.adapter.write(attempts, 0);\n\n try {\n for (let attempt = 0; attempt <= policy.retries; attempt += 1) {\n controller.adapter.write(attempts, attempt + 1);\n\n try {\n const result = await snail.send(...args);\n // `applySuccess` clears the error, so a request that failed twice and\n // then succeeded does not leave a stale failure on screen.\n const payload = controller.applySuccess(result);\n controller.emitSuccess(payload);\n return payload;\n } catch (error) {\n // Never retry a cancellation — that would be ignoring `abort()`.\n if (isCancellation(error)) throw error;\n\n const retryable = attempt < policy.retries && retryAllowed(error, attempt + 1);\n if (!retryable) throw error;\n\n // Surface the transient failure while backing off: the UI should see\n // \"retrying\" rather than the previous attempt's stale success.\n controller.applyFailure(error);\n await cancellableDelay(computeBackoffDelay(attempt + 1, policy), signal.signal);\n }\n }\n\n // Unreachable: the loop either returns or throws. Present so the function\n // has a return path for the type checker.\n throw new SnailCancelledError(\"retry loop ended without a result\");\n } catch (error) {\n if (!isCancellation(error)) {\n controller.applyFailure(error);\n controller.emitError(error);\n }\n throw error;\n } finally {\n if (backoff === signal) backoff = undefined;\n controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n return { ...state, send, attempts };\n}\n","import { noop } from \"../../utils/object\";\n\n/**\n * A bounded-concurrency queue of asynchronous tasks.\n *\n * `useUploader` needs this for one reason: a user dropping 200 files must not open\n * 200 sockets. The queue also owns the \"is everything finished?\" question, so\n * `upload()` can hand back a promise that resolves when the batch drains instead\n * of making the caller poll the file list.\n */\nexport interface TaskQueue {\n /** Enqueue a task; it starts as soon as a slot is free. */\n add(task: () => Promise<void>): void;\n\n /** Resolves once nothing is running and nothing is waiting. */\n drain(): Promise<void>;\n\n /** Drop every task that has not started yet. Running tasks are untouched. */\n clear(): void;\n\n /** Number of tasks currently running. */\n readonly active: number;\n\n /** Number of tasks waiting for a slot. */\n readonly pending: number;\n}\n\n/**\n * Create a queue that runs at most `concurrency` tasks at a time.\n *\n * A task that rejects is swallowed rather than failing the queue: one broken\n * upload must not stall the remaining ones, and the caller observes the failure\n * through per-file state anyway. `concurrency` is clamped to at least 1, because a\n * queue with zero slots would never drain and `drain()` would never resolve.\n */\nexport function createTaskQueue(concurrency = 1): TaskQueue {\n const limit = Number.isFinite(concurrency) ? Math.max(1, Math.floor(concurrency)) : 1;\n const waiting: Array<() => Promise<void>> = [];\n let active = 0;\n let idleWaiters: Array<() => void> = [];\n\n const settleIdle = (): void => {\n if (active > 0 || waiting.length > 0) return;\n const waiters = idleWaiters;\n idleWaiters = [];\n for (const waiter of waiters) waiter();\n };\n\n const pump = (): void => {\n while (active < limit && waiting.length > 0) {\n const task = waiting.shift()!;\n active += 1;\n void Promise.resolve()\n .then(task)\n .catch(noop)\n .then(() => {\n active -= 1;\n pump();\n settleIdle();\n });\n }\n settleIdle();\n };\n\n return {\n get active(): number {\n return active;\n },\n\n get pending(): number {\n return waiting.length;\n },\n\n add(task: () => Promise<void>): void {\n waiting.push(task);\n // Deferred by one microtask: `add()` is often called in a loop, and pumping\n // synchronously would start the first task before the loop finished queueing\n // the rest — observable as an out-of-order start when tasks log or abort.\n queueMicrotask(pump);\n },\n\n drain(): Promise<void> {\n if (active === 0 && waiting.length === 0) return Promise.resolve();\n return new Promise<void>((resolve) => {\n idleWaiters.push(resolve);\n });\n },\n\n clear(): void {\n waiting.length = 0;\n settleIdle();\n }\n };\n}\n","import type { SnailStateRef, SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport { isCancellation } from \"./shared/error\";\nimport { attachUploadProgress } from \"./shared/method\";\nimport type { SnailRequest, StrategyMethod } from \"./shared/method\";\nimport { createTaskQueue } from \"./shared/queue\";\nimport { createStrategyState } from \"./shared/state\";\nimport type { StrategyState } from \"./shared/state\";\n\n/** Lifecycle of one queued file. */\nexport type UploadFileStatus = \"pending\" | \"uploading\" | \"success\" | \"error\";\n\n/** Everything the hook tracks about one file. */\nexport interface UploadFileState {\n /** Stable id, used by `retry(id)`. */\n readonly id: string;\n\n /** The file itself. */\n readonly file: File;\n\n readonly status: UploadFileStatus;\n\n /** `0`–`1`, from the transport when it reports progress, otherwise completed-or-not. */\n readonly progress: number;\n\n /** Failure of this file. Untouched for a cancellation. */\n readonly error: unknown;\n\n /** Unwrapped payload of this file's successful response. */\n readonly response: unknown;\n}\n\n/** Snapshot handed to `onProgress`. */\nexport interface UploaderProgress {\n /** Average progress across every queued file, `0`–`1`. */\n progress: number;\n files: readonly UploadFileState[];\n}\n\n/** Options accepted by {@link useUploader}. */\nexport interface UseUploaderOptions<TData> extends SnailStrategyCommonOptions {\n /** Files in flight at once. Defaults to `3`. */\n concurrency?: number;\n\n /**\n * Accept more than one file per `upload()` call.\n *\n * Defaults to `true`. When `false`, only the first entry of a `FileList` is\n * queued and the rest are dropped — a single-file avatar input should not\n * silently upload 40 holiday photos because a user multi-selected.\n */\n multiple?: boolean;\n\n /** Called whenever aggregate or per-file progress changes. */\n onProgress?: (state: UploaderProgress) => void;\n\n /** FormData field the file is written to. Defaults to `\"file\"`. */\n fieldName?: string;\n}\n\n/** What {@link useUploader} returns. */\nexport interface UseUploaderResult<TData> extends StrategyState<TData> {\n /**\n * Queue files and resolve once the queue drains.\n *\n * Never rejects: per-file failures live on `files[i].error`, and one bad file\n * must not abort the batch — that is the difference between \"3 of 5 uploaded\"\n * and \"nothing happened\".\n */\n upload(files: File | File[] | FileList | null | undefined): Promise<void>;\n\n /** Every queued file, in queue order. */\n readonly files: SnailStateRef<UploadFileState[]>;\n\n /** Aggregate progress, `0`–`1`. Reaches `1` when every file succeeded. */\n readonly progress: SnailStateRef<number>;\n\n /** Re-queue one failed file. */\n retry(id: string): void;\n}\n\n/** Normalise the several shapes a file input hands out. */\nfunction normalizeFiles(input: File | File[] | FileList | null | undefined): File[] {\n if (!input) return [];\n if (Array.isArray(input)) return input;\n // A `FileList` is array-like and carries `length`; a single `File` does not.\n if (typeof (input as FileList).length === \"number\") {\n return Array.from(input as ArrayLike<File>);\n }\n return [input as File];\n}\n\n/** Keep a fraction inside `0`–`1`; a transport can report `loaded > total`. */\nfunction clamp01(value: number): number {\n if (!Number.isFinite(value)) return 0;\n return Math.min(1, Math.max(0, value));\n}\n\n/**\n * Upload files with a bounded number of parallel requests.\n *\n * ```ts\n * const uploader = useUploader(api.upload, { concurrency: 2 });\n * await uploader.upload(input.files);\n * uploader.files.value; // per-file status, progress, error, response\n * uploader.retry(id); // re-queue one failure\n * ```\n *\n * ## Progress\n *\n * Per-file progress comes from the transport via axios' `onUploadProgress`, which\n * the hook attaches to the **live** request config of each file (see\n * `shared/method.ts` for why that has to happen right after `send()` starts). The\n * aggregate is the mean of the per-file values, and a finished file counts as `1`\n * regardless of what the transport reported — a mocked or `fetch`-based adapter\n * reports nothing at all, and without that rule the bar would stop at `0`.\n *\n * ## `data` and `code`\n *\n * The state handles are shared by the whole batch, so `data` holds the payload of\n * the most recently finished file. Use `files[i].response` for anything\n * per-file. `error` mirrors the file list: it holds the first failed file's error\n * while any file is in the `error` state, and clears itself once the last failure\n * has been retried successfully.\n */\nexport function useUploader<TData>(\n method: StrategyMethod<[FormData], TData>,\n options: UseUploaderOptions<TData> = {}\n): UseUploaderResult<TData> {\n const fieldName = options.fieldName ?? \"file\";\n const queue = createTaskQueue(options.concurrency ?? 3);\n const inFlight = new Set<SnailRequest<TData>>();\n let states: UploadFileState[] = [];\n let sequence = 0;\n\n const controller = createStrategyState<TData>({\n adapter: options.adapter,\n onAbort: () => {\n // Two halves: abort what is on the wire, drop what has not started. Leaving\n // either half alive means `abort()` appears not to work.\n for (const snail of [...inFlight]) snail.abort();\n inFlight.clear();\n queue.clear();\n\n // An aborted file goes back to `pending` rather than `error`: cancelling is\n // not a failure, and the file is usually still there to retry.\n for (const entry of states) {\n if (entry.status === \"success\") continue;\n patch(entry.id, { status: \"pending\", progress: 0, error: undefined });\n }\n }\n });\n\n const { state } = controller;\n const adapter = controller.adapter;\n const files = adapter.create<UploadFileState[]>([]);\n const progress = adapter.create<number>(0);\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n function writeStates(): void {\n // A fresh array (and fresh entries) per change: React compares snapshots by\n // identity, so mutating in place would not re-render.\n adapter.write(files, states.slice());\n }\n\n function recalc(): void {\n const aggregate =\n states.length === 0\n ? 0\n : states.reduce((sum, entry) => sum + entry.progress, 0) / states.length;\n adapter.write(progress, aggregate);\n\n // The aggregate failure mirrors the file list rather than the last event: a\n // batch with no failed file is not failing, which is exactly what makes a\n // successful `retry()` clear the error again.\n const failure = states.find((entry) => entry.status === \"error\");\n controller.setError(failure ? failure.error : undefined);\n\n options.onProgress?.({ progress: aggregate, files: states.slice() });\n }\n\n function patch(id: string, changes: Partial<UploadFileState>): void {\n states = states.map((entry) => (entry.id === id ? { ...entry, ...changes } : entry));\n writeStates();\n recalc();\n }\n\n async function runFile(id: string): Promise<void> {\n const entry = states.find((candidate) => candidate.id === id);\n if (!entry || entry.status === \"success\" || entry.status === \"uploading\") return;\n\n patch(id, { status: \"uploading\", error: undefined });\n\n const form = new FormData();\n form.append(fieldName, entry.file, entry.file.name);\n\n const snail = method(form);\n inFlight.add(snail);\n\n try {\n const sending = snail.send(form);\n // Must follow `send()`: the core builds a fresh config synchronously at the\n // start of `send()`, so a callback attached earlier would be discarded.\n attachUploadProgress(snail, (event) => {\n if (!event.total || event.total <= 0) return;\n patch(id, { progress: clamp01(event.loaded / event.total) });\n });\n\n const result = await sending;\n patch(id, { status: \"success\", progress: 1, response: result.data, error: undefined });\n // Deliberately *not* `applySuccess`: that clears `error`, and one failed file\n // must stay visible even after another file in the same batch succeeds.\n controller.setData(result.data);\n controller.setCode(result.code);\n controller.setMessage(result.message);\n controller.emitSuccess(result.data);\n } catch (error) {\n if (isCancellation(error)) {\n patch(id, { status: \"pending\", progress: 0, error: undefined });\n return;\n }\n patch(id, { status: \"error\", error });\n controller.applyFailure(error);\n controller.emitError(error);\n } finally {\n inFlight.delete(snail);\n recalc();\n }\n }\n\n async function upload(input: File | File[] | FileList | null | undefined): Promise<void> {\n const incoming = normalizeFiles(input);\n const accepted = options.multiple === false ? incoming.slice(0, 1) : incoming;\n if (accepted.length === 0) return;\n\n const queued = accepted.map((file) => {\n sequence += 1;\n return {\n id: `upload-${sequence}`,\n file,\n status: \"pending\" as const,\n progress: 0,\n error: undefined,\n response: undefined\n };\n });\n\n // A new selection starts a new batch: the previous batch's aggregate failure\n // must not colour it. Per-file failures are still on their own entries.\n controller.resetForSend();\n\n states = [...states, ...queued];\n writeStates();\n recalc();\n\n for (const entry of queued) {\n queue.add(() => runFile(entry.id));\n }\n\n await queue.drain();\n }\n\n function retry(id: string): void {\n const entry = states.find((candidate) => candidate.id === id);\n if (!entry || entry.status === \"success\" || entry.status === \"uploading\") return;\n\n patch(id, { status: \"pending\", progress: 0, error: undefined });\n queue.add(() => runFile(id));\n }\n\n return { ...state, upload, files, progress, retry };\n}\n","import type { AxiosResponse, InternalAxiosRequestConfig } from \"axios\";\nimport type { SnailContext } from \"../core/context\";\nimport { createPlugin } from \"../core/plugin\";\nimport { deferred, noop } from \"../utils/object\";\nimport type { SnailNext, SnailPluginObject } from \"../typings/plugin\";\n\n/** Options accepted by {@link useTokenAuth}. */\nexport interface TokenAuthOptions {\n /**\n * Current token, from wherever the application keeps it.\n *\n * May be async (a secure store, a cookie API). Returning `null`/`undefined` is\n * normal — it means \"not logged in\", and the header is simply not injected.\n */\n token: () => string | null | undefined | Promise<string | null | undefined>;\n\n /**\n * Obtain a fresh token.\n *\n * Called **at most once** per wave of 401s; see the class documentation on\n * {@link useTokenAuth}. It must resolve with the new token; rejecting aborts the\n * whole queue.\n */\n refresh: () => Promise<string>;\n\n /** Header to write. Defaults to `\"authorization\"`. */\n header?: string;\n\n /** Prefix before the token. Defaults to `\"Bearer\"`; pass `\"\"` for a raw token. */\n scheme?: string;\n\n /** Called when a 401 could not be recovered — the refresh failed, or the replay 401'd again. */\n onUnauthorized?: (error: unknown) => void;\n}\n\n/** What {@link useTokenAuth} hands back. */\nexport interface TokenAuthHandle {\n /** Install with `Service.use(auth.plugin)`. */\n readonly plugin: SnailPluginObject<TokenAuthOptions>;\n\n /** Replace the cached token, e.g. after a successful login. */\n setToken(token: string | null | undefined): void;\n\n /** The cached token. Synchronous: an async `token()` cannot be awaited here. */\n getToken(): string | undefined;\n\n /** Forget the cached token so `token()` is consulted again on the next request. */\n clearToken(): void;\n}\n\n/** Hooks the token plugin contributes. Typed explicitly so `setup` needs no casts. */\ninterface TokenAuthHooks {\n beforeRequest(ctx: SnailContext, next: SnailNext): Promise<void>;\n}\n\n/** `ctx.state` key holding the token one request was actually sent with. */\nconst USED_TOKEN = \"token-auth:used\";\n\n/**\n * Read the HTTP status a failure carried, without assuming an axios error class.\n *\n * Only a transport-level 401 is recoverable here. A backend that answers HTTP 200\n * with `{ code: 401 }` produces its `SnailResponseError` in `SnailMethod.finalize`,\n * *after* the `beforeRequest` chain has already returned — and no hook can recover\n * a post-chain failure (`onError` is observe-only by design). Such a response is\n * left to the caller.\n */\nfunction statusOf(error: unknown): number | undefined {\n const candidate = error as\n | { status?: unknown; response?: { status?: unknown } }\n | null\n | undefined;\n const status = candidate?.response?.status ?? candidate?.status;\n return typeof status === \"number\" ? status : undefined;\n}\n\n/**\n * Bearer-token authentication with single-flight refresh, as a plugin.\n *\n * ```ts\n * const auth = useTokenAuth({\n * token: () => localStorage.getItem(\"token\"),\n * refresh: async () => (await api.refresh().send()).token,\n * onUnauthorized: () => router.push(\"/login\")\n * });\n *\n * Service.use(auth.plugin);\n * auth.setToken(\"...\");\n * ```\n *\n * ## Why a plugin and not a hook\n *\n * A token is global: every request of the service has to carry it, and a 401 on\n * any one of them has to invalidate the token for all of them. A per-method hook\n * could not see the other requests, so refresh coordination would be impossible.\n *\n * ## Exactly one refresh per wave of 401s\n *\n * Three parallel requests, one expired token, three 401s. Refreshing per request\n * would fire three refreshes and, with a rotating refresh token, two of them would\n * fail and log the user out. Two mechanisms prevent that:\n *\n * 1. a request that 401s while a refresh is already in flight **joins** it instead\n * of starting a second one;\n * 2. a request whose 401 was produced by a token that has since been replaced\n * replays directly, without refreshing again.\n *\n * The token each request was sent with is remembered in `ctx.state` (which the core\n * clears per send), and that is what makes rule 2 decidable without keeping\n * timers around.\n *\n * ## Replaying\n *\n * `onError` cannot recover a failure — the core rethrows after every `onError`\n * hook — so the recovery lives in `beforeRequest`, around `await next()`. The\n * replay re-runs the `requestInterceptor` reduce, the transport and the\n * `afterResponse` chain, so headers a plugin signs are recomputed for the retried\n * call. `beforeRequest` hooks are deliberately *not* re-entered: the refresh\n * decision has already been made, and re-entering could loop.\n *\n * ## What this cannot recover\n *\n * Only a **transport** 401 (HTTP 401). A backend that reports `{ code: 401 }` with\n * HTTP 200 fails in `finalize`, after the chain, where no hook can intervene; that\n * response reaches the caller as a `SnailResponseError`.\n */\nexport function useTokenAuth(options: TokenAuthOptions): TokenAuthHandle {\n const header = options.header ?? \"authorization\";\n const scheme = options.scheme ?? \"Bearer\";\n\n let cached: string | undefined;\n let refreshing: Promise<string> | undefined;\n let disposed = false;\n\n /** Resolves when the plugin is uninstalled; lets queued requests bail out. */\n const disposal = deferred<void>();\n\n async function readToken(): Promise<string | undefined> {\n if (cached !== undefined) return cached;\n\n const value = await options.token();\n if (typeof value === \"string\" && value.length > 0) cached = value;\n return cached;\n }\n\n function inject(ctx: SnailContext, token: string): void {\n ctx.request.headers.set(header, scheme ? `${scheme} ${token}` : token);\n }\n\n /** Start the one refresh this wave gets, or return the one already running. */\n function startRefresh(): Promise<string> {\n if (refreshing) return refreshing;\n\n const pending = Promise.resolve()\n .then(() => options.refresh())\n .then((token) => {\n if (typeof token === \"string\" && token.length > 0) cached = token;\n return cached ?? token;\n });\n\n const tracked = pending.finally(() => {\n if (refreshing === tracked) refreshing = undefined;\n });\n\n refreshing = tracked;\n // A refresh nobody awaits any more — every waiter was disposed — must not\n // surface as an unhandled rejection.\n void tracked.catch(noop);\n\n return tracked;\n }\n\n /**\n * Await a refresh, giving up early when the plugin is uninstalled.\n *\n * @returns `true` when a fresh token is available, `false` when the refresh\n * failed or the plugin went away — in both cases the caller rethrows the\n * **original** 401, because the refresh error is an implementation detail the\n * application cannot act on.\n */\n async function settleRefresh(pending: Promise<string>): Promise<boolean> {\n try {\n await Promise.race([pending, disposal.promise]);\n } catch {\n return false;\n }\n return !disposed;\n }\n\n /**\n * Re-send the request that 401'd, with the refreshed token.\n *\n * The replay reproduces `SnailMethod.dispatch` using public plugin-manager\n * surface. It intentionally skips the `beforeRequest` chain — see the hook\n * documentation.\n */\n async function replay(ctx: SnailContext): Promise<void> {\n const manager = ctx.server.pluginManager;\n const config = manager.reduce<InternalAxiosRequestConfig>(\n \"requestInterceptor\",\n ctx.request,\n ctx\n );\n ctx.request = config;\n\n const response: AxiosResponse = await ctx.server.axios.request(config);\n ctx.setResponse(response);\n\n await manager.runChain(\"afterResponse\", ctx);\n }\n\n const factory = createPlugin<TokenAuthOptions, TokenAuthHooks>({\n name: \"token-auth\",\n // The third-party band, above the default `0`: this hook wraps the rest of the\n // `beforeRequest` chain so it can observe a 401 raised anywhere downstream.\n priority: 20,\n\n setup(_pluginOptions, api) {\n // Registered here, not on the hook, so an uninstall cannot leave a queue of\n // 401'd requests waiting on a refresh that will never be observed.\n api.onDispose(() => {\n disposed = true;\n disposal.resolve();\n });\n\n return {\n async beforeRequest(ctx: SnailContext, next: SnailNext): Promise<void> {\n if (disposed) {\n await next();\n return;\n }\n\n // A refresh in flight means the stored token is stale. Waiting here is\n // cheaper than sending a request that is certain to 401.\n if (refreshing) await settleRefresh(refreshing);\n\n const token = await readToken();\n if (token) inject(ctx, token);\n ctx.state.set(USED_TOKEN, token);\n\n try {\n await next();\n } catch (error) {\n if (disposed || statusOf(error) !== 401) throw error;\n\n const used = ctx.state.get(USED_TOKEN) as string | undefined;\n const superseded =\n cached !== undefined && used !== undefined && cached !== used;\n\n // Someone else already replaced the token this request failed with:\n // replay immediately instead of triggering another refresh.\n if (!superseded) {\n const pending = refreshing ?? startRefresh();\n const recovered = await settleRefresh(pending);\n if (!recovered) {\n options.onUnauthorized?.(error);\n throw error;\n }\n }\n\n const refreshed = await readToken();\n if (refreshed) inject(ctx, refreshed);\n\n try {\n await replay(ctx);\n } catch (replayError) {\n if (statusOf(replayError) === 401) options.onUnauthorized?.(replayError);\n throw replayError;\n }\n }\n }\n };\n }\n });\n\n return {\n plugin: factory(options),\n\n setToken(token: string | null | undefined): void {\n cached = typeof token === \"string\" && token.length > 0 ? token : undefined;\n },\n\n getToken(): string | undefined {\n return cached;\n },\n\n clearToken(): void {\n cached = undefined;\n }\n };\n}\n","import type { SnailStateAdapter, SnailStateRef, SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport type { SnailConnection, SnailSseEndpoint, SnailSseMessage } from \"../typings/stream\";\nimport { noop } from \"../utils/object\";\nimport { bindRef, resolveStateAdapter } from \"./shared/adapter\";\n\n/**\n * A connection that can report the events it parsed.\n *\n * `SnailConnection` deliberately exposes only `close`/`connected`/`opened`/\n * `closed`, because a connection whose messages are delivered to `@SseEvent()`\n * handlers on the endpoint class needs no other surface. `useSSE` has no such\n * class, so it looks for this extension on the connection and, failing that, on\n * the endpoint. Without either, `connected` still tracks the transport but\n * `messages` stays empty — see the note on {@link useSSE}.\n */\nexport interface SseConnectionTap {\n /** Register a message listener. Returns an unsubscribe function. */\n onMessage?(listener: (message: SnailSseMessage) => void): () => void;\n}\n\n/** A `SnailSseEndpoint` that can also hand out messages directly. */\nexport interface SseEndpoint extends SnailSseEndpoint {\n /**\n * Register a message listener for the connections this endpoint opens.\n *\n * Preferred over {@link SseConnectionTap} because it can be called once, before\n * `open()`, which is the only way to be sure no message between the connect and\n * the subscription is lost.\n */\n subscribe?(listener: (message: SnailSseMessage) => void): () => void;\n}\n\n/** Options accepted by {@link useSSE}. */\nexport interface UseSseOptions {\n /** State adapter. Defaults to the globally registered one. */\n adapter?: SnailStateAdapter;\n\n /**\n * Open the connection as soon as the hook is created. Defaults to `false`,\n * matching `SnailSseEndpoint`'s \"nothing connects until `open()`\" contract.\n */\n immediate?: boolean;\n\n /**\n * Maximum buffered messages. Defaults to `100`, dropping the oldest first.\n *\n * Bounded on purpose: an SSE feed that runs for hours with an unbounded array\n * is a memory leak that ends in a frozen tab, and no view can render a hundred\n * thousand rows anyway.\n */\n maxMessages?: number;\n\n /** Keep only the messages this returns `true` for. */\n filter?: (message: SnailSseMessage) => boolean;\n\n /** Called for every accepted message, after the buffer was updated. */\n onMessage?: (message: SnailSseMessage) => void;\n}\n\n/** What {@link useSSE} returns. */\nexport interface UseSseResult {\n /** Buffered messages, oldest first. */\n readonly messages: SnailStateRef<SnailSseMessage[]>;\n\n /** The most recent accepted message. */\n readonly lastMessage: SnailStateRef<SnailSseMessage | undefined>;\n\n /** `true` between a successful connect and the next close. */\n readonly connected: SnailStateRef<boolean>;\n\n /** Why the connection failed, or why a filter/handler threw. */\n readonly error: SnailStateRef<unknown>;\n\n /** Connect. A no-op while an open connection is already live. */\n open(): void;\n\n /** Disconnect, stop the reconnect loop and detach the listener. */\n close(): void;\n\n /** Empty the message buffer. Leaves the connection alone. */\n clear(): void;\n\n /** Resolved values, subscribing the current component when the adapter supports it. */\n bind(): {\n messages: SnailSseMessage[];\n lastMessage: SnailSseMessage | undefined;\n connected: boolean;\n error: unknown;\n };\n}\n\n/** Find the message tap an endpoint or its connection offers, if any. */\nfunction attachTap(\n endpoint: SseEndpoint,\n connection: SnailConnection,\n listener: (message: SnailSseMessage) => void\n): () => void {\n if (typeof endpoint.subscribe === \"function\") {\n return endpoint.subscribe(listener) ?? noop;\n }\n\n const onMessage = (connection as SnailConnection & SseConnectionTap).onMessage;\n if (typeof onMessage === \"function\") {\n return onMessage.call(connection, listener) ?? noop;\n }\n\n // No tap: the connection still works, its messages just go to the endpoint\n // class's `@SseEvent()` handlers instead of to this hook's buffer.\n return noop;\n}\n\n/**\n * Consume a Server-Sent Events endpoint as reactive state.\n *\n * ```ts\n * const events = Service.createSse(Events);\n * const feed = useSSE(events, { maxMessages: 50 });\n * feed.open();\n * feed.messages.value; // SnailSseMessage[]\n * feed.close();\n * ```\n *\n * ## The message tap\n *\n * `Service.createSse()` returns an object whose only member is `open()`, and the\n * messages it parses are dispatched to the decorated class's methods\n * (`@SseEvent()`), not to the caller. `useSSE` therefore accepts an endpoint that\n * additionally implements `subscribe(listener)`, or a connection that implements\n * `onMessage(listener)`. Both extend the core contract without changing it; an\n * endpoint that implements neither still reports `connected`/`error` faithfully,\n * it just never fills `messages`.\n *\n * ## Never an unhandled rejection\n *\n * A rejected `opened` promise is exactly how a failed SSE connect is reported, and\n * nothing else in the application will ever await it. Both `opened` and `closed`\n * are handled here, so a dead server cannot take the process down with an\n * unhandled rejection.\n */\nexport function useSSE(endpoint: SseEndpoint, options: UseSseOptions = {}): UseSseResult {\n const adapter = resolveStateAdapter(options);\n const maxMessages = Number.isFinite(options.maxMessages)\n ? Math.max(1, Math.floor(options.maxMessages as number))\n : 100;\n\n const messages = adapter.create<SnailSseMessage[]>([]);\n const lastMessage = adapter.create<SnailSseMessage | undefined>(undefined);\n const connected = adapter.create<boolean>(false);\n const error = adapter.create<unknown>(undefined);\n\n let connection: SnailConnection | undefined;\n let detach: (() => void) | undefined;\n let listening = false;\n\n function handle(message: SnailSseMessage): void {\n try {\n if (options.filter && !options.filter(message)) return;\n\n const buffered = [...adapter.read(messages), message];\n if (buffered.length > maxMessages) buffered.splice(0, buffered.length - maxMessages);\n\n adapter.write(messages, buffered);\n adapter.write(lastMessage, message);\n options.onMessage?.(message);\n } catch (cause) {\n // A throwing filter or handler is the caller's bug; it must not kill the\n // stream, but it must be visible rather than swallowed.\n adapter.write(error, cause);\n }\n\n // A transport that dropped and reconnected reports it through `connected`;\n // reading it on every message is what keeps the flag honest without a timer.\n adapter.write(connected, connection?.connected ?? adapter.read(connected));\n }\n\n function open(): void {\n if (listening) return;\n\n // A previous connection that dropped on its own was never `close()`d, so its\n // tap is still registered. Releasing it here is what stops a reconnect from\n // delivering every message twice.\n detach?.();\n detach = undefined;\n\n const next = endpoint.open();\n connection = next;\n listening = true;\n\n // Attached before `opened` is awaited so no message is missed between the\n // connect and the first render.\n detach = attachTap(endpoint, next, handle);\n\n adapter.write(connected, next.connected);\n adapter.write(error, undefined);\n\n void next.opened.then(\n () => {\n if (connection !== next) return;\n adapter.write(connected, true);\n adapter.write(error, undefined);\n },\n (cause: unknown) => {\n if (connection !== next) return;\n adapter.write(connected, false);\n adapter.write(error, cause);\n }\n );\n\n void next.closed.then(\n () => {\n if (connection !== next) return;\n listening = false;\n adapter.write(connected, false);\n },\n () => {\n if (connection !== next) return;\n listening = false;\n adapter.write(connected, false);\n }\n );\n }\n\n function close(): void {\n const current = connection;\n connection = undefined;\n listening = false;\n adapter.write(connected, false);\n\n detach?.();\n detach = undefined;\n\n // `close()` on the transport aborts the request and resolves `closed`, which is\n // what actually stops the reconnect loop.\n current?.close();\n }\n\n function clear(): void {\n adapter.write(messages, []);\n adapter.write(lastMessage, undefined);\n }\n\n if (options.immediate) open();\n\n return {\n messages,\n lastMessage,\n connected,\n error,\n open,\n close,\n clear,\n bind() {\n return {\n messages: bindRef(adapter, messages),\n lastMessage: bindRef(adapter, lastMessage),\n connected: bindRef(adapter, connected),\n error: bindRef(adapter, error)\n };\n }\n };\n}\n","import type { SnailStateRef, SnailStrategyCommonOptions } from \"../typings/adapter\";\nimport {\n triggerDownload,\n type TriggerDownloadOptions,\n type TriggerDownloadResult\n} from \"../utils/download\";\nimport { isCancellation } from \"./shared/error\";\nimport { createMethodHolder, type StrategyMethod } from \"./shared/method\";\nimport { createStrategyState, type StrategyState } from \"./shared/state\";\n\n/**\n * The download descriptor a server is expected to return.\n *\n * The shape is intentionally tiny, because the whole design rests on the server\n * doing the work: it prepares the file out of band and answers with a short-lived\n * URL. The client never receives the bytes.\n */\nexport interface DownloadDescriptor {\n /** Where to fetch the file from. A relative path, an absolute URL or a `blob:` URL. */\n url: string;\n\n /** Filename to suggest. Honoured only for a same-origin URL — see `triggerDownload`. */\n filename?: string;\n}\n\n/** Options accepted by {@link useDownload}. */\nexport interface UseDownloadOptions<TPayload> extends SnailStrategyCommonOptions {\n /**\n * Read the descriptor out of the payload your endpoint returned.\n *\n * The default accepts a bare URL string, or an object carrying `url`,\n * `downloadUrl` or `fileUrl` plus an optional `filename`/`name`. Supply this when\n * your backend wraps them differently, so a naming choice never forces you to\n * reshape the response.\n */\n pick?: (payload: TPayload) => DownloadDescriptor;\n\n /**\n * Trigger the browser download as soon as the URL arrives. Defaults to `true`.\n *\n * Turn it off to inspect the descriptor first — for instance to show a\n * confirmation, or to hand the URL to a different download manager.\n */\n autoTrigger?: boolean;\n\n /** Open the URL in a new tab rather than downloading it. */\n openInNewTab?: boolean;\n\n /**\n * Filename override, taking precedence over whatever `pick` produced.\n *\n * Only `useDownload` can do this: a hook knows the local file's intended name,\n * the server does not.\n */\n filename?: string;\n\n /** Anchor container, for the old Firefox \"must be in the document\" behaviour. */\n container?: HTMLElement;\n\n /** `referrerpolicy` for the navigation. */\n referrerPolicy?: string;\n}\n\n/** What {@link useDownload} returns. */\nexport interface UseDownloadResult<TPayload> extends StrategyState<TPayload> {\n /**\n * Ask the server for the download URL, then hand it to the browser.\n *\n * Resolves once the download has been *started* — not completed. There is no\n * completion signal, and that is deliberate: the browser's own download manager\n * owns the transfer, which is exactly what makes a multi-gigabyte file work where\n * a buffered `blob` would exhaust memory.\n */\n download(...args: readonly unknown[]): Promise<TriggerDownloadResult>;\n\n /** The most recently resolved descriptor. `undefined` until the first success. */\n readonly info: SnailStateRef<DownloadDescriptor | undefined>;\n\n /** Called with the descriptor once it has been resolved AND triggered. */\n onDownload(callback: (info: DownloadDescriptor) => void): () => void;\n}\n\n/** Payload shapes the default `pick` understands. */\ntype DownloadPayload = string | { url?: string; downloadUrl?: string; fileUrl?: string; filename?: string; name?: string };\n\n/**\n * Drive a server-prepared download.\n *\n * ```ts\n * @Api(\"/report\")\n * class ReportApi {\n * /** Prepares the export server-side and answers with a temp url. *\\/\n * @Post(\"/export\")\n * create(@Data() query: ReportQuery): Promise<{ url: string; filename: string }> {\n * return null!;\n * }\n * }\n *\n * const { download, loading, error } = useDownload(reportApi.create);\n * await download({ from: \"2026-01-01\" });\n * ```\n *\n * ## Why it does not fetch the file\n *\n * `useDownload` awaits the request that *mints* a download URL, and only that. It\n * never fetches the file itself. Buffering a response into a `Blob` costs the whole\n * file in JavaScript memory — twice, once for the body and once for the object URL\n * — with no progress and no resume. Letting the browser fetch the URL instead gives\n * you the native download manager, streaming to disk, resume support and a download\n * that survives navigation.\n *\n * ## Why this is a strategy and not a plugin\n *\n * A plugin is for a cross-cutting concern that applies to requests an application\n * did not write specially: caching, interceptors, validation. A download is the\n * opposite — one explicit user action, with its own visible state and its own\n * failure modes, invoked from a click handler. That is the definition of a request\n * strategy. The reusable half that is *not* about reactivity — clicking a temporary\n * anchor — lives in `triggerDownload()`, which is exported from the package root so\n * a non-hook caller can use it directly.\n */\nexport function useDownload<TArgs extends readonly unknown[], TPayload>(\n method: StrategyMethod<TArgs, TPayload>,\n options: UseDownloadOptions<TPayload> = {}\n): UseDownloadResult<TPayload> {\n const holder = createMethodHolder<TArgs, TPayload>(method);\n\n const controller = createStrategyState<TPayload>({\n adapter: options.adapter,\n onAbort: () => holder.abort()\n });\n\n const { state, adapter } = controller;\n const info = adapter.create<DownloadDescriptor | undefined>(undefined);\n const downloadListeners = new Set<(descriptor: DownloadDescriptor) => void>();\n\n if (options.onSuccess) state.onSuccess(options.onSuccess);\n if (options.onError) state.onError(options.onError);\n if (options.onFinish) state.onFinish(options.onFinish);\n\n const triggerOptions: TriggerDownloadOptions = {\n openInNewTab: options.openInNewTab,\n container: options.container,\n referrerPolicy: options.referrerPolicy\n };\n\n /** Fire the browser download, surfacing a throwing callback as a state error. */\n function dispatch(descriptor: DownloadDescriptor): TriggerDownloadResult {\n const result = triggerDownload(descriptor.url, {\n ...triggerOptions,\n filename: options.filename ?? descriptor.filename\n });\n\n adapter.write(info, descriptor);\n for (const listener of [...downloadListeners]) {\n try {\n listener(descriptor);\n } catch {\n // One bad listener must not turn a started download into a failed request.\n }\n }\n\n return result;\n }\n\n async function download(...args: readonly unknown[]): Promise<TriggerDownloadResult> {\n controller.resetForSend();\n controller.setLoading(true);\n\n try {\n const result = await holder\n .resolve(args as unknown as TArgs)\n .send(...(args as unknown as TArgs));\n const descriptor = resolveDescriptor(result.data, options);\n\n // The transport is finished; what remains is browser navigation. A failure\n // here — most often `triggerDownload` throwing because there is no DOM, i.e.\n // `useDownload` called on a server — deliberately *does* fail the call rather\n // than resolving quietly. The caller asked for a download and none started;\n // reporting success would bury a real mistake. It surfaces as a rejected\n // promise plus the usual `error` state, and a subscribing `onDownload`\n // listener never fires because the download never began.\n let triggered: TriggerDownloadResult;\n if (options.autoTrigger === false) {\n adapter.write(info, descriptor);\n triggered = { url: descriptor.url, filename: descriptor.filename };\n } else {\n triggered = dispatch(descriptor);\n }\n\n controller.applySuccess(result);\n controller.emitSuccess(result.data);\n return triggered;\n } catch (error) {\n if (!isCancellation(error)) {\n controller.applyFailure(error);\n controller.emitError(error);\n }\n throw error;\n } finally {\n controller.setLoading(false);\n controller.emitFinish();\n }\n }\n\n return {\n ...state,\n download,\n info,\n onDownload(callback: (descriptor: DownloadDescriptor) => void): () => void {\n downloadListeners.add(callback);\n return () => {\n downloadListeners.delete(callback);\n };\n }\n };\n}\n\n/**\n * Turn a payload into a descriptor.\n *\n * Accepting several spellings is not sloppiness — it removes a naming argument from\n * every integration. A backend that answers `{ fileUrl }` should not force a `pick`\n * implementation for the sake of one word.\n */\nfunction resolveDescriptor<TPayload>(\n payload: TPayload,\n options: UseDownloadOptions<TPayload>\n): DownloadDescriptor {\n if (options.pick) {\n const descriptor = options.pick(payload);\n if (!descriptor || typeof descriptor.url !== \"string\" || descriptor.url.length === 0) {\n throw new TypeError(\n \"[snail] useDownload: `pick` must return a descriptor with a non-empty `url`\"\n );\n }\n return descriptor;\n }\n\n if (typeof payload === \"string\" && payload.length > 0) {\n return { url: payload };\n }\n\n const candidate = payload as DownloadPayload | null;\n if (candidate && typeof candidate === \"object\") {\n const url = candidate.url ?? candidate.downloadUrl ?? candidate.fileUrl;\n if (typeof url === \"string\" && url.length > 0) {\n const filename = candidate.filename ?? candidate.name;\n return filename ? { url, filename } : { url };\n }\n }\n\n throw new TypeError(\n \"[snail] useDownload: the response carried no download url. Expected a string, or an \" +\n \"object with `url` / `downloadUrl` / `fileUrl`. Pass `pick` if your server names it \" +\n \"differently.\"\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { isRef, ref } from "vue";
|
|
2
|
+
//#region src/adapter/vue.ts
|
|
3
|
+
/**
|
|
4
|
+
* Vue 3 state adapter.
|
|
5
|
+
*
|
|
6
|
+
* A Vue `Ref<T>` already *is* a `{ value: T }` box, so this adapter is almost
|
|
7
|
+
* pure identity — `create` hands back the ref and `read`/`write` touch `.value`.
|
|
8
|
+
* Vue's render effect tracks the `.value` access itself, which is why no
|
|
9
|
+
* `subscribe` implementation is needed: there is nothing to notify manually.
|
|
10
|
+
*
|
|
11
|
+
* This module is the only place in the library that imports `vue`, and it is only
|
|
12
|
+
* reachable from the strategies' default entry point and the `VueAdapter` plugin.
|
|
13
|
+
* Applications that never import either never pull Vue in.
|
|
14
|
+
*/
|
|
15
|
+
var vueStateAdapter = {
|
|
16
|
+
name: "vue",
|
|
17
|
+
create(initial) {
|
|
18
|
+
return ref(initial);
|
|
19
|
+
},
|
|
20
|
+
read(state) {
|
|
21
|
+
return state.value;
|
|
22
|
+
},
|
|
23
|
+
write(state, value) {
|
|
24
|
+
state.value = value;
|
|
25
|
+
},
|
|
26
|
+
/** `true` when a value already is a Vue ref. */
|
|
27
|
+
isState(value) {
|
|
28
|
+
return isRef(value);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
//#endregion
|
|
32
|
+
export { vueStateAdapter as t };
|
|
33
|
+
|
|
34
|
+
//# sourceMappingURL=vue-Dd9EhkQ3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vue-Dd9EhkQ3.js","names":[],"sources":["../../src/adapter/vue.ts"],"sourcesContent":["import { isRef, ref, type Ref } from \"vue\";\nimport type { SnailStateAdapter, SnailStateRef } from \"../typings/adapter\";\n\n/**\n * Vue 3 state adapter.\n *\n * A Vue `Ref<T>` already *is* a `{ value: T }` box, so this adapter is almost\n * pure identity — `create` hands back the ref and `read`/`write` touch `.value`.\n * Vue's render effect tracks the `.value` access itself, which is why no\n * `subscribe` implementation is needed: there is nothing to notify manually.\n *\n * This module is the only place in the library that imports `vue`, and it is only\n * reachable from the strategies' default entry point and the `VueAdapter` plugin.\n * Applications that never import either never pull Vue in.\n */\nexport const vueStateAdapter: SnailStateAdapter = {\n name: \"vue\",\n\n create<T>(initial: T): SnailStateRef<T> {\n return ref(initial) as unknown as SnailStateRef<T>;\n },\n\n read<T>(state: SnailStateRef<T>): T {\n return (state as Ref<T>).value;\n },\n\n write<T>(state: SnailStateRef<T>, value: T): void {\n (state as Ref<T>).value = value;\n },\n\n /** `true` when a value already is a Vue ref. */\n isState(value: unknown): boolean {\n return isRef(value);\n }\n};\n"],"mappings":";;;;;;;;;;;;;;AAeA,IAAa,kBAAqC;CAChD,MAAM;CAEN,OAAU,SAA8B;EACtC,OAAO,IAAI,OAAO;CACpB;CAEA,KAAQ,OAA4B;EAClC,OAAQ,MAAiB;CAC3B;CAEA,MAAS,OAAyB,OAAgB;EAChD,MAAkB,QAAQ;CAC5B;;CAGA,QAAQ,OAAyB;EAC/B,OAAO,MAAM,KAAK;CACpB;AACF"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { AxiosHeaders } from "axios";
|
|
2
|
+
import type { SnailBuiltinParamSource, SnailParamDescriptor, SnailParamResolver } from "../typings/args.js";
|
|
3
|
+
import type { SnailContext } from "./context.js";
|
|
4
|
+
/** `@Params()` — fills `:placeholder` values used to build the final url. */
|
|
5
|
+
export declare const resolvePathParams: SnailParamResolver;
|
|
6
|
+
/** `@Query()` — merges into the query string. */
|
|
7
|
+
export declare const resolveQuery: SnailParamResolver;
|
|
8
|
+
/**
|
|
9
|
+
* `@Data()` — builds the request body.
|
|
10
|
+
*
|
|
11
|
+
* With a key the value is merged into an object body. Without a key a plain
|
|
12
|
+
* object is merged, while anything else (`FormData`, `Blob`, a raw string,
|
|
13
|
+
* an array) *replaces* the body so non-JSON uploads stay possible.
|
|
14
|
+
*/
|
|
15
|
+
export declare const resolveBody: SnailParamResolver;
|
|
16
|
+
/** `@Header()` — merges into the request headers. */
|
|
17
|
+
export declare const resolveHeader: SnailParamResolver;
|
|
18
|
+
/**
|
|
19
|
+
* The resolver registry.
|
|
20
|
+
*
|
|
21
|
+
* `createParamDecorator` looks sources up here, so a plugin can either reuse a
|
|
22
|
+
* built-in source or register its own.
|
|
23
|
+
*/
|
|
24
|
+
export declare const paramResolvers: Record<string, SnailParamResolver>;
|
|
25
|
+
/** Register a resolver under a custom source name. */
|
|
26
|
+
export declare function registerParamResolver(source: string, resolver: SnailParamResolver): void;
|
|
27
|
+
/** Human-readable label used in decorator error messages. */
|
|
28
|
+
export declare function sourceLabel(source: string): string;
|
|
29
|
+
/** `true` when `source` has a registered resolver. */
|
|
30
|
+
export declare function hasParamResolver(source: string): boolean;
|
|
31
|
+
/** Names of every registered parameter source. */
|
|
32
|
+
export declare function paramSources(): string[];
|
|
33
|
+
/** Type guard used by the custom-decorator factory. */
|
|
34
|
+
export declare function isBuiltinParamSource(value: string): value is SnailBuiltinParamSource;
|
|
35
|
+
/**
|
|
36
|
+
* Apply every decorated argument to the request.
|
|
37
|
+
*
|
|
38
|
+
* Descriptors run in ascending parameter-index order so `@Data("a") a` followed
|
|
39
|
+
* by `@Data("b") b` produces `{ a, b }` regardless of decorator evaluation order
|
|
40
|
+
* (TypeScript applies parameter decorators in *reverse* index order).
|
|
41
|
+
*/
|
|
42
|
+
export declare function applyParamDescriptors(ctx: SnailContext, args: readonly unknown[]): void;
|
|
43
|
+
/**
|
|
44
|
+
* Substitute `:placeholder` segments and write the final url onto the request.
|
|
45
|
+
*
|
|
46
|
+
* Runs after the argument decorators, because the whole point is to have their
|
|
47
|
+
* values available.
|
|
48
|
+
*/
|
|
49
|
+
export declare function finalizeRequestURL(ctx: SnailContext): void;
|
|
50
|
+
/** Build an `AxiosHeaders` instance from a plain record. */
|
|
51
|
+
export declare function toAxiosHeaders(headers: Record<string, unknown> | AxiosHeaders | undefined): AxiosHeaders;
|
|
52
|
+
/** Narrow a `SnailParamDescriptor` list to one source. */
|
|
53
|
+
export declare function descriptorsOf(descriptors: readonly SnailParamDescriptor[], source: string): SnailParamDescriptor[];
|
|
54
|
+
//# sourceMappingURL=args.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"args.d.ts","sourceRoot":"","sources":["../../src/core/args.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAGrC,OAAO,KAAK,EACV,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAEnB,MAAM,iBAAiB,CAAC;AAIzB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAiC9C,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,EAAE,kBAO/B,CAAC;AAEF,iDAAiD;AACjD,eAAO,MAAM,YAAY,EAAE,kBAW1B,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,kBAazB,CAAC;AAEF,qDAAqD;AACrD,eAAO,MAAM,aAAa,EAAE,kBAU3B,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAK7D,CAAC;AAEF,sDAAsD;AACtD,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAExF;AAED,6DAA6D;AAC7D,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,sDAAsD;AACtD,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,kDAAkD;AAClD,wBAAgB,YAAY,IAAI,MAAM,EAAE,CAEvC;AAED,uDAAuD;AACvD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,uBAAuB,CAEpF;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,YAAY,EACjB,IAAI,EAAE,SAAS,OAAO,EAAE,GACvB,IAAI,CAeN;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAc1D;AAED,4DAA4D;AAC5D,wBAAgB,cAAc,CAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,SAAS,GAC1D,YAAY,CAGd;AAED,0DAA0D;AAC1D,wBAAgB,aAAa,CAC3B,WAAW,EAAE,SAAS,oBAAoB,EAAE,EAC5C,MAAM,EAAE,MAAM,GACb,oBAAoB,EAAE,CAExB"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { SnailConnection } from "../typings/stream.js";
|
|
2
|
+
/**
|
|
3
|
+
* Attach `Symbol.dispose` / `Symbol.asyncDispose` to a connection.
|
|
4
|
+
*
|
|
5
|
+
* Defined as a helper rather than as two methods on each transport because the
|
|
6
|
+
* symbols are *optional*: `esnext.disposable` is a recent addition, and an engine
|
|
7
|
+
* that lacks them would otherwise end up with a property literally named
|
|
8
|
+
* `"undefined"` if the keys were written inline.
|
|
9
|
+
*
|
|
10
|
+
* The result is that a connection can be released three equivalent ways —
|
|
11
|
+
* `close()`, `using`, or `await using` — with `close()` staying the only one that
|
|
12
|
+
* works everywhere.
|
|
13
|
+
*/
|
|
14
|
+
export declare function withDispose<T extends SnailConnection>(connection: T): T;
|
|
15
|
+
//# sourceMappingURL=connection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.d.ts","sourceRoot":"","sources":["../../src/core/connection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEzD;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,eAAe,EAAE,UAAU,EAAE,CAAC,GAAG,CAAC,CAiBvE"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { AxiosResponse, InternalAxiosRequestConfig } from "axios";
|
|
2
|
+
import type { SnailLogger } from "./logger.js";
|
|
3
|
+
import type { SnailServer } from "./server.js";
|
|
4
|
+
import { StateBag } from "./state-bag.js";
|
|
5
|
+
import type { SnailApiOptions, SnailMethodType } from "../typings/api.js";
|
|
6
|
+
import type { SnailParamDescriptor } from "../typings/args.js";
|
|
7
|
+
import type { SnailResult } from "../typings/response.js";
|
|
8
|
+
import type { ResolvedServerOptions } from "../typings/server.js";
|
|
9
|
+
/** Everything needed to construct a request context. */
|
|
10
|
+
export interface SnailContextInit {
|
|
11
|
+
server: SnailServer<any, any, any, any>;
|
|
12
|
+
serverOptions: ResolvedServerOptions;
|
|
13
|
+
apiClass: new () => unknown;
|
|
14
|
+
api: unknown;
|
|
15
|
+
apiName: string;
|
|
16
|
+
apiOptions: Required<SnailApiOptions>;
|
|
17
|
+
methodName: string;
|
|
18
|
+
methodType: SnailMethodType;
|
|
19
|
+
route: string;
|
|
20
|
+
request: InternalAxiosRequestConfig;
|
|
21
|
+
descriptors: readonly SnailParamDescriptor[];
|
|
22
|
+
logger: SnailLogger;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Per-request context — the single object every plugin hook receives.
|
|
26
|
+
*
|
|
27
|
+
* One context exists per `send()` call, so plugins may store freely in
|
|
28
|
+
* `ctx.state` without worrying about concurrent requests colliding. That is a
|
|
29
|
+
* deliberate fix over the pre-rewrite design, where the "event map" and the
|
|
30
|
+
* request config lived on the long-lived `SnailMethod` instance and two
|
|
31
|
+
* overlapping `send()` calls shared mutable state.
|
|
32
|
+
*/
|
|
33
|
+
export declare class SnailContext {
|
|
34
|
+
/** The server instance that owns this request. */
|
|
35
|
+
readonly server: SnailServer<any, any, any, any>;
|
|
36
|
+
/** Fully resolved server options. */
|
|
37
|
+
readonly serverOptions: ResolvedServerOptions;
|
|
38
|
+
/** The decorated api class (constructor). */
|
|
39
|
+
readonly apiClass: new () => unknown;
|
|
40
|
+
/** The instantiated api class. */
|
|
41
|
+
readonly api: unknown;
|
|
42
|
+
/** Resolved api name — `@Api({ name })` or the class name. */
|
|
43
|
+
readonly apiName: string;
|
|
44
|
+
/** Fully resolved api options. */
|
|
45
|
+
readonly apiOptions: Required<SnailApiOptions>;
|
|
46
|
+
/** Decorated method name, e.g. `"getUser"`. */
|
|
47
|
+
readonly methodName: string;
|
|
48
|
+
/** Request verb. */
|
|
49
|
+
readonly methodType: SnailMethodType;
|
|
50
|
+
/**
|
|
51
|
+
* Url template for this method — api prefix joined with the method path,
|
|
52
|
+
* **before** `:placeholder` substitution.
|
|
53
|
+
*/
|
|
54
|
+
readonly route: string;
|
|
55
|
+
/** `server.api.method`, used in logs and error messages. */
|
|
56
|
+
readonly fullName: string;
|
|
57
|
+
/** Level-gated logger configured from `@Server({ logLevel })`. */
|
|
58
|
+
readonly logger: SnailLogger;
|
|
59
|
+
/** Parameter descriptors captured by the argument decorators. */
|
|
60
|
+
readonly descriptors: readonly SnailParamDescriptor[];
|
|
61
|
+
/** Plugin scratch space. Not visible to the caller. */
|
|
62
|
+
readonly state: StateBag;
|
|
63
|
+
/** Caller-visible reactive values, populated by `initMeta` hooks. */
|
|
64
|
+
meta: Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* The live axios request config.
|
|
67
|
+
*
|
|
68
|
+
* Plugins and argument decorators mutate this object in place; replacing it
|
|
69
|
+
* wholesale is also supported and is what `ctx.request = ...` means.
|
|
70
|
+
*/
|
|
71
|
+
request: InternalAxiosRequestConfig;
|
|
72
|
+
/** Values gathered from `@Params()` for `:placeholder` substitution. */
|
|
73
|
+
pathParams: Record<string, unknown>;
|
|
74
|
+
/** Set once a response exists — from the network **or** from a cache. */
|
|
75
|
+
response: AxiosResponse | undefined;
|
|
76
|
+
/** Set when the request failed. */
|
|
77
|
+
error: unknown;
|
|
78
|
+
/** Set once the envelope passed validation. */
|
|
79
|
+
result: SnailResult<any, any, any, any, any> | undefined;
|
|
80
|
+
/** Timestamp when `send()` started. */
|
|
81
|
+
startedAt: number;
|
|
82
|
+
/** Timestamp when the request settled. */
|
|
83
|
+
finishedAt: number | undefined;
|
|
84
|
+
private interrupted;
|
|
85
|
+
private cacheHit;
|
|
86
|
+
constructor(init: SnailContextInit);
|
|
87
|
+
/** Milliseconds elapsed since `send()` started. */
|
|
88
|
+
get elapsed(): number;
|
|
89
|
+
/**
|
|
90
|
+
* Stop the request.
|
|
91
|
+
*
|
|
92
|
+
* With a `response` argument the network call is skipped entirely and that
|
|
93
|
+
* response is used instead — this is exactly how a cache hit works. Without
|
|
94
|
+
* one, the request is abandoned and `send()` rejects with a cancellation error.
|
|
95
|
+
*/
|
|
96
|
+
interrupt(response?: AxiosResponse): void;
|
|
97
|
+
/** `true` when a plugin short-circuited the request. */
|
|
98
|
+
get isInterrupted(): boolean;
|
|
99
|
+
/** Record that the current response came from a cache. */
|
|
100
|
+
markCacheHit(): void;
|
|
101
|
+
/** `true` when the response was served from a cache. */
|
|
102
|
+
get isCacheHit(): boolean;
|
|
103
|
+
/** Replace the current response. */
|
|
104
|
+
setResponse(response: AxiosResponse | undefined): void;
|
|
105
|
+
/** Read the current response. */
|
|
106
|
+
getResponse(): AxiosResponse | undefined;
|
|
107
|
+
/** Read the current response, throwing when there is none. */
|
|
108
|
+
requireResponse(): AxiosResponse;
|
|
109
|
+
/** Replace the request config. */
|
|
110
|
+
setRequest(request: InternalAxiosRequestConfig): void;
|
|
111
|
+
/** Read the request config. */
|
|
112
|
+
getRequest(): InternalAxiosRequestConfig;
|
|
113
|
+
/** Replace the parsed result. */
|
|
114
|
+
setResult(result: SnailResult<any, any, any, any, any>): void;
|
|
115
|
+
/**
|
|
116
|
+
* Clear everything that belongs to one `send()` while keeping the context
|
|
117
|
+
* identity, so `meta` — and therefore the caller's reactive handles — survive
|
|
118
|
+
* a re-send.
|
|
119
|
+
*/
|
|
120
|
+
reset(request: InternalAxiosRequestConfig): void;
|
|
121
|
+
/** Shallow copy of the fields worth logging. */
|
|
122
|
+
describe(): Record<string, unknown>;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=context.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/core/context.ts"],"names":[],"mappings":"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"}
|