@spoosh/core 0.8.2 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,6 +50,9 @@ import { createClient } from "@spoosh/core";
50
50
  const api = createClient<ApiSchema>({
51
51
  baseUrl: "/api",
52
52
  });
53
+
54
+ // Import body wrappers for explicit serialization
55
+ import { json, form, urlencoded } from "@spoosh/core";
53
56
  ```
54
57
 
55
58
  ### Make API Calls
@@ -79,14 +82,14 @@ const { data: updated } = await api("users/:id").PUT({
79
82
  // DELETE /api/users/123
80
83
  await api("users/:id").DELETE({ params: { id: 123 } });
81
84
 
82
- // POST with file upload (auto FormData when File detected)
85
+ // POST with file upload (using form() wrapper for multipart/form-data)
83
86
  const { data: uploaded } = await api("upload").POST({
84
- body: { file: myFile },
87
+ body: form({ file: myFile }),
85
88
  });
86
89
 
87
- // POST with form data
90
+ // POST with form data (using urlencoded() wrapper)
88
91
  const { data: payment } = await api("payments").POST({
89
- body: { amount: 1000, currency: "usd" },
92
+ body: urlencoded({ amount: 1000, currency: "usd" }),
90
93
  });
91
94
  ```
92
95
 
@@ -172,12 +175,12 @@ const updatedContext = await applyMiddlewares(context, middlewares, "before");
172
175
 
173
176
  ## Schema Types
174
177
 
175
- | Field | Description | Example |
176
- | ------- | --------------------------------------- | ------------------------------------------------ |
177
- | `data` | Response data type | `GET: { data: User[] }` |
178
- | `body` | Request body type (files auto-detected) | `POST: { data: User; body: CreateUserBody }` |
179
- | `query` | Query parameters type | `GET: { data: User[]; query: { page: number } }` |
180
- | `error` | Typed error type | `GET: { data: User; error: ApiError }` |
178
+ | Field | Description | Example |
179
+ | ------- | --------------------- | ------------------------------------------------ |
180
+ | `data` | Response data type | `GET: { data: User[] }` |
181
+ | `body` | Request body type | `POST: { data: User; body: CreateUserBody }` |
182
+ | `query` | Query parameters type | `GET: { data: User[]; query: { page: number } }` |
183
+ | `error` | Typed error type | `GET: { data: User; error: ApiError }` |
181
184
 
182
185
  Path parameters are defined using `:param` syntax in the path key (e.g., `"users/:id"`).
183
186
 
package/dist/index.d.mts CHANGED
@@ -1,4 +1,8 @@
1
1
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
+ type WriteMethod = "POST" | "PUT" | "PATCH" | "DELETE";
3
+ type Simplify<T> = {
4
+ [K in keyof T]: T[K];
5
+ } & {};
2
6
 
3
7
  type MiddlewarePhase = "before" | "after";
