@spoosh/core 0.9.2 → 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 +1 -1
- package/dist/index.d.mts +90 -39
- package/dist/index.d.ts +90 -39
- package/dist/index.js +118 -16
- package/dist/index.mjs +118 -16
- package/package.json +4 -4
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
|
@@ -28,40 +28,32 @@ type QueryField<TQuery> = [TQuery] extends [never] ? object : {
|
|
|
28
28
|
type BodyField<TBody> = [TBody] extends [never] ? object : {
|
|
29
29
|
body: TBody;
|
|
30
30
|
};
|
|
31
|
-
type FormDataField<TFormData> = [TFormData] extends [never] ? object : {
|
|
32
|
-
formData: TFormData;
|
|
33
|
-
};
|
|
34
|
-
type UrlEncodedField<TUrlEncoded> = [TUrlEncoded] extends [never] ? object : {
|
|
35
|
-
urlEncoded: TUrlEncoded;
|
|
36
|
-
};
|
|
37
31
|
type ParamsField<TParamNames extends string> = [TParamNames] extends [never] ? object : {
|
|
38
32
|
params: Record<TParamNames, string | number>;
|
|
39
33
|
};
|
|
40
|
-
type InputFields<TQuery, TBody,
|
|
41
|
-
type InputFieldWrapper<TQuery, TBody,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
] ? object : {
|
|
48
|
-
input: InputFields<TQuery, TBody, TFormData, TUrlEncoded, TParamNames>;
|
|
34
|
+
type InputFields<TQuery, TBody, TParamNames extends string> = QueryField<TQuery> & BodyField<TBody> & ParamsField<TParamNames>;
|
|
35
|
+
type InputFieldWrapper<TQuery, TBody, TParamNames extends string> = [
|
|
36
|
+
TQuery,
|
|
37
|
+
TBody,
|
|
38
|
+
TParamNames
|
|
39
|
+
] extends [never, never, never] ? object : {
|
|
40
|
+
input: InputFields<TQuery, TBody, TParamNames>;
|
|
49
41
|
};
|
|
50
|
-
type SpooshResponse<TData, TError, TRequestOptions = unknown, TQuery = never, TBody = never,
|
|
42
|
+
type SpooshResponse<TData, TError, TRequestOptions = unknown, TQuery = never, TBody = never, TParamNames extends string = never> = ({
|
|
51
43
|
status: number;
|
|
52
44
|
data: TData;
|
|
53
45
|
headers?: Headers;
|
|
54
46
|
error?: undefined;
|
|
55
47
|
aborted?: false;
|
|
56
48
|
readonly __requestOptions?: TRequestOptions;
|
|
57
|
-
} & InputFieldWrapper<TQuery, TBody,
|
|
49
|
+
} & InputFieldWrapper<TQuery, TBody, TParamNames>) | ({
|
|
58
50
|
status: number;
|
|
59
51
|
data?: undefined;
|
|
60
52
|
headers?: Headers;
|
|
61
53
|
error: TError;
|
|
62
54
|
aborted?: boolean;
|
|
63
55
|
readonly __requestOptions?: TRequestOptions;
|
|
64
|
-
} & InputFieldWrapper<TQuery, TBody,
|
|
56
|
+
} & InputFieldWrapper<TQuery, TBody, TParamNames>);
|
|
65
57
|
type SpooshOptionsExtra<TData = unknown, TError = unknown> = {
|
|
66
58
|
middlewares?: SpooshMiddleware<TData, TError>[];
|
|
67
59
|
};
|
|
@@ -80,6 +72,24 @@ declare function resolveRequestBody(rawBody: unknown): {
|
|
|
80
72
|
headers?: Record<string, string>;
|
|
81
73
|
} | undefined;
|
|
82
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
|
+
|
|
83
93
|
type RetryConfig = {
|
|
84
94
|
retries?: number | false;
|
|
85
95
|
retryDelay?: number;
|
|
@@ -87,7 +97,26 @@ type RetryConfig = {
|
|
|
87
97
|
type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
88
98
|
type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
|
|
89
99
|
headers?: HeadersInitOrGetter;
|
|
100
|
+
/** Default transport for all requests. */
|
|
101
|
+
transport?: TransportOption;
|
|
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";
|
|
90
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;
|
|
91
120
|
type BaseRequestOptions$1 = {
|
|
92
121
|
headers?: HeadersInitOrGetter;
|
|
93
122
|
cache?: RequestCache;
|
|
@@ -109,6 +138,10 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
|
|
|
109
138
|
query?: Record<string, string | number | boolean | undefined>;
|
|
110
139
|
params?: Record<string, string | number>;
|
|
111
140
|
signal?: AbortSignal;
|
|
141
|
+
/** Per-request transport override. */
|
|
142
|
+
transport?: TransportOption;
|
|
143
|
+
/** Transport-specific options passed through to the transport function. */
|
|
144
|
+
transportOptions?: unknown;
|
|
112
145
|
} & Partial<RetryConfig>;
|
|
113
146
|
type DynamicParamsOption = {
|
|
114
147
|
params?: Record<string, string | number>;
|
|
@@ -149,6 +182,7 @@ type EventCallback<T = unknown> = (payload: T) => void;
|
|
|
149
182
|
interface BuiltInEvents {
|
|
150
183
|
refetch: RefetchEvent;
|
|
151
184
|
invalidate: string[];
|
|
185
|
+
refetchAll: void;
|
|
152
186
|
}
|
|
153
187
|
/**
|
|
154
188
|
* Resolves event payload type. Built-in events get their specific type,
|
|
@@ -558,13 +592,14 @@ type PluginAccessor = {
|
|
|
558
592
|
get<K extends keyof PluginExportsRegistry>(name: K): PluginExportsRegistry[K] | undefined;
|
|
559
593
|
get(name: string): unknown;
|
|
560
594
|
};
|
|
595
|
+
type RefetchEventReason = "focus" | "reconnect" | "polling" | "invalidate";
|
|
561
596
|
/**
|
|
562
597
|
* Event emitted by plugins to request a refetch.
|
|
563
598
|
* Hooks subscribe to this event and trigger controller.execute().
|
|
564
599
|
*/
|
|
565
600
|
type RefetchEvent = {
|
|
566
601
|
queryKey: string;
|
|
567
|
-
reason:
|
|
602
|
+
reason: RefetchEventReason | Omit<string, RefetchEventReason>;
|
|
568
603
|
};
|
|
569
604
|
/**
|
|
570
605
|
* Minimal PluginExecutor interface for InstanceApiContext.
|
|
@@ -734,7 +769,7 @@ declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeCo
|
|
|
734
769
|
type ApiSchema = {
|
|
735
770
|
[path: string]: {
|
|
736
771
|
[method in HttpMethod]?: {
|
|
737
|
-
data
|
|
772
|
+
data?: unknown;
|
|
738
773
|
body?: unknown;
|
|
739
774
|
query?: unknown;
|
|
740
775
|
error?: unknown;
|
|
@@ -746,7 +781,7 @@ type ApiSchema = {
|
|
|
746
781
|
*/
|
|
747
782
|
type ExtractData<T> = T extends {
|
|
748
783
|
data: infer D;
|
|
749
|
-
} ? D :
|
|
784
|
+
} ? D : void;
|
|
750
785
|
/**
|
|
751
786
|
* Extract body type from an endpoint.
|
|
752
787
|
*/
|
|
@@ -786,16 +821,7 @@ type ExtractError<T, TDefault = unknown> = T extends {
|
|
|
786
821
|
* const api = createClient<ApiSchema>({ baseUrl: "/api" });
|
|
787
822
|
* ```
|
|
788
823
|
*/
|
|
789
|
-
type SpooshSchema<T extends
|
|
790
|
-
[path: string]: {
|
|
791
|
-
[M in HttpMethod]?: {
|
|
792
|
-
data: unknown;
|
|
793
|
-
body?: unknown;
|
|
794
|
-
query?: unknown;
|
|
795
|
-
error?: unknown;
|
|
796
|
-
};
|
|
797
|
-
};
|
|
798
|
-
}> = T;
|
|
824
|
+
type SpooshSchema<T extends ApiSchema> = T;
|
|
799
825
|
/**
|
|
800
826
|
* Convert a route pattern like "posts/:id" to a path matcher pattern like `posts/${string}`.
|
|
801
827
|
* This enables TypeScript to match actual paths like "posts/123" to their schema definitions.
|
|
@@ -807,7 +833,7 @@ type SpooshSchema<T extends {
|
|
|
807
833
|
* type C = RouteToPath<"posts">; // "posts"
|
|
808
834
|
* ```
|
|
809
835
|
*/
|
|
810
|
-
type RouteToPath<T extends string> = T extends
|
|
836
|
+
type RouteToPath<T extends string> = T extends `/${infer Rest}` ? `/${RouteToPath<Rest>}` : T extends `${infer Segment}/${infer Rest}` ? Segment extends `:${string}` ? `${string}/${RouteToPath<Rest>}` : `${Segment}/${RouteToPath<Rest>}` : T extends `:${string}` ? string : T;
|
|
811
837
|
/**
|
|
812
838
|
* Find which schema key matches a given path.
|
|
813
839
|
* First checks for exact match, then checks pattern matches.
|
|
@@ -875,12 +901,12 @@ type StripPrefixFromPath<TPath extends string, TPrefix extends string> = TPath e
|
|
|
875
901
|
* "api": { GET: { data: string } };
|
|
876
902
|
* "api/users": { GET: { data: User[] } };
|
|
877
903
|
* "api/posts/:id": { GET: { data: Post } };
|
|
878
|
-
* "health": { GET: { data: { status: string } } };
|
|
904
|
+
* "api/health": { GET: { data: { status: string } } };
|
|
879
905
|
* };
|
|
880
906
|
*
|
|
881
907
|
* type ApiSchema = StripPrefix<FullSchema, "api">;
|
|
882
908
|
* // {
|
|
883
|
-
* // "": { GET: { data: string } };
|
|
909
|
+
* // "/": { GET: { data: string } };
|
|
884
910
|
* // "users": { GET: { data: User[] } };
|
|
885
911
|
* // "posts/:id": { GET: { data: Post } };
|
|
886
912
|
* // "health": { GET: { data: { status: string } } };
|
|
@@ -1011,7 +1037,7 @@ type IsOptionsRequired<TMethodConfig, TUserPath extends string> = IsBodyRequired
|
|
|
1011
1037
|
/**
|
|
1012
1038
|
* Build response type for a method call.
|
|
1013
1039
|
*/
|
|
1014
|
-
type MethodResponse<TMethodConfig, TDefaultError, TUserPath extends string> = SpooshResponse<ExtractData<TMethodConfig>, ExtractError<TMethodConfig, TDefaultError>, RequestOptions<TMethodConfig, TUserPath>, ExtractQuery<TMethodConfig>, ExtractBody<TMethodConfig>,
|
|
1040
|
+
type MethodResponse<TMethodConfig, TDefaultError, TUserPath extends string> = SpooshResponse<ExtractData<TMethodConfig>, ExtractError<TMethodConfig, TDefaultError>, RequestOptions<TMethodConfig, TUserPath>, ExtractQuery<TMethodConfig>, ExtractBody<TMethodConfig>, ExtractParamNames<TUserPath>>;
|
|
1015
1041
|
/**
|
|
1016
1042
|
* Create a method function type.
|
|
1017
1043
|
* Direct lookup: Schema[Path][Method] → method config → build function type
|
|
@@ -1151,7 +1177,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1151
1177
|
* Creates a new Spoosh instance.
|
|
1152
1178
|
*
|
|
1153
1179
|
* @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
|
|
1154
|
-
* @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.)
|
|
1155
1181
|
* @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
|
|
1156
1182
|
*
|
|
1157
1183
|
* @example
|
|
@@ -1163,9 +1189,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1163
1189
|
* const client = new Spoosh<ApiSchema, Error>('/api', {
|
|
1164
1190
|
* headers: { 'X-API-Key': 'secret' }
|
|
1165
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
|
+
* });
|
|
1166
1198
|
* ```
|
|
1167
1199
|
*/
|
|
1168
|
-
constructor(baseUrl: string, defaultOptions?:
|
|
1200
|
+
constructor(baseUrl: string, defaultOptions?: SpooshOptionsInput, plugins?: TPlugins);
|
|
1169
1201
|
/**
|
|
1170
1202
|
* Adds plugins to the Spoosh instance.
|
|
1171
1203
|
*
|
|
@@ -1316,7 +1348,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1316
1348
|
|
|
1317
1349
|
type SpooshClientConfig = {
|
|
1318
1350
|
baseUrl: string;
|
|
1319
|
-
defaultOptions?:
|
|
1351
|
+
defaultOptions?: SpooshOptionsInput;
|
|
1320
1352
|
middlewares?: SpooshMiddleware[];
|
|
1321
1353
|
};
|
|
1322
1354
|
/**
|
|
@@ -1354,6 +1386,12 @@ type SpooshClientConfig = {
|
|
|
1354
1386
|
* const { data } = await api("posts").GET();
|
|
1355
1387
|
* const { data: post } = await api("posts/123").GET();
|
|
1356
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
|
+
* });
|
|
1357
1395
|
* ```
|
|
1358
1396
|
*/
|
|
1359
1397
|
declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
|
|
@@ -1542,6 +1580,19 @@ declare function extractPathFromSelector(fn: unknown): string;
|
|
|
1542
1580
|
*/
|
|
1543
1581
|
declare function extractMethodFromSelector(fn: unknown): string | undefined;
|
|
1544
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
|
+
|
|
1545
1596
|
declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise<SpooshResponse<TData, TError>>;
|
|
1546
1597
|
|
|
1547
1598
|
declare function createMiddleware<TData = unknown, TError = unknown>(name: string, phase: MiddlewarePhase, handler: SpooshMiddleware<TData, TError>["handler"]): SpooshMiddleware<TData, TError>;
|
|
@@ -1636,4 +1687,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
|
|
|
1636
1687
|
};
|
|
1637
1688
|
declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
|
|
1638
1689
|
|
|
1639
|
-
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
|
|
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
|
@@ -28,40 +28,32 @@ type QueryField<TQuery> = [TQuery] extends [never] ? object : {
|
|
|
28
28
|
type BodyField<TBody> = [TBody] extends [never] ? object : {
|
|
29
29
|
body: TBody;
|
|
30
30
|
};
|
|
31
|
-
type FormDataField<TFormData> = [TFormData] extends [never] ? object : {
|
|
32
|
-
formData: TFormData;
|
|
33
|
-
};
|
|
34
|
-
type UrlEncodedField<TUrlEncoded> = [TUrlEncoded] extends [never] ? object : {
|
|
35
|
-
urlEncoded: TUrlEncoded;
|
|
36
|
-
};
|
|
37
31
|
type ParamsField<TParamNames extends string> = [TParamNames] extends [never] ? object : {
|
|
38
32
|
params: Record<TParamNames, string | number>;
|
|
39
33
|
};
|
|
40
|
-
type InputFields<TQuery, TBody,
|
|
41
|
-
type InputFieldWrapper<TQuery, TBody,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
] ? object : {
|
|
48
|
-
input: InputFields<TQuery, TBody, TFormData, TUrlEncoded, TParamNames>;
|
|
34
|
+
type InputFields<TQuery, TBody, TParamNames extends string> = QueryField<TQuery> & BodyField<TBody> & ParamsField<TParamNames>;
|
|
35
|
+
type InputFieldWrapper<TQuery, TBody, TParamNames extends string> = [
|
|
36
|
+
TQuery,
|
|
37
|
+
TBody,
|
|
38
|
+
TParamNames
|
|
39
|
+
] extends [never, never, never] ? object : {
|
|
40
|
+
input: InputFields<TQuery, TBody, TParamNames>;
|
|
49
41
|
};
|
|
50
|
-
type SpooshResponse<TData, TError, TRequestOptions = unknown, TQuery = never, TBody = never,
|
|
42
|
+
type SpooshResponse<TData, TError, TRequestOptions = unknown, TQuery = never, TBody = never, TParamNames extends string = never> = ({
|
|
51
43
|
status: number;
|
|
52
44
|
data: TData;
|
|
53
45
|
headers?: Headers;
|
|
54
46
|
error?: undefined;
|
|
55
47
|
aborted?: false;
|
|
56
48
|
readonly __requestOptions?: TRequestOptions;
|
|
57
|
-
} & InputFieldWrapper<TQuery, TBody,
|
|
49
|
+
} & InputFieldWrapper<TQuery, TBody, TParamNames>) | ({
|
|
58
50
|
status: number;
|
|
59
51
|
data?: undefined;
|
|
60
52
|
headers?: Headers;
|
|
61
53
|
error: TError;
|
|
62
54
|
aborted?: boolean;
|
|
63
55
|
readonly __requestOptions?: TRequestOptions;
|
|
64
|
-
} & InputFieldWrapper<TQuery, TBody,
|
|
56
|
+
} & InputFieldWrapper<TQuery, TBody, TParamNames>);
|
|
65
57
|
type SpooshOptionsExtra<TData = unknown, TError = unknown> = {
|
|
66
58
|
middlewares?: SpooshMiddleware<TData, TError>[];
|
|
67
59
|
};
|
|
@@ -80,6 +72,24 @@ declare function resolveRequestBody(rawBody: unknown): {
|
|
|
80
72
|
headers?: Record<string, string>;
|
|
81
73
|
} | undefined;
|
|
82
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
|
+
|
|
83
93
|
type RetryConfig = {
|
|
84
94
|
retries?: number | false;
|
|
85
95
|
retryDelay?: number;
|
|
@@ -87,7 +97,26 @@ type RetryConfig = {
|
|
|
87
97
|
type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersInit>);
|
|
88
98
|
type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
|
|
89
99
|
headers?: HeadersInitOrGetter;
|
|
100
|
+
/** Default transport for all requests. */
|
|
101
|
+
transport?: TransportOption;
|
|
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";
|
|
90
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;
|
|
91
120
|
type BaseRequestOptions$1 = {
|
|
92
121
|
headers?: HeadersInitOrGetter;
|
|
93
122
|
cache?: RequestCache;
|
|
@@ -109,6 +138,10 @@ type AnyRequestOptions = BaseRequestOptions$1 & {
|
|
|
109
138
|
query?: Record<string, string | number | boolean | undefined>;
|
|
110
139
|
params?: Record<string, string | number>;
|
|
111
140
|
signal?: AbortSignal;
|
|
141
|
+
/** Per-request transport override. */
|
|
142
|
+
transport?: TransportOption;
|
|
143
|
+
/** Transport-specific options passed through to the transport function. */
|
|
144
|
+
transportOptions?: unknown;
|
|
112
145
|
} & Partial<RetryConfig>;
|
|
113
146
|
type DynamicParamsOption = {
|
|
114
147
|
params?: Record<string, string | number>;
|
|
@@ -149,6 +182,7 @@ type EventCallback<T = unknown> = (payload: T) => void;
|
|
|
149
182
|
interface BuiltInEvents {
|
|
150
183
|
refetch: RefetchEvent;
|
|
151
184
|
invalidate: string[];
|
|
185
|
+
refetchAll: void;
|
|
152
186
|
}
|
|
153
187
|
/**
|
|
154
188
|
* Resolves event payload type. Built-in events get their specific type,
|
|
@@ -558,13 +592,14 @@ type PluginAccessor = {
|
|
|
558
592
|
get<K extends keyof PluginExportsRegistry>(name: K): PluginExportsRegistry[K] | undefined;
|
|
559
593
|
get(name: string): unknown;
|
|
560
594
|
};
|
|
595
|
+
type RefetchEventReason = "focus" | "reconnect" | "polling" | "invalidate";
|
|
561
596
|
/**
|
|
562
597
|
* Event emitted by plugins to request a refetch.
|
|
563
598
|
* Hooks subscribe to this event and trigger controller.execute().
|
|
564
599
|
*/
|
|
565
600
|
type RefetchEvent = {
|
|
566
601
|
queryKey: string;
|
|
567
|
-
reason:
|
|
602
|
+
reason: RefetchEventReason | Omit<string, RefetchEventReason>;
|
|
568
603
|
};
|
|
569
604
|
/**
|
|
570
605
|
* Minimal PluginExecutor interface for InstanceApiContext.
|
|
@@ -734,7 +769,7 @@ declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeCo
|
|
|
734
769
|
type ApiSchema = {
|
|
735
770
|
[path: string]: {
|
|
736
771
|
[method in HttpMethod]?: {
|
|
737
|
-
data
|
|
772
|
+
data?: unknown;
|
|
738
773
|
body?: unknown;
|
|
739
774
|
query?: unknown;
|
|
740
775
|
error?: unknown;
|
|
@@ -746,7 +781,7 @@ type ApiSchema = {
|
|
|
746
781
|
*/
|
|
747
782
|
type ExtractData<T> = T extends {
|
|
748
783
|
data: infer D;
|
|
749
|
-
} ? D :
|
|
784
|
+
} ? D : void;
|
|
750
785
|
/**
|
|
751
786
|
* Extract body type from an endpoint.
|
|
752
787
|
*/
|
|
@@ -786,16 +821,7 @@ type ExtractError<T, TDefault = unknown> = T extends {
|
|
|
786
821
|
* const api = createClient<ApiSchema>({ baseUrl: "/api" });
|
|
787
822
|
* ```
|
|
788
823
|
*/
|
|
789
|
-
type SpooshSchema<T extends
|
|
790
|
-
[path: string]: {
|
|
791
|
-
[M in HttpMethod]?: {
|
|
792
|
-
data: unknown;
|
|
793
|
-
body?: unknown;
|
|
794
|
-
query?: unknown;
|
|
795
|
-
error?: unknown;
|
|
796
|
-
};
|
|
797
|
-
};
|
|
798
|
-
}> = T;
|
|
824
|
+
type SpooshSchema<T extends ApiSchema> = T;
|
|
799
825
|
/**
|
|
800
826
|
* Convert a route pattern like "posts/:id" to a path matcher pattern like `posts/${string}`.
|
|
801
827
|
* This enables TypeScript to match actual paths like "posts/123" to their schema definitions.
|
|
@@ -807,7 +833,7 @@ type SpooshSchema<T extends {
|
|
|
807
833
|
* type C = RouteToPath<"posts">; // "posts"
|
|
808
834
|
* ```
|
|
809
835
|
*/
|
|
810
|
-
type RouteToPath<T extends string> = T extends
|
|
836
|
+
type RouteToPath<T extends string> = T extends `/${infer Rest}` ? `/${RouteToPath<Rest>}` : T extends `${infer Segment}/${infer Rest}` ? Segment extends `:${string}` ? `${string}/${RouteToPath<Rest>}` : `${Segment}/${RouteToPath<Rest>}` : T extends `:${string}` ? string : T;
|
|
811
837
|
/**
|
|
812
838
|
* Find which schema key matches a given path.
|
|
813
839
|
* First checks for exact match, then checks pattern matches.
|
|
@@ -875,12 +901,12 @@ type StripPrefixFromPath<TPath extends string, TPrefix extends string> = TPath e
|
|
|
875
901
|
* "api": { GET: { data: string } };
|
|
876
902
|
* "api/users": { GET: { data: User[] } };
|
|
877
903
|
* "api/posts/:id": { GET: { data: Post } };
|
|
878
|
-
* "health": { GET: { data: { status: string } } };
|
|
904
|
+
* "api/health": { GET: { data: { status: string } } };
|
|
879
905
|
* };
|
|
880
906
|
*
|
|
881
907
|
* type ApiSchema = StripPrefix<FullSchema, "api">;
|
|
882
908
|
* // {
|
|
883
|
-
* // "": { GET: { data: string } };
|
|
909
|
+
* // "/": { GET: { data: string } };
|
|
884
910
|
* // "users": { GET: { data: User[] } };
|
|
885
911
|
* // "posts/:id": { GET: { data: Post } };
|
|
886
912
|
* // "health": { GET: { data: { status: string } } };
|
|
@@ -1011,7 +1037,7 @@ type IsOptionsRequired<TMethodConfig, TUserPath extends string> = IsBodyRequired
|
|
|
1011
1037
|
/**
|
|
1012
1038
|
* Build response type for a method call.
|
|
1013
1039
|
*/
|
|
1014
|
-
type MethodResponse<TMethodConfig, TDefaultError, TUserPath extends string> = SpooshResponse<ExtractData<TMethodConfig>, ExtractError<TMethodConfig, TDefaultError>, RequestOptions<TMethodConfig, TUserPath>, ExtractQuery<TMethodConfig>, ExtractBody<TMethodConfig>,
|
|
1040
|
+
type MethodResponse<TMethodConfig, TDefaultError, TUserPath extends string> = SpooshResponse<ExtractData<TMethodConfig>, ExtractError<TMethodConfig, TDefaultError>, RequestOptions<TMethodConfig, TUserPath>, ExtractQuery<TMethodConfig>, ExtractBody<TMethodConfig>, ExtractParamNames<TUserPath>>;
|
|
1015
1041
|
/**
|
|
1016
1042
|
* Create a method function type.
|
|
1017
1043
|
* Direct lookup: Schema[Path][Method] → method config → build function type
|
|
@@ -1151,7 +1177,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1151
1177
|
* Creates a new Spoosh instance.
|
|
1152
1178
|
*
|
|
1153
1179
|
* @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
|
|
1154
|
-
* @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.)
|
|
1155
1181
|
* @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
|
|
1156
1182
|
*
|
|
1157
1183
|
* @example
|
|
@@ -1163,9 +1189,15 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1163
1189
|
* const client = new Spoosh<ApiSchema, Error>('/api', {
|
|
1164
1190
|
* headers: { 'X-API-Key': 'secret' }
|
|
1165
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
|
+
* });
|
|
1166
1198
|
* ```
|
|
1167
1199
|
*/
|
|
1168
|
-
constructor(baseUrl: string, defaultOptions?:
|
|
1200
|
+
constructor(baseUrl: string, defaultOptions?: SpooshOptionsInput, plugins?: TPlugins);
|
|
1169
1201
|
/**
|
|
1170
1202
|
* Adds plugins to the Spoosh instance.
|
|
1171
1203
|
*
|
|
@@ -1316,7 +1348,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
|
|
|
1316
1348
|
|
|
1317
1349
|
type SpooshClientConfig = {
|
|
1318
1350
|
baseUrl: string;
|
|
1319
|
-
defaultOptions?:
|
|
1351
|
+
defaultOptions?: SpooshOptionsInput;
|
|
1320
1352
|
middlewares?: SpooshMiddleware[];
|
|
1321
1353
|
};
|
|
1322
1354
|
/**
|
|
@@ -1354,6 +1386,12 @@ type SpooshClientConfig = {
|
|
|
1354
1386
|
* const { data } = await api("posts").GET();
|
|
1355
1387
|
* const { data: post } = await api("posts/123").GET();
|
|
1356
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
|
+
* });
|
|
1357
1395
|
* ```
|
|
1358
1396
|
*/
|
|
1359
1397
|
declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
|
|
@@ -1542,6 +1580,19 @@ declare function extractPathFromSelector(fn: unknown): string;
|
|
|
1542
1580
|
*/
|
|
1543
1581
|
declare function extractMethodFromSelector(fn: unknown): string | undefined;
|
|
1544
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
|
+
|
|
1545
1596
|
declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise<SpooshResponse<TData, TError>>;
|
|
1546
1597
|
|
|
1547
1598
|
declare function createMiddleware<TData = unknown, TError = unknown>(name: string, phase: MiddlewarePhase, handler: SpooshMiddleware<TData, TError>["handler"]): SpooshMiddleware<TData, TError>;
|
|
@@ -1636,4 +1687,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
|
|
|
1636
1687
|
};
|
|
1637
1688
|
declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
|
|
1638
1689
|
|
|
1639
|
-
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
|
|
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
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
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:
|
|
489
|
-
headers:
|
|
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:
|
|
497
|
-
headers:
|
|
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 = {
|
|
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
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
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:
|
|
427
|
-
headers:
|
|
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:
|
|
435
|
-
headers:
|
|
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 = {
|
|
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.
|
|
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/
|
|
17
|
+
"url": "git+https://github.com/spooshdev/spoosh.git",
|
|
18
18
|
"directory": "packages/core"
|
|
19
19
|
},
|
|
20
20
|
"bugs": {
|
|
21
|
-
"url": "https://github.com/
|
|
21
|
+
"url": "https://github.com/spooshdev/spoosh/issues"
|
|
22
22
|
},
|
|
23
|
-
"homepage": "https://spoosh.dev/react/
|
|
23
|
+
"homepage": "https://spoosh.dev/docs/react/core",
|
|
24
24
|
"publishConfig": {
|
|
25
25
|
"access": "public"
|
|
26
26
|
},
|