@spoosh/core 0.11.1 → 0.12.1
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 +1 -1
- package/dist/index.d.mts +62 -49
- package/dist/index.d.ts +62 -49
- package/dist/index.js +57 -100
- package/dist/index.mjs +57 -100
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -164,7 +164,7 @@ import { Spoosh } from "@spoosh/core";
|
|
|
164
164
|
import { cachePlugin } from "@spoosh/plugin-cache";
|
|
165
165
|
import { retryPlugin } from "@spoosh/plugin-retry";
|
|
166
166
|
|
|
167
|
-
const
|
|
167
|
+
const spoosh = new Spoosh<ApiSchema, Error>("/api", {
|
|
168
168
|
headers: { Authorization: "Bearer token" },
|
|
169
169
|
}).use([cachePlugin({ staleTime: 5000 }), retryPlugin({ retries: 3 })]);
|
|
170
170
|
|
package/dist/index.d.mts
CHANGED
|
@@ -82,10 +82,6 @@ interface TransportOptionsMap {
|
|
|
82
82
|
fetch: never;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
type RetryConfig = {
|
|
86
|
-
retries?: number | false;
|
|
87
|
-
retryDelay?: number;
|
|
88
|
-
};
|
|
89
85
|
type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
90
86
|
type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
|
|
91
87
|
headers?: HeadersInitOrGetter;
|
|
@@ -134,7 +130,7 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
|
|
|
134
130
|
transport?: TransportOption;
|
|
135
131
|
/** Transport-specific options passed through to the transport function. */
|
|
136
132
|
transportOptions?: unknown;
|
|
137
|
-
}
|
|
133
|
+
};
|
|
138
134
|
type DynamicParamsOption = {
|
|
139
135
|
params?: Record<string, string | number>;
|
|
140
136
|
};
|
|
@@ -270,7 +266,7 @@ type CacheEntry<TData = unknown, TError = unknown> = {
|
|
|
270
266
|
stale?: boolean;
|
|
271
267
|
};
|
|
272
268
|
/** RequestOptions in plugin context have headers already resolved to Record */
|
|
273
|
-
type PluginRequestOptions = Omit<AnyRequestOptions, "headers"> & {
|
|
269
|
+
type PluginRequestOptions = Omit<AnyRequestOptions, "headers" | "cache"> & {
|
|
274
270
|
headers: Record<string, string>;
|
|
275
271
|
};
|
|
276
272
|
type PluginContext = {
|
|
@@ -279,13 +275,16 @@ type PluginContext = {
|
|
|
279
275
|
readonly method: HttpMethod;
|
|
280
276
|
readonly queryKey: string;
|
|
281
277
|
readonly tags: string[];
|
|
282
|
-
/** Timestamp when this request was initiated. Useful for tracing and debugging. */
|
|
283
278
|
readonly requestTimestamp: number;
|
|
284
|
-
/** Unique identifier for
|
|
285
|
-
readonly
|
|
279
|
+
/** Unique identifier for this usage instance. Persists across queryKey changes. */
|
|
280
|
+
readonly instanceId?: string;
|
|
281
|
+
/** Request options with resolved headers. Modify to customize the request (e.g., add headers, signal, body). */
|
|
286
282
|
request: PluginRequestOptions;
|
|
283
|
+
/** Temporary storage for inter-plugin communication during a single request lifecycle. Data stored here doesn't persist beyond the request. */
|
|
287
284
|
temp: Map<string, unknown>;
|
|
285
|
+
/** State manager for accessing and modifying cache entries. */
|
|
288
286
|
stateManager: StateManager;
|
|
287
|
+
/** Event emitter for triggering refetch, invalidation, and other events. */
|
|
289
288
|
eventEmitter: EventEmitter;
|
|
290
289
|
/** Access other plugins' exported APIs */
|
|
291
290
|
plugins: PluginAccessor;
|
|
@@ -315,12 +314,9 @@ type PluginContextInput = Omit<PluginContext, "plugins">;
|
|
|
315
314
|
* return next();
|
|
316
315
|
* }
|
|
317
316
|
*
|
|
318
|
-
* //
|
|
317
|
+
* // Auth middleware - add authentication headers
|
|
319
318
|
* middleware: async (context, next) => {
|
|
320
|
-
*
|
|
321
|
-
* const result = await next();
|
|
322
|
-
* if (!result.error) return result;
|
|
323
|
-
* }
|
|
319
|
+
* context.request.headers['Authorization'] = `Bearer ${getToken()}`;
|
|
324
320
|
* return next();
|
|
325
321
|
* }
|
|
326
322
|
* ```
|
|
@@ -376,7 +372,7 @@ type PluginTypeConfig = {
|
|
|
376
372
|
* Base interface for Spoosh plugins.
|
|
377
373
|
*
|
|
378
374
|
* Plugins can implement:
|
|
379
|
-
* - `middleware`: Wraps the fetch flow for full control (intercept,
|
|
375
|
+
* - `middleware`: Wraps the fetch flow for full control (intercept, transform, modify)
|
|
380
376
|
* - `afterResponse`: Called after every response, regardless of early returns
|
|
381
377
|
* - `lifecycle`: Component lifecycle hooks (onMount, onUpdate, onUnmount)
|
|
382
378
|
* - `exports`: Functions/variables accessible to other plugins
|
|
@@ -424,7 +420,7 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
|
|
|
424
420
|
/** Expose functions/variables for other plugins to access via `context.plugins.get(name)` */
|
|
425
421
|
exports?: (context: PluginContext) => object;
|
|
426
422
|
/**
|
|
427
|
-
* Expose functions/properties on the framework adapter return value (e.g.,
|
|
423
|
+
* Expose functions/properties on the framework adapter return value (e.g., create).
|
|
428
424
|
* Unlike `exports`, these are accessible directly from the instance, not just within plugin context.
|
|
429
425
|
*
|
|
430
426
|
* @example
|
|
@@ -438,7 +434,37 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
|
|
|
438
434
|
instanceApi?: (context: InstanceApiContext) => T extends {
|
|
439
435
|
instanceApi: infer A;
|
|
440
436
|
} ? A : object;
|
|
441
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* Plugin execution priority. Lower numbers run first, higher numbers run last.
|
|
439
|
+
* Default: 0
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* // Cache plugin runs early (checks cache before other plugins)
|
|
444
|
+
* { name: "cache", priority: -10, ... }
|
|
445
|
+
*
|
|
446
|
+
* // Throttle plugin runs last (blocks all requests including force fetches)
|
|
447
|
+
* { name: "throttle", priority: 100, ... }
|
|
448
|
+
*
|
|
449
|
+
* // Most plugins use default priority
|
|
450
|
+
* { name: "retry", priority: 0, ... } // or omit priority
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
priority?: number;
|
|
454
|
+
/**
|
|
455
|
+
* List of plugin names that this plugin depends on.
|
|
456
|
+
* Used to validate that required plugins are registered.
|
|
457
|
+
* Does not affect execution order - use `priority` for that.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* {
|
|
462
|
+
* name: "spoosh:optimistic",
|
|
463
|
+
* dependencies: ["spoosh:invalidation"],
|
|
464
|
+
* // ...
|
|
465
|
+
* }
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
442
468
|
dependencies?: string[];
|
|
443
469
|
/** @internal Type carrier for inference - do not use directly */
|
|
444
470
|
readonly _types?: T;
|
|
@@ -1129,8 +1155,8 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
|
|
|
1129
1155
|
*
|
|
1130
1156
|
* @example Basic usage
|
|
1131
1157
|
* ```ts
|
|
1132
|
-
* const
|
|
1133
|
-
* .use([cachePlugin(),
|
|
1158
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api')
|
|
1159
|
+
* .use([cachePlugin(), debugPlugin()]);
|
|
1134
1160
|
*
|
|
1135
1161
|
* const { api } = client;
|
|
1136
1162
|
* const response = await api("posts").GET();
|
|
@@ -1138,19 +1164,19 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
|
|
|
1138
1164
|
*
|
|
1139
1165
|
* @example With default options
|
|
1140
1166
|
* ```ts
|
|
1141
|
-
* const
|
|
1167
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1142
1168
|
* headers: { 'Authorization': 'Bearer token' }
|
|
1143
1169
|
* }).use([cachePlugin()]);
|
|
1144
1170
|
* ```
|
|
1145
1171
|
*
|
|
1146
1172
|
* @example With React hooks
|
|
1147
1173
|
* ```ts
|
|
1148
|
-
* import {
|
|
1174
|
+
* import { create } from '@spoosh/react';
|
|
1149
1175
|
*
|
|
1150
|
-
* const
|
|
1151
|
-
* .use([cachePlugin(),
|
|
1176
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api')
|
|
1177
|
+
* .use([cachePlugin(), prefetchPlugin()]);
|
|
1152
1178
|
*
|
|
1153
|
-
* const { useRead, useWrite } =
|
|
1179
|
+
* const { useRead, useWrite } = create(client);
|
|
1154
1180
|
*
|
|
1155
1181
|
* // In component
|
|
1156
1182
|
* const { data } = useRead((api) => api("posts").GET());
|
|
@@ -1173,15 +1199,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1173
1199
|
* @example
|
|
1174
1200
|
* ```ts
|
|
1175
1201
|
* // Simple usage
|
|
1176
|
-
* const
|
|
1202
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api');
|
|
1177
1203
|
*
|
|
1178
1204
|
* // With default headers
|
|
1179
|
-
* const
|
|
1205
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1180
1206
|
* headers: { 'X-API-Key': 'secret' }
|
|
1181
1207
|
* });
|
|
1182
1208
|
*
|
|
1183
1209
|
* // With XHR transport (narrows available options to XHR-compatible fields)
|
|
1184
|
-
* const
|
|
1210
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1185
1211
|
* transport: 'xhr',
|
|
1186
1212
|
* credentials: 'include',
|
|
1187
1213
|
* });
|
|
@@ -1198,26 +1224,10 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1198
1224
|
* @param plugins - Array of plugin instances to use
|
|
1199
1225
|
* @returns A new Spoosh instance with the specified plugins
|
|
1200
1226
|
*
|
|
1201
|
-
* @example Single use() call
|
|
1202
|
-
* ```ts
|
|
1203
|
-
* const client = new Spoosh<Schema, Error>('/api')
|
|
1204
|
-
* .use([cachePlugin(), retryPlugin(), debouncePlugin()]);
|
|
1205
|
-
* ```
|
|
1206
|
-
*
|
|
1207
|
-
* @example Chaining use() calls (replaces plugins)
|
|
1208
|
-
* ```ts
|
|
1209
|
-
* const client1 = new Spoosh<Schema, Error>('/api')
|
|
1210
|
-
* .use([cachePlugin()]);
|
|
1211
|
-
*
|
|
1212
|
-
* // This replaces cachePlugin with retryPlugin
|
|
1213
|
-
* const client2 = client1.use([retryPlugin()]);
|
|
1214
|
-
* ```
|
|
1215
|
-
*
|
|
1216
|
-
* @example With plugin configuration
|
|
1217
1227
|
* ```ts
|
|
1218
|
-
* const
|
|
1228
|
+
* const spoosh = new Spoosh<Schema, Error>('/api').use([
|
|
1219
1229
|
* cachePlugin({ staleTime: 5000 }),
|
|
1220
|
-
*
|
|
1230
|
+
* invalidationPlugin(),
|
|
1221
1231
|
* prefetchPlugin(),
|
|
1222
1232
|
* ]);
|
|
1223
1233
|
* ```
|
|
@@ -1242,7 +1252,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1242
1252
|
*
|
|
1243
1253
|
* @example
|
|
1244
1254
|
* ```ts
|
|
1245
|
-
* const
|
|
1255
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api').use([...]);
|
|
1246
1256
|
* const { api } = client;
|
|
1247
1257
|
*
|
|
1248
1258
|
* // GET request
|
|
@@ -1433,6 +1443,9 @@ type TagOptions = {
|
|
|
1433
1443
|
declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[];
|
|
1434
1444
|
declare function resolvePath(path: string[], params: Record<string, string | number> | undefined): string[];
|
|
1435
1445
|
|
|
1446
|
+
declare const isNetworkError: (err: unknown) => boolean;
|
|
1447
|
+
declare const isAbortError: (err: unknown) => boolean;
|
|
1448
|
+
|
|
1436
1449
|
type ProxyHandlerConfig<TOptions = SpooshOptions> = {
|
|
1437
1450
|
baseUrl: string;
|
|
1438
1451
|
defaultOptions: TOptions;
|
|
@@ -1610,7 +1623,7 @@ type CreateOperationOptions<TData, TError> = {
|
|
|
1610
1623
|
pluginExecutor: PluginExecutor;
|
|
1611
1624
|
fetchFn: (options: AnyRequestOptions) => Promise<SpooshResponse<TData, TError>>;
|
|
1612
1625
|
/** Unique identifier for the hook instance. Persists across queryKey changes. */
|
|
1613
|
-
|
|
1626
|
+
instanceId?: string;
|
|
1614
1627
|
};
|
|
1615
1628
|
declare function createOperationController<TData, TError>(options: CreateOperationOptions<TData, TError>): OperationController<TData, TError>;
|
|
1616
1629
|
|
|
@@ -1663,8 +1676,8 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
|
|
|
1663
1676
|
pluginExecutor: PluginExecutor;
|
|
1664
1677
|
fetchFn: (options: InfiniteRequestOptions, signal: AbortSignal) => Promise<SpooshResponse<TData, TError>>;
|
|
1665
1678
|
/** Unique identifier for the hook instance. Persists across queryKey changes. */
|
|
1666
|
-
|
|
1679
|
+
instanceId?: string;
|
|
1667
1680
|
};
|
|
1668
1681
|
declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
|
|
1669
1682
|
|
|
1670
|
-
export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type EventEmitter, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestOptions$1 as RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type
|
|
1683
|
+
export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type EventEmitter, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestOptions$1 as RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, __DEV__, buildUrl, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
|
package/dist/index.d.ts
CHANGED
|
@@ -82,10 +82,6 @@ interface TransportOptionsMap {
|
|
|
82
82
|
fetch: never;
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
type RetryConfig = {
|
|
86
|
-
retries?: number | false;
|
|
87
|
-
retryDelay?: number;
|
|
88
|
-
};
|
|
89
85
|
type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
90
86
|
type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
|
|
91
87
|
headers?: HeadersInitOrGetter;
|
|
@@ -134,7 +130,7 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
|
|
|
134
130
|
transport?: TransportOption;
|
|
135
131
|
/** Transport-specific options passed through to the transport function. */
|
|
136
132
|
transportOptions?: unknown;
|
|
137
|
-
}
|
|
133
|
+
};
|
|
138
134
|
type DynamicParamsOption = {
|
|
139
135
|
params?: Record<string, string | number>;
|
|
140
136
|
};
|
|
@@ -270,7 +266,7 @@ type CacheEntry<TData = unknown, TError = unknown> = {
|
|
|
270
266
|
stale?: boolean;
|
|
271
267
|
};
|
|
272
268
|
/** RequestOptions in plugin context have headers already resolved to Record */
|
|
273
|
-
type PluginRequestOptions = Omit<AnyRequestOptions, "headers"> & {
|
|
269
|
+
type PluginRequestOptions = Omit<AnyRequestOptions, "headers" | "cache"> & {
|
|
274
270
|
headers: Record<string, string>;
|
|
275
271
|
};
|
|
276
272
|
type PluginContext = {
|
|
@@ -279,13 +275,16 @@ type PluginContext = {
|
|
|
279
275
|
readonly method: HttpMethod;
|
|
280
276
|
readonly queryKey: string;
|
|
281
277
|
readonly tags: string[];
|
|
282
|
-
/** Timestamp when this request was initiated. Useful for tracing and debugging. */
|
|
283
278
|
readonly requestTimestamp: number;
|
|
284
|
-
/** Unique identifier for
|
|
285
|
-
readonly
|
|
279
|
+
/** Unique identifier for this usage instance. Persists across queryKey changes. */
|
|
280
|
+
readonly instanceId?: string;
|
|
281
|
+
/** Request options with resolved headers. Modify to customize the request (e.g., add headers, signal, body). */
|
|
286
282
|
request: PluginRequestOptions;
|
|
283
|
+
/** Temporary storage for inter-plugin communication during a single request lifecycle. Data stored here doesn't persist beyond the request. */
|
|
287
284
|
temp: Map<string, unknown>;
|
|
285
|
+
/** State manager for accessing and modifying cache entries. */
|
|
288
286
|
stateManager: StateManager;
|
|
287
|
+
/** Event emitter for triggering refetch, invalidation, and other events. */
|
|
289
288
|
eventEmitter: EventEmitter;
|
|
290
289
|
/** Access other plugins' exported APIs */
|
|
291
290
|
plugins: PluginAccessor;
|
|
@@ -315,12 +314,9 @@ type PluginContextInput = Omit<PluginContext, "plugins">;
|
|
|
315
314
|
* return next();
|
|
316
315
|
* }
|
|
317
316
|
*
|
|
318
|
-
* //
|
|
317
|
+
* // Auth middleware - add authentication headers
|
|
319
318
|
* middleware: async (context, next) => {
|
|
320
|
-
*
|
|
321
|
-
* const result = await next();
|
|
322
|
-
* if (!result.error) return result;
|
|
323
|
-
* }
|
|
319
|
+
* context.request.headers['Authorization'] = `Bearer ${getToken()}`;
|
|
324
320
|
* return next();
|
|
325
321
|
* }
|
|
326
322
|
* ```
|
|
@@ -376,7 +372,7 @@ type PluginTypeConfig = {
|
|
|
376
372
|
* Base interface for Spoosh plugins.
|
|
377
373
|
*
|
|
378
374
|
* Plugins can implement:
|
|
379
|
-
* - `middleware`: Wraps the fetch flow for full control (intercept,
|
|
375
|
+
* - `middleware`: Wraps the fetch flow for full control (intercept, transform, modify)
|
|
380
376
|
* - `afterResponse`: Called after every response, regardless of early returns
|
|
381
377
|
* - `lifecycle`: Component lifecycle hooks (onMount, onUpdate, onUnmount)
|
|
382
378
|
* - `exports`: Functions/variables accessible to other plugins
|
|
@@ -424,7 +420,7 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
|
|
|
424
420
|
/** Expose functions/variables for other plugins to access via `context.plugins.get(name)` */
|
|
425
421
|
exports?: (context: PluginContext) => object;
|
|
426
422
|
/**
|
|
427
|
-
* Expose functions/properties on the framework adapter return value (e.g.,
|
|
423
|
+
* Expose functions/properties on the framework adapter return value (e.g., create).
|
|
428
424
|
* Unlike `exports`, these are accessible directly from the instance, not just within plugin context.
|
|
429
425
|
*
|
|
430
426
|
* @example
|
|
@@ -438,7 +434,37 @@ interface SpooshPlugin<T extends PluginTypeConfig = PluginTypeConfig> {
|
|
|
438
434
|
instanceApi?: (context: InstanceApiContext) => T extends {
|
|
439
435
|
instanceApi: infer A;
|
|
440
436
|
} ? A : object;
|
|
441
|
-
/**
|
|
437
|
+
/**
|
|
438
|
+
* Plugin execution priority. Lower numbers run first, higher numbers run last.
|
|
439
|
+
* Default: 0
|
|
440
|
+
*
|
|
441
|
+
* @example
|
|
442
|
+
* ```ts
|
|
443
|
+
* // Cache plugin runs early (checks cache before other plugins)
|
|
444
|
+
* { name: "cache", priority: -10, ... }
|
|
445
|
+
*
|
|
446
|
+
* // Throttle plugin runs last (blocks all requests including force fetches)
|
|
447
|
+
* { name: "throttle", priority: 100, ... }
|
|
448
|
+
*
|
|
449
|
+
* // Most plugins use default priority
|
|
450
|
+
* { name: "retry", priority: 0, ... } // or omit priority
|
|
451
|
+
* ```
|
|
452
|
+
*/
|
|
453
|
+
priority?: number;
|
|
454
|
+
/**
|
|
455
|
+
* List of plugin names that this plugin depends on.
|
|
456
|
+
* Used to validate that required plugins are registered.
|
|
457
|
+
* Does not affect execution order - use `priority` for that.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* {
|
|
462
|
+
* name: "spoosh:optimistic",
|
|
463
|
+
* dependencies: ["spoosh:invalidation"],
|
|
464
|
+
* // ...
|
|
465
|
+
* }
|
|
466
|
+
* ```
|
|
467
|
+
*/
|
|
442
468
|
dependencies?: string[];
|
|
443
469
|
/** @internal Type carrier for inference - do not use directly */
|
|
444
470
|
readonly _types?: T;
|
|
@@ -1129,8 +1155,8 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
|
|
|
1129
1155
|
*
|
|
1130
1156
|
* @example Basic usage
|
|
1131
1157
|
* ```ts
|
|
1132
|
-
* const
|
|
1133
|
-
* .use([cachePlugin(),
|
|
1158
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api')
|
|
1159
|
+
* .use([cachePlugin(), debugPlugin()]);
|
|
1134
1160
|
*
|
|
1135
1161
|
* const { api } = client;
|
|
1136
1162
|
* const response = await api("posts").GET();
|
|
@@ -1138,19 +1164,19 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
|
|
|
1138
1164
|
*
|
|
1139
1165
|
* @example With default options
|
|
1140
1166
|
* ```ts
|
|
1141
|
-
* const
|
|
1167
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1142
1168
|
* headers: { 'Authorization': 'Bearer token' }
|
|
1143
1169
|
* }).use([cachePlugin()]);
|
|
1144
1170
|
* ```
|
|
1145
1171
|
*
|
|
1146
1172
|
* @example With React hooks
|
|
1147
1173
|
* ```ts
|
|
1148
|
-
* import {
|
|
1174
|
+
* import { create } from '@spoosh/react';
|
|
1149
1175
|
*
|
|
1150
|
-
* const
|
|
1151
|
-
* .use([cachePlugin(),
|
|
1176
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api')
|
|
1177
|
+
* .use([cachePlugin(), prefetchPlugin()]);
|
|
1152
1178
|
*
|
|
1153
|
-
* const { useRead, useWrite } =
|
|
1179
|
+
* const { useRead, useWrite } = create(client);
|
|
1154
1180
|
*
|
|
1155
1181
|
* // In component
|
|
1156
1182
|
* const { data } = useRead((api) => api("posts").GET());
|
|
@@ -1173,15 +1199,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1173
1199
|
* @example
|
|
1174
1200
|
* ```ts
|
|
1175
1201
|
* // Simple usage
|
|
1176
|
-
* const
|
|
1202
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api');
|
|
1177
1203
|
*
|
|
1178
1204
|
* // With default headers
|
|
1179
|
-
* const
|
|
1205
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1180
1206
|
* headers: { 'X-API-Key': 'secret' }
|
|
1181
1207
|
* });
|
|
1182
1208
|
*
|
|
1183
1209
|
* // With XHR transport (narrows available options to XHR-compatible fields)
|
|
1184
|
-
* const
|
|
1210
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1185
1211
|
* transport: 'xhr',
|
|
1186
1212
|
* credentials: 'include',
|
|
1187
1213
|
* });
|
|
@@ -1198,26 +1224,10 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1198
1224
|
* @param plugins - Array of plugin instances to use
|
|
1199
1225
|
* @returns A new Spoosh instance with the specified plugins
|
|
1200
1226
|
*
|
|
1201
|
-
* @example Single use() call
|
|
1202
|
-
* ```ts
|
|
1203
|
-
* const client = new Spoosh<Schema, Error>('/api')
|
|
1204
|
-
* .use([cachePlugin(), retryPlugin(), debouncePlugin()]);
|
|
1205
|
-
* ```
|
|
1206
|
-
*
|
|
1207
|
-
* @example Chaining use() calls (replaces plugins)
|
|
1208
|
-
* ```ts
|
|
1209
|
-
* const client1 = new Spoosh<Schema, Error>('/api')
|
|
1210
|
-
* .use([cachePlugin()]);
|
|
1211
|
-
*
|
|
1212
|
-
* // This replaces cachePlugin with retryPlugin
|
|
1213
|
-
* const client2 = client1.use([retryPlugin()]);
|
|
1214
|
-
* ```
|
|
1215
|
-
*
|
|
1216
|
-
* @example With plugin configuration
|
|
1217
1227
|
* ```ts
|
|
1218
|
-
* const
|
|
1228
|
+
* const spoosh = new Spoosh<Schema, Error>('/api').use([
|
|
1219
1229
|
* cachePlugin({ staleTime: 5000 }),
|
|
1220
|
-
*
|
|
1230
|
+
* invalidationPlugin(),
|
|
1221
1231
|
* prefetchPlugin(),
|
|
1222
1232
|
* ]);
|
|
1223
1233
|
* ```
|
|
@@ -1242,7 +1252,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1242
1252
|
*
|
|
1243
1253
|
* @example
|
|
1244
1254
|
* ```ts
|
|
1245
|
-
* const
|
|
1255
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api').use([...]);
|
|
1246
1256
|
* const { api } = client;
|
|
1247
1257
|
*
|
|
1248
1258
|
* // GET request
|
|
@@ -1433,6 +1443,9 @@ type TagOptions = {
|
|
|
1433
1443
|
declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[];
|
|
1434
1444
|
declare function resolvePath(path: string[], params: Record<string, string | number> | undefined): string[];
|
|
1435
1445
|
|
|
1446
|
+
declare const isNetworkError: (err: unknown) => boolean;
|
|
1447
|
+
declare const isAbortError: (err: unknown) => boolean;
|
|
1448
|
+
|
|
1436
1449
|
type ProxyHandlerConfig<TOptions = SpooshOptions> = {
|
|
1437
1450
|
baseUrl: string;
|
|
1438
1451
|
defaultOptions: TOptions;
|
|
@@ -1610,7 +1623,7 @@ type CreateOperationOptions<TData, TError> = {
|
|
|
1610
1623
|
pluginExecutor: PluginExecutor;
|
|
1611
1624
|
fetchFn: (options: AnyRequestOptions) => Promise<SpooshResponse<TData, TError>>;
|
|
1612
1625
|
/** Unique identifier for the hook instance. Persists across queryKey changes. */
|
|
1613
|
-
|
|
1626
|
+
instanceId?: string;
|
|
1614
1627
|
};
|
|
1615
1628
|
declare function createOperationController<TData, TError>(options: CreateOperationOptions<TData, TError>): OperationController<TData, TError>;
|
|
1616
1629
|
|
|
@@ -1663,8 +1676,8 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
|
|
|
1663
1676
|
pluginExecutor: PluginExecutor;
|
|
1664
1677
|
fetchFn: (options: InfiniteRequestOptions, signal: AbortSignal) => Promise<SpooshResponse<TData, TError>>;
|
|
1665
1678
|
/** Unique identifier for the hook instance. Persists across queryKey changes. */
|
|
1666
|
-
|
|
1679
|
+
instanceId?: string;
|
|
1667
1680
|
};
|
|
1668
1681
|
declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
|
|
1669
1682
|
|
|
1670
|
-
export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type EventEmitter, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestOptions$1 as RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type
|
|
1683
|
+
export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type ComputeRequestOptions, type CoreRequestOptionsBase, type CreateInfiniteReadOptions, type CreateOperationOptions, type DataAwareCallback, type DataAwareTransform, type EventEmitter, type ExtractBody$1 as ExtractBody, type ExtractData, type ExtractError, type ExtractMethodOptions, type ExtractParamNames, type ExtractQuery$1 as ExtractQuery, type FetchDirection, type FetchExecutor, type FindMatchingKey, HTTP_METHODS, type HasParams, type HasReadMethod, type HasWriteMethod, type HeadersInitOrGetter, type HttpMethod, type HttpMethodKey, type InfiniteReadController, type InfiniteReadState, type InfiniteRequestOptions, type InstanceApiContext, type InstanceApiResolvers, type InstancePluginExecutor, type LifecyclePhase, type MergePluginInstanceApi, type MergePluginOptions, type MergePluginResults, type MethodOptionsMap, type OperationController, type OperationState, type OperationType, type PageContext, type PluginAccessor, type PluginArray, type PluginContext, type PluginContextInput, type PluginExecutor, type PluginExportsRegistry, type PluginFactory, type PluginHandler, type PluginLifecycle, type PluginMiddleware, type PluginRegistry, type PluginRequestOptions, type PluginResolvers, type PluginResponseHandler, type PluginResultResolvers, type PluginTypeConfig, type PluginUpdateHandler, type ReadClient, type ReadPaths, type ReadSchemaHelper, type RefetchEvent, type RequestOptions$1 as RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshOptions, type SpooshOptionsInput, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StateManager, type StripPrefix, type TagMode, type TagOptions, type Transport, type TransportOption, type TransportOptionsMap, type TransportResponse, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, __DEV__, buildUrl, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, fetchTransport, form, generateTags, getContentType, isAbortError, isJsonBody, isNetworkError, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,9 @@ __export(src_exports, {
|
|
|
42
42
|
form: () => form,
|
|
43
43
|
generateTags: () => generateTags,
|
|
44
44
|
getContentType: () => getContentType,
|
|
45
|
+
isAbortError: () => isAbortError,
|
|
45
46
|
isJsonBody: () => isJsonBody,
|
|
47
|
+
isNetworkError: () => isNetworkError,
|
|
46
48
|
isSpooshBody: () => isSpooshBody,
|
|
47
49
|
json: () => json,
|
|
48
50
|
mergeHeaders: () => mergeHeaders,
|
|
@@ -352,6 +354,10 @@ function resolvePath(path, params) {
|
|
|
352
354
|
});
|
|
353
355
|
}
|
|
354
356
|
|
|
357
|
+
// src/utils/errors.ts
|
|
358
|
+
var isNetworkError = (err) => err instanceof TypeError;
|
|
359
|
+
var isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
|
|
360
|
+
|
|
355
361
|
// src/transport/fetch.ts
|
|
356
362
|
var fetchTransport = async (url, init) => {
|
|
357
363
|
const res = await fetch(url, init);
|
|
@@ -434,9 +440,6 @@ var xhrTransport = (url, init, options) => {
|
|
|
434
440
|
};
|
|
435
441
|
|
|
436
442
|
// src/fetch.ts
|
|
437
|
-
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
438
|
-
var isNetworkError = (err) => err instanceof TypeError;
|
|
439
|
-
var isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
|
|
440
443
|
async function executeFetch(baseUrl, path, method, defaultOptions, requestOptions, nextTags) {
|
|
441
444
|
return executeCoreFetch({
|
|
442
445
|
baseUrl,
|
|
@@ -486,9 +489,6 @@ async function executeCoreFetch(config) {
|
|
|
486
489
|
...fetchDefaults
|
|
487
490
|
} = defaultOptions;
|
|
488
491
|
const inputFields = buildInputFields(requestOptions);
|
|
489
|
-
const maxRetries = requestOptions?.retries ?? 3;
|
|
490
|
-
const baseDelay = requestOptions?.retryDelay ?? 1e3;
|
|
491
|
-
const retryCount = maxRetries === false ? 0 : maxRetries;
|
|
492
492
|
const finalPath = path;
|
|
493
493
|
const url = buildUrl(baseUrl, finalPath, requestOptions?.query);
|
|
494
494
|
let headers = await mergeHeaders(defaultHeaders, requestOptions?.headers);
|
|
@@ -529,50 +529,40 @@ async function executeCoreFetch(config) {
|
|
|
529
529
|
const resolvedTransport = resolveTransport(
|
|
530
530
|
requestOptions?.transport ?? defaultTransport
|
|
531
531
|
);
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
);
|
|
540
|
-
if (result.ok) {
|
|
541
|
-
return {
|
|
542
|
-
status: result.status,
|
|
543
|
-
data: result.data,
|
|
544
|
-
headers: result.headers,
|
|
545
|
-
error: void 0,
|
|
546
|
-
...inputFields
|
|
547
|
-
};
|
|
548
|
-
}
|
|
532
|
+
try {
|
|
533
|
+
const result = await resolvedTransport(
|
|
534
|
+
url,
|
|
535
|
+
fetchInit,
|
|
536
|
+
requestOptions?.transportOptions
|
|
537
|
+
);
|
|
538
|
+
if (result.ok) {
|
|
549
539
|
return {
|
|
550
540
|
status: result.status,
|
|
551
|
-
|
|
541
|
+
data: result.data,
|
|
552
542
|
headers: result.headers,
|
|
543
|
+
error: void 0,
|
|
544
|
+
...inputFields
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
return {
|
|
548
|
+
status: result.status,
|
|
549
|
+
error: result.data,
|
|
550
|
+
headers: result.headers,
|
|
551
|
+
data: void 0,
|
|
552
|
+
...inputFields
|
|
553
|
+
};
|
|
554
|
+
} catch (err) {
|
|
555
|
+
if (isAbortError(err)) {
|
|
556
|
+
return {
|
|
557
|
+
status: 0,
|
|
558
|
+
error: err,
|
|
553
559
|
data: void 0,
|
|
560
|
+
aborted: true,
|
|
554
561
|
...inputFields
|
|
555
562
|
};
|
|
556
|
-
} catch (err) {
|
|
557
|
-
if (isAbortError(err)) {
|
|
558
|
-
return {
|
|
559
|
-
status: 0,
|
|
560
|
-
error: err,
|
|
561
|
-
data: void 0,
|
|
562
|
-
aborted: true,
|
|
563
|
-
...inputFields
|
|
564
|
-
};
|
|
565
|
-
}
|
|
566
|
-
lastError = err;
|
|
567
|
-
if (isNetworkError(err) && attempt < retryCount) {
|
|
568
|
-
const delayMs = baseDelay * Math.pow(2, attempt);
|
|
569
|
-
await delay(delayMs);
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
return { status: 0, error: lastError, data: void 0, ...inputFields };
|
|
573
563
|
}
|
|
564
|
+
return { status: 0, error: err, data: void 0, ...inputFields };
|
|
574
565
|
}
|
|
575
|
-
return { status: 0, error: lastError, data: void 0, ...inputFields };
|
|
576
566
|
}
|
|
577
567
|
|
|
578
568
|
// src/proxy/handler.ts
|
|
@@ -865,46 +855,29 @@ function createEventEmitter() {
|
|
|
865
855
|
|
|
866
856
|
// src/plugins/executor.ts
|
|
867
857
|
function validateDependencies(plugins) {
|
|
868
|
-
const
|
|
858
|
+
const pluginNames = new Set(plugins.map((p) => p.name));
|
|
869
859
|
for (const plugin of plugins) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
860
|
+
if (plugin.dependencies) {
|
|
861
|
+
for (const dep of plugin.dependencies) {
|
|
862
|
+
if (!pluginNames.has(dep)) {
|
|
863
|
+
throw new Error(
|
|
864
|
+
`Plugin "${plugin.name}" depends on "${dep}", but "${dep}" is not registered.`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
875
867
|
}
|
|
876
868
|
}
|
|
877
869
|
}
|
|
878
870
|
}
|
|
879
|
-
function
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
if (visited.has(plugin.name)) return;
|
|
886
|
-
if (visiting.has(plugin.name)) {
|
|
887
|
-
throw new Error(
|
|
888
|
-
`Circular dependency detected involving "${plugin.name}"`
|
|
889
|
-
);
|
|
890
|
-
}
|
|
891
|
-
visiting.add(plugin.name);
|
|
892
|
-
for (const dep of plugin.dependencies ?? []) {
|
|
893
|
-
const depPlugin = pluginMap.get(dep);
|
|
894
|
-
if (depPlugin) visit(depPlugin);
|
|
895
|
-
}
|
|
896
|
-
visiting.delete(plugin.name);
|
|
897
|
-
visited.add(plugin.name);
|
|
898
|
-
sorted.push(plugin);
|
|
899
|
-
}
|
|
900
|
-
for (const plugin of plugins) {
|
|
901
|
-
visit(plugin);
|
|
902
|
-
}
|
|
903
|
-
return sorted;
|
|
871
|
+
function sortByPriority(plugins) {
|
|
872
|
+
return [...plugins].sort((a, b) => {
|
|
873
|
+
const priorityA = a.priority ?? 0;
|
|
874
|
+
const priorityB = b.priority ?? 0;
|
|
875
|
+
return priorityA - priorityB;
|
|
876
|
+
});
|
|
904
877
|
}
|
|
905
878
|
function createPluginExecutor(initialPlugins = []) {
|
|
906
879
|
validateDependencies(initialPlugins);
|
|
907
|
-
const plugins =
|
|
880
|
+
const plugins = sortByPriority(initialPlugins);
|
|
908
881
|
const frozenPlugins = Object.freeze([...plugins]);
|
|
909
882
|
const createPluginAccessor = (context) => ({
|
|
910
883
|
get(name) {
|
|
@@ -1007,15 +980,15 @@ var Spoosh = class _Spoosh {
|
|
|
1007
980
|
* @example
|
|
1008
981
|
* ```ts
|
|
1009
982
|
* // Simple usage
|
|
1010
|
-
* const
|
|
983
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api');
|
|
1011
984
|
*
|
|
1012
985
|
* // With default headers
|
|
1013
|
-
* const
|
|
986
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1014
987
|
* headers: { 'X-API-Key': 'secret' }
|
|
1015
988
|
* });
|
|
1016
989
|
*
|
|
1017
990
|
* // With XHR transport (narrows available options to XHR-compatible fields)
|
|
1018
|
-
* const
|
|
991
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
1019
992
|
* transport: 'xhr',
|
|
1020
993
|
* credentials: 'include',
|
|
1021
994
|
* });
|
|
@@ -1036,26 +1009,10 @@ var Spoosh = class _Spoosh {
|
|
|
1036
1009
|
* @param plugins - Array of plugin instances to use
|
|
1037
1010
|
* @returns A new Spoosh instance with the specified plugins
|
|
1038
1011
|
*
|
|
1039
|
-
* @example Single use() call
|
|
1040
|
-
* ```ts
|
|
1041
|
-
* const client = new Spoosh<Schema, Error>('/api')
|
|
1042
|
-
* .use([cachePlugin(), retryPlugin(), debouncePlugin()]);
|
|
1043
|
-
* ```
|
|
1044
|
-
*
|
|
1045
|
-
* @example Chaining use() calls (replaces plugins)
|
|
1046
|
-
* ```ts
|
|
1047
|
-
* const client1 = new Spoosh<Schema, Error>('/api')
|
|
1048
|
-
* .use([cachePlugin()]);
|
|
1049
|
-
*
|
|
1050
|
-
* // This replaces cachePlugin with retryPlugin
|
|
1051
|
-
* const client2 = client1.use([retryPlugin()]);
|
|
1052
|
-
* ```
|
|
1053
|
-
*
|
|
1054
|
-
* @example With plugin configuration
|
|
1055
1012
|
* ```ts
|
|
1056
|
-
* const
|
|
1013
|
+
* const spoosh = new Spoosh<Schema, Error>('/api').use([
|
|
1057
1014
|
* cachePlugin({ staleTime: 5000 }),
|
|
1058
|
-
*
|
|
1015
|
+
* invalidationPlugin(),
|
|
1059
1016
|
* prefetchPlugin(),
|
|
1060
1017
|
* ]);
|
|
1061
1018
|
* ```
|
|
@@ -1112,7 +1069,7 @@ var Spoosh = class _Spoosh {
|
|
|
1112
1069
|
*
|
|
1113
1070
|
* @example
|
|
1114
1071
|
* ```ts
|
|
1115
|
-
* const
|
|
1072
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api').use([...]);
|
|
1116
1073
|
* const { api } = client;
|
|
1117
1074
|
*
|
|
1118
1075
|
* // GET request
|
|
@@ -1232,7 +1189,7 @@ function createOperationController(options) {
|
|
|
1232
1189
|
eventEmitter,
|
|
1233
1190
|
pluginExecutor,
|
|
1234
1191
|
fetchFn,
|
|
1235
|
-
|
|
1192
|
+
instanceId
|
|
1236
1193
|
} = options;
|
|
1237
1194
|
const queryKey = stateManager.createQueryKey({
|
|
1238
1195
|
path,
|
|
@@ -1255,7 +1212,7 @@ function createOperationController(options) {
|
|
|
1255
1212
|
queryKey,
|
|
1256
1213
|
tags: resolvedTags,
|
|
1257
1214
|
requestTimestamp,
|
|
1258
|
-
|
|
1215
|
+
instanceId,
|
|
1259
1216
|
request: {
|
|
1260
1217
|
...initialRequestOptions,
|
|
1261
1218
|
...requestOptions,
|
|
@@ -1450,7 +1407,7 @@ function createInfiniteReadController(options) {
|
|
|
1450
1407
|
eventEmitter,
|
|
1451
1408
|
pluginExecutor,
|
|
1452
1409
|
fetchFn,
|
|
1453
|
-
|
|
1410
|
+
instanceId
|
|
1454
1411
|
} = options;
|
|
1455
1412
|
let pageKeys = [];
|
|
1456
1413
|
let pageRequests = /* @__PURE__ */ new Map();
|
|
@@ -1550,7 +1507,7 @@ function createInfiniteReadController(options) {
|
|
|
1550
1507
|
queryKey: pageKey,
|
|
1551
1508
|
tags,
|
|
1552
1509
|
requestTimestamp: Date.now(),
|
|
1553
|
-
|
|
1510
|
+
instanceId,
|
|
1554
1511
|
request: { headers: {} },
|
|
1555
1512
|
temp: /* @__PURE__ */ new Map(),
|
|
1556
1513
|
pluginOptions,
|
package/dist/index.mjs
CHANGED
|
@@ -291,6 +291,10 @@ function resolvePath(path, params) {
|
|
|
291
291
|
});
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// src/utils/errors.ts
|
|
295
|
+
var isNetworkError = (err) => err instanceof TypeError;
|
|
296
|
+
var isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
|
|
297
|
+
|
|
294
298
|
// src/transport/fetch.ts
|
|
295
299
|
var fetchTransport = async (url, init) => {
|
|
296
300
|
const res = await fetch(url, init);
|
|
@@ -373,9 +377,6 @@ var xhrTransport = (url, init, options) => {
|
|
|
373
377
|
};
|
|
374
378
|
|
|
375
379
|
// src/fetch.ts
|
|
376
|
-
var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
377
|
-
var isNetworkError = (err) => err instanceof TypeError;
|
|
378
|
-
var isAbortError = (err) => err instanceof DOMException && err.name === "AbortError";
|
|
379
380
|
async function executeFetch(baseUrl, path, method, defaultOptions, requestOptions, nextTags) {
|
|
380
381
|
return executeCoreFetch({
|
|
381
382
|
baseUrl,
|
|
@@ -425,9 +426,6 @@ async function executeCoreFetch(config) {
|
|
|
425
426
|
...fetchDefaults
|
|
426
427
|
} = defaultOptions;
|
|
427
428
|
const inputFields = buildInputFields(requestOptions);
|
|
428
|
-
const maxRetries = requestOptions?.retries ?? 3;
|
|
429
|
-
const baseDelay = requestOptions?.retryDelay ?? 1e3;
|
|
430
|
-
const retryCount = maxRetries === false ? 0 : maxRetries;
|
|
431
429
|
const finalPath = path;
|
|
432
430
|
const url = buildUrl(baseUrl, finalPath, requestOptions?.query);
|
|
433
431
|
let headers = await mergeHeaders(defaultHeaders, requestOptions?.headers);
|
|
@@ -468,50 +466,40 @@ async function executeCoreFetch(config) {
|
|
|
468
466
|
const resolvedTransport = resolveTransport(
|
|
469
467
|
requestOptions?.transport ?? defaultTransport
|
|
470
468
|
);
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
);
|
|
479
|
-
if (result.ok) {
|
|
480
|
-
return {
|
|
481
|
-
status: result.status,
|
|
482
|
-
data: result.data,
|
|
483
|
-
headers: result.headers,
|
|
484
|
-
error: void 0,
|
|
485
|
-
...inputFields
|
|
486
|
-
};
|
|
487
|
-
}
|
|
469
|
+
try {
|
|
470
|
+
const result = await resolvedTransport(
|
|
471
|
+
url,
|
|
472
|
+
fetchInit,
|
|
473
|
+
requestOptions?.transportOptions
|
|
474
|
+
);
|
|
475
|
+
if (result.ok) {
|
|
488
476
|
return {
|
|
489
477
|
status: result.status,
|
|
490
|
-
|
|
478
|
+
data: result.data,
|
|
491
479
|
headers: result.headers,
|
|
480
|
+
error: void 0,
|
|
481
|
+
...inputFields
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
return {
|
|
485
|
+
status: result.status,
|
|
486
|
+
error: result.data,
|
|
487
|
+
headers: result.headers,
|
|
488
|
+
data: void 0,
|
|
489
|
+
...inputFields
|
|
490
|
+
};
|
|
491
|
+
} catch (err) {
|
|
492
|
+
if (isAbortError(err)) {
|
|
493
|
+
return {
|
|
494
|
+
status: 0,
|
|
495
|
+
error: err,
|
|
492
496
|
data: void 0,
|
|
497
|
+
aborted: true,
|
|
493
498
|
...inputFields
|
|
494
499
|
};
|
|
495
|
-
} catch (err) {
|
|
496
|
-
if (isAbortError(err)) {
|
|
497
|
-
return {
|
|
498
|
-
status: 0,
|
|
499
|
-
error: err,
|
|
500
|
-
data: void 0,
|
|
501
|
-
aborted: true,
|
|
502
|
-
...inputFields
|
|
503
|
-
};
|
|
504
|
-
}
|
|
505
|
-
lastError = err;
|
|
506
|
-
if (isNetworkError(err) && attempt < retryCount) {
|
|
507
|
-
const delayMs = baseDelay * Math.pow(2, attempt);
|
|
508
|
-
await delay(delayMs);
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
return { status: 0, error: lastError, data: void 0, ...inputFields };
|
|
512
500
|
}
|
|
501
|
+
return { status: 0, error: err, data: void 0, ...inputFields };
|
|
513
502
|
}
|
|
514
|
-
return { status: 0, error: lastError, data: void 0, ...inputFields };
|
|
515
503
|
}
|
|
516
504
|
|
|
517
505
|
// src/proxy/handler.ts
|
|
@@ -804,46 +792,29 @@ function createEventEmitter() {
|
|
|
804
792
|
|
|
805
793
|
// src/plugins/executor.ts
|
|
806
794
|
function validateDependencies(plugins) {
|
|
807
|
-
const
|
|
795
|
+
const pluginNames = new Set(plugins.map((p) => p.name));
|
|
808
796
|
for (const plugin of plugins) {
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
797
|
+
if (plugin.dependencies) {
|
|
798
|
+
for (const dep of plugin.dependencies) {
|
|
799
|
+
if (!pluginNames.has(dep)) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
`Plugin "${plugin.name}" depends on "${dep}", but "${dep}" is not registered.`
|
|
802
|
+
);
|
|
803
|
+
}
|
|
814
804
|
}
|
|
815
805
|
}
|
|
816
806
|
}
|
|
817
807
|
}
|
|
818
|
-
function
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
if (visited.has(plugin.name)) return;
|
|
825
|
-
if (visiting.has(plugin.name)) {
|
|
826
|
-
throw new Error(
|
|
827
|
-
`Circular dependency detected involving "${plugin.name}"`
|
|
828
|
-
);
|
|
829
|
-
}
|
|
830
|
-
visiting.add(plugin.name);
|
|
831
|
-
for (const dep of plugin.dependencies ?? []) {
|
|
832
|
-
const depPlugin = pluginMap.get(dep);
|
|
833
|
-
if (depPlugin) visit(depPlugin);
|
|
834
|
-
}
|
|
835
|
-
visiting.delete(plugin.name);
|
|
836
|
-
visited.add(plugin.name);
|
|
837
|
-
sorted.push(plugin);
|
|
838
|
-
}
|
|
839
|
-
for (const plugin of plugins) {
|
|
840
|
-
visit(plugin);
|
|
841
|
-
}
|
|
842
|
-
return sorted;
|
|
808
|
+
function sortByPriority(plugins) {
|
|
809
|
+
return [...plugins].sort((a, b) => {
|
|
810
|
+
const priorityA = a.priority ?? 0;
|
|
811
|
+
const priorityB = b.priority ?? 0;
|
|
812
|
+
return priorityA - priorityB;
|
|
813
|
+
});
|
|
843
814
|
}
|
|
844
815
|
function createPluginExecutor(initialPlugins = []) {
|
|
845
816
|
validateDependencies(initialPlugins);
|
|
846
|
-
const plugins =
|
|
817
|
+
const plugins = sortByPriority(initialPlugins);
|
|
847
818
|
const frozenPlugins = Object.freeze([...plugins]);
|
|
848
819
|
const createPluginAccessor = (context) => ({
|
|
849
820
|
get(name) {
|
|
@@ -946,15 +917,15 @@ var Spoosh = class _Spoosh {
|
|
|
946
917
|
* @example
|
|
947
918
|
* ```ts
|
|
948
919
|
* // Simple usage
|
|
949
|
-
* const
|
|
920
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api');
|
|
950
921
|
*
|
|
951
922
|
* // With default headers
|
|
952
|
-
* const
|
|
923
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
953
924
|
* headers: { 'X-API-Key': 'secret' }
|
|
954
925
|
* });
|
|
955
926
|
*
|
|
956
927
|
* // With XHR transport (narrows available options to XHR-compatible fields)
|
|
957
|
-
* const
|
|
928
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api', {
|
|
958
929
|
* transport: 'xhr',
|
|
959
930
|
* credentials: 'include',
|
|
960
931
|
* });
|
|
@@ -975,26 +946,10 @@ var Spoosh = class _Spoosh {
|
|
|
975
946
|
* @param plugins - Array of plugin instances to use
|
|
976
947
|
* @returns A new Spoosh instance with the specified plugins
|
|
977
948
|
*
|
|
978
|
-
* @example Single use() call
|
|
979
|
-
* ```ts
|
|
980
|
-
* const client = new Spoosh<Schema, Error>('/api')
|
|
981
|
-
* .use([cachePlugin(), retryPlugin(), debouncePlugin()]);
|
|
982
|
-
* ```
|
|
983
|
-
*
|
|
984
|
-
* @example Chaining use() calls (replaces plugins)
|
|
985
|
-
* ```ts
|
|
986
|
-
* const client1 = new Spoosh<Schema, Error>('/api')
|
|
987
|
-
* .use([cachePlugin()]);
|
|
988
|
-
*
|
|
989
|
-
* // This replaces cachePlugin with retryPlugin
|
|
990
|
-
* const client2 = client1.use([retryPlugin()]);
|
|
991
|
-
* ```
|
|
992
|
-
*
|
|
993
|
-
* @example With plugin configuration
|
|
994
949
|
* ```ts
|
|
995
|
-
* const
|
|
950
|
+
* const spoosh = new Spoosh<Schema, Error>('/api').use([
|
|
996
951
|
* cachePlugin({ staleTime: 5000 }),
|
|
997
|
-
*
|
|
952
|
+
* invalidationPlugin(),
|
|
998
953
|
* prefetchPlugin(),
|
|
999
954
|
* ]);
|
|
1000
955
|
* ```
|
|
@@ -1051,7 +1006,7 @@ var Spoosh = class _Spoosh {
|
|
|
1051
1006
|
*
|
|
1052
1007
|
* @example
|
|
1053
1008
|
* ```ts
|
|
1054
|
-
* const
|
|
1009
|
+
* const spoosh = new Spoosh<ApiSchema, Error>('/api').use([...]);
|
|
1055
1010
|
* const { api } = client;
|
|
1056
1011
|
*
|
|
1057
1012
|
* // GET request
|
|
@@ -1171,7 +1126,7 @@ function createOperationController(options) {
|
|
|
1171
1126
|
eventEmitter,
|
|
1172
1127
|
pluginExecutor,
|
|
1173
1128
|
fetchFn,
|
|
1174
|
-
|
|
1129
|
+
instanceId
|
|
1175
1130
|
} = options;
|
|
1176
1131
|
const queryKey = stateManager.createQueryKey({
|
|
1177
1132
|
path,
|
|
@@ -1194,7 +1149,7 @@ function createOperationController(options) {
|
|
|
1194
1149
|
queryKey,
|
|
1195
1150
|
tags: resolvedTags,
|
|
1196
1151
|
requestTimestamp,
|
|
1197
|
-
|
|
1152
|
+
instanceId,
|
|
1198
1153
|
request: {
|
|
1199
1154
|
...initialRequestOptions,
|
|
1200
1155
|
...requestOptions,
|
|
@@ -1389,7 +1344,7 @@ function createInfiniteReadController(options) {
|
|
|
1389
1344
|
eventEmitter,
|
|
1390
1345
|
pluginExecutor,
|
|
1391
1346
|
fetchFn,
|
|
1392
|
-
|
|
1347
|
+
instanceId
|
|
1393
1348
|
} = options;
|
|
1394
1349
|
let pageKeys = [];
|
|
1395
1350
|
let pageRequests = /* @__PURE__ */ new Map();
|
|
@@ -1489,7 +1444,7 @@ function createInfiniteReadController(options) {
|
|
|
1489
1444
|
queryKey: pageKey,
|
|
1490
1445
|
tags,
|
|
1491
1446
|
requestTimestamp: Date.now(),
|
|
1492
|
-
|
|
1447
|
+
instanceId,
|
|
1493
1448
|
request: { headers: {} },
|
|
1494
1449
|
temp: /* @__PURE__ */ new Map(),
|
|
1495
1450
|
pluginOptions,
|
|
@@ -1733,7 +1688,9 @@ export {
|
|
|
1733
1688
|
form,
|
|
1734
1689
|
generateTags,
|
|
1735
1690
|
getContentType,
|
|
1691
|
+
isAbortError,
|
|
1736
1692
|
isJsonBody,
|
|
1693
|
+
isNetworkError,
|
|
1737
1694
|
isSpooshBody,
|
|
1738
1695
|
json,
|
|
1739
1696
|
mergeHeaders,
|