@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 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 client = new Spoosh<ApiSchema, Error>("/api", {
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
- } & Partial<RetryConfig>;
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 the hook instance. Persists across queryKey changes within the same hook. */
285
- readonly hookId?: string;
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
- * // Retry middleware - wrap and retry on error
317
+ * // Auth middleware - add authentication headers
319
318
  * middleware: async (context, next) => {
320
- * for (let i = 0; i < 3; i++) {
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, retry, transform)
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., createReactSpoosh).
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
- /** Declare plugin dependencies. These plugins must be registered before this one. */
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 client = new Spoosh<ApiSchema, Error>('/api')
1133
- * .use([cachePlugin(), retryPlugin()]);
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 { createReactSpoosh } from '@spoosh/react';
1174
+ * import { create } from '@spoosh/react';
1149
1175
  *
1150
- * const client = new Spoosh<ApiSchema, Error>('/api')
1151
- * .use([cachePlugin(), retryPlugin()]);
1176
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api')
1177
+ * .use([cachePlugin(), prefetchPlugin()]);
1152
1178
  *
1153
- * const { useRead, useWrite } = createReactSpoosh(client);
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 client = new Spoosh<ApiSchema, Error>('/api');
1202
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api');
1177
1203
  *
1178
1204
  * // With default headers
1179
- * const client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<Schema, Error>('/api').use([
1228
+ * const spoosh = new Spoosh<Schema, Error>('/api').use([
1219
1229
  * cachePlugin({ staleTime: 5000 }),
1220
- * retryPlugin({ retries: 3, retryDelay: 1000 }),
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 client = new Spoosh<ApiSchema, Error>('/api').use([...]);
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
- hookId?: string;
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
- hookId?: string;
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 RetryConfig, 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, isJsonBody, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
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
- } & Partial<RetryConfig>;
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 the hook instance. Persists across queryKey changes within the same hook. */
285
- readonly hookId?: string;
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
- * // Retry middleware - wrap and retry on error
317
+ * // Auth middleware - add authentication headers
319
318
  * middleware: async (context, next) => {
320
- * for (let i = 0; i < 3; i++) {
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, retry, transform)
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., createReactSpoosh).
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
- /** Declare plugin dependencies. These plugins must be registered before this one. */
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 client = new Spoosh<ApiSchema, Error>('/api')
1133
- * .use([cachePlugin(), retryPlugin()]);
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 { createReactSpoosh } from '@spoosh/react';
1174
+ * import { create } from '@spoosh/react';
1149
1175
  *
1150
- * const client = new Spoosh<ApiSchema, Error>('/api')
1151
- * .use([cachePlugin(), retryPlugin()]);
1176
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api')
1177
+ * .use([cachePlugin(), prefetchPlugin()]);
1152
1178
  *
1153
- * const { useRead, useWrite } = createReactSpoosh(client);
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 client = new Spoosh<ApiSchema, Error>('/api');
1202
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api');
1177
1203
  *
1178
1204
  * // With default headers
1179
- * const client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<Schema, Error>('/api').use([
1228
+ * const spoosh = new Spoosh<Schema, Error>('/api').use([
1219
1229
  * cachePlugin({ staleTime: 5000 }),
1220
- * retryPlugin({ retries: 3, retryDelay: 1000 }),
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 client = new Spoosh<ApiSchema, Error>('/api').use([...]);
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
- hookId?: string;
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
- hookId?: string;
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 RetryConfig, 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, isJsonBody, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded, xhrTransport };
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
- let lastError;
533
- for (let attempt = 0; attempt <= retryCount; attempt++) {
534
- try {
535
- const result = await resolvedTransport(
536
- url,
537
- fetchInit,
538
- requestOptions?.transportOptions
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
- error: result.data,
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 names = new Set(plugins.map((p) => p.name));
858
+ const pluginNames = new Set(plugins.map((p) => p.name));
869
859
  for (const plugin of plugins) {
870
- for (const dep of plugin.dependencies ?? []) {
871
- if (!names.has(dep)) {
872
- throw new Error(
873
- `Plugin "${plugin.name}" depends on "${dep}" which is not registered`
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 sortByDependencies(plugins) {
880
- const sorted = [];
881
- const visited = /* @__PURE__ */ new Set();
882
- const visiting = /* @__PURE__ */ new Set();
883
- const pluginMap = new Map(plugins.map((p) => [p.name, p]));
884
- function visit(plugin) {
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 = sortByDependencies(initialPlugins);
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 client = new Spoosh<ApiSchema, Error>('/api');
983
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api');
1011
984
  *
1012
985
  * // With default headers
1013
- * const client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<Schema, Error>('/api').use([
1013
+ * const spoosh = new Spoosh<Schema, Error>('/api').use([
1057
1014
  * cachePlugin({ staleTime: 5000 }),
1058
- * retryPlugin({ retries: 3, retryDelay: 1000 }),
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 client = new Spoosh<ApiSchema, Error>('/api').use([...]);
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
- hookId
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
- hookId,
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
- hookId
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
- hookId,
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
- let lastError;
472
- for (let attempt = 0; attempt <= retryCount; attempt++) {
473
- try {
474
- const result = await resolvedTransport(
475
- url,
476
- fetchInit,
477
- requestOptions?.transportOptions
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
- error: result.data,
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 names = new Set(plugins.map((p) => p.name));
795
+ const pluginNames = new Set(plugins.map((p) => p.name));
808
796
  for (const plugin of plugins) {
809
- for (const dep of plugin.dependencies ?? []) {
810
- if (!names.has(dep)) {
811
- throw new Error(
812
- `Plugin "${plugin.name}" depends on "${dep}" which is not registered`
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 sortByDependencies(plugins) {
819
- const sorted = [];
820
- const visited = /* @__PURE__ */ new Set();
821
- const visiting = /* @__PURE__ */ new Set();
822
- const pluginMap = new Map(plugins.map((p) => [p.name, p]));
823
- function visit(plugin) {
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 = sortByDependencies(initialPlugins);
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 client = new Spoosh<ApiSchema, Error>('/api');
920
+ * const spoosh = new Spoosh<ApiSchema, Error>('/api');
950
921
  *
951
922
  * // With default headers
952
- * const client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<ApiSchema, Error>('/api', {
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 client = new Spoosh<Schema, Error>('/api').use([
950
+ * const spoosh = new Spoosh<Schema, Error>('/api').use([
996
951
  * cachePlugin({ staleTime: 5000 }),
997
- * retryPlugin({ retries: 3, retryDelay: 1000 }),
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 client = new Spoosh<ApiSchema, Error>('/api').use([...]);
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
- hookId
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
- hookId,
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
- hookId
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
- hookId,
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,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API toolkit with plugin middleware system",
6
6
  "keywords": [