@batype/chainspec 1.0.0 → 2.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/README.md +34 -3
- package/lib/core/index.d.ts +1 -1
- package/lib/core/index.js +13 -9
- package/lib/core/templates.d.ts +2 -2
- package/lib/core/templates.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,30 @@ React + Vite本地生成器,普通Node/browser SDK与React/SWR入口分开。*
|
|
|
19
19
|
|
|
20
20
|
支持Swagger2.0、OpenAPI3.0/3.1 JSON的常见子集,本地引用、递归模型、枚举、组合类型、参数与JSON/平面表单。没有模型的响应使用unknown并提示;不是完整的OpenAPI或JSON Schema实现。远程引用、YAML和Swagger UI HTML需先转换。
|
|
21
21
|
|
|
22
|
+
## 2.0 调用约定
|
|
23
|
+
|
|
24
|
+
| 文档声明 | request / useRequest 参数 |
|
|
25
|
+
|---|---|
|
|
26
|
+
| 无请求体 | `(params, options)` |
|
|
27
|
+
| 有请求体 | `(params, body, options)` |
|
|
28
|
+
|
|
29
|
+
params包含`path/query/cookies`;`headers`统一放在options(标准HeadersInit),与signal、拦截器同级。是否有body取决于OpenAPI声明,而不是只看HTTP方法。没有path/query时用`{}`占位;可选body暂不传时,用`undefined`保留第二个位置。
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
// GET:params + options
|
|
33
|
+
API.main.users.getUser.request({path:{id:'42'}},{headers:{'X-Client':'web'}});
|
|
34
|
+
// POST:params + body + options
|
|
35
|
+
API.main.users.createUser.request({}, {name:'Ada',email:'ada@example.com'}, {headers:{'X-Client':'web'}});
|
|
36
|
+
// React 写hook:绑定body后,在事件里trigger();也可trigger(newBody)覆盖。
|
|
37
|
+
const bound=API.main.users.createUser.useRequest({}, {name:'Ada',email:'ada@example.com'}, {headers:{'X-Client':'web'}});
|
|
38
|
+
// 或者暂不绑定body,在事件里deferred.trigger(body)。
|
|
39
|
+
const deferred=API.main.users.createUser.useRequest({}, undefined, {headers:{'X-Client':'web'}});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
GET/HEAD hook options中的enabled、swr与headers同级;不再使用v1的`options.request.headers`。写hook不自动发送请求,必填body未绑定时,trigger也必须提供body。无body的写操作使用`useRequest(params, options)`及`trigger()`。
|
|
43
|
+
|
|
44
|
+
这是对v1生成SDK的破坏性变更。迁移步骤见[docs/migration-v2.md](docs/migration-v2.md)。
|
|
45
|
+
|
|
22
46
|
## 使用下载的SDK
|
|
23
47
|
|
|
24
48
|
将sdk文件夹放进TypeScript工程。普通入口不依赖React或SWR:
|
|
@@ -49,8 +73,8 @@ function UserView(){
|
|
|
49
73
|
return <span>{data?.name}</span>;
|
|
50
74
|
}
|
|
51
75
|
function Invite(){
|
|
52
|
-
const {trigger,isMutating}=API.main.users.createUser.useRequest();
|
|
53
|
-
return <button disabled={isMutating} onClick={()=>trigger({
|
|
76
|
+
const {trigger,isMutating}=API.main.users.createUser.useRequest({}, undefined, {headers:{'X-Client':'web'}});
|
|
77
|
+
return <button disabled={isMutating} onClick={()=>trigger({name:'Ada',email:'ada@example.com'})}>邀请</button>;
|
|
54
78
|
}
|
|
55
79
|
```
|
|
56
80
|
|
|
@@ -103,7 +127,14 @@ const project=generateSDK(jsonText,{namespace:'main',groupBy:'path',hooks:true})
|
|
|
103
127
|
// project.files: 文件名→内容;operations与diagnostics供自定义工具展示
|
|
104
128
|
```
|
|
105
129
|
|
|
106
|
-
npm
|
|
130
|
+
已正式发布到[npm:@batype/chainspec](https://www.npmjs.com/package/@batype/chainspec),也可下载[GitHub Release tarball](https://github.com/batype/chainspec/releases)。
|
|
131
|
+
|
|
132
|
+
```sh
|
|
133
|
+
npm install -D @batype/chainspec
|
|
134
|
+
npx --yes --package=@batype/chainspec chainspec --in openapi.json --out ./new-sdk --namespace main
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
生成器npm入口不需要React、SWR或ZIP依赖。生成SDK自身的React入口才需要SWR。
|
|
107
138
|
|
|
108
139
|
## 数据与安全边界
|
|
109
140
|
|
package/lib/core/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { GeneratorOptions, GeneratedProject } from "./model.js";
|
|
2
2
|
export type { GeneratorOptions, GeneratedProject, Diagnostic, Operation, } from "./model.js";
|
|
3
3
|
export { MAX_BYTES, MAX_OPERATIONS } from "./model.js";
|
|
4
|
-
export declare const VERSION = "
|
|
4
|
+
export declare const VERSION = "2.0.0";
|
|
5
5
|
export declare function generateSDK(input: string | Record<string, any>, options?: GeneratorOptions): GeneratedProject;
|
package/lib/core/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { parseDocument, normalize } from "./parse.js";
|
|
|
2
2
|
import { createSchemaWriter } from "./schema.js";
|
|
3
3
|
import { RUNTIME_SOURCE, HOOKS_SOURCE, RUNTIME_LICENSE } from "./templates.js";
|
|
4
4
|
export { MAX_BYTES, MAX_OPERATIONS } from "./model.js";
|
|
5
|
-
export const VERSION = "
|
|
5
|
+
export const VERSION = "2.0.0";
|
|
6
6
|
const q = JSON.stringify;
|
|
7
7
|
const banner = "// Generated by ChainSpec. Do not edit; regenerate from your OpenAPI document.\n";
|
|
8
8
|
export function generateSDK(input, options = {}) {
|
|
@@ -27,16 +27,19 @@ export function generateSDK(input, options = {}) {
|
|
|
27
27
|
types += "export interface Operations {\n";
|
|
28
28
|
for (const op of operations) {
|
|
29
29
|
const fields = [];
|
|
30
|
-
for (const loc of ["path", "query", "
|
|
30
|
+
for (const loc of ["path", "query", "cookie"]) {
|
|
31
31
|
const ps = op.parameters.filter((p) => p.in === loc);
|
|
32
32
|
if (!ps.length)
|
|
33
33
|
continue;
|
|
34
|
-
const field = loc === "
|
|
34
|
+
const field = loc === "cookie" ? "cookies" : loc;
|
|
35
35
|
fields.push(`${field}${ps.some((p) => p.required) ? "" : "?"}: { ${ps.map((p) => `${q(p.name)}${p.required ? "" : "?"}: ${writer.type(p.schema, "input", op.id + "." + loc + "." + p.name)};`).join(" ")} };`);
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const body = op.body
|
|
38
|
+
? writer.type(op.body.schema, "input", op.id + ".body")
|
|
39
|
+
: "never";
|
|
40
|
+
const headerParams = op.parameters.filter((p) => p.in === "header");
|
|
41
|
+
const headers = `{ ${headerParams.map((p) => `${q(p.name)}${p.required ? "" : "?"}: ${writer.type(p.schema, "input", op.id + ".header." + p.name)};`).join(" ")} }`;
|
|
42
|
+
types += ` ${q(op.id)}: { params: ${fields.length ? `{ ${fields.join(" ")} }` : "Record<string, never>"}; body: ${body}; headers: ${headers}; output: ${writer.type(op.response, "output", op.id + ".response")}; };\n`;
|
|
40
43
|
}
|
|
41
44
|
types += "}\n";
|
|
42
45
|
const groups = new Map();
|
|
@@ -61,7 +64,7 @@ export function generateSDK(input, options = {}) {
|
|
|
61
64
|
? "QueryEndpoint"
|
|
62
65
|
: "MutationEndpoint"
|
|
63
66
|
: "Endpoint";
|
|
64
|
-
text += `${op.deprecated ? "/** @deprecated */\n" : ""} ${q(op.name)}: ${ep}<${index(op.id, "
|
|
67
|
+
text += `${op.deprecated ? "/** @deprecated */\n" : ""} ${q(op.name)}: ${ep}<${index(op.id, "params")},${index(op.id, "body")},${index(op.id, "output")},${Boolean(op.body)},${op.body?.required ?? false}>;\n`;
|
|
65
68
|
}
|
|
66
69
|
text += " };\n";
|
|
67
70
|
}
|
|
@@ -77,11 +80,12 @@ export function generateSDK(input, options = {}) {
|
|
|
77
80
|
controller: op.controller,
|
|
78
81
|
name: op.name,
|
|
79
82
|
parameters: op.parameters.map(({ schema, ...p }) => p),
|
|
83
|
+
hasBody: Boolean(op.body),
|
|
80
84
|
bodyRequired: op.body?.required,
|
|
81
85
|
mediaType: op.body?.mediaType,
|
|
82
86
|
responseType: op.responseType,
|
|
83
87
|
};
|
|
84
|
-
let endpoint = `client.endpoint<${index(op.id, "
|
|
88
|
+
let endpoint = `client.endpoint<${index(op.id, "params")},${index(op.id, "body")},${index(op.id, "output")},${Boolean(op.body)},${op.body?.required ?? false}>(${q(definition)})`;
|
|
85
89
|
if (react)
|
|
86
90
|
endpoint = `${op.method === "GET" || op.method === "HEAD" ? "withQuery" : "withMutation"}(${endpoint})`;
|
|
87
91
|
text += `${q(op.name)}: ${endpoint},\n`;
|
|
@@ -120,7 +124,7 @@ export function generateSDK(input, options = {}) {
|
|
|
120
124
|
const first = operations.find((o) => o.method === "GET") ?? operations[0];
|
|
121
125
|
const access = `API.${namespace}[${q(first.controller)}][${q(first.name)}]`;
|
|
122
126
|
files["README.md"] =
|
|
123
|
-
`# Generated SDK\n\nGenerated locally with ChainSpec ${VERSION}. Copy these files into your TypeScript project. No runtime code evaluation or request is performed by the generator.\n\n## Setup\n\nNode22+ or a browser with Fetch, Headers, FormData, Blob. TypeScript moduleResolution Bundler or NodeNext; .js imports resolve to the corresponding .ts sources.\n\nPlain SDK: import {createAPI} from './index.js'. No React/SWR dependency.\n\n${options.hooks === false ? "" : "React SDK: npm install swr@^2.5.1 react@^19.3.1, then import {createReactAPI} from './react.js'. Use the returned API as a module-level singleton (or memoize it per Provider/SSR request). Do not create clients on every render.\n\n"}## Calls\n\n\`\`\`ts\nimport {createAPI} from './index.js';\nconst API=createAPI({baseUrl:${q(baseUrl)},cacheScope:()=> 'tenant:user:auth-revision'});\n// Inspect generated Operations
|
|
127
|
+
`# Generated SDK\n\nGenerated locally with ChainSpec ${VERSION}. Copy these files into your TypeScript project. No runtime code evaluation or request is performed by the generator.\n\n## Setup\n\nNode22+ or a browser with Fetch, Headers, FormData, Blob. TypeScript moduleResolution Bundler or NodeNext; .js imports resolve to the corresponding .ts sources.\n\nPlain SDK: import {createAPI} from './index.js'. No React/SWR dependency.\n\n${options.hooks === false ? "" : "React SDK: npm install swr@^2.5.1 react@^19.3.1, then import {createReactAPI} from './react.js'. Use the returned API as a module-level singleton (or memoize it per Provider/SSR request). Do not create clients on every render.\n\n"}## Calls\n\n\`\`\`ts\nimport {createAPI} from './index.js';\nconst API=createAPI({baseUrl:${q(baseUrl)},cacheScope:()=> 'tenant:user:auth-revision'});\n// Inspect generated Operations params/body before calling:\n// ${access}.request(params${first.body ? ", body" : ""}, options);\n\`\`\`\n\n.request() returns the documented response body. The first params object contains path/query/cookies. With a documented request body: request(params, body, options). Without one: request(params?, options). Headers are options.headers (standard HeadersInit) and may also be set globally or by beforeRequest. Empty params are {} when a body is present. Operations exposes params/body/headers/output types. Required arguments are required in TypeScript and checked for presence by the default runtime. Schema validation is NOT performed. Missing response schemas are unknown. JS numbers can lose int64 precision: ask your server for strings if required.\n\nGET/HEAD .useRequest(params, {enabled,headers,signal,swr}) reads with SWR; null params or enabled:false pauses it. If a query operation documents a body, use the same three positions as request. Other methods .useRequest(params, body, {headers,signal,swr}) (also .useMutation) bind params and an optional body, never send automatically. trigger() uses the bound body; trigger(body) supplies or overrides it. If a required body was not bound, TypeScript requires trigger(body). For operations without a body use .useRequest(params, options) then trigger(). Trigger options, such as throwOnError, are its second argument. Hook options are flat: headers/signal/interceptors are alongside swr, not under request. Hooks must be called unconditionally at React component/custom-hook top level. HEAD returns no body in Fetch.\n\nCache keys isolate client instances, base URL, operation, inputs, request headers and cacheScope. Change cacheScope after switching users/tenants or changing authorization within one client. Do not put tokens in cacheScope. Logout should also clear your SWR Provider cache. Request interceptors do not automatically change cache keys.\n\n## Extension points\n\n- request: async context => ({data,status,headers:new Headers()}) replaces the entire Fetch transport. context includes URL, method, headers, structured body, mediaType, operation and AbortSignal. Custom adapters must preserve the declared response shape; returning an Axios response unchanged is NOT compatible (normalize response.headers).\n- beforeRequest: async context => context (or void); global handlers run before per-call handlers. Add auth headers, tracing, or cancellation here.\n- afterResponse: async (response,context) => response (or void), for successful responses only. Unwrapping changes the schema contract; do not unwrap unless the server schema already describes the unwrapped body.\n- onError: async (error,context) => void; errors are rethrown. Non-2xx status becomes ApiError with status/data/headers. Network and AbortError are preserved. No automatic retry in .request(). SWR has its own configurable retries.\n\nSupported query serialization: form (arrays and flat objects), spaceDelimited, pipeDelimited, flat deepObject. Path/header simple serialization; header objects and nested query objects need a custom serializer. Cookie parameters require a custom Node request adapter (Fetch in browsers cannot set Cookie headers). JSON, flat multipart and URL-encoded bodies supported; nested forms need a custom adapter. JSON-schema constraints, auth schemes, oneOf exclusivity, server variables, parameter serialization and streaming may need application validation.\n\nThe generator supports local refs, not remote refs. Review manifest.json diagnostics, media choices and operation names. Do not add this SDK or its spec to a public repository without checking that disclosure is intended.\n`;
|
|
124
128
|
diagnostics.push({
|
|
125
129
|
level: "info",
|
|
126
130
|
code: "static-types",
|
package/lib/core/templates.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const RUNTIME_SOURCE = "/** ChainSpec runtime. Generated SDK types describe the spec, not runtime validation. */\nexport type Parameter = {\n name: string;\n in: \"path\" | \"query\" | \"header\" | \"cookie\";\n required?: boolean;\n style?: string;\n explode?: boolean;\n};\nexport type Definition = {\n id: string;\n method: string;\n path: string;\n controller: string;\n name: string;\n parameters: Parameter[];\n bodyRequired?: boolean;\n mediaType?: string;\n responseType: \"json\" | \"text\" | \"blob\" | \"void\" | \"auto\";\n};\nexport type Input = {\n path?: Record<string, unknown>;\n query?: Record<string, unknown>;\n headers?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n body?: unknown;\n};\nexport interface RequestContext {\n url: string;\n method: string;\n headers: Headers;\n body: unknown;\n mediaType?: string;\n signal?: AbortSignal;\n readonly operation: Definition;\n}\nexport interface TransportResponse {\n data: unknown;\n status: number;\n headers: Headers;\n}\nexport type RequestAdapter = (\n context: RequestContext,\n) => Promise<TransportResponse>;\nexport type BeforeRequest = (\n context: RequestContext,\n) => void | RequestContext | Promise<void | RequestContext>;\nexport type AfterResponse = (\n response: TransportResponse,\n context: RequestContext,\n) => void | TransportResponse | Promise<void | TransportResponse>;\nexport type OnError = (\n error: unknown,\n context: RequestContext | undefined,\n) => void | Promise<void>;\nexport interface RequestOptions {\n signal?: AbortSignal;\n headers?: HeadersInit;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n}\nexport interface ClientConfig {\n baseUrl?: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n request?: RequestAdapter;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n /** Change on tenant/user/auth changes. Never put bearer tokens here. */ cacheScope?:\n | string\n | (() => string);\n}\nexport type RequestArgs<I> = {} extends I\n ? [input?: I, options?: RequestOptions]\n : [input: I, options?: RequestOptions];\nexport interface Endpoint<I, R> {\n readonly definition: Definition;\n request(...args: RequestArgs<I>): Promise<R>;\n getKey(input?: I, options?: RequestOptions): readonly unknown[];\n}\nexport class ApiError extends Error {\n constructor(\n public status: number,\n public data: unknown,\n public headers: Headers,\n ) {\n super(`HTTP ${status}`);\n this.name = \"ApiError\";\n }\n}\nconst scalar = (value: unknown): string => {\n if (value === null) return \"\";\n if ([\"string\", \"number\", \"boolean\"].includes(typeof value))\n return String(value);\n throw new Error(\"Parameter must be a scalar or a declared array/object\");\n};\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\nfunction queryEntries(\n name: string,\n value: unknown,\n style = \"form\",\n explode = true,\n): [string, string][] {\n if (value === undefined) return [];\n if (style === \"deepObject\") {\n if (!isRecord(value)) throw new Error(\"deepObject needs an object\");\n return Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [`${name}[${k}]`, scalar(v)]);\n }\n if (Array.isArray(value)) {\n if (style === \"form\" && explode) return value.map((v) => [name, scalar(v)]);\n const delimiter =\n style === \"spaceDelimited\" ? \" \" : style === \"pipeDelimited\" ? \"|\" : \",\";\n return [[name, value.map(scalar).join(delimiter)]];\n }\n if (isRecord(value)) {\n const values = Object.entries(value).filter(([, v]) => v !== undefined);\n return explode\n ? values.map(([k, v]) => [k, scalar(v)])\n : [[name, values.flatMap(([k, v]) => [k, scalar(v)]).join(\",\")]];\n }\n return [[name, scalar(value)]];\n}\nfunction pathValue(value: unknown, explode = false): string {\n const enc = (v: unknown) => encodeURIComponent(scalar(v));\n if (Array.isArray(value)) return value.map(enc).join(\",\");\n if (isRecord(value))\n return Object.entries(value)\n .map(([k, v]) =>\n explode ? `${enc(k)}=${enc(v)}` : `${enc(k)},${enc(v)}`,\n )\n .join(\",\");\n return enc(value);\n}\nexport async function defaultRequest(\n ctx: RequestContext,\n): Promise<TransportResponse> {\n const headers = new Headers(ctx.headers);\n let body: BodyInit | undefined;\n if (ctx.body !== undefined) {\n if (ctx.method === \"GET\" || ctx.method === \"HEAD\")\n throw new Error(\n \"Fetch cannot send GET/HEAD bodies; configure a request adapter\",\n );\n if (ctx.mediaType?.includes(\"json\")) {\n body = JSON.stringify(ctx.body);\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (ctx.mediaType?.startsWith(\"multipart/form-data\")) {\n if (!isRecord(ctx.body))\n throw new Error(\"Multipart body must be an object\");\n const form = new FormData();\n for (const [name, value] of Object.entries(ctx.body)) {\n if (value === undefined) continue;\n for (const v of Array.isArray(value) ? value : [value])\n form.append(name, v instanceof Blob ? v : scalar(v));\n }\n body = form;\n headers.delete(\"Content-Type\");\n } else if (ctx.mediaType?.startsWith(\"application/x-www-form-urlencoded\")) {\n if (!isRecord(ctx.body)) throw new Error(\"Form body must be an object\");\n const form = new URLSearchParams();\n for (const [name, value] of Object.entries(ctx.body))\n for (const [k, v] of queryEntries(name, value)) form.append(k, v);\n body = form.toString();\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (\n typeof ctx.body === \"string\" ||\n ctx.body instanceof Blob ||\n ctx.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(ctx.body)\n ) {\n body = ctx.body as BodyInit;\n if (ctx.mediaType && !headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else\n throw new Error(\"Unsupported body format; configure a request adapter\");\n }\n const response = await fetch(ctx.url, {\n method: ctx.method,\n headers,\n body,\n signal: ctx.signal,\n });\n let data: unknown;\n const mode = ctx.operation.responseType;\n if (\n response.status === 204 ||\n response.status === 205 ||\n ctx.method === \"HEAD\"\n )\n data = undefined;\n else if (!response.ok) {\n const text = await response.text();\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n } else if (mode === \"void\") data = undefined;\n else if (\n mode === \"blob\" ||\n (mode === \"auto\" &&\n !response.headers.get(\"Content-Type\")?.includes(\"json\") &&\n !response.headers.get(\"Content-Type\")?.startsWith(\"text/\"))\n )\n data = await response.blob();\n else {\n const text = await response.text();\n data =\n mode === \"json\" ||\n (mode === \"auto\" &&\n response.headers.get(\"Content-Type\")?.includes(\"json\"))\n ? text\n ? JSON.parse(text)\n : undefined\n : text;\n }\n return { data, status: response.status, headers: response.headers };\n}\nconst runtimeId = Math.random().toString(36).slice(2);\nlet nextClient = 0;\nexport function createClient(config: ClientConfig = {}) {\n const clientId = `chainspec-${runtimeId}-${++nextClient}`;\n const base = config.baseUrl ?? \"\";\n const before = [...(config.beforeRequest ?? [])],\n after = [...(config.afterResponse ?? [])],\n errors = [...(config.onError ?? [])];\n return {\n endpoint<I, R>(definition: Definition): Endpoint<I, R> {\n const def = Object.freeze({\n ...definition,\n parameters: definition.parameters.map((p) => Object.freeze({ ...p })),\n });\n async function execute(\n input: Input = {},\n options: RequestOptions = {},\n ): Promise<R> {\n let context: RequestContext | undefined;\n try {\n let path = def.path;\n for (const p of def.parameters.filter((p) => p.in === \"path\")) {\n const value = input.path?.[p.name];\n if (value === undefined || value === null)\n throw new Error(`Missing path parameter: ${p.name}`);\n path = path.split(`{${p.name}}`).join(pathValue(value, p.explode));\n }\n if (/\\{[^}]+\\}/.test(path))\n throw new Error(\"Unresolved path parameter\");\n const query = new URLSearchParams();\n const headers = new Headers(\n typeof config.headers === \"function\"\n ? await config.headers()\n : config.headers,\n );\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n for (const p of def.parameters.filter((p) => p.in !== \"path\")) {\n const bag =\n p.in === \"query\"\n ? input.query\n : p.in === \"header\"\n ? input.headers\n : input.cookies;\n const value = bag?.[p.name];\n if (p.required && (value === undefined || value === null))\n throw new Error(`Missing ${p.in} parameter: ${p.name}`);\n if (value === undefined) continue;\n if (p.in === \"query\")\n for (const [k, v] of queryEntries(\n p.name,\n value,\n p.style,\n p.explode ?? true,\n ))\n query.append(k, v);\n else if (p.in === \"header\")\n headers.set(\n p.name,\n Array.isArray(value)\n ? value.map(scalar).join(\",\")\n : scalar(value),\n );\n else if (config.request)\n headers.append(\n \"Cookie\",\n `${p.name}=${encodeURIComponent(scalar(value))}`,\n );\n else\n throw new Error(\n \"Cookie parameters require a custom Node request adapter; browser Fetch cannot set Cookie headers\",\n );\n }\n if (def.bodyRequired && input.body === undefined)\n throw new Error(\"Request body is required\");\n const url = `${base.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}${query.size ? `?${query}` : \"\"}`;\n context = {\n url,\n method: def.method,\n headers,\n body: input.body,\n mediaType: def.mediaType,\n signal: options.signal,\n operation: def,\n };\n for (const fn of [...before, ...(options.beforeRequest ?? [])])\n context = (await fn(context)) ?? context;\n let response = await (config.request ?? defaultRequest)(context);\n if (\n !Number.isInteger(response.status) ||\n response.status < 100 ||\n response.status > 599\n )\n throw new Error(\"Adapter must return {data,status,headers}\");\n if (response.status < 200 || response.status >= 300)\n throw new ApiError(\n response.status,\n response.data,\n response.headers,\n );\n for (const fn of [...after, ...(options.afterResponse ?? [])])\n response = (await fn(response, context)) ?? response;\n return response.data as R;\n } catch (error) {\n for (const fn of [...errors, ...(options.onError ?? [])])\n await fn(error, context);\n throw error;\n }\n }\n return {\n definition: def,\n request: execute as Endpoint<I, R>[\"request\"],\n getKey(input?: I, options?: RequestOptions) {\n const scope =\n typeof config.cacheScope === \"function\"\n ? config.cacheScope()\n : (config.cacheScope ?? \"\");\n return [\n clientId,\n base,\n scope,\n def.id,\n input ?? {},\n Array.from(new Headers(options?.headers).entries()),\n ];\n },\n };\n },\n };\n}\n";
|
|
2
|
-
export declare const HOOKS_SOURCE = "import useSWR, { type SWRConfiguration, type SWRResponse } from \"swr\";\nimport useSWRMutation, {\n type SWRMutationConfiguration,\n type SWRMutationResponse,\n} from \"swr/mutation\";\nimport type
|
|
1
|
+
export declare const RUNTIME_SOURCE = "/** ChainSpec runtime. Generated SDK types describe the spec, not runtime validation. */\nexport type Parameter = {\n name: string;\n in: \"path\" | \"query\" | \"header\" | \"cookie\";\n required?: boolean;\n style?: string;\n explode?: boolean;\n};\nexport type Definition = {\n id: string;\n method: string;\n path: string;\n controller: string;\n name: string;\n parameters: Parameter[];\n hasBody: boolean;\n bodyRequired?: boolean;\n mediaType?: string;\n responseType: \"json\" | \"text\" | \"blob\" | \"void\" | \"auto\";\n};\nexport type Input = {\n path?: Record<string, unknown>;\n query?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n};\nexport interface RequestContext {\n url: string;\n method: string;\n headers: Headers;\n body: unknown;\n mediaType?: string;\n signal?: AbortSignal;\n readonly operation: Definition;\n}\nexport interface TransportResponse {\n data: unknown;\n status: number;\n headers: Headers;\n}\nexport type RequestAdapter = (\n context: RequestContext,\n) => Promise<TransportResponse>;\nexport type BeforeRequest = (\n context: RequestContext,\n) => void | RequestContext | Promise<void | RequestContext>;\nexport type AfterResponse = (\n response: TransportResponse,\n context: RequestContext,\n) => void | TransportResponse | Promise<void | TransportResponse>;\nexport type OnError = (\n error: unknown,\n context: RequestContext | undefined,\n) => void | Promise<void>;\nexport interface RequestOptions {\n signal?: AbortSignal;\n headers?: HeadersInit;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n}\nexport interface ClientConfig {\n baseUrl?: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n request?: RequestAdapter;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n /** Change on tenant/user/auth changes. Never put bearer tokens here. */ cacheScope?:\n | string\n | (() => string);\n}\nexport type RequestArgs<\n P,\n B,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? Required extends true\n ? [params: P, body: B, options?: RequestOptions]\n : [params: P, body?: B, options?: RequestOptions]\n : {} extends P\n ? [params?: P, options?: RequestOptions]\n : [params: P, options?: RequestOptions];\nexport interface Endpoint<\n P,\n B,\n R,\n H extends boolean = false,\n Required extends boolean = false,\n> {\n readonly definition: Definition;\n request(...args: RequestArgs<P, B, H, Required>): Promise<R>;\n getKey(...args: RequestArgs<P, B, H, Required>): readonly unknown[];\n}\n/** Body position is fixed by the generated operation contract, never inferred from object keys. */\nexport function splitArgs(\n definition: Definition,\n args: readonly unknown[],\n): { params: Input; body: unknown; options: RequestOptions } {\n return {\n params: (args[0] ?? {}) as Input,\n body: definition.hasBody ? args[1] : undefined,\n options: (args[definition.hasBody ? 2 : 1] ?? {}) as RequestOptions,\n };\n}\nexport class ApiError extends Error {\n constructor(\n public status: number,\n public data: unknown,\n public headers: Headers,\n ) {\n super(`HTTP ${status}`);\n this.name = \"ApiError\";\n }\n}\nconst scalar = (value: unknown): string => {\n if (value === null) return \"\";\n if ([\"string\", \"number\", \"boolean\"].includes(typeof value))\n return String(value);\n throw new Error(\"Parameter must be a scalar or a declared array/object\");\n};\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\nfunction queryEntries(\n name: string,\n value: unknown,\n style = \"form\",\n explode = true,\n): [string, string][] {\n if (value === undefined) return [];\n if (style === \"deepObject\") {\n if (!isRecord(value)) throw new Error(\"deepObject needs an object\");\n return Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [`${name}[${k}]`, scalar(v)]);\n }\n if (Array.isArray(value)) {\n if (style === \"form\" && explode) return value.map((v) => [name, scalar(v)]);\n const delimiter =\n style === \"spaceDelimited\" ? \" \" : style === \"pipeDelimited\" ? \"|\" : \",\";\n return [[name, value.map(scalar).join(delimiter)]];\n }\n if (isRecord(value)) {\n const values = Object.entries(value).filter(([, v]) => v !== undefined);\n return explode\n ? values.map(([k, v]) => [k, scalar(v)])\n : [[name, values.flatMap(([k, v]) => [k, scalar(v)]).join(\",\")]];\n }\n return [[name, scalar(value)]];\n}\nfunction pathValue(value: unknown, explode = false): string {\n const enc = (v: unknown) => encodeURIComponent(scalar(v));\n if (Array.isArray(value)) return value.map(enc).join(\",\");\n if (isRecord(value))\n return Object.entries(value)\n .map(([k, v]) =>\n explode ? `${enc(k)}=${enc(v)}` : `${enc(k)},${enc(v)}`,\n )\n .join(\",\");\n return enc(value);\n}\nexport async function defaultRequest(\n ctx: RequestContext,\n): Promise<TransportResponse> {\n const headers = new Headers(ctx.headers);\n let body: BodyInit | undefined;\n if (ctx.body !== undefined) {\n if (ctx.method === \"GET\" || ctx.method === \"HEAD\")\n throw new Error(\n \"Fetch cannot send GET/HEAD bodies; configure a request adapter\",\n );\n if (ctx.mediaType?.includes(\"json\")) {\n body = JSON.stringify(ctx.body);\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (ctx.mediaType?.startsWith(\"multipart/form-data\")) {\n if (!isRecord(ctx.body))\n throw new Error(\"Multipart body must be an object\");\n const form = new FormData();\n for (const [name, value] of Object.entries(ctx.body)) {\n if (value === undefined) continue;\n for (const v of Array.isArray(value) ? value : [value])\n form.append(name, v instanceof Blob ? v : scalar(v));\n }\n body = form;\n headers.delete(\"Content-Type\");\n } else if (ctx.mediaType?.startsWith(\"application/x-www-form-urlencoded\")) {\n if (!isRecord(ctx.body)) throw new Error(\"Form body must be an object\");\n const form = new URLSearchParams();\n for (const [name, value] of Object.entries(ctx.body))\n for (const [k, v] of queryEntries(name, value)) form.append(k, v);\n body = form.toString();\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (\n typeof ctx.body === \"string\" ||\n ctx.body instanceof Blob ||\n ctx.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(ctx.body)\n ) {\n body = ctx.body as BodyInit;\n if (ctx.mediaType && !headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else\n throw new Error(\"Unsupported body format; configure a request adapter\");\n }\n const response = await fetch(ctx.url, {\n method: ctx.method,\n headers,\n body,\n signal: ctx.signal,\n });\n let data: unknown;\n const mode = ctx.operation.responseType;\n if (\n response.status === 204 ||\n response.status === 205 ||\n ctx.method === \"HEAD\"\n )\n data = undefined;\n else if (!response.ok) {\n const text = await response.text();\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n } else if (mode === \"void\") data = undefined;\n else if (\n mode === \"blob\" ||\n (mode === \"auto\" &&\n !response.headers.get(\"Content-Type\")?.includes(\"json\") &&\n !response.headers.get(\"Content-Type\")?.startsWith(\"text/\"))\n )\n data = await response.blob();\n else {\n const text = await response.text();\n data =\n mode === \"json\" ||\n (mode === \"auto\" &&\n response.headers.get(\"Content-Type\")?.includes(\"json\"))\n ? text\n ? JSON.parse(text)\n : undefined\n : text;\n }\n return { data, status: response.status, headers: response.headers };\n}\nconst runtimeId = Math.random().toString(36).slice(2);\nlet nextClient = 0;\nexport function createClient(config: ClientConfig = {}) {\n const clientId = `chainspec-${runtimeId}-${++nextClient}`;\n const base = config.baseUrl ?? \"\";\n const before = [...(config.beforeRequest ?? [])],\n after = [...(config.afterResponse ?? [])],\n errors = [...(config.onError ?? [])];\n return {\n endpoint<P, B, R, H extends boolean, Required extends boolean>(\n definition: Definition,\n ): Endpoint<P, B, R, H, Required> {\n const def = Object.freeze({\n ...definition,\n parameters: definition.parameters.map((p) => Object.freeze({ ...p })),\n });\n async function execute(...args: unknown[]): Promise<R> {\n const { params: input, body, options } = splitArgs(def, args);\n let context: RequestContext | undefined;\n try {\n let path = def.path;\n for (const p of def.parameters.filter((p) => p.in === \"path\")) {\n const value = input.path?.[p.name];\n if (value === undefined || value === null)\n throw new Error(`Missing path parameter: ${p.name}`);\n path = path.split(`{${p.name}}`).join(pathValue(value, p.explode));\n }\n if (/\\{[^}]+\\}/.test(path))\n throw new Error(\"Unresolved path parameter\");\n const query = new URLSearchParams();\n const headers = new Headers(\n typeof config.headers === \"function\"\n ? await config.headers()\n : config.headers,\n );\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n for (const p of def.parameters.filter(\n (p) => p.in === \"query\" || p.in === \"cookie\",\n )) {\n const bag = p.in === \"query\" ? input.query : input.cookies;\n const value = bag?.[p.name];\n if (p.required && (value === undefined || value === null))\n throw new Error(`Missing ${p.in} parameter: ${p.name}`);\n if (value === undefined) continue;\n if (p.in === \"query\")\n for (const [k, v] of queryEntries(\n p.name,\n value,\n p.style,\n p.explode ?? true,\n ))\n query.append(k, v);\n else if (config.request)\n headers.append(\n \"Cookie\",\n `${p.name}=${encodeURIComponent(scalar(value))}`,\n );\n else\n throw new Error(\n \"Cookie parameters require a custom Node request adapter; browser Fetch cannot set Cookie headers\",\n );\n }\n if (def.bodyRequired && body === undefined)\n throw new Error(\"Request body is required\");\n const url = `${base.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}${query.size ? `?${query}` : \"\"}`;\n context = {\n url,\n method: def.method,\n headers,\n body,\n mediaType: def.mediaType,\n signal: options.signal,\n operation: def,\n };\n for (const fn of [...before, ...(options.beforeRequest ?? [])])\n context = (await fn(context)) ?? context;\n for (const p of def.parameters.filter(\n (p) => p.in === \"header\" && p.required,\n )) {\n if (!context.headers.has(p.name))\n throw new Error(`Missing header parameter: ${p.name}`);\n }\n let response = await (config.request ?? defaultRequest)(context);\n if (\n !Number.isInteger(response.status) ||\n response.status < 100 ||\n response.status > 599\n )\n throw new Error(\"Adapter must return {data,status,headers}\");\n if (response.status < 200 || response.status >= 300)\n throw new ApiError(\n response.status,\n response.data,\n response.headers,\n );\n for (const fn of [...after, ...(options.afterResponse ?? [])])\n response = (await fn(response, context)) ?? response;\n return response.data as R;\n } catch (error) {\n for (const fn of [...errors, ...(options.onError ?? [])])\n await fn(error, context);\n throw error;\n }\n }\n return {\n definition: def,\n request: execute as Endpoint<P, B, R, H, Required>[\"request\"],\n getKey(...args: RequestArgs<P, B, H, Required>) {\n const { params, body, options } = splitArgs(def, args);\n const scope =\n typeof config.cacheScope === \"function\"\n ? config.cacheScope()\n : (config.cacheScope ?? \"\");\n return [\n clientId,\n base,\n scope,\n def.id,\n params,\n body,\n Array.from(new Headers(options?.headers).entries()),\n ];\n },\n };\n },\n };\n}\n";
|
|
2
|
+
export declare const HOOKS_SOURCE = "import useSWR, { type SWRConfiguration, type SWRResponse } from \"swr\";\nimport useSWRMutation, {\n type SWRMutationConfiguration,\n type SWRMutationResponse,\n} from \"swr/mutation\";\nimport {\n splitArgs,\n type Endpoint,\n type RequestArgs,\n type RequestOptions,\n} from \"./runtime.js\";\nexport type QueryOptions<R> = RequestOptions & {\n enabled?: boolean;\n swr?: SWRConfiguration<R, Error>;\n};\nexport type QueryArgs<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? Required extends true\n ? [params: P | null, body: B, options?: QueryOptions<R>]\n : [params: P | null, body?: B, options?: QueryOptions<R>]\n : {} extends P\n ? [params?: P | null, options?: QueryOptions<R>]\n : [params: P | null, options?: QueryOptions<R>];\nexport type MutationOptions<B, R> = RequestOptions & {\n swr?: SWRMutationConfiguration<R, Error, readonly unknown[], B> & {\n throwOnError?: boolean;\n };\n};\nexport type TriggerOptions<B, R> = SWRMutationConfiguration<\n R,\n Error,\n readonly unknown[],\n B\n> & { throwOnError?: boolean };\nexport type MutationResult<B, R, Bound extends boolean> = Omit<\n SWRMutationResponse<R, Error, readonly unknown[], B>,\n \"trigger\"\n> & {\n trigger(\n ...args: Bound extends true\n ? [body?: B, options?: TriggerOptions<B, R>]\n : [body: B, options?: TriggerOptions<B, R>]\n ): Promise<R | undefined>;\n};\nexport type MutationHook<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? {\n (\n params: P,\n body: B,\n options?: MutationOptions<B, R>,\n ): MutationResult<B, R, true>;\n (\n params: P,\n body?: undefined,\n options?: MutationOptions<B, R>,\n ): MutationResult<B, R, Required extends true ? false : true>;\n }\n : (\n ...args: {} extends P\n ? [params?: P, options?: MutationOptions<never, R>]\n : [params: P, options?: MutationOptions<never, R>]\n ) => MutationResult<never, R, true>;\nexport type QueryEndpoint<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = Endpoint<P, B, R, H, Required> & {\n useRequest(...args: QueryArgs<P, B, R, H, Required>): SWRResponse<R, Error>;\n};\nexport type MutationEndpoint<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = Endpoint<P, B, R, H, Required> & {\n useRequest: MutationHook<P, B, R, H, Required>;\n useMutation: MutationHook<P, B, R, H, Required>;\n};\nexport function withQuery<P, B, R, H extends boolean, Required extends boolean>(\n endpoint: Endpoint<P, B, R, H, Required>,\n): QueryEndpoint<P, B, R, H, Required> {\n function useRequest(...args: QueryArgs<P, B, R, H, Required>) {\n const { options } = splitArgs(endpoint.definition, args);\n const config = options as QueryOptions<R>;\n const requestArgs = args as unknown as RequestArgs<P, B, H, Required>;\n return useSWR<R, Error>(\n args[0] !== null && config.enabled !== false\n ? endpoint.getKey(...requestArgs)\n : null,\n () => endpoint.request(...requestArgs),\n config.swr,\n );\n }\n return Object.assign(endpoint, { useRequest });\n}\nexport function withMutation<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n>(\n endpoint: Endpoint<P, B, R, H, Required>,\n): MutationEndpoint<P, B, R, H, Required> {\n function useMutation(...args: unknown[]) {\n const {\n params,\n body: preset,\n options,\n } = splitArgs(endpoint.definition, args);\n const config = options as MutationOptions<B, R>;\n const boundArgs = (endpoint.definition.hasBody\n ? [params, preset, options]\n : [params, options]) as unknown as RequestArgs<P, B, H, Required>;\n const state = useSWRMutation<R, Error, readonly unknown[], B>(\n endpoint.getKey(...boundArgs),\n (_key, { arg }) => {\n const requestArgs = (endpoint.definition.hasBody\n ? [params, arg, options]\n : [params, options]) as unknown as RequestArgs<P, B, H, Required>;\n return endpoint.request(...requestArgs);\n },\n config.swr,\n );\n return {\n ...state,\n trigger: (...triggerArgs: unknown[]) => {\n const body = triggerArgs.length ? triggerArgs[0] : preset;\n const trigger = state.trigger as (\n body: B,\n options?: TriggerOptions<B, R>,\n ) => Promise<R | undefined>;\n return trigger(\n body as B,\n triggerArgs[1] as TriggerOptions<B, R> | undefined,\n );\n },\n };\n }\n return Object.assign(endpoint, {\n useRequest: useMutation,\n useMutation,\n }) as MutationEndpoint<P, B, R, H, Required>;\n}\n";
|
|
3
3
|
export declare const RUNTIME_LICENSE = "MIT License\n\nCopyright (c) 2026 batype and SnapNest contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n";
|
package/lib/core/templates.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Generated by scripts/embed-templates.mjs. Edit templates/ instead.
|
|
2
|
-
export const RUNTIME_SOURCE = "/** ChainSpec runtime. Generated SDK types describe the spec, not runtime validation. */\nexport type Parameter = {\n name: string;\n in: \"path\" | \"query\" | \"header\" | \"cookie\";\n required?: boolean;\n style?: string;\n explode?: boolean;\n};\nexport type Definition = {\n id: string;\n method: string;\n path: string;\n controller: string;\n name: string;\n parameters: Parameter[];\n bodyRequired?: boolean;\n mediaType?: string;\n responseType: \"json\" | \"text\" | \"blob\" | \"void\" | \"auto\";\n};\nexport type Input = {\n path?: Record<string, unknown>;\n query?: Record<string, unknown>;\n headers?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n body?: unknown;\n};\nexport interface RequestContext {\n url: string;\n method: string;\n headers: Headers;\n body: unknown;\n mediaType?: string;\n signal?: AbortSignal;\n readonly operation: Definition;\n}\nexport interface TransportResponse {\n data: unknown;\n status: number;\n headers: Headers;\n}\nexport type RequestAdapter = (\n context: RequestContext,\n) => Promise<TransportResponse>;\nexport type BeforeRequest = (\n context: RequestContext,\n) => void | RequestContext | Promise<void | RequestContext>;\nexport type AfterResponse = (\n response: TransportResponse,\n context: RequestContext,\n) => void | TransportResponse | Promise<void | TransportResponse>;\nexport type OnError = (\n error: unknown,\n context: RequestContext | undefined,\n) => void | Promise<void>;\nexport interface RequestOptions {\n signal?: AbortSignal;\n headers?: HeadersInit;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n}\nexport interface ClientConfig {\n baseUrl?: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n request?: RequestAdapter;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n /** Change on tenant/user/auth changes. Never put bearer tokens here. */ cacheScope?:\n | string\n | (() => string);\n}\nexport type RequestArgs<I> = {} extends I\n ? [input?: I, options?: RequestOptions]\n : [input: I, options?: RequestOptions];\nexport interface Endpoint<I, R> {\n readonly definition: Definition;\n request(...args: RequestArgs<I>): Promise<R>;\n getKey(input?: I, options?: RequestOptions): readonly unknown[];\n}\nexport class ApiError extends Error {\n constructor(\n public status: number,\n public data: unknown,\n public headers: Headers,\n ) {\n super(`HTTP ${status}`);\n this.name = \"ApiError\";\n }\n}\nconst scalar = (value: unknown): string => {\n if (value === null) return \"\";\n if ([\"string\", \"number\", \"boolean\"].includes(typeof value))\n return String(value);\n throw new Error(\"Parameter must be a scalar or a declared array/object\");\n};\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\nfunction queryEntries(\n name: string,\n value: unknown,\n style = \"form\",\n explode = true,\n): [string, string][] {\n if (value === undefined) return [];\n if (style === \"deepObject\") {\n if (!isRecord(value)) throw new Error(\"deepObject needs an object\");\n return Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [`${name}[${k}]`, scalar(v)]);\n }\n if (Array.isArray(value)) {\n if (style === \"form\" && explode) return value.map((v) => [name, scalar(v)]);\n const delimiter =\n style === \"spaceDelimited\" ? \" \" : style === \"pipeDelimited\" ? \"|\" : \",\";\n return [[name, value.map(scalar).join(delimiter)]];\n }\n if (isRecord(value)) {\n const values = Object.entries(value).filter(([, v]) => v !== undefined);\n return explode\n ? values.map(([k, v]) => [k, scalar(v)])\n : [[name, values.flatMap(([k, v]) => [k, scalar(v)]).join(\",\")]];\n }\n return [[name, scalar(value)]];\n}\nfunction pathValue(value: unknown, explode = false): string {\n const enc = (v: unknown) => encodeURIComponent(scalar(v));\n if (Array.isArray(value)) return value.map(enc).join(\",\");\n if (isRecord(value))\n return Object.entries(value)\n .map(([k, v]) =>\n explode ? `${enc(k)}=${enc(v)}` : `${enc(k)},${enc(v)}`,\n )\n .join(\",\");\n return enc(value);\n}\nexport async function defaultRequest(\n ctx: RequestContext,\n): Promise<TransportResponse> {\n const headers = new Headers(ctx.headers);\n let body: BodyInit | undefined;\n if (ctx.body !== undefined) {\n if (ctx.method === \"GET\" || ctx.method === \"HEAD\")\n throw new Error(\n \"Fetch cannot send GET/HEAD bodies; configure a request adapter\",\n );\n if (ctx.mediaType?.includes(\"json\")) {\n body = JSON.stringify(ctx.body);\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (ctx.mediaType?.startsWith(\"multipart/form-data\")) {\n if (!isRecord(ctx.body))\n throw new Error(\"Multipart body must be an object\");\n const form = new FormData();\n for (const [name, value] of Object.entries(ctx.body)) {\n if (value === undefined) continue;\n for (const v of Array.isArray(value) ? value : [value])\n form.append(name, v instanceof Blob ? v : scalar(v));\n }\n body = form;\n headers.delete(\"Content-Type\");\n } else if (ctx.mediaType?.startsWith(\"application/x-www-form-urlencoded\")) {\n if (!isRecord(ctx.body)) throw new Error(\"Form body must be an object\");\n const form = new URLSearchParams();\n for (const [name, value] of Object.entries(ctx.body))\n for (const [k, v] of queryEntries(name, value)) form.append(k, v);\n body = form.toString();\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (\n typeof ctx.body === \"string\" ||\n ctx.body instanceof Blob ||\n ctx.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(ctx.body)\n ) {\n body = ctx.body as BodyInit;\n if (ctx.mediaType && !headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else\n throw new Error(\"Unsupported body format; configure a request adapter\");\n }\n const response = await fetch(ctx.url, {\n method: ctx.method,\n headers,\n body,\n signal: ctx.signal,\n });\n let data: unknown;\n const mode = ctx.operation.responseType;\n if (\n response.status === 204 ||\n response.status === 205 ||\n ctx.method === \"HEAD\"\n )\n data = undefined;\n else if (!response.ok) {\n const text = await response.text();\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n } else if (mode === \"void\") data = undefined;\n else if (\n mode === \"blob\" ||\n (mode === \"auto\" &&\n !response.headers.get(\"Content-Type\")?.includes(\"json\") &&\n !response.headers.get(\"Content-Type\")?.startsWith(\"text/\"))\n )\n data = await response.blob();\n else {\n const text = await response.text();\n data =\n mode === \"json\" ||\n (mode === \"auto\" &&\n response.headers.get(\"Content-Type\")?.includes(\"json\"))\n ? text\n ? JSON.parse(text)\n : undefined\n : text;\n }\n return { data, status: response.status, headers: response.headers };\n}\nconst runtimeId = Math.random().toString(36).slice(2);\nlet nextClient = 0;\nexport function createClient(config: ClientConfig = {}) {\n const clientId = `chainspec-${runtimeId}-${++nextClient}`;\n const base = config.baseUrl ?? \"\";\n const before = [...(config.beforeRequest ?? [])],\n after = [...(config.afterResponse ?? [])],\n errors = [...(config.onError ?? [])];\n return {\n endpoint<I, R>(definition: Definition): Endpoint<I, R> {\n const def = Object.freeze({\n ...definition,\n parameters: definition.parameters.map((p) => Object.freeze({ ...p })),\n });\n async function execute(\n input: Input = {},\n options: RequestOptions = {},\n ): Promise<R> {\n let context: RequestContext | undefined;\n try {\n let path = def.path;\n for (const p of def.parameters.filter((p) => p.in === \"path\")) {\n const value = input.path?.[p.name];\n if (value === undefined || value === null)\n throw new Error(`Missing path parameter: ${p.name}`);\n path = path.split(`{${p.name}}`).join(pathValue(value, p.explode));\n }\n if (/\\{[^}]+\\}/.test(path))\n throw new Error(\"Unresolved path parameter\");\n const query = new URLSearchParams();\n const headers = new Headers(\n typeof config.headers === \"function\"\n ? await config.headers()\n : config.headers,\n );\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n for (const p of def.parameters.filter((p) => p.in !== \"path\")) {\n const bag =\n p.in === \"query\"\n ? input.query\n : p.in === \"header\"\n ? input.headers\n : input.cookies;\n const value = bag?.[p.name];\n if (p.required && (value === undefined || value === null))\n throw new Error(`Missing ${p.in} parameter: ${p.name}`);\n if (value === undefined) continue;\n if (p.in === \"query\")\n for (const [k, v] of queryEntries(\n p.name,\n value,\n p.style,\n p.explode ?? true,\n ))\n query.append(k, v);\n else if (p.in === \"header\")\n headers.set(\n p.name,\n Array.isArray(value)\n ? value.map(scalar).join(\",\")\n : scalar(value),\n );\n else if (config.request)\n headers.append(\n \"Cookie\",\n `${p.name}=${encodeURIComponent(scalar(value))}`,\n );\n else\n throw new Error(\n \"Cookie parameters require a custom Node request adapter; browser Fetch cannot set Cookie headers\",\n );\n }\n if (def.bodyRequired && input.body === undefined)\n throw new Error(\"Request body is required\");\n const url = `${base.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}${query.size ? `?${query}` : \"\"}`;\n context = {\n url,\n method: def.method,\n headers,\n body: input.body,\n mediaType: def.mediaType,\n signal: options.signal,\n operation: def,\n };\n for (const fn of [...before, ...(options.beforeRequest ?? [])])\n context = (await fn(context)) ?? context;\n let response = await (config.request ?? defaultRequest)(context);\n if (\n !Number.isInteger(response.status) ||\n response.status < 100 ||\n response.status > 599\n )\n throw new Error(\"Adapter must return {data,status,headers}\");\n if (response.status < 200 || response.status >= 300)\n throw new ApiError(\n response.status,\n response.data,\n response.headers,\n );\n for (const fn of [...after, ...(options.afterResponse ?? [])])\n response = (await fn(response, context)) ?? response;\n return response.data as R;\n } catch (error) {\n for (const fn of [...errors, ...(options.onError ?? [])])\n await fn(error, context);\n throw error;\n }\n }\n return {\n definition: def,\n request: execute as Endpoint<I, R>[\"request\"],\n getKey(input?: I, options?: RequestOptions) {\n const scope =\n typeof config.cacheScope === \"function\"\n ? config.cacheScope()\n : (config.cacheScope ?? \"\");\n return [\n clientId,\n base,\n scope,\n def.id,\n input ?? {},\n Array.from(new Headers(options?.headers).entries()),\n ];\n },\n };\n },\n };\n}\n";
|
|
3
|
-
export const HOOKS_SOURCE = "import useSWR, { type SWRConfiguration, type SWRResponse } from \"swr\";\nimport useSWRMutation, {\n type SWRMutationConfiguration,\n type SWRMutationResponse,\n} from \"swr/mutation\";\nimport type
|
|
2
|
+
export const RUNTIME_SOURCE = "/** ChainSpec runtime. Generated SDK types describe the spec, not runtime validation. */\nexport type Parameter = {\n name: string;\n in: \"path\" | \"query\" | \"header\" | \"cookie\";\n required?: boolean;\n style?: string;\n explode?: boolean;\n};\nexport type Definition = {\n id: string;\n method: string;\n path: string;\n controller: string;\n name: string;\n parameters: Parameter[];\n hasBody: boolean;\n bodyRequired?: boolean;\n mediaType?: string;\n responseType: \"json\" | \"text\" | \"blob\" | \"void\" | \"auto\";\n};\nexport type Input = {\n path?: Record<string, unknown>;\n query?: Record<string, unknown>;\n cookies?: Record<string, unknown>;\n};\nexport interface RequestContext {\n url: string;\n method: string;\n headers: Headers;\n body: unknown;\n mediaType?: string;\n signal?: AbortSignal;\n readonly operation: Definition;\n}\nexport interface TransportResponse {\n data: unknown;\n status: number;\n headers: Headers;\n}\nexport type RequestAdapter = (\n context: RequestContext,\n) => Promise<TransportResponse>;\nexport type BeforeRequest = (\n context: RequestContext,\n) => void | RequestContext | Promise<void | RequestContext>;\nexport type AfterResponse = (\n response: TransportResponse,\n context: RequestContext,\n) => void | TransportResponse | Promise<void | TransportResponse>;\nexport type OnError = (\n error: unknown,\n context: RequestContext | undefined,\n) => void | Promise<void>;\nexport interface RequestOptions {\n signal?: AbortSignal;\n headers?: HeadersInit;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n}\nexport interface ClientConfig {\n baseUrl?: string;\n headers?: HeadersInit | (() => HeadersInit | Promise<HeadersInit>);\n request?: RequestAdapter;\n beforeRequest?: BeforeRequest[];\n afterResponse?: AfterResponse[];\n onError?: OnError[];\n /** Change on tenant/user/auth changes. Never put bearer tokens here. */ cacheScope?:\n | string\n | (() => string);\n}\nexport type RequestArgs<\n P,\n B,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? Required extends true\n ? [params: P, body: B, options?: RequestOptions]\n : [params: P, body?: B, options?: RequestOptions]\n : {} extends P\n ? [params?: P, options?: RequestOptions]\n : [params: P, options?: RequestOptions];\nexport interface Endpoint<\n P,\n B,\n R,\n H extends boolean = false,\n Required extends boolean = false,\n> {\n readonly definition: Definition;\n request(...args: RequestArgs<P, B, H, Required>): Promise<R>;\n getKey(...args: RequestArgs<P, B, H, Required>): readonly unknown[];\n}\n/** Body position is fixed by the generated operation contract, never inferred from object keys. */\nexport function splitArgs(\n definition: Definition,\n args: readonly unknown[],\n): { params: Input; body: unknown; options: RequestOptions } {\n return {\n params: (args[0] ?? {}) as Input,\n body: definition.hasBody ? args[1] : undefined,\n options: (args[definition.hasBody ? 2 : 1] ?? {}) as RequestOptions,\n };\n}\nexport class ApiError extends Error {\n constructor(\n public status: number,\n public data: unknown,\n public headers: Headers,\n ) {\n super(`HTTP ${status}`);\n this.name = \"ApiError\";\n }\n}\nconst scalar = (value: unknown): string => {\n if (value === null) return \"\";\n if ([\"string\", \"number\", \"boolean\"].includes(typeof value))\n return String(value);\n throw new Error(\"Parameter must be a scalar or a declared array/object\");\n};\nconst isRecord = (v: unknown): v is Record<string, unknown> =>\n typeof v === \"object\" && v !== null && !Array.isArray(v);\nfunction queryEntries(\n name: string,\n value: unknown,\n style = \"form\",\n explode = true,\n): [string, string][] {\n if (value === undefined) return [];\n if (style === \"deepObject\") {\n if (!isRecord(value)) throw new Error(\"deepObject needs an object\");\n return Object.entries(value)\n .filter(([, v]) => v !== undefined)\n .map(([k, v]) => [`${name}[${k}]`, scalar(v)]);\n }\n if (Array.isArray(value)) {\n if (style === \"form\" && explode) return value.map((v) => [name, scalar(v)]);\n const delimiter =\n style === \"spaceDelimited\" ? \" \" : style === \"pipeDelimited\" ? \"|\" : \",\";\n return [[name, value.map(scalar).join(delimiter)]];\n }\n if (isRecord(value)) {\n const values = Object.entries(value).filter(([, v]) => v !== undefined);\n return explode\n ? values.map(([k, v]) => [k, scalar(v)])\n : [[name, values.flatMap(([k, v]) => [k, scalar(v)]).join(\",\")]];\n }\n return [[name, scalar(value)]];\n}\nfunction pathValue(value: unknown, explode = false): string {\n const enc = (v: unknown) => encodeURIComponent(scalar(v));\n if (Array.isArray(value)) return value.map(enc).join(\",\");\n if (isRecord(value))\n return Object.entries(value)\n .map(([k, v]) =>\n explode ? `${enc(k)}=${enc(v)}` : `${enc(k)},${enc(v)}`,\n )\n .join(\",\");\n return enc(value);\n}\nexport async function defaultRequest(\n ctx: RequestContext,\n): Promise<TransportResponse> {\n const headers = new Headers(ctx.headers);\n let body: BodyInit | undefined;\n if (ctx.body !== undefined) {\n if (ctx.method === \"GET\" || ctx.method === \"HEAD\")\n throw new Error(\n \"Fetch cannot send GET/HEAD bodies; configure a request adapter\",\n );\n if (ctx.mediaType?.includes(\"json\")) {\n body = JSON.stringify(ctx.body);\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (ctx.mediaType?.startsWith(\"multipart/form-data\")) {\n if (!isRecord(ctx.body))\n throw new Error(\"Multipart body must be an object\");\n const form = new FormData();\n for (const [name, value] of Object.entries(ctx.body)) {\n if (value === undefined) continue;\n for (const v of Array.isArray(value) ? value : [value])\n form.append(name, v instanceof Blob ? v : scalar(v));\n }\n body = form;\n headers.delete(\"Content-Type\");\n } else if (ctx.mediaType?.startsWith(\"application/x-www-form-urlencoded\")) {\n if (!isRecord(ctx.body)) throw new Error(\"Form body must be an object\");\n const form = new URLSearchParams();\n for (const [name, value] of Object.entries(ctx.body))\n for (const [k, v] of queryEntries(name, value)) form.append(k, v);\n body = form.toString();\n if (!headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else if (\n typeof ctx.body === \"string\" ||\n ctx.body instanceof Blob ||\n ctx.body instanceof ArrayBuffer ||\n ArrayBuffer.isView(ctx.body)\n ) {\n body = ctx.body as BodyInit;\n if (ctx.mediaType && !headers.has(\"Content-Type\"))\n headers.set(\"Content-Type\", ctx.mediaType);\n } else\n throw new Error(\"Unsupported body format; configure a request adapter\");\n }\n const response = await fetch(ctx.url, {\n method: ctx.method,\n headers,\n body,\n signal: ctx.signal,\n });\n let data: unknown;\n const mode = ctx.operation.responseType;\n if (\n response.status === 204 ||\n response.status === 205 ||\n ctx.method === \"HEAD\"\n )\n data = undefined;\n else if (!response.ok) {\n const text = await response.text();\n try {\n data = JSON.parse(text);\n } catch {\n data = text;\n }\n } else if (mode === \"void\") data = undefined;\n else if (\n mode === \"blob\" ||\n (mode === \"auto\" &&\n !response.headers.get(\"Content-Type\")?.includes(\"json\") &&\n !response.headers.get(\"Content-Type\")?.startsWith(\"text/\"))\n )\n data = await response.blob();\n else {\n const text = await response.text();\n data =\n mode === \"json\" ||\n (mode === \"auto\" &&\n response.headers.get(\"Content-Type\")?.includes(\"json\"))\n ? text\n ? JSON.parse(text)\n : undefined\n : text;\n }\n return { data, status: response.status, headers: response.headers };\n}\nconst runtimeId = Math.random().toString(36).slice(2);\nlet nextClient = 0;\nexport function createClient(config: ClientConfig = {}) {\n const clientId = `chainspec-${runtimeId}-${++nextClient}`;\n const base = config.baseUrl ?? \"\";\n const before = [...(config.beforeRequest ?? [])],\n after = [...(config.afterResponse ?? [])],\n errors = [...(config.onError ?? [])];\n return {\n endpoint<P, B, R, H extends boolean, Required extends boolean>(\n definition: Definition,\n ): Endpoint<P, B, R, H, Required> {\n const def = Object.freeze({\n ...definition,\n parameters: definition.parameters.map((p) => Object.freeze({ ...p })),\n });\n async function execute(...args: unknown[]): Promise<R> {\n const { params: input, body, options } = splitArgs(def, args);\n let context: RequestContext | undefined;\n try {\n let path = def.path;\n for (const p of def.parameters.filter((p) => p.in === \"path\")) {\n const value = input.path?.[p.name];\n if (value === undefined || value === null)\n throw new Error(`Missing path parameter: ${p.name}`);\n path = path.split(`{${p.name}}`).join(pathValue(value, p.explode));\n }\n if (/\\{[^}]+\\}/.test(path))\n throw new Error(\"Unresolved path parameter\");\n const query = new URLSearchParams();\n const headers = new Headers(\n typeof config.headers === \"function\"\n ? await config.headers()\n : config.headers,\n );\n new Headers(options.headers).forEach((v, k) => headers.set(k, v));\n for (const p of def.parameters.filter(\n (p) => p.in === \"query\" || p.in === \"cookie\",\n )) {\n const bag = p.in === \"query\" ? input.query : input.cookies;\n const value = bag?.[p.name];\n if (p.required && (value === undefined || value === null))\n throw new Error(`Missing ${p.in} parameter: ${p.name}`);\n if (value === undefined) continue;\n if (p.in === \"query\")\n for (const [k, v] of queryEntries(\n p.name,\n value,\n p.style,\n p.explode ?? true,\n ))\n query.append(k, v);\n else if (config.request)\n headers.append(\n \"Cookie\",\n `${p.name}=${encodeURIComponent(scalar(value))}`,\n );\n else\n throw new Error(\n \"Cookie parameters require a custom Node request adapter; browser Fetch cannot set Cookie headers\",\n );\n }\n if (def.bodyRequired && body === undefined)\n throw new Error(\"Request body is required\");\n const url = `${base.replace(/\\/$/, \"\")}/${path.replace(/^\\//, \"\")}${query.size ? `?${query}` : \"\"}`;\n context = {\n url,\n method: def.method,\n headers,\n body,\n mediaType: def.mediaType,\n signal: options.signal,\n operation: def,\n };\n for (const fn of [...before, ...(options.beforeRequest ?? [])])\n context = (await fn(context)) ?? context;\n for (const p of def.parameters.filter(\n (p) => p.in === \"header\" && p.required,\n )) {\n if (!context.headers.has(p.name))\n throw new Error(`Missing header parameter: ${p.name}`);\n }\n let response = await (config.request ?? defaultRequest)(context);\n if (\n !Number.isInteger(response.status) ||\n response.status < 100 ||\n response.status > 599\n )\n throw new Error(\"Adapter must return {data,status,headers}\");\n if (response.status < 200 || response.status >= 300)\n throw new ApiError(\n response.status,\n response.data,\n response.headers,\n );\n for (const fn of [...after, ...(options.afterResponse ?? [])])\n response = (await fn(response, context)) ?? response;\n return response.data as R;\n } catch (error) {\n for (const fn of [...errors, ...(options.onError ?? [])])\n await fn(error, context);\n throw error;\n }\n }\n return {\n definition: def,\n request: execute as Endpoint<P, B, R, H, Required>[\"request\"],\n getKey(...args: RequestArgs<P, B, H, Required>) {\n const { params, body, options } = splitArgs(def, args);\n const scope =\n typeof config.cacheScope === \"function\"\n ? config.cacheScope()\n : (config.cacheScope ?? \"\");\n return [\n clientId,\n base,\n scope,\n def.id,\n params,\n body,\n Array.from(new Headers(options?.headers).entries()),\n ];\n },\n };\n },\n };\n}\n";
|
|
3
|
+
export const HOOKS_SOURCE = "import useSWR, { type SWRConfiguration, type SWRResponse } from \"swr\";\nimport useSWRMutation, {\n type SWRMutationConfiguration,\n type SWRMutationResponse,\n} from \"swr/mutation\";\nimport {\n splitArgs,\n type Endpoint,\n type RequestArgs,\n type RequestOptions,\n} from \"./runtime.js\";\nexport type QueryOptions<R> = RequestOptions & {\n enabled?: boolean;\n swr?: SWRConfiguration<R, Error>;\n};\nexport type QueryArgs<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? Required extends true\n ? [params: P | null, body: B, options?: QueryOptions<R>]\n : [params: P | null, body?: B, options?: QueryOptions<R>]\n : {} extends P\n ? [params?: P | null, options?: QueryOptions<R>]\n : [params: P | null, options?: QueryOptions<R>];\nexport type MutationOptions<B, R> = RequestOptions & {\n swr?: SWRMutationConfiguration<R, Error, readonly unknown[], B> & {\n throwOnError?: boolean;\n };\n};\nexport type TriggerOptions<B, R> = SWRMutationConfiguration<\n R,\n Error,\n readonly unknown[],\n B\n> & { throwOnError?: boolean };\nexport type MutationResult<B, R, Bound extends boolean> = Omit<\n SWRMutationResponse<R, Error, readonly unknown[], B>,\n \"trigger\"\n> & {\n trigger(\n ...args: Bound extends true\n ? [body?: B, options?: TriggerOptions<B, R>]\n : [body: B, options?: TriggerOptions<B, R>]\n ): Promise<R | undefined>;\n};\nexport type MutationHook<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = H extends true\n ? {\n (\n params: P,\n body: B,\n options?: MutationOptions<B, R>,\n ): MutationResult<B, R, true>;\n (\n params: P,\n body?: undefined,\n options?: MutationOptions<B, R>,\n ): MutationResult<B, R, Required extends true ? false : true>;\n }\n : (\n ...args: {} extends P\n ? [params?: P, options?: MutationOptions<never, R>]\n : [params: P, options?: MutationOptions<never, R>]\n ) => MutationResult<never, R, true>;\nexport type QueryEndpoint<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = Endpoint<P, B, R, H, Required> & {\n useRequest(...args: QueryArgs<P, B, R, H, Required>): SWRResponse<R, Error>;\n};\nexport type MutationEndpoint<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n> = Endpoint<P, B, R, H, Required> & {\n useRequest: MutationHook<P, B, R, H, Required>;\n useMutation: MutationHook<P, B, R, H, Required>;\n};\nexport function withQuery<P, B, R, H extends boolean, Required extends boolean>(\n endpoint: Endpoint<P, B, R, H, Required>,\n): QueryEndpoint<P, B, R, H, Required> {\n function useRequest(...args: QueryArgs<P, B, R, H, Required>) {\n const { options } = splitArgs(endpoint.definition, args);\n const config = options as QueryOptions<R>;\n const requestArgs = args as unknown as RequestArgs<P, B, H, Required>;\n return useSWR<R, Error>(\n args[0] !== null && config.enabled !== false\n ? endpoint.getKey(...requestArgs)\n : null,\n () => endpoint.request(...requestArgs),\n config.swr,\n );\n }\n return Object.assign(endpoint, { useRequest });\n}\nexport function withMutation<\n P,\n B,\n R,\n H extends boolean,\n Required extends boolean,\n>(\n endpoint: Endpoint<P, B, R, H, Required>,\n): MutationEndpoint<P, B, R, H, Required> {\n function useMutation(...args: unknown[]) {\n const {\n params,\n body: preset,\n options,\n } = splitArgs(endpoint.definition, args);\n const config = options as MutationOptions<B, R>;\n const boundArgs = (endpoint.definition.hasBody\n ? [params, preset, options]\n : [params, options]) as unknown as RequestArgs<P, B, H, Required>;\n const state = useSWRMutation<R, Error, readonly unknown[], B>(\n endpoint.getKey(...boundArgs),\n (_key, { arg }) => {\n const requestArgs = (endpoint.definition.hasBody\n ? [params, arg, options]\n : [params, options]) as unknown as RequestArgs<P, B, H, Required>;\n return endpoint.request(...requestArgs);\n },\n config.swr,\n );\n return {\n ...state,\n trigger: (...triggerArgs: unknown[]) => {\n const body = triggerArgs.length ? triggerArgs[0] : preset;\n const trigger = state.trigger as (\n body: B,\n options?: TriggerOptions<B, R>,\n ) => Promise<R | undefined>;\n return trigger(\n body as B,\n triggerArgs[1] as TriggerOptions<B, R> | undefined,\n );\n },\n };\n }\n return Object.assign(endpoint, {\n useRequest: useMutation,\n useMutation,\n }) as MutationEndpoint<P, B, R, H, Required>;\n}\n";
|
|
4
4
|
export const RUNTIME_LICENSE = "MIT License\n\nCopyright (c) 2026 batype and SnapNest contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n";
|