@spoosh/core 0.9.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Core client and plugin system for Spoosh - a type-safe API client framework.
4
4
 
5
- **[Documentation](https://spoosh.dev/docs)** · **Requirements:** TypeScript >= 5.0
5
+ **[Documentation](https://spoosh.dev/docs/react)** · **Requirements:** TypeScript >= 5.0
6
6
 
7
7
  ## Installation
8
8
 
package/dist/index.d.mts CHANGED
@@ -72,6 +72,24 @@ declare function resolveRequestBody(rawBody: unknown): {
72
72
  headers?: Record<string, string>;
73
73
  } | undefined;
74
74
 
75
+ interface TransportResponse {
76
+ ok: boolean;
77
+ status: number;
78
+ headers: Headers;
79
+ data: unknown;
80
+ }
81
+ type Transport<TOptions = unknown> = (url: string, init: RequestInit, options?: TOptions) => Promise<TransportResponse>;
82
+ /**
83
+ * Transport layer used for requests.
84
+ *
85
+ * - `"fetch"` — Uses the Fetch API (default).
86
+ * - `"xhr"` — Uses XMLHttpRequest. Required for upload/download progress tracking.
87
+ */
88
+ type TransportOption = "fetch" | "xhr";
89
+ interface TransportOptionsMap {
90
+ fetch: never;
91
+ }
92
+
75
93
  type RetryConfig = {
76
94
  retries?: number | false;
77
95
  retryDelay?: number;
@@ -79,7 +97,26 @@ type RetryConfig = {
79
97
  type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
80
98
  type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
81
99
  headers?: HeadersInitOrGetter;
100
+ /** Default transport for all requests. */
101
+ transport?: TransportOption;
82
102
  };
103
+ type FetchOnlyInitKeys = "mode" | "cache" | "integrity" | "keepalive" | "next" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "window";
104
+ type SharedSpooshOptions = Omit<RequestInit, "signal" | "method" | "body" | "headers" | FetchOnlyInitKeys> & {
105
+ headers?: HeadersInitOrGetter;
106
+ };
107
+ type SpooshFetchOptions = SharedSpooshOptions & Pick<RequestInit, Extract<keyof RequestInit, FetchOnlyInitKeys>> & {
108
+ transport?: "fetch";
109
+ };
110
+ type SpooshXhrOptions = SharedSpooshOptions & {
111
+ transport: "xhr";
112
+ };
113
+ /**
114
+ * Constructor-level options with transport-aware type narrowing.
115
+ *
116
+ * When `transport` is `"xhr"`, fetch-only fields (e.g. `mode`, `cache`, `redirect`) are
117
+ * excluded from autocomplete since they have no effect on XMLHttpRequest.
118
+ */
119
+ type SpooshOptionsInput = SpooshFetchOptions | SpooshXhrOptions;
83
120
  type BaseRequestOptions$1 = {
84
121
  headers?: HeadersInitOrGetter;
85
122
  cache?: RequestCache;
@@ -101,6 +138,10 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
101
138
  query?: Record<string, string | number | boolean | undefined>;
102
139
  params?: Record<string, string | number>;
103
140
  signal?: AbortSignal;
141
+ /** Per-request transport override. */
142
+ transport?: TransportOption;
143
+ /** Transport-specific options passed through to the transport function. */
144
+ transportOptions?: unknown;
104
145
  } & Partial<RetryConfig>;
105
146
  type DynamicParamsOption = {
106
147
  params?: Record<string, string | number>;
@@ -728,7 +769,7 @@ declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeCo
728
769
  type ApiSchema = {
729
770
  [path: string]: {
730
771
  [method in HttpMethod]?: {
731
- data: unknown;
772
+ data?: unknown;
732
773
  body?: unknown;
733
774
  query?: unknown;
734
775
  error?: unknown;
@@ -740,7 +781,7 @@ type ApiSchema = {
740
781
  */
741
782
  type ExtractData<T> = T extends {
742
783
  data: infer D;
743
- } ? D : never;
784
+ } ? D : void;
744
785
  /**
745
786
  * Extract body type from an endpoint.
746
787
  */
@@ -1136,7 +1177,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1136
1177
  * Creates a new Spoosh instance.
1137
1178
  *
1138
1179
  * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
1139
- * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
1180
+ * @param defaultOptions - Optional default options applied to all requests (headers, credentials, transport, etc.)
1140
1181
  * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
1141
1182
  *
1142
1183
  * @example
@@ -1148,9 +1189,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1148
1189
  * const client = new Spoosh<ApiSchema, Error>('/api', {
1149
1190
  * headers: { 'X-API-Key': 'secret' }
1150
1191
  * });
1192
+ *
1193
+ * // With XHR transport (narrows available options to XHR-compatible fields)
1194
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
1195
+ * transport: 'xhr',
1196
+ * credentials: 'include',
1197
+ * });
1151
1198
  * ```
1152
1199
  */
1153
- constructor(baseUrl: string, defaultOptions?: SpooshOptions, plugins?: TPlugins);
1200
+ constructor(baseUrl: string, defaultOptions?: SpooshOptionsInput, plugins?: TPlugins);
1154
1201
  /**
1155
1202
  * Adds plugins to the Spoosh instance.
1156
1203
  *
@@ -1301,7 +1348,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1301
1348
 
1302
1349
  type SpooshClientConfig = {
1303
1350
  baseUrl: string;
1304
- defaultOptions?: SpooshOptions;
1351
+ defaultOptions?: SpooshOptionsInput;
1305
1352
  middlewares?: SpooshMiddleware[];
1306
1353
  };
1307
1354
  /**
@@ -1339,6 +1386,12 @@ type SpooshClientConfig = {
1339
1386
  * const { data } = await api("posts").GET();
1340
1387
  * const { data: post } = await api("posts/123").GET();
1341
1388
  * await api("posts/:id").GET({ params: { id: 123 } });
1389
+ *
1390
+ * // With XHR transport
1391
+ * const api = createClient<ApiSchema, ApiError>({
1392
+ * baseUrl: "/api",
1393
+ * defaultOptions: { transport: "xhr" },
1394
+ * });
1342
1395
  * ```
1343
1396
  */
1344
1397
  declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
@@ -1527,6 +1580,19 @@ declare function extractPathFromSelector(fn: unknown): string;
1527
1580
  */
1528
1581
  declare function extractMethodFromSelector(fn: unknown): string | undefined;
1529
1582
 
1583
+ declare const fetchTransport: Transport;
1584
+
1585
+ interface XhrTransportOptions {
1586
+ /** Called on upload and download progress events. */
1587
+ onProgress?: (event: ProgressEvent, xhr: XMLHttpRequest) => void;
1588
+ }
1589
+ declare module "./types" {
1590
+ interface TransportOptionsMap {
1591
+ xhr: XhrTransportOptions;
1592
+ }
1593
+ }
1594
+ declare const xhrTransport: Transport<XhrTransportOptions>;
1595
+
1530
1596
  declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise<SpooshResponse<TData, TError>>;
1531
1597
 
1532
1598
  declare function createMiddleware<TData = unknown, TError = unknown>(name: string, phase: MiddlewarePhase, handler: SpooshMiddleware<TData, TError>["handler"]): SpooshMiddleware<TData, TError>;
@@ -1621,4 +1687,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1621
1687
  };
1622
1688
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1623
1689
 
1624
- export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type SpooshClientConfig as ClientConfig, 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 MiddlewareContext, type MiddlewareHandler, type MiddlewarePhase, 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 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 SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StateManager, type StripPrefix, type TagMode, type TagOptions, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, __DEV__, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, form, generateTags, getContentType, isJsonBody, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded };
1690
+ export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type SpooshClientConfig as ClientConfig, 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 MiddlewareContext, type MiddlewareHandler, type MiddlewarePhase, 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 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 SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, 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__, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, 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 };
package/dist/index.d.ts CHANGED
@@ -72,6 +72,24 @@ declare function resolveRequestBody(rawBody: unknown): {
72
72
  headers?: Record<string, string>;
73
73
  } | undefined;
74
74
 
75
+ interface TransportResponse {
76
+ ok: boolean;
77
+ status: number;
78
+ headers: Headers;
79
+ data: unknown;
80
+ }
81
+ type Transport<TOptions = unknown> = (url: string, init: RequestInit, options?: TOptions) => Promise<TransportResponse>;
82
+ /**
83
+ * Transport layer used for requests.
84
+ *
85
+ * - `"fetch"` — Uses the Fetch API (default).
86
+ * - `"xhr"` — Uses XMLHttpRequest. Required for upload/download progress tracking.
87
+ */
88
+ type TransportOption = "fetch" | "xhr";
89
+ interface TransportOptionsMap {
90
+ fetch: never;
91
+ }
92
+
75
93
  type RetryConfig = {
76
94
  retries?: number | false;
77
95
  retryDelay?: number;
@@ -79,7 +97,26 @@ type RetryConfig = {
79
97
  type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
80
98
  type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
81
99
  headers?: HeadersInitOrGetter;
100
+ /** Default transport for all requests. */
101
+ transport?: TransportOption;
82
102
  };
103
+ type FetchOnlyInitKeys = "mode" | "cache" | "integrity" | "keepalive" | "next" | "priority" | "redirect" | "referrer" | "referrerPolicy" | "window";
104
+ type SharedSpooshOptions = Omit<RequestInit, "signal" | "method" | "body" | "headers" | FetchOnlyInitKeys> & {
105
+ headers?: HeadersInitOrGetter;
106
+ };
107
+ type SpooshFetchOptions = SharedSpooshOptions & Pick<RequestInit, Extract<keyof RequestInit, FetchOnlyInitKeys>> & {
108
+ transport?: "fetch";
109
+ };
110
+ type SpooshXhrOptions = SharedSpooshOptions & {
111
+ transport: "xhr";
112
+ };
113
+ /**
114
+ * Constructor-level options with transport-aware type narrowing.
115
+ *
116
+ * When `transport` is `"xhr"`, fetch-only fields (e.g. `mode`, `cache`, `redirect`) are
117
+ * excluded from autocomplete since they have no effect on XMLHttpRequest.
118
+ */
119
+ type SpooshOptionsInput = SpooshFetchOptions | SpooshXhrOptions;
83
120
  type BaseRequestOptions$1 = {
84
121
  headers?: HeadersInitOrGetter;
85
122
  cache?: RequestCache;
@@ -101,6 +138,10 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
101
138
  query?: Record<string, string | number | boolean | undefined>;
102
139
  params?: Record<string, string | number>;
103
140
  signal?: AbortSignal;
141
+ /** Per-request transport override. */
142
+ transport?: TransportOption;
143
+ /** Transport-specific options passed through to the transport function. */
144
+ transportOptions?: unknown;
104
145
  } & Partial<RetryConfig>;
105
146
  type DynamicParamsOption = {
106
147
  params?: Record<string, string | number>;
@@ -728,7 +769,7 @@ declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeCo
728
769
  type ApiSchema = {
729
770
  [path: string]: {
730
771
  [method in HttpMethod]?: {
731
- data: unknown;
772
+ data?: unknown;
732
773
  body?: unknown;
733
774
  query?: unknown;
734
775
  error?: unknown;
@@ -740,7 +781,7 @@ type ApiSchema = {
740
781
  */
741
782
  type ExtractData<T> = T extends {
742
783
  data: infer D;
743
- } ? D : never;
784
+ } ? D : void;
744
785
  /**
745
786
  * Extract body type from an endpoint.
746
787
  */
@@ -1136,7 +1177,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1136
1177
  * Creates a new Spoosh instance.
1137
1178
  *
1138
1179
  * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
1139
- * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
1180
+ * @param defaultOptions - Optional default options applied to all requests (headers, credentials, transport, etc.)
1140
1181
  * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
1141
1182
  *
1142
1183
  * @example
@@ -1148,9 +1189,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1148
1189
  * const client = new Spoosh<ApiSchema, Error>('/api', {
1149
1190
  * headers: { 'X-API-Key': 'secret' }
1150
1191
  * });
1192
+ *
1193
+ * // With XHR transport (narrows available options to XHR-compatible fields)
1194
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
1195
+ * transport: 'xhr',
1196
+ * credentials: 'include',
1197
+ * });
1151
1198
  * ```
1152
1199
  */
1153
- constructor(baseUrl: string, defaultOptions?: SpooshOptions, plugins?: TPlugins);
1200
+ constructor(baseUrl: string, defaultOptions?: SpooshOptionsInput, plugins?: TPlugins);
1154
1201
  /**
1155
1202
  * Adds plugins to the Spoosh instance.
1156
1203
  *
@@ -1301,7 +1348,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1301
1348
 
1302
1349
  type SpooshClientConfig = {
1303
1350
  baseUrl: string;
1304
- defaultOptions?: SpooshOptions;
1351
+ defaultOptions?: SpooshOptionsInput;
1305
1352
  middlewares?: SpooshMiddleware[];
1306
1353
  };
1307
1354
  /**
@@ -1339,6 +1386,12 @@ type SpooshClientConfig = {
1339
1386
  * const { data } = await api("posts").GET();
1340
1387
  * const { data: post } = await api("posts/123").GET();
1341
1388
  * await api("posts/:id").GET({ params: { id: 123 } });
1389
+ *
1390
+ * // With XHR transport
1391
+ * const api = createClient<ApiSchema, ApiError>({
1392
+ * baseUrl: "/api",
1393
+ * defaultOptions: { transport: "xhr" },
1394
+ * });
1342
1395
  * ```
1343
1396
  */
1344
1397
  declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
@@ -1527,6 +1580,19 @@ declare function extractPathFromSelector(fn: unknown): string;
1527
1580
  */
1528
1581
  declare function extractMethodFromSelector(fn: unknown): string | undefined;
1529
1582
 
1583
+ declare const fetchTransport: Transport;
1584
+
1585
+ interface XhrTransportOptions {
1586
+ /** Called on upload and download progress events. */
1587
+ onProgress?: (event: ProgressEvent, xhr: XMLHttpRequest) => void;
1588
+ }
1589
+ declare module "./types" {
1590
+ interface TransportOptionsMap {
1591
+ xhr: XhrTransportOptions;
1592
+ }
1593
+ }
1594
+ declare const xhrTransport: Transport<XhrTransportOptions>;
1595
+
1530
1596
  declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise<SpooshResponse<TData, TError>>;
1531
1597
 
1532
1598
  declare function createMiddleware<TData = unknown, TError = unknown>(name: string, phase: MiddlewarePhase, handler: SpooshMiddleware<TData, TError>["handler"]): SpooshMiddleware<TData, TError>;
@@ -1621,4 +1687,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1621
1687
  };
1622
1688
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1623
1689
 
1624
- export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type SpooshClientConfig as ClientConfig, 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 MiddlewareContext, type MiddlewareHandler, type MiddlewarePhase, 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 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 SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StateManager, type StripPrefix, type TagMode, type TagOptions, type WriteClient, type WriteMethod, type WritePaths, type WriteSchemaHelper, __DEV__, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, form, generateTags, getContentType, isJsonBody, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveTags, setHeaders, sortObjectKeys, urlencoded };
1690
+ export { type AnyRequestOptions, type ApiSchema, type BuiltInEvents, type CacheEntry, type CacheEntryWithKey, type CapturedCall, type SpooshClientConfig as ClientConfig, 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 MiddlewareContext, type MiddlewareHandler, type MiddlewarePhase, 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 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 SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, 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__, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, 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 };
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ __export(src_exports, {
41
41
  executeFetch: () => executeFetch,
42
42
  extractMethodFromSelector: () => extractMethodFromSelector,
43
43
  extractPathFromSelector: () => extractPathFromSelector,
44
+ fetchTransport: () => fetchTransport,
44
45
  form: () => form,
45
46
  generateTags: () => generateTags,
46
47
  getContentType: () => getContentType,
@@ -56,7 +57,8 @@ __export(src_exports, {
56
57
  resolveTags: () => resolveTags,
57
58
  setHeaders: () => setHeaders,
58
59
  sortObjectKeys: () => sortObjectKeys,
59
- urlencoded: () => urlencoded
60
+ urlencoded: () => urlencoded,
61
+ xhrTransport: () => xhrTransport
60
62
  });
61
63
  module.exports = __toCommonJS(src_exports);
62
64
 
@@ -368,6 +370,87 @@ function resolvePath(path, params) {
368
370
  });
369
371
  }
370
372
 
373
+ // src/transport/fetch.ts
374
+ var fetchTransport = async (url, init) => {
375
+ const res = await fetch(url, init);
376
+ const contentType = res.headers.get("content-type");
377
+ const isJson = contentType?.includes("application/json");
378
+ const data = isJson ? await res.json() : res;
379
+ return { ok: res.ok, status: res.status, headers: res.headers, data };
380
+ };
381
+
382
+ // src/transport/xhr.ts
383
+ var xhrTransport = (url, init, options) => {
384
+ return new Promise((resolve, reject) => {
385
+ const xhr = new XMLHttpRequest();
386
+ xhr.open(init.method ?? "GET", url);
387
+ if (init.headers) {
388
+ const headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
389
+ headers.forEach((value, key) => {
390
+ xhr.setRequestHeader(key, value);
391
+ });
392
+ }
393
+ if (init.credentials === "include") {
394
+ xhr.withCredentials = true;
395
+ }
396
+ const onAbort = () => xhr.abort();
397
+ if (init.signal) {
398
+ if (init.signal.aborted) {
399
+ xhr.abort();
400
+ return;
401
+ }
402
+ init.signal.addEventListener("abort", onAbort);
403
+ }
404
+ const cleanup = () => {
405
+ init.signal?.removeEventListener("abort", onAbort);
406
+ };
407
+ if (options?.onProgress) {
408
+ xhr.upload.addEventListener("progress", (event) => {
409
+ options.onProgress(event, xhr);
410
+ });
411
+ xhr.addEventListener("progress", (event) => {
412
+ options.onProgress(event, xhr);
413
+ });
414
+ }
415
+ xhr.addEventListener("load", () => {
416
+ cleanup();
417
+ const status = xhr.status;
418
+ const ok = status >= 200 && status < 300;
419
+ const responseHeaders = new Headers();
420
+ const rawHeaders = xhr.getAllResponseHeaders().trim();
421
+ if (rawHeaders) {
422
+ rawHeaders.split("\r\n").forEach((line) => {
423
+ const idx = line.indexOf(": ");
424
+ if (idx > 0) {
425
+ responseHeaders.append(
426
+ line.substring(0, idx),
427
+ line.substring(idx + 2)
428
+ );
429
+ }
430
+ });
431
+ }
432
+ const contentType = responseHeaders.get("content-type");
433
+ const isJson = contentType?.includes("application/json");
434
+ let data;
435
+ try {
436
+ data = isJson ? JSON.parse(xhr.responseText) : xhr.responseText;
437
+ } catch {
438
+ data = xhr.responseText;
439
+ }
440
+ resolve({ ok, status, headers: responseHeaders, data });
441
+ });
442
+ xhr.addEventListener("error", () => {
443
+ cleanup();
444
+ reject(new TypeError("Network request failed"));
445
+ });
446
+ xhr.addEventListener("abort", () => {
447
+ cleanup();
448
+ reject(new DOMException("Aborted", "AbortError"));
449
+ });
450
+ xhr.send(init.body);
451
+ });
452
+ };
453
+
371
454
  // src/fetch.ts
372
455
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
373
456
  var isNetworkError = (err) => err instanceof TypeError;
@@ -416,6 +499,12 @@ function buildInputFields(requestOptions) {
416
499
  }
417
500
  return { input: fields };
418
501
  }
502
+ function resolveTransport(option) {
503
+ if (option === "xhr" && typeof XMLHttpRequest !== "undefined") {
504
+ return xhrTransport;
505
+ }
506
+ return fetchTransport;
507
+ }
419
508
  async function executeCoreFetch(config) {
420
509
  const {
421
510
  baseUrl,
@@ -429,6 +518,7 @@ async function executeCoreFetch(config) {
429
518
  const {
430
519
  middlewares: _,
431
520
  headers: defaultHeaders,
521
+ transport: defaultTransport,
432
522
  ...fetchDefaults
433
523
  } = defaultOptions;
434
524
  void _;
@@ -473,28 +563,30 @@ async function executeCoreFetch(config) {
473
563
  }
474
564
  }
475
565
  }
566
+ const resolvedTransport = resolveTransport(
567
+ requestOptions?.transport ?? defaultTransport
568
+ );
476
569
  let lastError;
477
570
  for (let attempt = 0; attempt <= retryCount; attempt++) {
478
571
  try {
479
- const res = await fetch(url, fetchInit);
480
- const status = res.status;
481
- const resHeaders = res.headers;
482
- const contentType = resHeaders.get("content-type");
483
- const isJson = contentType?.includes("application/json");
484
- const body = isJson ? await res.json() : res;
485
- if (res.ok) {
572
+ const result = await resolvedTransport(
573
+ url,
574
+ fetchInit,
575
+ requestOptions?.transportOptions
576
+ );
577
+ if (result.ok) {
486
578
  return {
487
- status,
488
- data: body,
489
- headers: resHeaders,
579
+ status: result.status,
580
+ data: result.data,
581
+ headers: result.headers,
490
582
  error: void 0,
491
583
  ...inputFields
492
584
  };
493
585
  }
494
586
  return {
495
- status,
496
- error: body,
497
- headers: resHeaders,
587
+ status: result.status,
588
+ error: result.data,
589
+ headers: result.headers,
498
590
  data: void 0,
499
591
  ...inputFields
500
592
  };
@@ -738,6 +830,7 @@ function createStateManager() {
738
830
  for (const [name, value] of Object.entries(data)) {
739
831
  entry.meta.set(name, value);
740
832
  }
833
+ entry.state = { ...entry.state };
741
834
  notifySubscribers(key);
742
835
  },
743
836
  markStale(tags) {
@@ -949,7 +1042,7 @@ var Spoosh = class _Spoosh {
949
1042
  * Creates a new Spoosh instance.
950
1043
  *
951
1044
  * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
952
- * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
1045
+ * @param defaultOptions - Optional default options applied to all requests (headers, credentials, transport, etc.)
953
1046
  * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
954
1047
  *
955
1048
  * @example
@@ -961,6 +1054,12 @@ var Spoosh = class _Spoosh {
961
1054
  * const client = new Spoosh<ApiSchema, Error>('/api', {
962
1055
  * headers: { 'X-API-Key': 'secret' }
963
1056
  * });
1057
+ *
1058
+ * // With XHR transport (narrows available options to XHR-compatible fields)
1059
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
1060
+ * transport: 'xhr',
1061
+ * credentials: 'include',
1062
+ * });
964
1063
  * ```
965
1064
  */
966
1065
  constructor(baseUrl, defaultOptions, plugins) {
@@ -1156,7 +1255,10 @@ var Spoosh = class _Spoosh {
1156
1255
  // src/createClient.ts
1157
1256
  function createClient(config) {
1158
1257
  const { baseUrl, defaultOptions = {}, middlewares = [] } = config;
1159
- const optionsWithMiddlewares = { ...defaultOptions, middlewares };
1258
+ const optionsWithMiddlewares = {
1259
+ ...defaultOptions,
1260
+ middlewares
1261
+ };
1160
1262
  return createProxyHandler({
1161
1263
  baseUrl,
1162
1264
  defaultOptions: optionsWithMiddlewares,
package/dist/index.mjs CHANGED
@@ -306,6 +306,87 @@ function resolvePath(path, params) {
306
306
  });
307
307
  }
308
308
 
309
+ // src/transport/fetch.ts
310
+ var fetchTransport = async (url, init) => {
311
+ const res = await fetch(url, init);
312
+ const contentType = res.headers.get("content-type");
313
+ const isJson = contentType?.includes("application/json");
314
+ const data = isJson ? await res.json() : res;
315
+ return { ok: res.ok, status: res.status, headers: res.headers, data };
316
+ };
317
+
318
+ // src/transport/xhr.ts
319
+ var xhrTransport = (url, init, options) => {
320
+ return new Promise((resolve, reject) => {
321
+ const xhr = new XMLHttpRequest();
322
+ xhr.open(init.method ?? "GET", url);
323
+ if (init.headers) {
324
+ const headers = init.headers instanceof Headers ? init.headers : new Headers(init.headers);
325
+ headers.forEach((value, key) => {
326
+ xhr.setRequestHeader(key, value);
327
+ });
328
+ }
329
+ if (init.credentials === "include") {
330
+ xhr.withCredentials = true;
331
+ }
332
+ const onAbort = () => xhr.abort();
333
+ if (init.signal) {
334
+ if (init.signal.aborted) {
335
+ xhr.abort();
336
+ return;
337
+ }
338
+ init.signal.addEventListener("abort", onAbort);
339
+ }
340
+ const cleanup = () => {
341
+ init.signal?.removeEventListener("abort", onAbort);
342
+ };
343
+ if (options?.onProgress) {
344
+ xhr.upload.addEventListener("progress", (event) => {
345
+ options.onProgress(event, xhr);
346
+ });
347
+ xhr.addEventListener("progress", (event) => {
348
+ options.onProgress(event, xhr);
349
+ });
350
+ }
351
+ xhr.addEventListener("load", () => {
352
+ cleanup();
353
+ const status = xhr.status;
354
+ const ok = status >= 200 && status < 300;
355
+ const responseHeaders = new Headers();
356
+ const rawHeaders = xhr.getAllResponseHeaders().trim();
357
+ if (rawHeaders) {
358
+ rawHeaders.split("\r\n").forEach((line) => {
359
+ const idx = line.indexOf(": ");
360
+ if (idx > 0) {
361
+ responseHeaders.append(
362
+ line.substring(0, idx),
363
+ line.substring(idx + 2)
364
+ );
365
+ }
366
+ });
367
+ }
368
+ const contentType = responseHeaders.get("content-type");
369
+ const isJson = contentType?.includes("application/json");
370
+ let data;
371
+ try {
372
+ data = isJson ? JSON.parse(xhr.responseText) : xhr.responseText;
373
+ } catch {
374
+ data = xhr.responseText;
375
+ }
376
+ resolve({ ok, status, headers: responseHeaders, data });
377
+ });
378
+ xhr.addEventListener("error", () => {
379
+ cleanup();
380
+ reject(new TypeError("Network request failed"));
381
+ });
382
+ xhr.addEventListener("abort", () => {
383
+ cleanup();
384
+ reject(new DOMException("Aborted", "AbortError"));
385
+ });
386
+ xhr.send(init.body);
387
+ });
388
+ };
389
+
309
390
  // src/fetch.ts
310
391
  var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
311
392
  var isNetworkError = (err) => err instanceof TypeError;
@@ -354,6 +435,12 @@ function buildInputFields(requestOptions) {
354
435
  }
355
436
  return { input: fields };
356
437
  }
438
+ function resolveTransport(option) {
439
+ if (option === "xhr" && typeof XMLHttpRequest !== "undefined") {
440
+ return xhrTransport;
441
+ }
442
+ return fetchTransport;
443
+ }
357
444
  async function executeCoreFetch(config) {
358
445
  const {
359
446
  baseUrl,
@@ -367,6 +454,7 @@ async function executeCoreFetch(config) {
367
454
  const {
368
455
  middlewares: _,
369
456
  headers: defaultHeaders,
457
+ transport: defaultTransport,
370
458
  ...fetchDefaults
371
459
  } = defaultOptions;
372
460
  void _;
@@ -411,28 +499,30 @@ async function executeCoreFetch(config) {
411
499
  }
412
500
  }
413
501
  }
502
+ const resolvedTransport = resolveTransport(
503
+ requestOptions?.transport ?? defaultTransport
504
+ );
414
505
  let lastError;
415
506
  for (let attempt = 0; attempt <= retryCount; attempt++) {
416
507
  try {
417
- const res = await fetch(url, fetchInit);
418
- const status = res.status;
419
- const resHeaders = res.headers;
420
- const contentType = resHeaders.get("content-type");
421
- const isJson = contentType?.includes("application/json");
422
- const body = isJson ? await res.json() : res;
423
- if (res.ok) {
508
+ const result = await resolvedTransport(
509
+ url,
510
+ fetchInit,
511
+ requestOptions?.transportOptions
512
+ );
513
+ if (result.ok) {
424
514
  return {
425
- status,
426
- data: body,
427
- headers: resHeaders,
515
+ status: result.status,
516
+ data: result.data,
517
+ headers: result.headers,
428
518
  error: void 0,
429
519
  ...inputFields
430
520
  };
431
521
  }
432
522
  return {
433
- status,
434
- error: body,
435
- headers: resHeaders,
523
+ status: result.status,
524
+ error: result.data,
525
+ headers: result.headers,
436
526
  data: void 0,
437
527
  ...inputFields
438
528
  };
@@ -676,6 +766,7 @@ function createStateManager() {
676
766
  for (const [name, value] of Object.entries(data)) {
677
767
  entry.meta.set(name, value);
678
768
  }
769
+ entry.state = { ...entry.state };
679
770
  notifySubscribers(key);
680
771
  },
681
772
  markStale(tags) {
@@ -887,7 +978,7 @@ var Spoosh = class _Spoosh {
887
978
  * Creates a new Spoosh instance.
888
979
  *
889
980
  * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
890
- * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
981
+ * @param defaultOptions - Optional default options applied to all requests (headers, credentials, transport, etc.)
891
982
  * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
892
983
  *
893
984
  * @example
@@ -899,6 +990,12 @@ var Spoosh = class _Spoosh {
899
990
  * const client = new Spoosh<ApiSchema, Error>('/api', {
900
991
  * headers: { 'X-API-Key': 'secret' }
901
992
  * });
993
+ *
994
+ * // With XHR transport (narrows available options to XHR-compatible fields)
995
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
996
+ * transport: 'xhr',
997
+ * credentials: 'include',
998
+ * });
902
999
  * ```
903
1000
  */
904
1001
  constructor(baseUrl, defaultOptions, plugins) {
@@ -1094,7 +1191,10 @@ var Spoosh = class _Spoosh {
1094
1191
  // src/createClient.ts
1095
1192
  function createClient(config) {
1096
1193
  const { baseUrl, defaultOptions = {}, middlewares = [] } = config;
1097
- const optionsWithMiddlewares = { ...defaultOptions, middlewares };
1194
+ const optionsWithMiddlewares = {
1195
+ ...defaultOptions,
1196
+ middlewares
1197
+ };
1098
1198
  return createProxyHandler({
1099
1199
  baseUrl,
1100
1200
  defaultOptions: optionsWithMiddlewares,
@@ -1681,6 +1781,7 @@ export {
1681
1781
  executeFetch,
1682
1782
  extractMethodFromSelector,
1683
1783
  extractPathFromSelector,
1784
+ fetchTransport,
1684
1785
  form,
1685
1786
  generateTags,
1686
1787
  getContentType,
@@ -1696,5 +1797,6 @@ export {
1696
1797
  resolveTags,
1697
1798
  setHeaders,
1698
1799
  sortObjectKeys,
1699
- urlencoded
1800
+ urlencoded,
1801
+ xhrTransport
1700
1802
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.9.3",
3
+ "version": "0.10.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [
@@ -14,13 +14,13 @@
14
14
  ],
15
15
  "repository": {
16
16
  "type": "git",
17
- "url": "git+https://github.com/nxnom/spoosh.git",
17
+ "url": "git+https://github.com/spooshdev/spoosh.git",
18
18
  "directory": "packages/core"
19
19
  },
20
20
  "bugs": {
21
- "url": "https://github.com/nxnom/spoosh/issues"
21
+ "url": "https://github.com/spooshdev/spoosh/issues"
22
22
  },
23
- "homepage": "https://spoosh.dev/react/docs/core",
23
+ "homepage": "https://spoosh.dev/docs/react/core",
24
24
  "publishConfig": {
25
25
  "access": "public"
26
26
  },