4
8
  type MiddlewareContext<TData = unknown, TError = unknown> = {
@@ -62,6 +66,20 @@ type SpooshOptionsExtra<TData = unknown, TError = unknown> = {
62
66
  middlewares?: SpooshMiddleware<TData, TError>[];
63
67
  };
64
68
 
69
+ type SpooshBody<T = unknown> = {
70
+ readonly __spooshBody: true;
71
+ readonly kind: "form" | "json" | "urlencoded";
72
+ readonly value: T;
73
+ };
74
+ declare function isSpooshBody(value: unknown): value is SpooshBody;
75
+ declare function form<T>(value: T): SpooshBody<T>;
76
+ declare function json<T>(value: T): SpooshBody<T>;
77
+ declare function urlencoded<T>(value: T): SpooshBody<T>;
78
+ declare function resolveRequestBody(rawBody: unknown): {
79
+ body: BodyInit;
80
+ headers?: Record<string, string>;
81
+ } | undefined;
82
+
65
83
  type RetryConfig = {
66
84
  retries?: number | false;
67
85
  retryDelay?: number;
@@ -76,9 +94,9 @@ type BaseRequestOptions$1 = {
76
94
  signal?: AbortSignal;
77
95
  };
78
96
  type BodyOption$1<TBody> = [TBody] extends [never] ? object : undefined extends TBody ? {
79
- body?: Exclude<TBody, undefined>;
97
+ body?: Exclude<TBody, undefined> | SpooshBody<Exclude<TBody, undefined>>;
80
98
  } : {
81
- body: TBody;
99
+ body: TBody | SpooshBody<TBody>;
82
100
  };
83
101
  type QueryOption$1<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
84
102
  query?: Exclude<TQuery, undefined>;
@@ -106,7 +124,7 @@ type MethodOptionsMap<TQueryOptions = object, TMutationOptions = object> = {
106
124
  DELETE: TMutationOptions;
107
125
  };
108
126
  type ExtractMethodOptions<TOptionsMap, TMethod extends HttpMethod> = TOptionsMap extends MethodOptionsMap<infer TQuery, infer TMutation> ? TMethod extends "GET" ? TQuery : TMutation : TOptionsMap;
109
- type FetchExecutor<TOptions = SpooshOptions, TRequestOptions = AnyRequestOptions> = <TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: TOptions, requestOptions?: TRequestOptions, nextTags?: boolean) => Promise<SpooshResponse<TData, TError>>;
127
+ type FetchExecutor<TOptions = SpooshOptions, TRequestOptions = AnyRequestOptions> = <TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: TOptions, requestOptions?: TRequestOptions, nextTags?: boolean, tagPath?: string[]) => Promise<SpooshResponse<TData, TError>>;
110
128
  type TypedParamsOption<TParamNames extends string> = [TParamNames] extends [
111
129
  never
112
130
  ] ? object : {
@@ -233,6 +251,11 @@ type PluginContext<TData = unknown, TError = unknown> = {
233
251
  readonly requestTimestamp: number;
234
252
  /** Unique identifier for the hook instance. Persists across queryKey changes within the same hook. */
235
253
  readonly hookId?: string;
254
+ /**
255
+ * Prefix to strip from tags. Configured at the Spoosh instance level.
256
+ * Plugins can use this to normalize tags before emitting events.
257
+ */
258
+ readonly stripTagPrefix?: string;
236
259
  requestOptions: AnyRequestOptions;
237
260
  state: OperationState<TData, TError>;
238
261
  response?: SpooshResponse<TData, TError>;
@@ -578,7 +601,10 @@ type PluginExecutor = {
578
601
  /** Creates a full PluginContext with plugins accessor injected */
579
602
  createContext: <TData, TError>(input: PluginContextInput<TData, TError>) => PluginContext<TData, TError>;
580
603
  };
581
- declare function createPluginExecutor(initialPlugins?: SpooshPlugin[]): PluginExecutor;
604
+ type PluginExecutorOptions = {
605
+ stripTagPrefix?: string;
606
+ };
607
+ declare function createPluginExecutor(initialPlugins?: SpooshPlugin[], options?: PluginExecutorOptions): PluginExecutor;
582
608
 
583
609
  /**
584
610
  * Resolves plugin option types based on the full context.
@@ -825,10 +851,54 @@ type ExtractParamNames<T extends string> = T extends `${string}:${infer Param}/$
825
851
  * ```
826
852
  */
827
853
  type HasParams<T extends string> = T extends `${string}:${string}` ? true : false;
854
+ /**
855
+ * Extract paths that have GET methods.
856
+ */
857
+ type ReadPaths<TSchema> = {
858
+ [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never;
859
+ }[keyof TSchema & string];
860
+ /**
861
+ * Extract paths that have write methods (POST, PUT, PATCH, DELETE).
862
+ */
863
+ type WritePaths<TSchema> = {
864
+ [K in keyof TSchema & string]: Extract<keyof TSchema[K], WriteMethod> extends never ? never : K;
865
+ }[keyof TSchema & string];
866
+ /**
867
+ * Check if a schema path has a GET method.
868
+ */
869
+ type HasReadMethod<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? true : false : false : false;
870
+ /**
871
+ * Check if a schema path has any write methods.
872
+ */
873
+ type HasWriteMethod<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? WriteMethod extends never ? false : Extract<keyof TSchema[TKey], WriteMethod> extends never ? false : true : false : false;
874
+ type NormalizePrefix<T extends string> = T extends `/${infer Rest}` ? NormalizePrefix<Rest> : T extends `${infer Rest}/` ? NormalizePrefix<Rest> : T;
875
+ type StripPrefixFromPath<TPath extends string, TPrefix extends string> = TPath extends TPrefix ? "" : TPath extends `${TPrefix}/${infer Rest}` ? Rest : TPath;
876
+ /**
877
+ * Strips a prefix from all path keys in a schema.
878
+ * Works with any schema (Elysia, Hono, or manual).
879
+ *
880
+ * @example
881
+ * ```ts
882
+ * type FullSchema = {
883
+ * "api": { GET: { data: string } };
884
+ * "api/users": { GET: { data: User[] } };
885
+ * "api/posts/:id": { GET: { data: Post } };
886
+ * "health": { GET: { data: { status: string } } };
887
+ * };
888
+ *
889
+ * type ApiSchema = StripPrefix<FullSchema, "api">;
890
+ * // {
891
+ * // "": { GET: { data: string } };
892
+ * // "users": { GET: { data: User[] } };
893
+ * // "posts/:id": { GET: { data: Post } };
894
+ * // "health": { GET: { data: { status: string } } };
895
+ * // }
896
+ * ```
897
+ */
898
+ type StripPrefix<TSchema, TPrefix extends string> = TPrefix extends "" ? TSchema : {
899
+ [K in keyof TSchema as K extends string ? StripPrefixFromPath<K, NormalizePrefix<TPrefix>> : K]: TSchema[K];
900
+ };
828
901
 
829
- type Simplify$1<T> = {
830
- [K in keyof T]: T[K];
831
- } & {};
832
902
  type IsNever<T> = [T] extends [never] ? true : false;
833
903
  type EndpointRequestOptions<TEndpoint, TPath extends string> = (IsNever<ExtractBody$1<TEndpoint>> extends true ? object : {
834
904
  body: ExtractBody$1<TEndpoint>;
@@ -837,14 +907,10 @@ type EndpointRequestOptions<TEndpoint, TPath extends string> = (IsNever<ExtractB
837
907
  }) & (HasParams<TPath> extends true ? {
838
908
  params: Record<ExtractParamNames<TPath>, string | number>;
839
909
  } : object);
840
- type EndpointMethodFn<TEndpoint, TPath extends string> = (options?: Simplify$1<EndpointRequestOptions<TEndpoint, TPath>>) => Promise<SpooshResponse<ExtractData<TEndpoint>, unknown, EndpointRequestOptions<TEndpoint, TPath>>>;
841
- type QueryPathMethods<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify$1<{
910
+ type EndpointMethodFn<TEndpoint, TPath extends string> = (options?: Simplify<EndpointRequestOptions<TEndpoint, TPath>>) => Promise<SpooshResponse<ExtractData<TEndpoint>, unknown, EndpointRequestOptions<TEndpoint, TPath>>>;
911
+ type ReadPathMethods$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify<{
842
912
  GET: EndpointMethodFn<TSchema[TKey]["GET"], TPath>;
843
913
  }> : never : never : never;
844
- type ReadPaths$1<TSchema> = {
845
- [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never;
846
- }[keyof TSchema & string];
847
- type HasGetMethod$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? true : false : false : false;
848
914
  /**
849
915
  * Schema navigation helper for plugins that need type-safe API schema access.
850
916
  *
@@ -858,7 +924,7 @@ type HasGetMethod$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TP
858
924
  * ```ts
859
925
  * // Define your plugin's callback type
860
926
  * type MyCallbackFn<TSchema = unknown> = (
861
- * api: QuerySchemaHelper<TSchema>
927
+ * api: ReadSchemaHelper<TSchema>
862
928
  * ) => unknown;
863
929
  *
864
930
  * // Usage in plugin options
@@ -886,25 +952,17 @@ type HasGetMethod$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TP
886
952
  * });
887
953
  * ```
888
954
  */
889
- type QuerySchemaHelper<TSchema> = <TPath extends ReadPaths$1<TSchema> | (string & {})>(path: TPath) => HasGetMethod$1<TSchema, TPath> extends true ? QueryPathMethods<TSchema, TPath> : never;
890
- type MutationMethod$1 = "POST" | "PUT" | "PATCH" | "DELETE";
891
- type MutationPathMethods<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify$1<{
892
- [M in MutationMethod$1 as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? EndpointMethodFn<TSchema[TKey][M], TPath> : never;
955
+ type ReadSchemaHelper<TSchema> = <TPath extends ReadPaths<TSchema> | (string & {})>(path: TPath) => HasReadMethod<TSchema, TPath> extends true ? ReadPathMethods$1<TSchema, TPath> : never;
956
+ type WritePathMethods$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify<{
957
+ [M in WriteMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? EndpointMethodFn<TSchema[TKey][M], TPath> : never;
893
958
  }> : never : never;
894
- type WritePaths$1<TSchema> = {
895
- [K in keyof TSchema & string]: Extract<keyof TSchema[K], MutationMethod$1> extends never ? never : K;
896
- }[keyof TSchema & string];
897
- type HasMutationMethod$1<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? MutationMethod$1 extends never ? false : Extract<keyof TSchema[TKey], MutationMethod$1> extends never ? false : true : false : false;
898
959
  /**
899
960
  * Schema navigation helper for plugins that need type-safe API schema access for mutations.
900
961
  *
901
- * Similar to QuerySchemaHelper but exposes mutation methods (POST, PUT, PATCH, DELETE).
962
+ * Similar to ReadSchemaHelper but exposes write methods (POST, PUT, PATCH, DELETE).
902
963
  */
903
- type MutationSchemaHelper<TSchema> = <TPath extends WritePaths$1<TSchema> | (string & {})>(path: TPath) => HasMutationMethod$1<TSchema, TPath> extends true ? MutationPathMethods<TSchema, TPath> : never;
964
+ type WriteSchemaHelper<TSchema> = <TPath extends WritePaths<TSchema> | (string & {})>(path: TPath) => HasWriteMethod<TSchema, TPath> extends true ? WritePathMethods$1<TSchema, TPath> : never;
904
965
 
905
- type Simplify<T> = {
906
- [K in keyof T]: T[K];
907
- } & {};
908
966
  /**
909
967
  * Base request options available on all methods.
910
968
  */
@@ -941,9 +999,9 @@ type IsQueryRequired<T> = T extends {
941
999
  * Build the options type for a method.
942
1000
  */
943
1001
  type BodyOption<T> = [ExtractBody<T>] extends [never] ? {} : IsBodyRequired<T> extends true ? {
944
- body: ExtractBody<T>;
1002
+ body: ExtractBody<T> | SpooshBody<ExtractBody<T>>;
945
1003
  } : {
946
- body?: ExtractBody<T>;
1004
+ body?: ExtractBody<T> | SpooshBody<ExtractBody<T>>;
947
1005
  };
948
1006
  type QueryOption<T> = [ExtractQuery<T>] extends [never] ? {} : IsQueryRequired<T> extends true ? {
949
1007
  query: ExtractQuery<T>;
@@ -1014,49 +1072,52 @@ type SpooshClient<TSchema, TDefaultError = unknown> = <TPath extends SchemaPaths
1014
1072
  type ReadPathMethods<TSchema, TPath extends string, TDefaultError> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify<{
1015
1073
  GET: MethodFn<TSchema[TKey]["GET"], TDefaultError, TPath>;
1016
1074
  }> : never : never : never;
1017
- /**
1018
- * Check if a schema path has a GET method.
1019
- */
1020
- type HasGetMethod<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? true : false : false : false;
1021
- /**
1022
- * Extract paths that have GET methods.
1023
- */
1024
- type ReadPaths<TSchema> = {
1025
- [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never;
1026
- }[keyof TSchema & string];
1027
1075
  /**
1028
1076
  * A read-only API client that only exposes GET methods.
1029
1077
  * Used by useRead and injectRead hooks.
1030
1078
  */
1031
- type ReadClient<TSchema, TDefaultError = unknown> = <TPath extends ReadPaths<TSchema> | (string & {})>(path: TPath) => HasGetMethod<TSchema, TPath> extends true ? ReadPathMethods<TSchema, TPath, TDefaultError> : never;
1032
- /**
1033
- * Mutation methods (non-GET).
1034
- */
1035
- type MutationMethod = "POST" | "PUT" | "PATCH" | "DELETE";
1079
+ type ReadClient<TSchema, TDefaultError = unknown> = <TPath extends ReadPaths<TSchema> | (string & {})>(path: TPath) => HasReadMethod<TSchema, TPath> extends true ? ReadPathMethods<TSchema, TPath, TDefaultError> : never;
1036
1080
  /**
1037
1081
  * Write-only client type that only exposes mutation methods (POST, PUT, PATCH, DELETE).
1038
1082
  * Used by useWrite/injectWrite hooks to ensure only mutation operations are selected.
1039
1083
  */
1040
1084
  type WritePathMethods<TSchema, TPath extends string, TDefaultError> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify<{
1041
- [M in MutationMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? MethodFn<TSchema[TKey][M], TDefaultError, TPath> : never;
1085
+ [M in WriteMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? MethodFn<TSchema[TKey][M], TDefaultError, TPath> : never;
1042
1086
  }> : never : never;
1043
- /**
1044
- * Check if a schema path has any mutation methods.
1045
- */
1046
- type HasMutationMethod<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? MutationMethod extends never ? false : Extract<keyof TSchema[TKey], MutationMethod> extends never ? false : true : false : false;
1047
- /**
1048
- * Extract paths that have mutation methods.
1049
- */
1050
- type WritePaths<TSchema> = {
1051
- [K in keyof TSchema & string]: Extract<keyof TSchema[K], MutationMethod> extends never ? never : K;
1052
- }[keyof TSchema & string];
1053
1087
  /**
1054
1088
  * A write-only API client that only exposes mutation methods (POST, PUT, PATCH, DELETE).
1055
1089
  * Used by useWrite and injectWrite hooks.
1056
1090
  */
1057
- type WriteClient<TSchema, TDefaultError = unknown> = <TPath extends WritePaths<TSchema> | (string & {})>(path: TPath) => HasMutationMethod<TSchema, TPath> extends true ? WritePathMethods<TSchema, TPath, TDefaultError> : never;
1091
+ type WriteClient<TSchema, TDefaultError = unknown> = <TPath extends WritePaths<TSchema> | (string & {})>(path: TPath) => HasWriteMethod<TSchema, TPath> extends true ? WritePathMethods<TSchema, TPath, TDefaultError> : never;
1058
1092
 
1059
1093
  type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
1094
+ /**
1095
+ * Configuration options for Spoosh runtime behavior.
1096
+ */
1097
+ type SpooshConfigOptions = {
1098
+ /**
1099
+ * Prefix to strip from tag generation.
1100
+ *
1101
+ * URL prefix stripping always auto-detects from baseUrl.
1102
+ * This option only affects tag generation for cache invalidation.
1103
+ *
1104
+ * - `undefined`: Auto-detect from baseUrl (default)
1105
+ * - `string`: Explicit prefix to strip from tags
1106
+ *
1107
+ * @example
1108
+ * ```ts
1109
+ * // Default: auto-detect from baseUrl
1110
+ * // baseUrl="/api", schema="api/posts" → tags: ["posts"]
1111
+ * new Spoosh<Schema>('https://localhost:3000/api')
1112
+ *
1113
+ * // Explicit prefix (when baseUrl doesn't have it)
1114
+ * // baseUrl="/", schema="api/v1/posts" → tags: ["posts"]
1115
+ * new Spoosh<Schema>('http://localhost:3000')
1116
+ * .configure({ stripTagPrefix: "api/v1" })
1117
+ * ```
1118
+ */
1119
+ stripTagPrefix?: string;
1120
+ };
1060
1121
  interface SpooshConfig<TPlugins extends PluginArray = PluginArray> {
1061
1122
  baseUrl: string;
1062
1123
  defaultOptions?: SpooshOptions;
@@ -1070,6 +1131,8 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
1070
1131
  config: {
1071
1132
  baseUrl: string;
1072
1133
  defaultOptions: SpooshOptions;
1134
+ /** Resolved prefix to strip from tags (used for cache invalidation matching) */
1135
+ stripTagPrefix?: string;
1073
1136
  };
1074
1137
  _types: {
1075
1138
  schema: TSchema;
@@ -1121,12 +1184,14 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1121
1184
  private baseUrl;
1122
1185
  private defaultOptions;
1123
1186
  private _plugins;
1187
+ private _config;
1124
1188
  /**
1125
1189
  * Creates a new Spoosh instance.
1126
1190
  *
1127
1191
  * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
1128
1192
  * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
1129
1193
  * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
1194
+ * @param configOptions - Internal parameter used by the `.config()` method. Do not pass directly.
1130
1195
  *
1131
1196
  * @example
1132
1197
  * ```ts
@@ -1139,7 +1204,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1139
1204
  * });
1140
1205
  * ```
1141
1206
  */
1142
- constructor(baseUrl: string, defaultOptions?: SpooshOptions, plugins?: TPlugins);
1207
+ constructor(baseUrl: string, defaultOptions?: SpooshOptions, plugins?: TPlugins, configOptions?: SpooshConfigOptions);
1143
1208
  /**
1144
1209
  * Adds plugins to the Spoosh instance.
1145
1210
  *
@@ -1175,6 +1240,34 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1175
1240
  * ```
1176
1241
  */
1177
1242
  use<const TNewPlugins extends PluginArray>(plugins: TNewPlugins): Spoosh<TSchema, TError, TNewPlugins>;
1243
+ /**
1244
+ * Configures runtime options for the Spoosh instance.
1245
+ *
1246
+ * Returns a **new** Spoosh instance with the updated configuration (immutable pattern).
1247
+ * Configuration is preserved across `.use()` calls.
1248
+ *
1249
+ * URL prefix stripping always auto-detects from baseUrl.
1250
+ * Tag prefix stripping defaults to URL prefix but can be overridden.
1251
+ *
1252
+ * @param options - Configuration options
1253
+ * @returns A new Spoosh instance with the specified configuration
1254
+ *
1255
+ * @example Default behavior (auto-detect from baseUrl for both URL and tags)
1256
+ * ```ts
1257
+ * // baseUrl="/api", schema="api/posts"
1258
+ * // URL: /api/posts, Tags: ["posts"]
1259
+ * const client = new Spoosh<Schema, Error>('https://localhost:3000/api');
1260
+ * ```
1261
+ *
1262
+ * @example Override tag prefix (when baseUrl doesn't have the prefix you want to strip from tags)
1263
+ * ```ts
1264
+ * // baseUrl="/", schema="api/v1/posts"
1265
+ * // URL: /api/v1/posts, Tags: ["posts"] (strips "api/v1" from tags only)
1266
+ * const client = new Spoosh<Schema, Error>('http://localhost:3000')
1267
+ * .configure({ stripTagPrefix: "api/v1" });
1268
+ * ```
1269
+ */
1270
+ configure(options: SpooshConfigOptions): Spoosh<TSchema, TError, TPlugins>;
1178
1271
  /**
1179
1272
  * Cached instance of the underlying SpooshInstance.
1180
1273
  * Created lazily on first property access.
@@ -1274,6 +1367,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1274
1367
  get config(): {
1275
1368
  baseUrl: string;
1276
1369
  defaultOptions: SpooshOptions;
1370
+ stripTagPrefix?: string;
1277
1371
  };
1278
1372
  /**
1279
1373
  * Type information carrier for generic type inference.
@@ -1292,6 +1386,16 @@ type SpooshClientConfig = {
1292
1386
  baseUrl: string;
1293
1387
  defaultOptions?: SpooshOptions;
1294
1388
  middlewares?: SpooshMiddleware[];
1389
+ /**
1390
+ * Prefix to strip from tag generation.
1391
+ *
1392
+ * URL prefix stripping always auto-detects from baseUrl.
1393
+ * This option only affects tag generation for cache invalidation.
1394
+ *
1395
+ * - `undefined`: Auto-detect from baseUrl (default, same as URL prefix)
1396
+ * - `string`: Explicit prefix to strip from tags
1397
+ */
1398
+ stripTagPrefix?: string;
1295
1399
  };
1296
1400
  /**
1297
1401
  * Creates a lightweight type-safe API client for vanilla JavaScript/TypeScript usage.
@@ -1334,6 +1438,8 @@ declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshCl
1334
1438
 
1335
1439
  declare function buildUrl(baseUrl: string, path: string[], query?: Record<string, string | number | boolean | undefined>): string;
1336
1440
 
1441
+ declare function __DEV__(): boolean;
1442
+
1337
1443
  /**
1338
1444
  * Generate cache tags from URL path segments.
1339
1445
  * e.g., ['posts', '1'] → ['posts', 'posts/1']
@@ -1383,11 +1489,61 @@ type TagOptions = {
1383
1489
  declare function resolveTags(options: TagOptions | undefined, resolvedPath: string[]): string[];
1384
1490
  declare function resolvePath(path: string[], params: Record<string, string | number> | undefined): string[];
1385
1491
 
1492
+ /**
1493
+ * Extracts the path prefix from a base URL.
1494
+ *
1495
+ * @param baseUrl - The base URL (absolute or relative)
1496
+ * @returns The path portion without leading/trailing slashes
1497
+ *
1498
+ * @example
1499
+ * ```ts
1500
+ * extractPrefixFromBaseUrl("https://localhost:3000/api"); // "api"
1501
+ * extractPrefixFromBaseUrl("/api/v1"); // "api/v1"
1502
+ * extractPrefixFromBaseUrl("api"); // "api"
1503
+ * ```
1504
+ */
1505
+ declare function extractPrefixFromBaseUrl(baseUrl: string): string;
1506
+ /**
1507
+ * Strips a prefix from path segments if the path starts with that prefix.
1508
+ *
1509
+ * @param pathSegments - Array of path segments
1510
+ * @param prefix - Prefix to strip (e.g., "api" or "api/v1")
1511
+ * @returns Path segments with prefix removed
1512
+ *
1513
+ * @example
1514
+ * ```ts
1515
+ * stripPrefixFromPath(["api", "posts"], "api"); // ["posts"]
1516
+ * stripPrefixFromPath(["api", "v1", "users"], "api/v1"); // ["users"]
1517
+ * stripPrefixFromPath(["posts"], "api"); // ["posts"] (no match, unchanged)
1518
+ * ```
1519
+ */
1520
+ declare function stripPrefixFromPath(pathSegments: string[], prefix: string): string[];
1521
+ /**
1522
+ * Resolves the strip prefix value based on configuration.
1523
+ *
1524
+ * @param stripPathPrefix - Configuration value (boolean, string, or undefined)
1525
+ * @param baseUrl - The base URL to extract prefix from when true
1526
+ * @returns The resolved prefix string to strip
1527
+ *
1528
+ * @example
1529
+ * ```ts
1530
+ * resolveStripPrefix(true, "https://localhost:3000/api"); // "api"
1531
+ * resolveStripPrefix("api/v1", "https://localhost:3000/api"); // "api/v1"
1532
+ * resolveStripPrefix(false, "https://localhost:3000/api"); // ""
1533
+ * resolveStripPrefix(undefined, "https://localhost:3000/api"); // ""
1534
+ * ```
1535
+ */
1536
+ declare function resolveStripPrefix(stripPathPrefix: boolean | string | undefined, baseUrl: string): string;
1537
+
1386
1538
  type ProxyHandlerConfig<TOptions = SpooshOptions> = {
1387
1539
  baseUrl: string;
1388
1540
  defaultOptions: TOptions;
1389
1541
  fetchExecutor?: FetchExecutor<TOptions, AnyRequestOptions>;
1390
1542
  nextTags?: boolean;
1543
+ /** Prefix to strip from URL path (auto-detected from baseUrl, always applied) */
1544
+ urlPrefix?: string;
1545
+ /** Prefix to strip from tag generation (defaults to urlPrefix) */
1546
+ tagPrefix?: string;
1391
1547
  };
1392
1548
  /**
1393
1549
  * Creates an API client proxy that uses path strings instead of chained property access.
@@ -1514,7 +1670,7 @@ declare function extractPathFromSelector(fn: unknown): string;
1514
1670
  */
1515
1671
  declare function extractMethodFromSelector(fn: unknown): string | undefined;
1516
1672
 
1517
- declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean): Promise<SpooshResponse<TData, TError>>;
1673
+ declare function executeFetch<TData, TError>(baseUrl: string, path: string[], method: HttpMethod, defaultOptions: SpooshOptions & SpooshOptionsExtra, requestOptions?: AnyRequestOptions, nextTags?: boolean, tagPath?: string[]): Promise<SpooshResponse<TData, TError>>;
1518
1674
 
1519
1675
  declare function createMiddleware<TData = unknown, TError = unknown>(name: string, phase: MiddlewarePhase, handler: SpooshMiddleware<TData, TError>["handler"]): SpooshMiddleware<TData, TError>;
1520
1676
  declare function applyMiddlewares<TData = unknown, TError = unknown>(context: MiddlewareContext<TData, TError>, middlewares: SpooshMiddleware<TData, TError>[], phase: MiddlewarePhase): Promise<MiddlewareContext<TData, TError>>;
@@ -1608,4 +1764,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1608
1764
  };
1609
1765
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1610
1766
 
1611
- 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 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 MutationSchemaHelper, 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 QuerySchemaHelper, type ReadClient, type RefetchEvent, type RequestOptions$1 as RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type RetryConfig, type RouteToPath, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, Spoosh, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, type SpooshPlugin, type SpooshResponse, type SpooshSchema, type StateManager, type TagMode, type TagOptions, type WriteClient, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, generateTags, getContentType, isJsonBody, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveTags, setHeaders, sortObjectKeys };
1767
+ 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 PluginExecutorOptions, 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 RouteToPath, type SchemaPaths, type SelectedEndpoint, type SelectorFunction, type SelectorResult, type Simplify, Spoosh, type SpooshBody, type SpooshClient, type SpooshConfig, type SpooshConfigOptions, 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, extractPrefixFromBaseUrl, form, generateTags, getContentType, isJsonBody, isSpooshBody, json, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveRequestBody, resolveStripPrefix, resolveTags, setHeaders, sortObjectKeys, stripPrefixFromPath, urlencoded };