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/dist/react.cjs CHANGED
@@ -1,951 +1 @@
1
- 'use strict';
2
-
3
- var react = require('react');
4
-
5
- // src/react/fetch-provider.tsx
6
-
7
- // src/plugins/auth.ts
8
- var createAuthInterceptor = (auth) => ({
9
- name: "auth",
10
- order: 15,
11
- onRequest: async (context) => {
12
- const token = await auth.getToken();
13
- if (!token) {
14
- return { action: "continue", context };
15
- }
16
- const header = auth.header ?? "authorization";
17
- const scheme = auth.scheme === void 0 ? "Bearer" : auth.scheme;
18
- const value = scheme ? `${scheme} ${token}` : token;
19
- context.request.headers.set(header, value);
20
- return { action: "continue", context };
21
- }
22
- });
23
-
24
- // src/plugins/status.ts
25
- var resolveStatusHandler = (status, handlers) => {
26
- const exact = handlers[status];
27
- if (exact) {
28
- return exact;
29
- }
30
- if (status >= 500 && status < 600 && handlers["5xx"]) {
31
- return handlers["5xx"];
32
- }
33
- if (status >= 400 && status < 500 && handlers["4xx"]) {
34
- return handlers["4xx"];
35
- }
36
- return handlers.default;
37
- };
38
- var createStatusInterceptor = (handlers) => ({
39
- name: "on-status",
40
- order: 95,
41
- onResponseError: async (context) => {
42
- const status = context.error?.status ?? context.response?.status;
43
- if (status === void 0 || !context.error) {
44
- return { action: "continue", context };
45
- }
46
- const handler = resolveStatusHandler(status, handlers);
47
- if (!handler) {
48
- return { action: "continue", context };
49
- }
50
- const decision = await handler({ status, error: context.error, context });
51
- if (decision?.action === "retry") {
52
- return { action: "retry", delayMs: decision.delayMs };
53
- }
54
- return { action: "continue", context };
55
- }
56
- });
57
-
58
- // src/plugins/config-interceptors.ts
59
- var mergeStatusHandlers = (options) => {
60
- const handlers = { ...options.onStatus ?? {} };
61
- if (options.onUnauthorized && handlers[401] === void 0) {
62
- handlers[401] = options.onUnauthorized;
63
- }
64
- if (options.onForbidden && handlers[403] === void 0) {
65
- handlers[403] = options.onForbidden;
66
- }
67
- if (options.onNotFound && handlers[404] === void 0) {
68
- handlers[404] = options.onNotFound;
69
- }
70
- if (options.onServerError && handlers["5xx"] === void 0 && handlers[500] === void 0) {
71
- handlers["5xx"] = options.onServerError;
72
- }
73
- const hasHandler = Object.entries(handlers).some(([, handler]) => Boolean(handler));
74
- return hasHandler ? handlers : void 0;
75
- };
76
- var resolveAuthConfig = (options) => {
77
- if (options.auth) {
78
- return options.auth;
79
- }
80
- if (options.getToken) {
81
- return { getToken: options.getToken };
82
- }
83
- return void 0;
84
- };
85
- var createConfigInterceptors = (options) => {
86
- const interceptors = [];
87
- const auth = resolveAuthConfig(options);
88
- if (auth) {
89
- interceptors.push(createAuthInterceptor(auth));
90
- }
91
- const statusHandlers = mergeStatusHandlers(options);
92
- if (statusHandlers) {
93
- interceptors.push(createStatusInterceptor(statusHandlers));
94
- }
95
- return interceptors;
96
- };
97
-
98
- // src/constants.ts
99
- var DEFAULT_TIMEOUT_MS = 3e4;
100
- var DEFAULT_MAX_RETRIES = 2;
101
- var DEFAULT_INTERCEPTOR_ORDER = 100;
102
- var IDEMPOTENT_METHODS = /* @__PURE__ */ new Set(["GET", "HEAD", "OPTIONS"]);
103
- var RETRY_STATUSES = /* @__PURE__ */ new Set([502, 503, 504]);
104
-
105
- // src/plugins/retry-idempotent.ts
106
- var createRetryIdempotentInterceptor = () => ({
107
- name: "retry-idempotent",
108
- order: 80,
109
- onResponseError: (context) => {
110
- const canRetry = IDEMPOTENT_METHODS.has(context.request.method) && context.meta.attempt < context.meta.maxRetries && Boolean(context.error && RETRY_STATUSES.has(context.error.status));
111
- if (!canRetry) {
112
- return { action: "continue", context };
113
- }
114
- return { action: "retry", delayMs: 200 * (context.meta.attempt + 1) };
115
- }
116
- });
117
-
118
- // src/plugins/sse-resume.ts
119
- var createSseResumeInterceptor = () => ({
120
- name: "sse-resume",
121
- order: 20,
122
- onSseEvent: (context) => {
123
- if (context.event.event === "ping" || context.event.event === "heartbeat") {
124
- return { action: "drop" };
125
- }
126
- return { action: "continue", context };
127
- },
128
- onSseReconnect: (context) => {
129
- if (context.meta.lastEventId) {
130
- context.request.headers.set("last-event-id", context.meta.lastEventId);
131
- }
132
- return { action: "continue", context };
133
- }
134
- });
135
-
136
- // src/plugins/ssr-forward.ts
137
- var createSsrForwardInterceptor = () => ({
138
- name: "ssr-forward",
139
- order: 10,
140
- onRequest: (context) => {
141
- if (context.meta.source === "browser") {
142
- return { action: "skip" };
143
- }
144
- if (context.incoming?.cookie) {
145
- context.request.headers.set("cookie", context.incoming.cookie);
146
- }
147
- if (context.incoming?.authorization) {
148
- context.request.headers.set("authorization", context.incoming.authorization);
149
- }
150
- if (context.incoming?.requestId) {
151
- context.request.headers.set("x-request-id", context.incoming.requestId);
152
- }
153
- return { action: "continue", context };
154
- }
155
- });
156
-
157
- // src/plugins/trace.ts
158
- var createTraceInterceptor = () => ({
159
- name: "trace",
160
- order: 0,
161
- onRequest: (context) => {
162
- const requestId = context.incoming?.requestId ?? globalThis.crypto.randomUUID();
163
- context.request.headers.set("x-request-id", requestId);
164
- context.meta.requestId = requestId;
165
- return { action: "continue", context };
166
- }
167
- });
168
-
169
- // src/plugins/index.ts
170
- var pluginFactories = {
171
- trace: createTraceInterceptor,
172
- "ssr-forward": createSsrForwardInterceptor,
173
- "retry-idempotent": createRetryIdempotentInterceptor,
174
- "sse-resume": createSseResumeInterceptor
175
- };
176
-
177
- // src/fetch-error.ts
178
- var createFetchError = (result) => {
179
- const info = result.ok ? {
180
- status: result.status,
181
- code: "UNKNOWN",
182
- message: "Request failed",
183
- body: result.data
184
- } : result.error;
185
- const error = new Error(info.message);
186
- Object.defineProperties(error, {
187
- name: { value: "FetchError" },
188
- status: { value: info.status },
189
- code: { value: info.code },
190
- body: { value: info.body },
191
- headers: { value: result.headers },
192
- result: {
193
- value: result.ok ? { ok: false, status: info.status, error: info, headers: result.headers } : result
194
- }
195
- });
196
- return error;
197
- };
198
- var isFetchError = (error) => Boolean(
199
- error && typeof error === "object" && error.name === "FetchError" && "status" in error && "code" in error
200
- );
201
- var isAbortError = (error) => error instanceof DOMException && error.name === "AbortError" || error instanceof Error && error.name === "AbortError";
202
-
203
- // src/interceptors/run-interceptors.ts
204
- var matchesInterceptor = (interceptor, context) => {
205
- const match = interceptor.match;
206
- if (!match) {
207
- return true;
208
- }
209
- if (match.operation && match.operation !== context.meta.operation) {
210
- return false;
211
- }
212
- if (match.method && match.method !== context.request.method) {
213
- return false;
214
- }
215
- if (match.pathPrefix && !context.request.url.pathname.startsWith(match.pathPrefix)) {
216
- return false;
217
- }
218
- if (match.status !== void 0 && context.response?.status !== match.status) {
219
- return false;
220
- }
221
- return true;
222
- };
223
- var resolveInterceptors = (clientInterceptors, extras) => {
224
- const ejected = new Set(extras?.eject ?? []);
225
- const merged = [...clientInterceptors, ...extras?.use ?? []].filter(
226
- (item) => !ejected.has(item.name)
227
- );
228
- return merged.sort(
229
- (left, right) => (left.order ?? DEFAULT_INTERCEPTOR_ORDER) - (right.order ?? DEFAULT_INTERCEPTOR_ORDER)
230
- );
231
- };
232
- var runHook = async (interceptors, getHandler, context) => {
233
- let current = context;
234
- for (const interceptor of interceptors) {
235
- if (!matchesInterceptor(interceptor, current)) {
236
- continue;
237
- }
238
- const handler = getHandler(interceptor);
239
- if (!handler) {
240
- continue;
241
- }
242
- const decision = await handler(current) ?? { action: "continue", context: current };
243
- if (decision.action === "skip") {
244
- continue;
245
- }
246
- if (decision.action === "drop") {
247
- return { type: "drop" };
248
- }
249
- if (decision.action === "retry") {
250
- return { type: "retry", delayMs: decision.delayMs };
251
- }
252
- if (decision.action === "short-circuit") {
253
- return { type: "short-circuit", result: decision.result };
254
- }
255
- current = decision.context ?? current;
256
- }
257
- return { type: "continue", context: current };
258
- };
259
-
260
- // src/utils/parse-body.ts
261
- var isJsonContentType = (contentType) => Boolean(contentType && contentType.includes("json"));
262
- var parseBody = async (response, parseAs) => {
263
- if (response.status === 204) {
264
- return null;
265
- }
266
- if (parseAs === "blob") {
267
- return response.blob();
268
- }
269
- if (parseAs === "text") {
270
- return response.text();
271
- }
272
- const contentType = response.headers.get("content-type");
273
- if (parseAs === "json" || isJsonContentType(contentType)) {
274
- const text = await response.text();
275
- if (!text) {
276
- return null;
277
- }
278
- return JSON.parse(text);
279
- }
280
- return response.text();
281
- };
282
- var encodeBody = (body, headers) => {
283
- if (body === void 0 || body === null) {
284
- return void 0;
285
- }
286
- if (typeof body === "string" || body instanceof FormData || body instanceof Blob || body instanceof URLSearchParams || body instanceof ArrayBuffer) {
287
- return body;
288
- }
289
- if (!headers.has("content-type")) {
290
- headers.set("content-type", "application/json");
291
- }
292
- return JSON.stringify(body);
293
- };
294
-
295
- // src/utils/result.ts
296
- var toFetchErrorInfo = (status, body) => {
297
- const record = body && typeof body === "object" ? body : void 0;
298
- const code = typeof record?.code === "string" ? record.code : "HTTP_ERROR";
299
- const message = typeof record?.message === "string" ? record.message : `Request failed with status ${status}`;
300
- return { status, code, message, body };
301
- };
302
- var toOkResult = (status, data, headers) => ({
303
- ok: true,
304
- status,
305
- data,
306
- headers
307
- });
308
- var toErrResult = (status, error, headers) => ({
309
- ok: false,
310
- status,
311
- error,
312
- headers
313
- });
314
-
315
- // src/request-execute.ts
316
- var executeFetch = async (input) => {
317
- const { context, interceptors, fetchImpl, client, requestOptions } = input;
318
- try {
319
- const response = await fetchImpl(context.request.url, {
320
- method: context.request.method,
321
- headers: context.request.headers,
322
- body: encodeBody(context.request.body, context.request.headers),
323
- signal: context.request.signal,
324
- credentials: client.credentials
325
- });
326
- return handleResponse({ interceptors, context, response, requestOptions });
327
- } catch (error) {
328
- if (isAbortError(error)) {
329
- throw error;
330
- }
331
- context.error = toFetchErrorInfo(0, {
332
- code: "NETWORK_ERROR",
333
- message: error instanceof Error ? error.message : "Network error"
334
- });
335
- const failed = await runHook(interceptors, (item) => item.onRequestError, context);
336
- if (failed.type === "retry") {
337
- return { kind: "retry", delayMs: failed.delayMs };
338
- }
339
- if (failed.type === "short-circuit") {
340
- return { kind: "result", result: failed.result };
341
- }
342
- throw createFetchError(
343
- toErrResult(0, context.error, new Headers())
344
- );
345
- }
346
- };
347
- var handleResponse = async (input) => {
348
- const { interceptors, response, requestOptions } = input;
349
- const context = {
350
- ...input.context,
351
- response,
352
- data: await parseBody(response, requestOptions?.parseAs)
353
- };
354
- const hook = response.ok ? (item) => item.onResponse : (item) => item.onResponseError;
355
- if (!response.ok) {
356
- context.error = toFetchErrorInfo(response.status, context.data);
357
- }
358
- const after = await runHook(interceptors, hook, context);
359
- if (after.type === "retry") {
360
- return { kind: "retry", delayMs: after.delayMs };
361
- }
362
- if (after.type === "short-circuit") {
363
- return { kind: "result", result: after.result };
364
- }
365
- const nextContext = after.type === "continue" ? after.context : context;
366
- const result = response.ok ? toOkResult(response.status, nextContext.data, response.headers) : toErrResult(
367
- response.status,
368
- nextContext.error ?? toFetchErrorInfo(response.status, nextContext.data),
369
- response.headers
370
- );
371
- return { kind: "result", result };
372
- };
373
-
374
- // src/utils/build-url.ts
375
- var fillPath = (path, params) => {
376
- return path.replace(/:([A-Za-z0-9_]+)/g, (_match, key) => {
377
- const value = params?.[key];
378
- if (value === void 0) {
379
- throw new Error(`tanstack-fetch: missing path param "${key}"`);
380
- }
381
- return encodeURIComponent(String(value));
382
- });
383
- };
384
- var appendQuery = (url, query) => {
385
- if (!query) {
386
- return;
387
- }
388
- Object.entries(query).forEach(([key, value]) => {
389
- if (value === void 0 || value === null) {
390
- return;
391
- }
392
- url.searchParams.set(key, String(value));
393
- });
394
- };
395
- var assertAbsoluteUrl = (baseUrl, source) => {
396
- if (source === "browser" || !baseUrl) {
397
- return;
398
- }
399
- if (!/^https?:\/\//i.test(baseUrl)) {
400
- throw new Error("tanstack-fetch: set an absolute baseUrl for SSR and Edge runtimes");
401
- }
402
- };
403
- var buildUrl = (baseUrl, path, params, query) => {
404
- const filledPath = fillPath(path.replace(/\{([A-Za-z0-9_]+)\}/g, ":$1"), params);
405
- const absolutePath = /^https?:\/\//i.test(filledPath) ? filledPath : `${(baseUrl ?? "").replace(/\/$/, "")}/${filledPath.replace(/^\//, "")}`;
406
- const url = new URL(absolutePath);
407
- appendQuery(url, query);
408
- return url;
409
- };
410
-
411
- // src/utils/headers.ts
412
- var toHeaders = (init) => new Headers(init);
413
- var mergeHeaders = (...inits) => {
414
- const headers = new Headers();
415
- inits.forEach((init) => {
416
- if (!init) {
417
- return;
418
- }
419
- new Headers(init).forEach((value, key) => {
420
- headers.set(key, value);
421
- });
422
- });
423
- return headers;
424
- };
425
- var resolveHeaders = async (value) => {
426
- if (!value) {
427
- return new Headers();
428
- }
429
- const resolved = typeof value === "function" ? await value() : value;
430
- return toHeaders(resolved);
431
- };
432
-
433
- // src/utils/signals.ts
434
- var wait = (delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs));
435
- var combineSignals = (signals) => {
436
- const active = signals.filter((signal) => Boolean(signal));
437
- if (active.length === 0) {
438
- return void 0;
439
- }
440
- if (active.length === 1) {
441
- return active[0];
442
- }
443
- if (typeof AbortSignal.any === "function") {
444
- return AbortSignal.any(active);
445
- }
446
- const controller = new AbortController();
447
- active.forEach((signal) => {
448
- if (signal.aborted) {
449
- controller.abort(signal.reason);
450
- return;
451
- }
452
- signal.addEventListener("abort", () => controller.abort(signal.reason), { once: true });
453
- });
454
- return controller.signal;
455
- };
456
- var createTimeoutSignal = (timeoutMs) => {
457
- if (!timeoutMs || timeoutMs <= 0) {
458
- return void 0;
459
- }
460
- const controller = new AbortController();
461
- const timer = setTimeout(
462
- () => controller.abort(new Error("tanstack-fetch: request timed out")),
463
- timeoutMs
464
- );
465
- controller.signal.addEventListener("abort", () => clearTimeout(timer), { once: true });
466
- return controller.signal;
467
- };
468
-
469
- // src/request.ts
470
- var resolveIncoming = async (client) => {
471
- if (!client.incoming) {
472
- return void 0;
473
- }
474
- return typeof client.incoming === "function" ? client.incoming() : client.incoming;
475
- };
476
- var createContext = async (args, attempt) => {
477
- const { method, path, requestOptions, client } = args;
478
- const source = client.source ?? "browser";
479
- assertAbsoluteUrl(client.baseUrl, source);
480
- const url = buildUrl(client.baseUrl, path, requestOptions?.params, requestOptions?.query);
481
- const headers = mergeHeaders(await resolveHeaders(client.headers), requestOptions?.headers);
482
- const timeoutMs = requestOptions?.timeoutMs ?? client.timeoutMs ?? DEFAULT_TIMEOUT_MS;
483
- const signal = combineSignals([requestOptions?.signal, createTimeoutSignal(timeoutMs)]);
484
- return {
485
- request: { method, url, headers, body: requestOptions?.body, signal },
486
- incoming: await resolveIncoming(client),
487
- meta: {
488
- attempt,
489
- maxRetries: client.maxRetries ?? DEFAULT_MAX_RETRIES,
490
- source,
491
- operation: requestOptions?.operation
492
- }
493
- };
494
- };
495
- var sendRequest = async (args) => {
496
- const interceptors = resolveInterceptors(args.interceptors, args.requestOptions?.interceptors);
497
- const fetchImpl = args.client.fetch ?? globalThis.fetch;
498
- if (!fetchImpl) {
499
- throw new Error("tanstack-fetch: fetch is not available");
500
- }
501
- let attempt = 0;
502
- const maxRetries = args.client.maxRetries ?? DEFAULT_MAX_RETRIES;
503
- while (attempt <= maxRetries) {
504
- const outcome = await runAttempt({ args, interceptors, fetchImpl, attempt });
505
- if (outcome.kind === "result") {
506
- return outcome.result;
507
- }
508
- attempt += 1;
509
- if (outcome.delayMs) {
510
- await wait(outcome.delayMs);
511
- }
512
- }
513
- throw new Error("tanstack-fetch: exceeded retry budget");
514
- };
515
- var runAttempt = async (input) => {
516
- let context = await createContext(input.args, input.attempt);
517
- const before = await runHook(input.interceptors, (item) => item.onRequest, context);
518
- if (before.type === "short-circuit") {
519
- return { kind: "result", result: before.result };
520
- }
521
- if (before.type === "retry") {
522
- return { kind: "retry", delayMs: before.delayMs };
523
- }
524
- if (before.type === "continue") {
525
- context = before.context;
526
- }
527
- return executeFetch({
528
- requestOptions: input.args.requestOptions,
529
- client: input.args.client,
530
- interceptors: input.interceptors,
531
- fetchImpl: input.fetchImpl,
532
- context
533
- });
534
- };
535
-
536
- // src/sse-parse.ts
537
- var parseSseBlock = (block) => {
538
- const dataLines = [];
539
- let eventName;
540
- let id;
541
- let retry;
542
- block.split("\n").forEach((line) => {
543
- if (line.startsWith(":")) {
544
- return;
545
- }
546
- if (line.startsWith("data:")) {
547
- dataLines.push(line.slice(5).replace(/^ /, ""));
548
- return;
549
- }
550
- if (line.startsWith("event:")) {
551
- eventName = line.slice(6).replace(/^ /, "");
552
- return;
553
- }
554
- if (line.startsWith("id:")) {
555
- id = line.slice(3).replace(/^ /, "");
556
- return;
557
- }
558
- if (line.startsWith("retry:")) {
559
- const parsed = Number.parseInt(line.slice(6).trim(), 10);
560
- if (!Number.isNaN(parsed)) {
561
- retry = parsed;
562
- }
563
- }
564
- });
565
- if (dataLines.length === 0 && !eventName) {
566
- return void 0;
567
- }
568
- const raw = dataLines.join("\n");
569
- let data = raw;
570
- if (raw) {
571
- try {
572
- data = JSON.parse(raw);
573
- } catch {
574
- data = raw;
575
- }
576
- }
577
- return { event: eventName, data, id, retry };
578
- };
579
- var consumeSseBuffer = (buffer) => {
580
- const normalized = buffer.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
581
- const chunks = normalized.split("\n\n");
582
- const rest = chunks.pop() ?? "";
583
- const events = chunks.map((block) => parseSseBlock(block.trim())).filter((event) => Boolean(event));
584
- return { events, rest };
585
- };
586
-
587
- // src/sse-context.ts
588
- var resolveIncoming2 = async (client) => {
589
- if (!client.incoming) {
590
- return void 0;
591
- }
592
- return typeof client.incoming === "function" ? client.incoming() : client.incoming;
593
- };
594
- var createSseContext = async (args, attempt, lastEventId) => {
595
- const source = args.client.source ?? "browser";
596
- assertAbsoluteUrl(args.client.baseUrl, source);
597
- const url = buildUrl(
598
- args.client.baseUrl,
599
- args.path,
600
- args.requestOptions?.params,
601
- args.requestOptions?.query
602
- );
603
- const headers = mergeHeaders(
604
- await resolveHeaders(args.client.headers),
605
- args.requestOptions?.headers
606
- );
607
- headers.set("accept", "text/event-stream");
608
- if (lastEventId) {
609
- headers.set("last-event-id", lastEventId);
610
- }
611
- return {
612
- request: {
613
- method: "GET",
614
- url,
615
- headers,
616
- body: args.requestOptions?.body,
617
- signal: args.requestOptions?.signal
618
- },
619
- incoming: await resolveIncoming2(args.client),
620
- meta: {
621
- attempt,
622
- maxRetries: args.client.maxRetries ?? DEFAULT_MAX_RETRIES,
623
- source,
624
- operation: args.requestOptions?.operation,
625
- lastEventId
626
- }
627
- };
628
- };
629
-
630
- // src/sse-iterator.ts
631
- var createSseIterator = (args) => {
632
- const interceptors = resolveInterceptors(args.interceptors, args.requestOptions?.interceptors);
633
- const fetchImpl = args.client.fetch ?? globalThis.fetch;
634
- const queue = [];
635
- let reader;
636
- let buffer = "";
637
- let lastEventId = args.requestOptions?.headers ? new Headers(args.requestOptions.headers).get("last-event-id") ?? void 0 : void 0;
638
- let attempt = 0;
639
- let closed = false;
640
- const pull = async () => {
641
- if (queue.length > 0) {
642
- return { value: queue.shift(), done: false };
643
- }
644
- if (closed) {
645
- return { value: void 0, done: true };
646
- }
647
- if (!reader) {
648
- const started = await openStream();
649
- if (!started) {
650
- closed = true;
651
- return { value: void 0, done: true };
652
- }
653
- }
654
- return readNext();
655
- };
656
- const openStream = async () => {
657
- if (!fetchImpl) {
658
- throw new Error("tanstack-fetch: fetch is not available");
659
- }
660
- let context = await createSseContext(args, attempt, lastEventId);
661
- const before = await runHook(interceptors, (item) => item.onRequest, context);
662
- if (before.type === "short-circuit" || before.type === "retry") {
663
- return false;
664
- }
665
- if (before.type === "continue") {
666
- context = before.context;
667
- }
668
- const response = await fetchImpl(context.request.url, {
669
- method: "GET",
670
- headers: context.request.headers,
671
- signal: context.request.signal,
672
- credentials: args.client.credentials
673
- });
674
- context.response = response;
675
- if (!response.ok || !response.body) {
676
- context.error = toFetchErrorInfo(response.status, await response.text());
677
- const failed = await runHook(interceptors, (item) => item.onSseError, context);
678
- if (failed.type === "retry" && attempt < context.meta.maxRetries) {
679
- attempt += 1;
680
- await wait(failed.delayMs ?? 500);
681
- const reconnect = await runHook(interceptors, (item) => item.onSseReconnect, context);
682
- if (reconnect.type === "continue") {
683
- lastEventId = reconnect.context.meta.lastEventId ?? lastEventId;
684
- }
685
- return openStream();
686
- }
687
- throw Object.assign(new Error(context.error.message), { error: context.error });
688
- }
689
- await runHook(interceptors, (item) => item.onSseOpen, context);
690
- reader = response.body.pipeThrough(new TextDecoderStream()).getReader();
691
- return true;
692
- };
693
- const readNext = async () => {
694
- const currentReader = reader;
695
- if (!currentReader) {
696
- closed = true;
697
- return { value: void 0, done: true };
698
- }
699
- const chunk = await currentReader.read();
700
- if (chunk.done) {
701
- reader = void 0;
702
- closed = true;
703
- return pull();
704
- }
705
- const parsed = consumeSseBuffer(buffer + chunk.value);
706
- buffer = parsed.rest;
707
- for (const event of parsed.events) {
708
- lastEventId = event.id ?? lastEventId;
709
- const context = await createSseContext(args, attempt, lastEventId);
710
- const after = await runHook(interceptors, (item) => item.onSseEvent, { ...context, event });
711
- if (after.type === "drop") {
712
- continue;
713
- }
714
- if (after.type === "continue" && after.context.event) {
715
- queue.push(after.context.event);
716
- }
717
- }
718
- return pull();
719
- };
720
- return { next: pull };
721
- };
722
-
723
- // src/sse.ts
724
- var sendSse = (args) => ({
725
- [Symbol.asyncIterator]: () => createSseIterator(args)
726
- });
727
-
728
- // src/sse-listen.ts
729
- var HANDLER_KEYS = ["onMessage", "onEvent", "onOpen", "onError", "onClose", "lastEventId"];
730
- var splitSseOptions = (options) => {
731
- const handlers = {};
732
- const requestOptions = { ...options ?? {} };
733
- const lastEventId = options?.lastEventId;
734
- for (const key of HANDLER_KEYS) {
735
- if (key === "lastEventId") {
736
- delete requestOptions.lastEventId;
737
- continue;
738
- }
739
- if (options && key in options) {
740
- handlers[key] = options[key];
741
- delete requestOptions[key];
742
- }
743
- }
744
- if (lastEventId) {
745
- const headers = new Headers(requestOptions.headers);
746
- headers.set("last-event-id", lastEventId);
747
- requestOptions.headers = headers;
748
- }
749
- return { handlers, requestOptions, lastEventId };
750
- };
751
- var hasSseHandlers = (options) => Boolean(
752
- options && (options.onMessage || options.onEvent || options.onOpen || options.onError || options.onClose)
753
- );
754
- var listenSse = (args, handlers) => {
755
- const controller = new AbortController();
756
- const signal = combineSignals([args.requestOptions?.signal, controller.signal]);
757
- const run = async () => {
758
- try {
759
- handlers.onOpen?.();
760
- for await (const event of sendSse({
761
- ...args,
762
- requestOptions: { ...args.requestOptions, signal }
763
- })) {
764
- handlers.onEvent?.(event);
765
- handlers.onMessage?.(event.data, event);
766
- }
767
- handlers.onClose?.();
768
- } catch (error) {
769
- if (isAbortError(error) || controller.signal.aborted) {
770
- handlers.onClose?.();
771
- return;
772
- }
773
- handlers.onError?.(error);
774
- }
775
- };
776
- void run();
777
- return {
778
- close: () => controller.abort()
779
- };
780
- };
781
- var createSseApi = (base) => {
782
- const sse = ((path, options) => {
783
- const { handlers, requestOptions } = splitSseOptions(options);
784
- const args = {
785
- path,
786
- requestOptions,
787
- client: base.client,
788
- interceptors: base.interceptors
789
- };
790
- if (hasSseHandlers(options)) {
791
- return listenSse(args, handlers);
792
- }
793
- return sendSse(args);
794
- });
795
- return sse;
796
- };
797
-
798
- // src/create-fetch.ts
799
- var createFetch = (options = {}) => {
800
- const clientOptions = {
801
- throwOnError: true,
802
- ...options
803
- };
804
- const interceptors = [
805
- ...createConfigInterceptors(clientOptions),
806
- ...(clientOptions.plugins ?? []).map((name) => pluginFactories[name]()),
807
- ...clientOptions.interceptors ?? []
808
- ];
809
- const use = (name, interceptor, config) => {
810
- const next = {
811
- ...interceptor,
812
- name,
813
- order: config?.order ?? interceptor.order
814
- };
815
- const existing = interceptors.findIndex((item) => item.name === name);
816
- if (existing >= 0) {
817
- interceptors.splice(existing, 1, next);
818
- return;
819
- }
820
- interceptors.push(next);
821
- };
822
- const eject = (name) => {
823
- const index = interceptors.findIndex((item) => item.name === name);
824
- if (index >= 0) {
825
- interceptors.splice(index, 1);
826
- }
827
- };
828
- const request = (async (method, path, requestOptions) => {
829
- const result = await sendRequest({
830
- method,
831
- path,
832
- requestOptions,
833
- client: clientOptions,
834
- interceptors
835
- });
836
- const shouldThrow = requestOptions?.throwOnError ?? clientOptions.throwOnError ?? true;
837
- if (!result.ok && shouldThrow) {
838
- throw createFetchError(result);
839
- }
840
- if (shouldThrow && result.ok) {
841
- return result.data;
842
- }
843
- return result;
844
- });
845
- const bindMethod = (httpMethod) => {
846
- const bound = (path, requestOptions) => request(httpMethod, path, requestOptions);
847
- return bound;
848
- };
849
- const sse = createSseApi({ client: clientOptions, interceptors });
850
- return {
851
- use,
852
- eject,
853
- request,
854
- get: bindMethod("GET"),
855
- post: bindMethod("POST"),
856
- put: bindMethod("PUT"),
857
- patch: bindMethod("PATCH"),
858
- delete: bindMethod("DELETE"),
859
- sse
860
- };
861
- };
862
- var FetchContext = react.createContext(null);
863
- var buildClient = (props) => {
864
- if (props.client) {
865
- return props.client;
866
- }
867
- const { children: _children, client: _client, ...options } = props;
868
- return createFetch(options);
869
- };
870
- var useFetchProviderState = (props) => {
871
- const createdRef = react.useRef(null);
872
- if (props.client) {
873
- return { value: props.client, children: props.children };
874
- }
875
- if (!createdRef.current) {
876
- createdRef.current = buildClient(props);
877
- }
878
- return { value: createdRef.current, children: props.children };
879
- };
880
- var useFetch = () => {
881
- const client = react.useContext(FetchContext);
882
- if (!client) {
883
- throw new Error("tanstack-fetch: useFetch() must be used inside <FetchProvider>");
884
- }
885
- return client;
886
- };
887
-
888
- // src/react/fetch-provider.tsx
889
- var FetchProvider = (props) => {
890
- const { value, children } = useFetchProviderState(props);
891
- return react.createElement(FetchContext.Provider, { value }, children);
892
- };
893
- var fetch_provider_default = FetchProvider;
894
- var useSse = (path, options = {}) => {
895
- const api = useFetch();
896
- const [data, setData] = react.useState(void 0);
897
- const [event, setEvent] = react.useState(void 0);
898
- const [error, setError] = react.useState(void 0);
899
- const [isConnected, setIsConnected] = react.useState(false);
900
- const closeRef = react.useRef(null);
901
- const optionsRef = react.useRef(options);
902
- optionsRef.current = options;
903
- const paramsKey = JSON.stringify(options.params ?? null);
904
- const queryKey = JSON.stringify(options.query ?? null);
905
- react.useEffect(() => {
906
- if (options.enabled === false) {
907
- return;
908
- }
909
- setError(void 0);
910
- const subscription = api.sse(path, {
911
- params: optionsRef.current.params,
912
- query: optionsRef.current.query,
913
- lastEventId: optionsRef.current.lastEventId,
914
- onOpen: () => setIsConnected(true),
915
- onMessage: (message, nextEvent) => {
916
- setData(message);
917
- setEvent(nextEvent);
918
- optionsRef.current.onMessage?.(message, nextEvent);
919
- },
920
- onEvent: (nextEvent) => {
921
- optionsRef.current.onEvent?.(nextEvent);
922
- },
923
- onError: (nextError) => {
924
- setIsConnected(false);
925
- const normalized = nextError instanceof Error ? nextError : new Error("SSE connection failed");
926
- setError(isFetchError(nextError) ? nextError : normalized);
927
- optionsRef.current.onError?.(nextError);
928
- },
929
- onClose: () => setIsConnected(false)
930
- });
931
- closeRef.current = subscription.close;
932
- return () => {
933
- subscription.close();
934
- closeRef.current = null;
935
- setIsConnected(false);
936
- };
937
- }, [api, path, options.enabled, options.lastEventId, paramsKey, queryKey]);
938
- return {
939
- data,
940
- event,
941
- error,
942
- isConnected,
943
- close: () => closeRef.current?.()
944
- };
945
- };
946
-
947
- exports.FetchProvider = fetch_provider_default;
948
- exports.useFetch = useFetch;
949
- exports.useSse = useSse;
950
- //# sourceMappingURL=react.cjs.map
951
- //# sourceMappingURL=react.cjs.map
1
+ 'use strict';var react=require('react'),tanstackFetch=require('tanstack-fetch');var a=react.createContext(null),O=e=>{if(e.client)return e.client;let{children:t,client:o,...l}=e;return tanstackFetch.createFetch(l)},p=e=>{let t=react.useRef(null);return e.client?{value:e.client,children:e.children}:(t.current||(t.current=O(e)),{value:t.current,children:e.children})},f=()=>{let e=react.useContext(a);if(!e)throw new Error("tanstack-fetch: useFetch() must be used inside <FetchProvider>");return e};var x=e=>{let{value:t,children:o}=p(e);return react.createElement(a.Provider,{value:t},o)},k=x;var q=e=>!!(e&&typeof e=="object"&&"sse"in e&&typeof e.sse=="function"),I=(e,t={})=>{let o=f(),[l,v]=react.useState(void 0),[F,y]=react.useState(void 0),[C,u]=react.useState(void 0),[S,s]=react.useState(false),d=react.useRef(null),n=react.useRef(t);n.current=t;let E=JSON.stringify(t.params??null),P=JSON.stringify(t.query??null);return react.useEffect(()=>{if(t.enabled===false)return;if(!q(o)){u(new Error('tanstack-fetch: useSse() needs a client from "tanstack-fetch/sse" (pass client to FetchProvider)'));return}u(void 0);let h=o.sse(e,{params:n.current.params,query:n.current.query,lastEventId:n.current.lastEventId,onOpen:()=>s(true),onMessage:(r,c)=>{v(r),y(c),n.current.onMessage?.(r,c);},onEvent:r=>{n.current.onEvent?.(r);},onError:r=>{s(false);let c=r instanceof Error?r:new Error("SSE connection failed");u(tanstackFetch.isFetchError(r)?r:c),n.current.onError?.(r);},onClose:()=>s(false)});return d.current=h.close,()=>{h.close(),d.current=null,s(false);}},[o,e,t.enabled,t.lastEventId,E,P]),{data:l,event:F,error:C,isConnected:S,close:()=>d.current?.()}};exports.FetchProvider=k;exports.useFetch=f;exports.useSse=I;