tanstack-fetch 1.0.0 → 1.0.2
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 +134 -3
- package/dist/cli.js +22 -239
- package/dist/client.type-BBYqVrTM.d.cts +156 -0
- package/dist/client.type-Btgj3NQn.d.ts +156 -0
- package/dist/config.type-eG_cuxpu.d.cts +132 -0
- package/dist/config.type-eG_cuxpu.d.ts +132 -0
- package/dist/index.cjs +2 -881
- package/dist/index.d.cts +23 -17
- package/dist/index.d.ts +23 -17
- package/dist/index.js +2 -3
- package/dist/plugins.cjs +1 -0
- package/dist/plugins.d.cts +19 -0
- package/dist/plugins.d.ts +19 -0
- package/dist/plugins.js +1 -0
- package/dist/react.cjs +1 -951
- package/dist/react.d.cts +6 -6
- package/dist/react.d.ts +6 -6
- package/dist/react.js +1 -91
- package/dist/sse.cjs +7 -0
- package/dist/sse.d.cts +9 -0
- package/dist/sse.d.ts +9 -0
- package/dist/sse.js +7 -0
- package/package.json +19 -6
- package/dist/chunk-T27KPHCL.js +0 -867
- package/dist/chunk-T27KPHCL.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/fetch-error-B10Od9AT.d.cts +0 -257
- package/dist/fetch-error-B10Od9AT.d.ts +0 -257
- package/dist/index.cjs.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/react.cjs.map +0 -1
- package/dist/react.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
# tanstack-fetch
|
|
2
2
|
|
|
3
|
-
Typed `fetch` client shaped for **TanStack Query
|
|
3
|
+
Typed `fetch` client shaped for **TanStack Query** — tiny HTTP core, optional SSE / React.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
| Import | What you get | Typical gzip |
|
|
6
|
+
| --- | --- | --- |
|
|
7
|
+
| `tanstack-fetch` | HTTP only (`get/post/…`) | **~3.5KB** |
|
|
8
|
+
| `tanstack-fetch/sse` | + `api.sse()` | **~4.7KB** |
|
|
9
|
+
| `tanstack-fetch/plugins` | plugin factories | **~0.9KB** |
|
|
10
|
+
| `tanstack-fetch/react` | `FetchProvider` / hooks (peer: core) | **~1KB** |
|
|
6
11
|
|
|
7
12
|
```bash
|
|
8
13
|
npm install tanstack-fetch
|
|
@@ -14,6 +19,24 @@ Node 18+ (native `fetch`).
|
|
|
14
19
|
|
|
15
20
|
---
|
|
16
21
|
|
|
22
|
+
## Bundle size
|
|
23
|
+
|
|
24
|
+
Tree-shake by importing only what you need:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// smallest — HTTP for TanStack Query
|
|
28
|
+
import { createFetch } from 'tanstack-fetch'
|
|
29
|
+
|
|
30
|
+
// only when you need streams
|
|
31
|
+
import { createFetch } from 'tanstack-fetch/sse'
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
`yaml` is an **optional** peer (CLI YAML specs only). React is optional too.
|
|
35
|
+
|
|
36
|
+
Run `npm run size` after build to print local gzip numbers.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
17
40
|
## Two ways to configure
|
|
18
41
|
|
|
19
42
|
### 1) Simple path — `baseUrl`, token, status handlers
|
|
@@ -451,6 +474,87 @@ await api.request<User>('GET', '/users/:id', { params: { id: '1' } })
|
|
|
451
474
|
|
|
452
475
|
---
|
|
453
476
|
|
|
477
|
+
## Upload
|
|
478
|
+
|
|
479
|
+
`FormData` / `Blob` / `File` are sent as-is (no JSON, no forced `Content-Type` — the boundary stays correct).
|
|
480
|
+
|
|
481
|
+
The call still returns the **typed response body** from the server (same as `post`), not a special upload envelope.
|
|
482
|
+
|
|
483
|
+
### `api.upload()` — file + fields + progress
|
|
484
|
+
|
|
485
|
+
```ts
|
|
486
|
+
type UploadResponse = { id: string; url: string }
|
|
487
|
+
|
|
488
|
+
const file = input.files[0]
|
|
489
|
+
|
|
490
|
+
const uploaded = await api.upload<UploadResponse>('/files', {
|
|
491
|
+
file,
|
|
492
|
+
fieldName: 'avatar', // default: 'file'
|
|
493
|
+
fields: { folder: 'avatars', public: true },
|
|
494
|
+
onUploadProgress: ({ loaded, total, progress }) => {
|
|
495
|
+
// progress is 0–1 when total is known (browser / XHR)
|
|
496
|
+
console.log(loaded, total, progress)
|
|
497
|
+
},
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
uploaded.url
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Multiple files or raw `FormData`:
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
await api.upload('/docs', {
|
|
507
|
+
method: 'PUT',
|
|
508
|
+
files: [fileA, fileB],
|
|
509
|
+
fieldName: 'docs',
|
|
510
|
+
})
|
|
511
|
+
|
|
512
|
+
await api.upload<UploadResponse>('/files', {
|
|
513
|
+
body: createFormData({ file, note: 'cv' }),
|
|
514
|
+
})
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### `createFormData` helper
|
|
518
|
+
|
|
519
|
+
```ts
|
|
520
|
+
import { createFetch, createFormData } from 'tanstack-fetch'
|
|
521
|
+
|
|
522
|
+
const api = createFetch({ baseUrl: 'https://api.example.com' })
|
|
523
|
+
|
|
524
|
+
const body = createFormData({
|
|
525
|
+
title: 'Report',
|
|
526
|
+
tags: ['a', 'b'], // repeated field
|
|
527
|
+
file,
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
await api.post<UploadResponse>('/files', {
|
|
531
|
+
body,
|
|
532
|
+
onUploadProgress: ({ progress }) => console.log(progress),
|
|
533
|
+
})
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
| Option | Notes |
|
|
537
|
+
| --- | --- |
|
|
538
|
+
| `file` / `files` | Appended under `fieldName` (default `"file"`) |
|
|
539
|
+
| `fields` | Extra multipart values (string / number / boolean / `Blob` / arrays) |
|
|
540
|
+
| `body` | Pre-built `FormData` / `Blob` / … |
|
|
541
|
+
| `method` | `POST` (default), `PUT`, or `PATCH` |
|
|
542
|
+
| `onUploadProgress` | Browser-only — uses XHR under the hood (`fetch` has no upload progress). No-ops on runtimes without `XMLHttpRequest` (falls back to `fetch`) |
|
|
543
|
+
|
|
544
|
+
Works with TanStack Query mutations the same way as `post`:
|
|
545
|
+
|
|
546
|
+
```ts
|
|
547
|
+
useMutation({
|
|
548
|
+
mutationFn: (file: File) =>
|
|
549
|
+
api.upload<UploadResponse>('/files', {
|
|
550
|
+
file,
|
|
551
|
+
onUploadProgress: ({ progress }) => setProgress(progress ?? 0),
|
|
552
|
+
}),
|
|
553
|
+
})
|
|
554
|
+
```
|
|
555
|
+
|
|
556
|
+
---
|
|
557
|
+
|
|
454
558
|
## `FetchError`
|
|
455
559
|
|
|
456
560
|
```ts
|
|
@@ -555,6 +659,8 @@ await api.post('/orders', { body: { sku: 'A' } }) // not retried
|
|
|
555
659
|
### `sse-resume`
|
|
556
660
|
|
|
557
661
|
```ts
|
|
662
|
+
import { createFetch } from 'tanstack-fetch/sse'
|
|
663
|
+
|
|
558
664
|
const api = createFetch({
|
|
559
665
|
baseUrl: 'https://api.example.com',
|
|
560
666
|
plugins: ['sse-resume'],
|
|
@@ -703,6 +809,13 @@ Uses `fetch` streams (not `EventSource`) — Authorization, cookies, and SSR wor
|
|
|
703
809
|
### Simple — `onMessage`
|
|
704
810
|
|
|
705
811
|
```ts
|
|
812
|
+
import { createFetch } from 'tanstack-fetch/sse'
|
|
813
|
+
|
|
814
|
+
const api = createFetch({
|
|
815
|
+
baseUrl: 'https://api.example.com',
|
|
816
|
+
plugins: ['sse-resume'],
|
|
817
|
+
})
|
|
818
|
+
|
|
706
819
|
const stream = api.sse<OrderEvent>('/orders/stream', {
|
|
707
820
|
onMessage: (data) => {
|
|
708
821
|
console.log(data) // just the payload
|
|
@@ -716,8 +829,22 @@ stream.close()
|
|
|
716
829
|
|
|
717
830
|
### React — `useSse`
|
|
718
831
|
|
|
832
|
+
Pass a client created from `tanstack-fetch/sse`:
|
|
833
|
+
|
|
719
834
|
```tsx
|
|
720
|
-
import {
|
|
835
|
+
import { createFetch } from 'tanstack-fetch/sse'
|
|
836
|
+
import { FetchProvider, useSse } from 'tanstack-fetch/react'
|
|
837
|
+
|
|
838
|
+
const api = createFetch({
|
|
839
|
+
baseUrl: import.meta.env.VITE_API_URL,
|
|
840
|
+
plugins: ['sse-resume'],
|
|
841
|
+
})
|
|
842
|
+
|
|
843
|
+
const App = () => (
|
|
844
|
+
<FetchProvider client={api}>
|
|
845
|
+
<OrdersLive />
|
|
846
|
+
</FetchProvider>
|
|
847
|
+
)
|
|
721
848
|
|
|
722
849
|
const OrdersLive = () => {
|
|
723
850
|
const { data, isConnected, error } = useSse<OrderEvent>('/orders/stream')
|
|
@@ -734,6 +861,10 @@ const OrdersLive = () => {
|
|
|
734
861
|
### Advanced — `for await`
|
|
735
862
|
|
|
736
863
|
```ts
|
|
864
|
+
import { createFetch } from 'tanstack-fetch/sse'
|
|
865
|
+
|
|
866
|
+
const api = createFetch({ baseUrl: 'https://api.example.com' })
|
|
867
|
+
|
|
737
868
|
for await (const event of api.sse<OrderEvent>('/orders/stream', { signal })) {
|
|
738
869
|
event.event
|
|
739
870
|
event.data
|
package/dist/cli.js
CHANGED
|
@@ -1,234 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return "unknown";
|
|
20
|
-
}
|
|
21
|
-
if (schema.$ref) {
|
|
22
|
-
return refName(schema.$ref);
|
|
23
|
-
}
|
|
24
|
-
if (schema.enum && schema.enum.length > 0) {
|
|
25
|
-
return schema.enum.map((value) => JSON.stringify(value)).join(" | ");
|
|
26
|
-
}
|
|
27
|
-
const typeValue = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
28
|
-
if (typeValue === "string") {
|
|
29
|
-
return "string";
|
|
30
|
-
}
|
|
31
|
-
if (typeValue === "integer" || typeValue === "number") {
|
|
32
|
-
return "number";
|
|
33
|
-
}
|
|
34
|
-
if (typeValue === "boolean") {
|
|
35
|
-
return "boolean";
|
|
36
|
-
}
|
|
37
|
-
if (typeValue === "array") {
|
|
38
|
-
return `Array<${schemaToTs(schema.items)}>`;
|
|
39
|
-
}
|
|
40
|
-
if (typeValue === "object" || schema.properties) {
|
|
41
|
-
return objectToTs(schema);
|
|
42
|
-
}
|
|
43
|
-
return "unknown";
|
|
44
|
-
};
|
|
45
|
-
var objectToTs = (schema) => {
|
|
46
|
-
const required = new Set(schema.required ?? []);
|
|
47
|
-
const fields = Object.entries(schema.properties ?? {}).map(([key, value]) => {
|
|
48
|
-
const optional = required.has(key) ? "" : "?";
|
|
49
|
-
return ` ${key}${optional}: ${schemaToTs(value)}`;
|
|
50
|
-
});
|
|
51
|
-
if (fields.length === 0) {
|
|
52
|
-
return "Record<string, unknown>";
|
|
53
|
-
}
|
|
54
|
-
return `{
|
|
55
|
-
${fields.join("\n")}
|
|
56
|
-
}`;
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// src/cli/collect-operations.ts
|
|
60
|
-
var HTTP_METHODS = ["get", "post", "put", "patch", "delete"];
|
|
61
|
-
var isSseOperation = (operation) => {
|
|
62
|
-
const contents = Object.values(operation.responses ?? {}).flatMap(
|
|
63
|
-
(response) => Object.keys(response.content ?? {})
|
|
64
|
-
);
|
|
65
|
-
return contents.some((type) => type.includes("event-stream"));
|
|
66
|
-
};
|
|
67
|
-
var collectOperations = (spec) => {
|
|
68
|
-
const operations = [];
|
|
69
|
-
Object.entries(spec.paths ?? {}).forEach(([path, methods]) => {
|
|
70
|
-
HTTP_METHODS.forEach((method) => {
|
|
71
|
-
const operation = methods?.[method];
|
|
72
|
-
if (!operation) {
|
|
73
|
-
return;
|
|
74
|
-
}
|
|
75
|
-
const tag = operation.tags?.[0] ?? "api";
|
|
76
|
-
const fallbackId = `${method}_${path}`;
|
|
77
|
-
operations.push({
|
|
78
|
-
method: method.toUpperCase(),
|
|
79
|
-
path,
|
|
80
|
-
operationId: toCamel(operation.operationId ?? fallbackId),
|
|
81
|
-
tag: toCamel(tag),
|
|
82
|
-
isSse: isSseOperation(operation),
|
|
83
|
-
parameters: operation.parameters ?? [],
|
|
84
|
-
bodySchema: operation.requestBody?.content?.["application/json"]?.schema,
|
|
85
|
-
successSchema: pickSuccessSchema(operation)
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
});
|
|
89
|
-
return operations;
|
|
90
|
-
};
|
|
91
|
-
var pickSuccessSchema = (operation) => {
|
|
92
|
-
const success = operation.responses?.["200"] ?? operation.responses?.["201"];
|
|
93
|
-
const content = success?.content ?? {};
|
|
94
|
-
return content["application/json"]?.schema ?? content["text/event-stream"]?.schema;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// src/cli/generate.ts
|
|
98
|
-
var generateTypesFile = (spec) => {
|
|
99
|
-
const schemas = Object.entries(spec.components?.schemas ?? {});
|
|
100
|
-
const types = schemas.map(([name, schema]) => `type ${toPascal(name)} = ${schemaToTs(schema)}`);
|
|
101
|
-
const exports = schemas.map(([name]) => toPascal(name));
|
|
102
|
-
if (exports.length === 0) {
|
|
103
|
-
return "export {}\n";
|
|
104
|
-
}
|
|
105
|
-
const body = `${types.join("\n\n")}
|
|
106
|
-
|
|
107
|
-
export type { ${exports.join(", ")} }
|
|
108
|
-
`;
|
|
109
|
-
return body.trimStart();
|
|
110
|
-
};
|
|
111
|
-
var paramsType = (operation, kind) => {
|
|
112
|
-
const params = operation.parameters.filter((item) => item.in === kind);
|
|
113
|
-
if (params.length === 0) {
|
|
114
|
-
return void 0;
|
|
115
|
-
}
|
|
116
|
-
const fields = params.map((item) => {
|
|
117
|
-
const optional = item.required ? "" : "?";
|
|
118
|
-
return `${item.name}${optional}: ${schemaToTs(item.schema)}`;
|
|
119
|
-
});
|
|
120
|
-
return `{ ${fields.join("; ")} }`;
|
|
121
|
-
};
|
|
122
|
-
var optionsType = (operation) => {
|
|
123
|
-
const fields = [];
|
|
124
|
-
const pathType = paramsType(operation, "path");
|
|
125
|
-
const queryType = paramsType(operation, "query");
|
|
126
|
-
if (pathType) {
|
|
127
|
-
fields.push(`params: ${pathType}`);
|
|
128
|
-
}
|
|
129
|
-
if (queryType) {
|
|
130
|
-
fields.push(`query?: ${queryType}`);
|
|
131
|
-
}
|
|
132
|
-
if (operation.bodySchema) {
|
|
133
|
-
fields.push(`body: ${schemaToTs(operation.bodySchema)}`);
|
|
134
|
-
}
|
|
135
|
-
if (fields.length === 0) {
|
|
136
|
-
return "RequestOptions | undefined";
|
|
137
|
-
}
|
|
138
|
-
return `Omit<RequestOptions, 'params' | 'query' | 'body'> & { ${fields.join("; ")} }`;
|
|
139
|
-
};
|
|
140
|
-
var clientPath = (path) => path.replace(/\{([A-Za-z0-9_]+)\}/g, ":$1");
|
|
141
|
-
var methodLine = (operation) => {
|
|
142
|
-
const response = schemaToTs(operation.successSchema);
|
|
143
|
-
const options = optionsType(operation);
|
|
144
|
-
const optional = options.endsWith("| undefined") ? "?" : "";
|
|
145
|
-
if (operation.isSse) {
|
|
146
|
-
return ` ${operation.operationId}: (options${optional}: ${options}) => api.sse<${response}>('${clientPath(operation.path)}', options),`;
|
|
147
|
-
}
|
|
148
|
-
const method = operation.method.toLowerCase();
|
|
149
|
-
const call = method === "delete" ? "delete" : method;
|
|
150
|
-
return ` ${operation.operationId}: (options${optional}: ${options}) => api.${call}<${response}>('${clientPath(operation.path)}', options),`;
|
|
151
|
-
};
|
|
152
|
-
var generateClientFile = (spec, operations) => {
|
|
153
|
-
const tags = [...new Set(operations.map((item) => item.tag))];
|
|
154
|
-
const groups = tags.map((tag) => {
|
|
155
|
-
const lines = operations.filter((item) => item.tag === tag).map(methodLine);
|
|
156
|
-
return ` ${tag}: {
|
|
157
|
-
${lines.join("\n")}
|
|
158
|
-
},`;
|
|
159
|
-
});
|
|
160
|
-
const schemaNames = Object.keys(spec.components?.schemas ?? {}).map((name) => toPascal(name));
|
|
161
|
-
const typesImport = schemaNames.length > 0 ? `import type { ${schemaNames.join(", ")} } from './types'
|
|
162
|
-
` : "";
|
|
163
|
-
return `import { createFetch } from 'tanstack-fetch'
|
|
164
|
-
import type { CreateFetchOptions, RequestOptions } from 'tanstack-fetch'
|
|
165
|
-
${typesImport}
|
|
2
|
+
import{mkdir as T,writeFile as f}from"fs/promises";import{join as h}from"path";var i=e=>e.replace(/[^A-Za-z0-9]+/g," ").trim().split(" ").filter(Boolean).map(t=>t[0].toUpperCase()+t.slice(1)).join("")||"Schema",u=e=>{let t=i(e);return t[0].toLowerCase()+t.slice(1)},k=e=>{let t=e.split("/");return i(t[t.length-1]??"Schema")},p=e=>{if(!e)return"unknown";if(e.$ref)return k(e.$ref);if(e.enum&&e.enum.length>0)return e.enum.map(n=>JSON.stringify(n)).join(" | ");let t=Array.isArray(e.type)?e.type[0]:e.type;return t==="string"?"string":t==="integer"||t==="number"?"number":t==="boolean"?"boolean":t==="array"?`Array<${p(e.items)}>`:t==="object"||e.properties?C(e):"unknown"},C=e=>{let t=new Set(e.required??[]),n=Object.entries(e.properties??{}).map(([r,s])=>{let o=t.has(r)?"":"?";return` ${r}${o}: ${p(s)}`});return n.length===0?"Record<string, unknown>":`{
|
|
3
|
+
${n.join(`
|
|
4
|
+
`)}
|
|
5
|
+
}`};var j=["get","post","put","patch","delete"],b=e=>Object.values(e.responses??{}).flatMap(n=>Object.keys(n.content??{})).some(n=>n.includes("event-stream")),d=e=>{let t=[];return Object.entries(e.paths??{}).forEach(([n,r])=>{j.forEach(s=>{let o=r?.[s];if(!o)return;let a=o.tags?.[0]??"api",m=`${s}_${n}`;t.push({method:s.toUpperCase(),path:n,operationId:u(o.operationId??m),tag:u(a),isSse:b(o),parameters:o.parameters??[],bodySchema:o.requestBody?.content?.["application/json"]?.schema,successSchema:x(o)})})}),t},x=e=>{let n=(e.responses?.["200"]??e.responses?.["201"])?.content??{};return n["application/json"]?.schema??n["text/event-stream"]?.schema};var q=e=>{let t=Object.entries(e.components?.schemas??{}),n=t.map(([o,a])=>`type ${i(o)} = ${p(a)}`),r=t.map(([o])=>i(o));return r.length===0?`export {}
|
|
6
|
+
`:`${n.join(`
|
|
7
|
+
|
|
8
|
+
`)}
|
|
9
|
+
|
|
10
|
+
export type { ${r.join(", ")} }
|
|
11
|
+
`.trimStart()},g=(e,t)=>{let n=e.parameters.filter(s=>s.in===t);return n.length===0?void 0:`{ ${n.map(s=>{let o=s.required?"":"?";return`${s.name}${o}: ${p(s.schema)}`}).join("; ")} }`},F=e=>{let t=[],n=g(e,"path"),r=g(e,"query");return n&&t.push(`params: ${n}`),r&&t.push(`query?: ${r}`),e.bodySchema&&t.push(`body: ${p(e.bodySchema)}`),t.length===0?"RequestOptions | undefined":`Omit<RequestOptions, 'params' | 'query' | 'body'> & { ${t.join("; ")} }`},y=e=>e.replace(/\{([A-Za-z0-9_]+)\}/g,":$1"),I=e=>{let t=p(e.successSchema),n=F(e),r=n.endsWith("| undefined")?"?":"";if(e.isSse)return` ${e.operationId}: (options${r}: ${n}) => api.sse<${t}>('${y(e.path)}', options),`;let s=e.method.toLowerCase(),o=s==="delete"?"delete":s;return` ${e.operationId}: (options${r}: ${n}) => api.${o}<${t}>('${y(e.path)}', options),`},E=(e,t)=>{let r=[...new Set(t.map(c=>c.tag))].map(c=>{let l=t.filter(A=>A.tag===c).map(I);return` ${c}: {
|
|
12
|
+
${l.join(`
|
|
13
|
+
`)}
|
|
14
|
+
},`}),s=Object.keys(e.components?.schemas??{}).map(c=>i(c)),o=s.length>0?`import type { ${s.join(", ")} } from './types'
|
|
15
|
+
`:"";return`${t.some(c=>c.isSse)?`import { createFetch } from 'tanstack-fetch/sse'
|
|
16
|
+
import type { CreateFetchOptions, RequestOptions } from 'tanstack-fetch'`:`import { createFetch } from 'tanstack-fetch'
|
|
17
|
+
import type { CreateFetchOptions, RequestOptions } from 'tanstack-fetch'`}
|
|
18
|
+
${o}
|
|
166
19
|
const createApi = (options: CreateFetchOptions = {}) => {
|
|
167
20
|
const api = createFetch(options)
|
|
168
21
|
return {
|
|
169
|
-
${
|
|
22
|
+
${r.join(`
|
|
23
|
+
`)}
|
|
170
24
|
}
|
|
171
25
|
}
|
|
172
26
|
|
|
173
27
|
export { createApi }
|
|
174
|
-
|
|
175
|
-
};
|
|
176
|
-
var generateIndexFile = () => `import { createApi } from './client'
|
|
28
|
+
`},J=()=>`import { createApi } from './client'
|
|
177
29
|
|
|
178
30
|
export { createApi }
|
|
179
31
|
export type * from './types'
|
|
180
|
-
|
|
181
|
-
var generateClient = async (spec, outDir) => {
|
|
182
|
-
const operations = collectOperations(spec);
|
|
183
|
-
await mkdir(outDir, { recursive: true });
|
|
184
|
-
await writeFile(join(outDir, "types.ts"), generateTypesFile(spec), "utf8");
|
|
185
|
-
await writeFile(join(outDir, "client.ts"), generateClientFile(spec, operations), "utf8");
|
|
186
|
-
await writeFile(join(outDir, "index.ts"), generateIndexFile(), "utf8");
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
// src/cli/load-spec.ts
|
|
190
|
-
import { readFile } from "fs/promises";
|
|
191
|
-
import { parse as parseYaml } from "yaml";
|
|
192
|
-
var loadSpec = async (specPath) => {
|
|
193
|
-
const raw = await readFile(specPath, "utf8");
|
|
194
|
-
const parsed = specPath.endsWith(".yaml") || specPath.endsWith(".yml") ? parseYaml(raw) : JSON.parse(raw);
|
|
195
|
-
if (!parsed || typeof parsed !== "object") {
|
|
196
|
-
throw new Error("tanstack-fetch: OpenAPI spec must be an object");
|
|
197
|
-
}
|
|
198
|
-
return parsed;
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
// src/cli/parse-args.ts
|
|
202
|
-
var parseArgs = (argv) => {
|
|
203
|
-
const [command, ...rest] = argv;
|
|
204
|
-
if (!command || command === "--help" || command === "help" || command === "-h") {
|
|
205
|
-
return { command: "help" };
|
|
206
|
-
}
|
|
207
|
-
if (command !== "generate") {
|
|
208
|
-
throw new Error(`tanstack-fetch: unknown command "${command}"`);
|
|
209
|
-
}
|
|
210
|
-
const flags = /* @__PURE__ */ new Map();
|
|
211
|
-
for (let index = 0; index < rest.length; index += 1) {
|
|
212
|
-
const token = rest[index];
|
|
213
|
-
if (!token.startsWith("--")) {
|
|
214
|
-
continue;
|
|
215
|
-
}
|
|
216
|
-
const key = token.slice(2);
|
|
217
|
-
const value = rest[index + 1];
|
|
218
|
-
if (!value || value.startsWith("--")) {
|
|
219
|
-
throw new Error(`tanstack-fetch: missing value for --${key}`);
|
|
220
|
-
}
|
|
221
|
-
flags.set(key, value);
|
|
222
|
-
index += 1;
|
|
223
|
-
}
|
|
224
|
-
const spec = flags.get("spec");
|
|
225
|
-
const out = flags.get("out");
|
|
226
|
-
if (!spec || !out) {
|
|
227
|
-
throw new Error("tanstack-fetch: generate requires --spec and --out");
|
|
228
|
-
}
|
|
229
|
-
return { command: "generate", spec, out };
|
|
230
|
-
};
|
|
231
|
-
var helpText = `tanstack-fetch
|
|
32
|
+
`,O=async(e,t)=>{let n=d(e);await T(t,{recursive:!0}),await f(h(t,"types.ts"),q(e),"utf8"),await f(h(t,"client.ts"),E(e,n),"utf8"),await f(h(t,"index.ts"),J(),"utf8")};import{readFile as v}from"fs/promises";var L=async e=>{try{return(await import("yaml")).parse(e)}catch{throw new Error('tanstack-fetch: install optional peer "yaml" to load YAML specs (npm i yaml)')}},$=async e=>{let t=await v(e,"utf8"),n=e.endsWith(".yaml")||e.endsWith(".yml")?await L(t):JSON.parse(t);if(!n||typeof n!="object")throw new Error("tanstack-fetch: OpenAPI spec must be an object");return n};var S=e=>{let[t,...n]=e;if(!t||t==="--help"||t==="help"||t==="-h")return{command:"help"};if(t!=="generate")throw new Error(`tanstack-fetch: unknown command "${t}"`);let r=new Map;for(let a=0;a<n.length;a+=1){let m=n[a];if(!m.startsWith("--"))continue;let c=m.slice(2),l=n[a+1];if(!l||l.startsWith("--"))throw new Error(`tanstack-fetch: missing value for --${c}`);r.set(c,l),a+=1}let s=r.get("spec"),o=r.get("out");if(!s||!o)throw new Error("tanstack-fetch: generate requires --spec and --out");return{command:"generate",spec:s,out:o}},w=`tanstack-fetch
|
|
232
33
|
|
|
233
34
|
Usage:
|
|
234
35
|
tanstack-fetch generate --spec ./openapi.json --out ./src/api
|
|
@@ -236,22 +37,4 @@ Usage:
|
|
|
236
37
|
Flags:
|
|
237
38
|
--spec OpenAPI/Swagger JSON or YAML file
|
|
238
39
|
--out Directory for generated client files
|
|
239
|
-
`;
|
|
240
|
-
|
|
241
|
-
// src/cli/index.ts
|
|
242
|
-
var runCli = async (argv = process.argv.slice(2)) => {
|
|
243
|
-
const args = parseArgs(argv);
|
|
244
|
-
if (args.command === "help") {
|
|
245
|
-
console.log(helpText);
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
const spec = await loadSpec(args.spec);
|
|
249
|
-
await generateClient(spec, args.out);
|
|
250
|
-
console.log(`tanstack-fetch: generated client in ${args.out}`);
|
|
251
|
-
};
|
|
252
|
-
runCli().catch((error) => {
|
|
253
|
-
const message = error instanceof Error ? error.message : "Unknown CLI error";
|
|
254
|
-
console.error(message);
|
|
255
|
-
process.exitCode = 1;
|
|
256
|
-
});
|
|
257
|
-
//# sourceMappingURL=cli.js.map
|
|
40
|
+
`;var P=async(e=process.argv.slice(2))=>{let t=S(e);if(t.command==="help"){console.log(w);return}let n=await $(t.spec);await O(n,t.out),console.log(`tanstack-fetch: generated client in ${t.out}`)};P().catch(e=>{let t=e instanceof Error?e.message:"Unknown CLI error";console.error(t),process.exitCode=1});
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { M as MaybePromise, C as ClientSource, I as IncomingHeaders, b as HttpInterceptor, P as PluginName, A as AuthConfig, S as StatusHandler, g as StatusHandlers, c as HttpMethod, h as PathParams, Q as QueryParams, a as FetchErrorInfo, F as FetchResult } from './config.type-eG_cuxpu.cjs';
|
|
2
|
+
|
|
3
|
+
type SseEvent<T = unknown> = {
|
|
4
|
+
event?: string;
|
|
5
|
+
data: T;
|
|
6
|
+
id?: string;
|
|
7
|
+
retry?: number;
|
|
8
|
+
};
|
|
9
|
+
type SseSubscription = {
|
|
10
|
+
/** Stop the stream. */
|
|
11
|
+
close: () => void;
|
|
12
|
+
};
|
|
13
|
+
type SseHandlers<T = unknown> = {
|
|
14
|
+
/** Simple path — only the payload. */
|
|
15
|
+
onMessage?: (data: T, event: SseEvent<T>) => void;
|
|
16
|
+
/** Full SSE event (`event`, `data`, `id`). */
|
|
17
|
+
onEvent?: (event: SseEvent<T>) => void;
|
|
18
|
+
onOpen?: () => void;
|
|
19
|
+
onError?: (error: unknown) => void;
|
|
20
|
+
onClose?: () => void;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
type UploadProgressEvent = {
|
|
24
|
+
loaded: number;
|
|
25
|
+
total?: number;
|
|
26
|
+
/** `loaded / total` when `total` is known (0–1). */
|
|
27
|
+
progress?: number;
|
|
28
|
+
};
|
|
29
|
+
type UploadProgressHandler = (event: UploadProgressEvent) => void;
|
|
30
|
+
type FormDataPrimitive = string | number | boolean | Blob;
|
|
31
|
+
type FormDataFieldValue = FormDataPrimitive | null | undefined | FormDataPrimitive[];
|
|
32
|
+
type FormDataFields = Record<string, FormDataFieldValue>;
|
|
33
|
+
type UploadBody = FormData | Blob | ArrayBuffer | URLSearchParams | string;
|
|
34
|
+
type UploadOptions = {
|
|
35
|
+
/** Pre-built body (`FormData`, `Blob`, `File`, …). */
|
|
36
|
+
body?: UploadBody;
|
|
37
|
+
/** Single file — appended as `fieldName` (default `"file"`). */
|
|
38
|
+
file?: Blob;
|
|
39
|
+
/** Multiple files — same field name repeated. */
|
|
40
|
+
files?: Blob[];
|
|
41
|
+
/** Extra multipart fields (strings, numbers, Blobs). */
|
|
42
|
+
fields?: FormDataFields;
|
|
43
|
+
/** Form field name for `file` / `files`. Default `"file"`. */
|
|
44
|
+
fieldName?: string;
|
|
45
|
+
/** HTTP method. Default `POST`. */
|
|
46
|
+
method?: 'POST' | 'PUT' | 'PATCH';
|
|
47
|
+
onUploadProgress?: UploadProgressHandler;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
type RequestInterceptorConfig = {
|
|
51
|
+
use?: HttpInterceptor[];
|
|
52
|
+
eject?: string[];
|
|
53
|
+
};
|
|
54
|
+
type RequestOptions = {
|
|
55
|
+
params?: PathParams;
|
|
56
|
+
query?: QueryParams;
|
|
57
|
+
body?: unknown;
|
|
58
|
+
headers?: HeadersInit;
|
|
59
|
+
signal?: AbortSignal;
|
|
60
|
+
timeoutMs?: number;
|
|
61
|
+
/** Default `true` — matches TanStack Query `queryFn` (throw on HTTP error). */
|
|
62
|
+
throwOnError?: boolean;
|
|
63
|
+
parseAs?: 'json' | 'text' | 'blob';
|
|
64
|
+
operation?: string;
|
|
65
|
+
interceptors?: RequestInterceptorConfig;
|
|
66
|
+
/** Browser-only — uses XHR under the hood when set (fetch has no upload progress). */
|
|
67
|
+
onUploadProgress?: UploadProgressHandler;
|
|
68
|
+
};
|
|
69
|
+
type CreateFetchOptions = {
|
|
70
|
+
baseUrl?: string;
|
|
71
|
+
headers?: HeadersInit | (() => MaybePromise<HeadersInit>);
|
|
72
|
+
source?: ClientSource;
|
|
73
|
+
incoming?: IncomingHeaders | (() => MaybePromise<IncomingHeaders>);
|
|
74
|
+
timeoutMs?: number;
|
|
75
|
+
/** Default `true` for TanStack Query. Set `false` to get `FetchResult`. */
|
|
76
|
+
throwOnError?: boolean;
|
|
77
|
+
interceptors?: HttpInterceptor[];
|
|
78
|
+
plugins?: PluginName[];
|
|
79
|
+
fetch?: typeof fetch;
|
|
80
|
+
credentials?: RequestCredentials;
|
|
81
|
+
maxRetries?: number;
|
|
82
|
+
/** Simple auth: attach Bearer token on every request. */
|
|
83
|
+
getToken?: () => MaybePromise<string | null | undefined>;
|
|
84
|
+
/** Advanced auth config (overrides `getToken` when both set via `auth`). */
|
|
85
|
+
auth?: AuthConfig;
|
|
86
|
+
/** Called on HTTP 401 before the error is thrown / returned. */
|
|
87
|
+
onUnauthorized?: StatusHandler;
|
|
88
|
+
/** Called on HTTP 403. */
|
|
89
|
+
onForbidden?: StatusHandler;
|
|
90
|
+
/** Called on HTTP 404. */
|
|
91
|
+
onNotFound?: StatusHandler;
|
|
92
|
+
/** Called on HTTP 5xx (500–599). */
|
|
93
|
+
onServerError?: StatusHandler;
|
|
94
|
+
/** Advanced per-status map (`401`, `403`, `4xx`, `5xx`, `default`, …). */
|
|
95
|
+
onStatus?: StatusHandlers;
|
|
96
|
+
};
|
|
97
|
+
type ThrowingOptions = Omit<RequestOptions, 'throwOnError'> & {
|
|
98
|
+
throwOnError?: true;
|
|
99
|
+
};
|
|
100
|
+
type ResultOptions = Omit<RequestOptions, 'throwOnError'> & {
|
|
101
|
+
throwOnError: false;
|
|
102
|
+
};
|
|
103
|
+
type FetchMethod = {
|
|
104
|
+
<T>(path: string, options?: ThrowingOptions): Promise<T>;
|
|
105
|
+
<T, E = FetchErrorInfo>(path: string, options: ResultOptions): Promise<FetchResult<T, E>>;
|
|
106
|
+
};
|
|
107
|
+
type FetchRequest = {
|
|
108
|
+
<T>(method: HttpMethod, path: string, options?: ThrowingOptions): Promise<T>;
|
|
109
|
+
<T, E = FetchErrorInfo>(method: HttpMethod, path: string, options: ResultOptions): Promise<FetchResult<T, E>>;
|
|
110
|
+
};
|
|
111
|
+
type UploadCallOptions = Omit<RequestOptions, 'body' | 'onUploadProgress'> & UploadOptions;
|
|
112
|
+
type UploadMethod = {
|
|
113
|
+
<T>(path: string, options?: UploadCallOptions & {
|
|
114
|
+
throwOnError?: true;
|
|
115
|
+
}): Promise<T>;
|
|
116
|
+
<T, E = FetchErrorInfo>(path: string, options: UploadCallOptions & {
|
|
117
|
+
throwOnError: false;
|
|
118
|
+
}): Promise<FetchResult<T, E>>;
|
|
119
|
+
};
|
|
120
|
+
type SseCallOptions<T = unknown> = RequestOptions & SseHandlers<T> & {
|
|
121
|
+
lastEventId?: string;
|
|
122
|
+
};
|
|
123
|
+
type FetchClient = {
|
|
124
|
+
use: (name: string, interceptor: Omit<HttpInterceptor, 'name'> & {
|
|
125
|
+
name?: string;
|
|
126
|
+
}, config?: {
|
|
127
|
+
order?: number;
|
|
128
|
+
}) => void;
|
|
129
|
+
eject: (name: string) => void;
|
|
130
|
+
request: FetchRequest;
|
|
131
|
+
get: FetchMethod;
|
|
132
|
+
post: FetchMethod;
|
|
133
|
+
put: FetchMethod;
|
|
134
|
+
patch: FetchMethod;
|
|
135
|
+
delete: FetchMethod;
|
|
136
|
+
/** Multipart / file upload — returns the same typed response body as `post`. */
|
|
137
|
+
upload: UploadMethod;
|
|
138
|
+
/**
|
|
139
|
+
* Simple: pass `onMessage` / `onEvent` → returns `{ close }`.
|
|
140
|
+
* Advanced: no handlers → `AsyncIterable` for `for await`.
|
|
141
|
+
*/
|
|
142
|
+
sse: {
|
|
143
|
+
<T>(path: string, options: SseCallOptions<T> & ({
|
|
144
|
+
onMessage: SseHandlers<T>['onMessage'];
|
|
145
|
+
} | {
|
|
146
|
+
onEvent: SseHandlers<T>['onEvent'];
|
|
147
|
+
})): SseSubscription;
|
|
148
|
+
<T>(path: string, options?: SseCallOptions<T>): AsyncIterable<SseEvent<T>>;
|
|
149
|
+
};
|
|
150
|
+
};
|
|
151
|
+
/** @deprecated Use CreateFetchOptions */
|
|
152
|
+
type CreateClientOptions = CreateFetchOptions;
|
|
153
|
+
/** @deprecated Use FetchClient */
|
|
154
|
+
type HttpClient = FetchClient;
|
|
155
|
+
|
|
156
|
+
export type { CreateFetchOptions as C, FetchClient as F, HttpClient as H, RequestOptions as R, SseCallOptions as S, UploadCallOptions as U, FormDataFields as a, CreateClientOptions as b, FormDataFieldValue as c, SseEvent as d, SseHandlers as e, SseSubscription as f, UploadOptions as g, UploadProgressEvent as h, UploadProgressHandler as i };
|