@spoosh/core 0.4.3 → 0.6.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/dist/index.d.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
2
- type SchemaMethod = "$get" | "$post" | "$put" | "$patch" | "$delete";
3
2
 
4
3
  type MiddlewarePhase = "before" | "after";
5
4
  type MiddlewareContext<TData = unknown, TError = unknown> = {
@@ -71,41 +70,27 @@ type HeadersInitOrGetter = HeadersInit | (() => HeadersInit | Promise<HeadersIni
71
70
  type SpooshOptions = Omit<RequestInit, "method" | "body" | "headers"> & {
72
71
  headers?: HeadersInitOrGetter;
73
72
  };
74
- type BaseRequestOptions = {
73
+ type BaseRequestOptions$1 = {
75
74
  headers?: HeadersInitOrGetter;
76
75
  cache?: RequestCache;
77
76
  signal?: AbortSignal;
78
77
  };
79
- type BodyOption<TBody> = [TBody] extends [never] ? object : undefined extends TBody ? {
78
+ type BodyOption$1<TBody> = [TBody] extends [never] ? object : undefined extends TBody ? {
80
79
  body?: Exclude<TBody, undefined>;
81
80
  } : {
82
81
  body: TBody;
83
82
  };
84
- type QueryOption<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
83
+ type QueryOption$1<TQuery> = [TQuery] extends [never] ? object : undefined extends TQuery ? {
85
84
  query?: Exclude<TQuery, undefined>;
86
85
  } : {
87
86
  query: TQuery;
88
87
  };
89
- type FormDataOption<TFormData> = [TFormData] extends [never] ? object : undefined extends TFormData ? {
90
- formData?: Exclude<TFormData, undefined>;
91
- } : {
92
- formData: TFormData;
93
- };
94
- type UrlEncodedOption<TUrlEncoded> = [TUrlEncoded] extends [never] ? object : undefined extends TUrlEncoded ? {
95
- urlEncoded?: Exclude<TUrlEncoded, undefined>;
96
- } : {
97
- urlEncoded: TUrlEncoded;
98
- };
99
- type RequestOptions<TBody = never, TQuery = never, TFormData = never, TUrlEncoded = never> = BaseRequestOptions & BodyOption<TBody> & QueryOption<TQuery> & FormDataOption<TFormData> & UrlEncodedOption<TUrlEncoded>;
100
- type AnyRequestOptions = BaseRequestOptions & {
88
+ type RequestOptions$1<TBody = never, TQuery = never> = BaseRequestOptions$1 & BodyOption$1<TBody> & QueryOption$1<TQuery>;
89
+ type AnyRequestOptions = BaseRequestOptions$1 & {
101
90
  body?: unknown;
102
91
  query?: Record<string, string | number | boolean | undefined>;
103
- formData?: Record<string, unknown>;
104
- urlEncoded?: Record<string, unknown>;
105
92
  params?: Record<string, string | number>;
106
93
  signal?: AbortSignal;
107
- /** @internal Path transformer function. Set by plugins like path-case. */
108
- _pathTransformer?: (path: string[]) => string[];
109
94
  } & Partial<RetryConfig>;
110
95
  type DynamicParamsOption = {
111
96
  params?: Record<string, string | number>;
@@ -114,13 +99,13 @@ type CoreRequestOptionsBase = {
114
99
  __hasDynamicParams?: DynamicParamsOption;
115
100
  };
116
101
  type MethodOptionsMap<TQueryOptions = object, TMutationOptions = object> = {
117
- $get: TQueryOptions;
118
- $post: TMutationOptions;
119
- $put: TMutationOptions;
120
- $patch: TMutationOptions;
121
- $delete: TMutationOptions;
102
+ GET: TQueryOptions;
103
+ POST: TMutationOptions;
104
+ PUT: TMutationOptions;
105
+ PATCH: TMutationOptions;
106
+ DELETE: TMutationOptions;
122
107
  };
123
- type ExtractMethodOptions<TOptionsMap, TMethod extends SchemaMethod> = TOptionsMap extends MethodOptionsMap<infer TQuery, infer TMutation> ? TMethod extends "$get" ? TQuery : TMutation : TOptionsMap;
108
+ type ExtractMethodOptions<TOptionsMap, TMethod extends HttpMethod> = TOptionsMap extends MethodOptionsMap<infer TQuery, infer TMutation> ? TMethod extends "GET" ? TQuery : TMutation : TOptionsMap;
124
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>>;
125
110
  type TypedParamsOption<TParamNames extends string> = [TParamNames] extends [
126
111
  never
@@ -205,7 +190,7 @@ type StateManager = {
205
190
  getCacheByTags: <TData>(tags: string[]) => CacheEntry<TData> | undefined;
206
191
  getCacheEntriesByTags: <TData, TError>(tags: string[]) => CacheEntryWithKey<TData, TError>[];
207
192
  getCacheEntriesBySelfTag: <TData, TError>(selfTag: string) => CacheEntryWithKey<TData, TError>[];
208
- setPluginResult: (key: string, data: Record<string, unknown>) => void;
193
+ setMeta: (key: string, data: Record<string, unknown>) => void;
209
194
  /** Mark all cache entries with matching tags as stale */
210
195
  markStale: (tags: string[]) => void;
211
196
  /** Get all cache entries */
@@ -231,7 +216,7 @@ type CacheEntry<TData = unknown, TError = unknown> = {
231
216
  state: OperationState<TData, TError>;
232
217
  tags: string[];
233
218
  /** Plugin-contributed result data (e.g., isOptimistic, isStale). Merged into hook result. */
234
- pluginResult: Map<string, unknown>;
219
+ meta: Map<string, unknown>;
235
220
  /** The original path-derived tag (e.g., "posts/1/comments"). Used for exact matching in cache */
236
221
  selfTag?: string;
237
222
  previousData?: TData;
@@ -448,7 +433,7 @@ type DataAwareTransform<TData = unknown, TError = unknown> = (data: TData | unde
448
433
  * Context object containing all type information available for resolution.
449
434
  * 3rd party plugins can access any combination of these types.
450
435
  */
451
- type ResolverContext<TSchema = unknown, TData = unknown, TError = unknown, TQuery = unknown, TBody = unknown, TParams = unknown, TFormData = unknown, TUrlEncoded = unknown> = {
436
+ type ResolverContext<TSchema = unknown, TData = unknown, TError = unknown, TQuery = unknown, TBody = unknown, TParams = unknown> = {
452
437
  schema: TSchema;
453
438
  data: TData;
454
439
  error: TError;
@@ -456,8 +441,6 @@ type ResolverContext<TSchema = unknown, TData = unknown, TError = unknown, TQuer
456
441
  query: TQuery;
457
442
  body: TBody;
458
443
  params: TParams;
459
- formData: TFormData;
460
- urlEncoded: TUrlEncoded;
461
444
  };
462
445
  };
463
446
  /**
@@ -704,175 +687,166 @@ type PluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]> = {
704
687
  };
705
688
  declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]>(plugins: [...TPlugins]): PluginRegistry<TPlugins>;
706
689
 
707
- declare const EndpointBrand: unique symbol;
708
690
  /**
709
- * Define an API endpoint with its data, request options, and error types.
691
+ * An API schema where routes are defined as string keys with path patterns.
692
+ * Define data, body, query, and error directly on each method.
710
693
  *
711
694
  * @example
712
- * ```typescript
713
- * // Simple GET endpoint
714
- * $get: Endpoint<{ data: User[] }>
715
- *
716
- * // GET with query parameters
717
- * $get: Endpoint<{ data: User[]; query: { page: number; limit: number } }>
718
- *
719
- * // POST with JSON body
720
- * $post: Endpoint<{ data: User; body: CreateUserBody }>
721
- *
722
- * // POST with form data (file upload)
723
- * $post: Endpoint<{ data: UploadResult; formData: { file: File; name: string } }>
724
- *
725
- * // POST with URL-encoded body (Stripe-style)
726
- * $post: Endpoint<{ data: Payment; urlEncoded: { amount: number; currency: string } }>
727
- *
728
- * // With error type
729
- * $get: Endpoint<{ data: User; error: ApiError }>
730
- *
731
- * // Complex: query + body + error
732
- * $post: Endpoint<{ data: User; body: CreateUserBody; query: { notify?: boolean }; error: ApiError }>
695
+ * ```ts
696
+ * type ApiSchema = {
697
+ * "posts": {
698
+ * GET: { data: Post[] };
699
+ * POST: { data: Post; body: CreatePostBody };
700
+ * };
701
+ * "posts/:id": {
702
+ * GET: { data: Post };
703
+ * PUT: { data: Post; body: UpdatePostBody };
704
+ * DELETE: { data: void };
705
+ * };
706
+ * "posts/:id/comments": {
707
+ * GET: { data: Comment[]; query: { page?: number } };
708
+ * };
709
+ * };
733
710
  * ```
734
711
  */
735
- type Endpoint<T extends {
736
- data: unknown;
737
- body?: unknown;
738
- query?: unknown;
739
- formData?: unknown;
740
- urlEncoded?: unknown;
741
- error?: unknown;
742
- }> = {
743
- [EndpointBrand]: true;
744
- } & T;
745
- type ExtractProperty<T, K extends string, TDefault = never> = K extends keyof T ? T[K] : TDefault;
746
- type NormalizeEndpoint<T, TDefaultError> = T extends {
747
- [EndpointBrand]: true;
748
- } ? {
749
- data: ExtractProperty<T, "data">;
750
- error: ExtractProperty<T, "error", TDefaultError>;
751
- body: ExtractProperty<T, "body">;
752
- query: ExtractProperty<T, "query">;
753
- formData: ExtractProperty<T, "formData">;
754
- urlEncoded: ExtractProperty<T, "urlEncoded">;
755
- } : {
756
- data: T;
757
- error: TDefaultError;
758
- body: never;
759
- query: never;
760
- formData: never;
761
- urlEncoded: never;
712
+ type ApiSchema = {
713
+ [path: string]: {
714
+ [method in HttpMethod]?: {
715
+ data: unknown;
716
+ body?: unknown;
717
+ query?: unknown;
718
+ error?: unknown;
719
+ };
720
+ };
762
721
  };
763
- type ExtractMethodDef<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = TSchema extends {
764
- [K in TMethod]: infer M;
765
- } ? NormalizeEndpoint<M, TDefaultError> : never;
766
- type ExtractData<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
722
+ /**
723
+ * Extract data type from an endpoint.
724
+ */
725
+ type ExtractData<T> = T extends {
767
726
  data: infer D;
768
727
  } ? D : never;
769
- type ExtractError<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
770
- error: infer E;
771
- } ? E : TDefaultError;
772
- type ExtractBody<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
728
+ /**
729
+ * Extract body type from an endpoint.
730
+ */
731
+ type ExtractBody$1<T> = T extends {
773
732
  body: infer B;
774
733
  } ? B : never;
775
- type ExtractQuery<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
734
+ /**
735
+ * Extract query type from an endpoint.
736
+ */
737
+ type ExtractQuery$1<T> = T extends {
776
738
  query: infer Q;
777
739
  } ? Q : never;
778
- type ExtractFormData<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
779
- formData: infer F;
780
- } ? F : never;
781
- type ExtractUrlEncoded<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = ExtractMethodDef<TSchema, TMethod, TDefaultError> extends {
782
- urlEncoded: infer U;
783
- } ? U : never;
784
- type HasMethod<TSchema, TMethod extends SchemaMethod> = TSchema extends {
785
- [K in TMethod]: unknown;
786
- } ? true : false;
787
- type HasRequiredOptions<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown> = [ExtractBody<TSchema, TMethod, TDefaultError>] extends [never] ? [ExtractFormData<TSchema, TMethod, TDefaultError>] extends [never] ? [ExtractUrlEncoded<TSchema, TMethod, TDefaultError>] extends [never] ? false : true : true : true;
788
-
789
- type ExtractParamName$1<S> = S extends `:${infer P}` ? P : never;
790
- type MethodRequestOptions<TSchema, TMethod extends SchemaMethod, TDefaultError, TOptionsMap, TParamNames extends string, TRequired extends boolean> = TRequired extends true ? RequestOptions<ExtractBody<TSchema, TMethod, TDefaultError>, ExtractQuery<TSchema, TMethod, TDefaultError>, ExtractFormData<TSchema, TMethod, TDefaultError>, ExtractUrlEncoded<TSchema, TMethod, TDefaultError>> & ComputeRequestOptions<ExtractMethodOptions<TOptionsMap, TMethod>, TParamNames> : RequestOptions<never, ExtractQuery<TSchema, TMethod, TDefaultError>, never, never> & ComputeRequestOptions<ExtractMethodOptions<TOptionsMap, TMethod>, TParamNames>;
791
- type MethodFn<TSchema, TMethod extends SchemaMethod, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = HasMethod<TSchema, TMethod> extends true ? HasRequiredOptions<TSchema, TMethod, TDefaultError> extends true ? (options: MethodRequestOptions<TSchema, TMethod, TDefaultError, TOptionsMap, TParamNames, true>) => Promise<SpooshResponse<ExtractData<TSchema, TMethod, TDefaultError>, ExtractError<TSchema, TMethod, TDefaultError>, MethodRequestOptions<TSchema, TMethod, TDefaultError, TOptionsMap, TParamNames, true>, ExtractQuery<TSchema, TMethod, TDefaultError>, ExtractBody<TSchema, TMethod, TDefaultError>, ExtractFormData<TSchema, TMethod, TDefaultError>, ExtractUrlEncoded<TSchema, TMethod, TDefaultError>, TParamNames>> : (options?: MethodRequestOptions<TSchema, TMethod, TDefaultError, TOptionsMap, TParamNames, false>) => Promise<SpooshResponse<ExtractData<TSchema, TMethod, TDefaultError>, ExtractError<TSchema, TMethod, TDefaultError>, MethodRequestOptions<TSchema, TMethod, TDefaultError, TOptionsMap, TParamNames, false>, ExtractQuery<TSchema, TMethod, TDefaultError>, ExtractBody<TSchema, TMethod, TDefaultError>, ExtractFormData<TSchema, TMethod, TDefaultError>, ExtractUrlEncoded<TSchema, TMethod, TDefaultError>, TParamNames>> : never;
792
- type IsSpecialKey<K> = K extends SchemaMethod | "_" ? true : false;
793
- type StaticPathKeys<TSchema> = {
794
- [K in keyof TSchema as IsSpecialKey<K> extends true ? never : K extends string ? K : never]: TSchema[K];
795
- };
796
- type ExtractDynamicSchema<TSchema> = TSchema extends {
797
- _: infer D;
798
- } ? D : never;
799
- type HttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
800
- [K in SchemaMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
801
- };
802
- type DynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = ExtractDynamicSchema<TSchema> extends never ? object : {
803
- /**
804
- * Dynamic path segment with typed param name.
805
- * Use `:paramName` format to get typed params in the response.
806
- *
807
- * @example
808
- * ```ts
809
- * // With number
810
- * await api.users(123).$get()
811
- *
812
- * // With typed params
813
- * const { data, params } = await api.users(':userId').$get({ params: { userId: 123 } })
814
- * ```
815
- */
816
- <TKey extends string | number>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
- };
818
- type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = HttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & DynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
819
- [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
820
- };
821
-
822
- type QueryMethod = "$get";
823
- type MutationMethod = "$post" | "$put" | "$patch" | "$delete";
824
- type HasQueryMethods<TSchema> = TSchema extends object ? "$get" extends keyof TSchema ? true : TSchema extends {
825
- _: infer D;
826
- } ? HasQueryMethods<D> : {
827
- [K in keyof TSchema]: K extends SchemaMethod | "_" ? never : HasQueryMethods<TSchema[K]>;
828
- }[keyof TSchema] extends never ? false : true extends {
829
- [K in keyof TSchema]: K extends SchemaMethod | "_" ? never : HasQueryMethods<TSchema[K]>;
830
- }[keyof TSchema] ? true : false : false;
831
- type HasMutationMethods<TSchema> = TSchema extends object ? MutationMethod extends never ? false : Extract<keyof TSchema, MutationMethod> extends never ? TSchema extends {
832
- _: infer D;
833
- } ? HasMutationMethods<D> : {
834
- [K in keyof TSchema]: K extends SchemaMethod | "_" ? never : HasMutationMethods<TSchema[K]>;
835
- }[keyof TSchema] extends never ? false : true extends {
836
- [K in keyof TSchema]: K extends SchemaMethod | "_" ? never : HasMutationMethods<TSchema[K]>;
837
- }[keyof TSchema] ? true : false : true : false;
838
- type QueryHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
839
- [K in QueryMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
840
- };
841
- type ExtractParamName<S> = S extends `:${infer P}` ? P : never;
842
- type QueryDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
843
- _: infer D;
844
- } ? HasQueryMethods<D> extends true ? {
845
- <TKey extends string | number>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
846
- } : object : object;
847
- type QueryOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = QueryHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & QueryDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
848
- [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: QueryOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
849
- };
850
- type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
851
- [K in MutationMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
852
- };
853
- type MutationDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = TSchema extends {
854
- _: infer D;
855
- } ? HasMutationMethods<D> extends true ? {
856
- <TKey extends string | number>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
857
- } : object : object;
858
- type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
859
- [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
860
- };
740
+ /**
741
+ * Extract error type from an endpoint.
742
+ */
743
+ type ExtractError<T, TDefault = unknown> = T extends {
744
+ error: infer E;
745
+ } ? E : TDefault;
746
+ /**
747
+ * Helper type to define a type-safe API schema.
748
+ * Use this to get type checking on your schema definition.
749
+ *
750
+ * @example
751
+ * ```ts
752
+ * type ApiSchema = SpooshSchema<{
753
+ * "posts": {
754
+ * GET: { data: Post[] };
755
+ * POST: { data: Post; body: CreatePostBody };
756
+ * };
757
+ * "posts/:id": {
758
+ * GET: { data: Post };
759
+ * PUT: { data: Post; body: UpdatePostBody };
760
+ * DELETE: { data: void };
761
+ * };
762
+ * }>;
763
+ *
764
+ * const api = createClient<ApiSchema>({ baseUrl: "/api" });
765
+ * ```
766
+ */
767
+ type SpooshSchema<T extends {
768
+ [path: string]: {
769
+ [M in HttpMethod]?: {
770
+ data: unknown;
771
+ body?: unknown;
772
+ query?: unknown;
773
+ error?: unknown;
774
+ };
775
+ };
776
+ }> = T;
777
+ /**
778
+ * Convert a route pattern like "posts/:id" to a path matcher pattern like `posts/${string}`.
779
+ * This enables TypeScript to match actual paths like "posts/123" to their schema definitions.
780
+ *
781
+ * @example
782
+ * ```ts
783
+ * type A = RouteToPath<"posts/:id">; // "posts/${string}"
784
+ * type B = RouteToPath<"posts/:id/comments/:cid">; // "posts/${string}/comments/${string}"
785
+ * type C = RouteToPath<"posts">; // "posts"
786
+ * ```
787
+ */
788
+ type RouteToPath<T extends string> = T extends `${infer Start}/:${string}/${infer Rest}` ? `${Start}/${string}/${RouteToPath<Rest>}` : T extends `${infer Start}/:${string}` ? `${Start}/${string}` : T;
789
+ /**
790
+ * Find which schema key matches a given path.
791
+ * First checks for exact match, then checks pattern matches.
792
+ *
793
+ * @example
794
+ * ```ts
795
+ * type Schema = { "posts": {...}; "posts/:id": {...} };
796
+ * type A = FindMatchingKey<Schema, "posts">; // "posts" (exact match)
797
+ * type B = FindMatchingKey<Schema, "posts/123">; // "posts/:id" (pattern match)
798
+ * ```
799
+ */
800
+ type FindMatchingKey<TSchema, TPath extends string> = TPath extends keyof TSchema ? TPath : {
801
+ [K in keyof TSchema]: TPath extends RouteToPath<K & string> ? K : never;
802
+ }[keyof TSchema];
803
+ /**
804
+ * Extract parameter names from a route pattern.
805
+ *
806
+ * @example
807
+ * ```ts
808
+ * type A = ExtractParamNames<"posts/:id">; // "id"
809
+ * type B = ExtractParamNames<"posts/:id/comments/:cid">; // "id" | "cid"
810
+ * type C = ExtractParamNames<"posts">; // never
811
+ * ```
812
+ */
813
+ type ExtractParamNames<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? Param | ExtractParamNames<Rest> : T extends `${string}:${infer Param}` ? Param : never;
814
+ /**
815
+ * Check if a route pattern has any parameters.
816
+ *
817
+ * @example
818
+ * ```ts
819
+ * type A = HasParams<"posts/:id">; // true
820
+ * type B = HasParams<"posts">; // false
821
+ * ```
822
+ */
823
+ type HasParams<T extends string> = T extends `${string}:${string}` ? true : false;
861
824
 
862
- type ExtractEndpointData<T> = T extends {
863
- data: infer D;
864
- } ? D : T extends void ? void : T;
865
- type ExtractEndpointRequestOptions<T> = {
866
- [K in Extract<keyof T, "query" | "body" | "params">]?: T[K];
867
- };
868
- type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promise<SpooshResponse<ExtractEndpointData<T>, unknown, ExtractEndpointRequestOptions<T>>>;
825
+ type Simplify$1<T> = {
826
+ [K in keyof T]: T[K];
827
+ } & {};
828
+ type IsNever<T> = [T] extends [never] ? true : false;
829
+ type EndpointRequestOptions<TEndpoint, TPath extends string> = (IsNever<ExtractBody$1<TEndpoint>> extends true ? object : {
830
+ body: ExtractBody$1<TEndpoint>;
831
+ }) & (IsNever<ExtractQuery$1<TEndpoint>> extends true ? object : {
832
+ query: ExtractQuery$1<TEndpoint>;
833
+ }) & (HasParams<TPath> extends true ? {
834
+ params: Record<ExtractParamNames<TPath>, string | number>;
835
+ } : object);
836
+ type EndpointMethodFn<TEndpoint, TPath extends string> = (options?: Simplify$1<EndpointRequestOptions<TEndpoint, TPath>>) => Promise<SpooshResponse<ExtractData<TEndpoint>, unknown, EndpointRequestOptions<TEndpoint, TPath>>>;
837
+ type QueryPathMethods<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify$1<{
838
+ GET: EndpointMethodFn<TSchema[TKey]["GET"], TPath>;
839
+ }> : never : never : never;
840
+ type ReadPaths$1<TSchema> = {
841
+ [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never;
842
+ }[keyof TSchema & string];
843
+ 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;
869
844
  /**
870
845
  * Schema navigation helper for plugins that need type-safe API schema access.
871
846
  *
872
- * This type transforms the API schema into a navigable structure where:
873
- * - Static path segments become nested properties
874
- * - Dynamic segments (`_`) become index signatures
875
- * - `$get` endpoints become callable method types
847
+ * This type transforms the API schema into a callable function where:
848
+ * - Path strings are used to select endpoints
849
+ * - Only GET methods are exposed (for query operations)
876
850
  *
877
851
  * Use this in plugin option types that need to reference API endpoints:
878
852
  *
@@ -901,22 +875,182 @@ type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promi
901
875
  * // User's code - paths are type-checked!
902
876
  * trigger({
903
877
  * myCallback: (api) => [
904
- * api.posts.$get, // ✓ Valid
905
- * api.users(1).$get, // ✓ Dynamic segment
906
- * api.nonexistent.$get, // ✗ Type error
878
+ * api("posts").GET, // ✓ Valid
879
+ * api("posts/:id").GET, // ✓ Dynamic segment
880
+ * api("nonexistent").GET, // ✗ Type error
907
881
  * ],
908
882
  * });
909
883
  * ```
910
884
  */
911
- type QuerySchemaHelper<TSchema> = {
912
- [K in keyof TSchema as K extends SchemaMethod | "_" ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: K extends keyof TSchema ? QuerySchemaHelper<TSchema[K]> : never;
913
- } & {
914
- [K in "$get" as K extends keyof TSchema ? K : never]: K extends keyof TSchema ? EndpointToMethod<TSchema[K]> : never;
915
- } & (TSchema extends {
916
- _: infer D;
917
- } ? HasQueryMethods<D> extends true ? {
918
- <TKey extends string | number>(key: TKey): QuerySchemaHelper<D>;
919
- } : object : object);
885
+ type QuerySchemaHelper<TSchema> = <TPath extends ReadPaths$1<TSchema> | (string & {})>(path: TPath) => HasGetMethod$1<TSchema, TPath> extends true ? QueryPathMethods<TSchema, TPath> : never;
886
+ type MutationMethod$1 = "POST" | "PUT" | "PATCH" | "DELETE";
887
+ type MutationPathMethods<TSchema, TPath extends string> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify$1<{
888
+ [M in MutationMethod$1 as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? EndpointMethodFn<TSchema[TKey][M], TPath> : never;
889
+ }> : never : never;
890
+ type WritePaths$1<TSchema> = {
891
+ [K in keyof TSchema & string]: Extract<keyof TSchema[K], MutationMethod$1> extends never ? never : K;
892
+ }[keyof TSchema & string];
893
+ 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;
894
+ /**
895
+ * Schema navigation helper for plugins that need type-safe API schema access for mutations.
896
+ *
897
+ * Similar to QuerySchemaHelper but exposes mutation methods (POST, PUT, PATCH, DELETE).
898
+ */
899
+ type MutationSchemaHelper<TSchema> = <TPath extends WritePaths$1<TSchema> | (string & {})>(path: TPath) => HasMutationMethod$1<TSchema, TPath> extends true ? MutationPathMethods<TSchema, TPath> : never;
900
+
901
+ type Simplify<T> = {
902
+ [K in keyof T]: T[K];
903
+ } & {};
904
+ /**
905
+ * Base request options available on all methods.
906
+ */
907
+ type BaseRequestOptions = {
908
+ headers?: HeadersInitOrGetter;
909
+ cache?: RequestCache;
910
+ signal?: AbortSignal;
911
+ };
912
+ /**
913
+ * Extract body type from method config.
914
+ */
915
+ type ExtractBody<T> = T extends {
916
+ body?: infer B;
917
+ } ? B : never;
918
+ /**
919
+ * Extract query type from method config.
920
+ */
921
+ type ExtractQuery<T> = T extends {
922
+ query?: infer Q;
923
+ } ? Q : never;
924
+ /**
925
+ * Check if body is required (not optional).
926
+ */
927
+ type IsBodyRequired<T> = T extends {
928
+ body: infer B;
929
+ } ? undefined extends B ? false : true : false;
930
+ /**
931
+ * Check if query is required (not optional).
932
+ */
933
+ type IsQueryRequired<T> = T extends {
934
+ query: infer Q;
935
+ } ? undefined extends Q ? false : true : false;
936
+ /**
937
+ * Build the options type for a method.
938
+ */
939
+ type BodyOption<T> = [ExtractBody<T>] extends [never] ? {} : IsBodyRequired<T> extends true ? {
940
+ body: ExtractBody<T>;
941
+ } : {
942
+ body?: ExtractBody<T>;
943
+ };
944
+ type QueryOption<T> = [ExtractQuery<T>] extends [never] ? {} : IsQueryRequired<T> extends true ? {
945
+ query: ExtractQuery<T>;
946
+ } : {
947
+ query?: ExtractQuery<T>;
948
+ };
949
+ type ParamsOption<TUserPath extends string> = HasParams<TUserPath> extends true ? {
950
+ params: Record<ExtractParamNames<TUserPath>, string | number>;
951
+ } : {};
952
+ type RequestOptions<TMethodConfig, TUserPath extends string> = Simplify<BaseRequestOptions & BodyOption<TMethodConfig> & QueryOption<TMethodConfig> & ParamsOption<TUserPath>>;
953
+ /**
954
+ * Check if options argument is required.
955
+ */
956
+ type IsOptionsRequired<TMethodConfig, TUserPath extends string> = IsBodyRequired<TMethodConfig> extends true ? true : IsQueryRequired<TMethodConfig> extends true ? true : HasParams<TUserPath> extends true ? true : false;
957
+ /**
958
+ * Build response type for a method call.
959
+ */
960
+ type MethodResponse<TMethodConfig, TDefaultError, TUserPath extends string> = SpooshResponse<ExtractData<TMethodConfig>, ExtractError<TMethodConfig, TDefaultError>, RequestOptions<TMethodConfig, TUserPath>, ExtractQuery<TMethodConfig>, ExtractBody<TMethodConfig>, never, never, ExtractParamNames<TUserPath>>;
961
+ /**
962
+ * Create a method function type.
963
+ * Direct lookup: Schema[Path][Method] → method config → build function type
964
+ */
965
+ type MethodFn<TMethodConfig, TDefaultError, TUserPath extends string> = IsOptionsRequired<TMethodConfig, TUserPath> extends true ? (options: RequestOptions<TMethodConfig, TUserPath>) => Promise<MethodResponse<TMethodConfig, TDefaultError, TUserPath>> : (options?: RequestOptions<TMethodConfig, TUserPath>) => Promise<MethodResponse<TMethodConfig, TDefaultError, TUserPath>>;
966
+ /**
967
+ * HTTP methods available on a path.
968
+ * Direct lookup: Schema[Path] → route config → map methods
969
+ */
970
+ type HttpMethods<TRoute, TDefaultError, TUserPath extends string> = {
971
+ [M in HttpMethod as M extends keyof TRoute ? M : never]: M extends keyof TRoute ? MethodFn<TRoute[M], TDefaultError, TUserPath> : never;
972
+ };
973
+ /**
974
+ * Build the return type when calling a client with a path.
975
+ * Uses FindMatchingKey for pattern matching (e.g., "posts/123" → "posts/:id")
976
+ */
977
+ type PathMethods<TSchema, TPath extends string, TDefaultError> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify<HttpMethods<TSchema[TKey], TDefaultError, TPath>> : never : never;
978
+ /**
979
+ * Extract all valid paths from a schema (for autocomplete).
980
+ */
981
+ type SchemaPaths<TSchema> = keyof TSchema & string;
982
+ /**
983
+ * An API client that uses path strings instead of chained property access.
984
+ * Methods use HTTP names directly: GET, POST, PUT, PATCH, DELETE.
985
+ *
986
+ * @example
987
+ * ```ts
988
+ * type ApiSchema = {
989
+ * "posts": { GET: { data: Post[] } };
990
+ * "posts/:id": { GET: { data: Post } };
991
+ * };
992
+ *
993
+ * const api = createClient<ApiSchema>({ baseUrl: "/api" });
994
+ *
995
+ * // Call with exact path (autocomplete available)
996
+ * await api("posts").GET();
997
+ *
998
+ * // Call with embedded params (matches "posts/:id")
999
+ * await api("posts/123").GET();
1000
+ *
1001
+ * // Call with explicit params
1002
+ * await api("posts/:id").GET({ params: { id: 123 } });
1003
+ * ```
1004
+ */
1005
+ type SpooshClient<TSchema, TDefaultError = unknown> = <TPath extends SchemaPaths<TSchema> | (string & {})>(path: TPath) => PathMethods<TSchema, TPath, TDefaultError>;
1006
+ /**
1007
+ * Read-only client type that only exposes GET methods.
1008
+ * Used by useRead/injectRead hooks to ensure only query operations are selected.
1009
+ */
1010
+ type ReadPathMethods<TSchema, TPath extends string, TDefaultError> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? "GET" extends keyof TSchema[TKey] ? Simplify<{
1011
+ GET: MethodFn<TSchema[TKey]["GET"], TDefaultError, TPath>;
1012
+ }> : never : never : never;
1013
+ /**
1014
+ * Check if a schema path has a GET method.
1015
+ */
1016
+ 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;
1017
+ /**
1018
+ * Extract paths that have GET methods.
1019
+ */
1020
+ type ReadPaths<TSchema> = {
1021
+ [K in keyof TSchema & string]: "GET" extends keyof TSchema[K] ? K : never;
1022
+ }[keyof TSchema & string];
1023
+ /**
1024
+ * A read-only API client that only exposes GET methods.
1025
+ * Used by useRead and injectRead hooks.
1026
+ */
1027
+ type ReadClient<TSchema, TDefaultError = unknown> = <TPath extends ReadPaths<TSchema> | (string & {})>(path: TPath) => HasGetMethod<TSchema, TPath> extends true ? ReadPathMethods<TSchema, TPath, TDefaultError> : never;
1028
+ /**
1029
+ * Mutation methods (non-GET).
1030
+ */
1031
+ type MutationMethod = "POST" | "PUT" | "PATCH" | "DELETE";
1032
+ /**
1033
+ * Write-only client type that only exposes mutation methods (POST, PUT, PATCH, DELETE).
1034
+ * Used by useWrite/injectWrite hooks to ensure only mutation operations are selected.
1035
+ */
1036
+ type WritePathMethods<TSchema, TPath extends string, TDefaultError> = FindMatchingKey<TSchema, TPath> extends infer TKey ? TKey extends keyof TSchema ? Simplify<{
1037
+ [M in MutationMethod as M extends keyof TSchema[TKey] ? M : never]: M extends keyof TSchema[TKey] ? MethodFn<TSchema[TKey][M], TDefaultError, TPath> : never;
1038
+ }> : never : never;
1039
+ /**
1040
+ * Check if a schema path has any mutation methods.
1041
+ */
1042
+ 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;
1043
+ /**
1044
+ * Extract paths that have mutation methods.
1045
+ */
1046
+ type WritePaths<TSchema> = {
1047
+ [K in keyof TSchema & string]: Extract<keyof TSchema[K], MutationMethod> extends never ? never : K;
1048
+ }[keyof TSchema & string];
1049
+ /**
1050
+ * A write-only API client that only exposes mutation methods (POST, PUT, PATCH, DELETE).
1051
+ * Used by useWrite and injectWrite hooks.
1052
+ */
1053
+ type WriteClient<TSchema, TDefaultError = unknown> = <TPath extends WritePaths<TSchema> | (string & {})>(path: TPath) => HasMutationMethod<TSchema, TPath> extends true ? WritePathMethods<TSchema, TPath, TDefaultError> : never;
920
1054
 
921
1055
  type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
922
1056
  interface SpooshConfig<TPlugins extends PluginArray = PluginArray> {
@@ -925,7 +1059,7 @@ interface SpooshConfig<TPlugins extends PluginArray = PluginArray> {
925
1059
  plugins?: TPlugins;
926
1060
  }
927
1061
  type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends PluginArray = PluginArray> = {
928
- api: SpooshClient<TSchema, TDefaultError, CoreRequestOptionsBase>;
1062
+ api: SpooshClient<TSchema, TDefaultError>;
929
1063
  stateManager: StateManager;
930
1064
  eventEmitter: EventEmitter;
931
1065
  pluginExecutor: PluginExecutor;
@@ -953,7 +1087,7 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
953
1087
  * .use([cachePlugin(), retryPlugin()]);
954
1088
  *
955
1089
  * const { api } = client;
956
- * const response = await api.posts.$get();
1090
+ * const response = await api("posts").GET();
957
1091
  * ```
958
1092
  *
959
1093
  * @example With default options
@@ -971,6 +1105,10 @@ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends
971
1105
  * .use([cachePlugin(), retryPlugin()]);
972
1106
  *
973
1107
  * const { useRead, useWrite } = createReactSpoosh(client);
1108
+ *
1109
+ * // In component
1110
+ * const { data } = useRead((api) => api("posts").GET());
1111
+ * const { trigger } = useWrite((api) => api("posts").POST);
974
1112
  * ```
975
1113
  *
976
1114
  * @since 0.1.0
@@ -1056,16 +1194,16 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1056
1194
  * const { api } = client;
1057
1195
  *
1058
1196
  * // GET request
1059
- * const { data } = await api.posts.$get();
1197
+ * const { data } = await api("posts").GET();
1060
1198
  *
1061
1199
  * // POST request with body
1062
- * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
1200
+ * const { data } = await api("posts").POST({ body: { title: 'Hello' } });
1063
1201
  *
1064
1202
  * // Dynamic path parameters
1065
- * const { data } = await api.posts(postId).$get();
1203
+ * const { data } = await api("posts/:id").GET({ params: { id: postId } });
1066
1204
  * ```
1067
1205
  */
1068
- get api(): SpooshClient<TSchema, TError, CoreRequestOptionsBase>;
1206
+ get api(): SpooshClient<TSchema, TError>;
1069
1207
  /**
1070
1208
  * State manager for cache and state operations.
1071
1209
  *
@@ -1076,7 +1214,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1076
1214
  * const { stateManager } = client;
1077
1215
  *
1078
1216
  * // Get cached data
1079
- * const cache = stateManager.getCache('posts.$get');
1217
+ * const cache = stateManager.getCache('posts.GET');
1080
1218
  *
1081
1219
  * // Invalidate cache by tag
1082
1220
  * stateManager.invalidate(['posts']);
@@ -1162,16 +1300,17 @@ type SpooshClientConfig = {
1162
1300
  *
1163
1301
  * @example
1164
1302
  * ```ts
1165
- * type ApiSchema = {
1166
- * posts: {
1167
- * $get: Endpoint<Post[]>;
1168
- * $post: Endpoint<Post, CreatePostBody>;
1169
- * _: {
1170
- * $get: Endpoint<Post>;
1171
- * $delete: Endpoint<void>;
1172
- * };
1303
+ * type ApiSchema = SpooshSchema<{
1304
+ * "posts": {
1305
+ * GET: { data: Post[] };
1306
+ * POST: { data: Post; body: CreatePostBody };
1173
1307
  * };
1174
- * };
1308
+ * "posts/:id": {
1309
+ * GET: { data: Post };
1310
+ * PUT: { data: Post; body: UpdatePostBody };
1311
+ * DELETE: { data: void };
1312
+ * };
1313
+ * }>;
1175
1314
  *
1176
1315
  * type ApiError = {
1177
1316
  * message: string;
@@ -1181,9 +1320,10 @@ type SpooshClientConfig = {
1181
1320
  * baseUrl: "/api",
1182
1321
  * });
1183
1322
  *
1184
- * // Type-safe API calls
1185
- * const { data } = await api.posts.$get();
1186
- * const { data: post } = await api.posts(123).$get();
1323
+ * // Type-safe API calls with path strings
1324
+ * const { data } = await api("posts").GET();
1325
+ * const { data: post } = await api("posts/123").GET();
1326
+ * await api("posts/:id").GET({ params: { id: 123 } });
1187
1327
  * ```
1188
1328
  */
1189
1329
  declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
@@ -1208,6 +1348,11 @@ declare function mergeHeaders(defaultHeaders?: HeadersInitOrGetter, requestHeade
1208
1348
  declare function setHeaders(requestOptions: {
1209
1349
  headers?: HeadersInitOrGetter;
1210
1350
  }, newHeaders: Record<string, string>): void;
1351
+ /**
1352
+ * Extracts the Content-Type header value from HeadersInit.
1353
+ * Returns undefined if no Content-Type is set.
1354
+ */
1355
+ declare function getContentType(headers?: HeadersInit): string | undefined;
1211
1356
 
1212
1357
  declare function objectToFormData(obj: Record<string, unknown>): FormData;
1213
1358
 
@@ -1230,41 +1375,30 @@ declare function resolvePath(path: string[], params: Record<string, string | num
1230
1375
  type ProxyHandlerConfig<TOptions = SpooshOptions> = {
1231
1376
  baseUrl: string;
1232
1377
  defaultOptions: TOptions;
1233
- path?: string[];
1234
1378
  fetchExecutor?: FetchExecutor<TOptions, AnyRequestOptions>;
1235
1379
  nextTags?: boolean;
1236
1380
  };
1237
1381
  /**
1238
- * Creates the real API client proxy that executes actual HTTP requests.
1239
- *
1240
- * This proxy intercepts property access and function calls to build URL paths,
1241
- * then executes fetch requests when an HTTP method ($get, $post, etc.) is called.
1242
- *
1243
- * Used internally by `createClient` and `createSpoosh` to create typed API clients.
1382
+ * Creates an API client proxy that uses path strings instead of chained property access.
1383
+ * Methods use HTTP names directly: GET, POST, PUT, PATCH, DELETE.
1244
1384
  *
1245
1385
  * @param config - Proxy handler configuration
1246
- *
1247
- * @returns A proxy object typed as TSchema that executes real HTTP requests
1386
+ * @returns A function that takes a path and returns an object with HTTP method functions
1248
1387
  *
1249
1388
  * @example
1250
1389
  * ```ts
1251
1390
  * const api = createProxyHandler<ApiSchema>({ baseUrl: '/api', defaultOptions: {} });
1252
1391
  *
1253
- * // Accessing api.posts.$get() will:
1254
- * // 1. Build path: ['posts']
1255
- * // 2. Execute: GET /api/posts
1256
- * await api.posts.$get();
1257
- *
1258
- * // Dynamic segments via function call:
1259
- * // api.posts(123).$get() or api.posts('123').$get()
1260
- * // Executes: GET /api/posts/123
1261
- * await api.posts(123).$get();
1392
+ * // Call with path string
1393
+ * await api("posts").GET();
1394
+ * await api("posts/123").GET();
1395
+ * await api("posts/:id").GET({ params: { id: 123 } });
1262
1396
  * ```
1263
1397
  */
1264
- declare function createProxyHandler<TSchema extends object, TOptions = SpooshOptions>(config: ProxyHandlerConfig<TOptions>): TSchema;
1398
+ declare function createProxyHandler<TSchema, TDefaultError = unknown, TOptions = SpooshOptions>(config: ProxyHandlerConfig<TOptions>): SpooshClient<TSchema, TDefaultError>;
1265
1399
 
1266
1400
  /** All supported HTTP method keys used in the API client */
1267
- declare const HTTP_METHODS: readonly ["$get", "$post", "$put", "$patch", "$delete"];
1401
+ declare const HTTP_METHODS: readonly ["GET", "POST", "PUT", "PATCH", "DELETE"];
1268
1402
  /** Union type of all HTTP method keys */
1269
1403
  type HttpMethodKey = (typeof HTTP_METHODS)[number];
1270
1404
  /**
@@ -1275,9 +1409,9 @@ type HttpMethodKey = (typeof HTTP_METHODS)[number];
1275
1409
  type SelectorFunction = (() => Promise<{
1276
1410
  data: undefined;
1277
1411
  }>) & {
1278
- /** The path segments leading to this endpoint (e.g., ['posts', 'comments']) */
1279
- __selectorPath?: string[];
1280
- /** The HTTP method selected (e.g., '$get', '$post') */
1412
+ /** The path string for this endpoint (e.g., 'posts', 'posts/:id') */
1413
+ __selectorPath?: string;
1414
+ /** The HTTP method selected (e.g., 'GET', 'POST') */
1281
1415
  __selectorMethod?: string;
1282
1416
  };
1283
1417
  /**
@@ -1286,9 +1420,9 @@ type SelectorFunction = (() => Promise<{
1286
1420
  * Captured when an HTTP method is invoked with options.
1287
1421
  */
1288
1422
  type CapturedCall = {
1289
- /** Path segments to the endpoint (e.g., ['posts', ':id']) */
1290
- path: string[];
1291
- /** HTTP method called (e.g., '$get', '$post') */
1423
+ /** Path string to the endpoint (e.g., 'posts/:id') */
1424
+ path: string;
1425
+ /** HTTP method called (e.g., 'GET', 'POST') */
1292
1426
  method: string;
1293
1427
  /** Request options passed to the method (query, body, params, etc.) */
1294
1428
  options: unknown;
@@ -1299,8 +1433,8 @@ type CapturedCall = {
1299
1433
  * Captured when an HTTP method is accessed but not yet called.
1300
1434
  */
1301
1435
  type SelectedEndpoint = {
1302
- /** Path segments to the endpoint */
1303
- path: string[];
1436
+ /** Path string to the endpoint */
1437
+ path: string;
1304
1438
  /** HTTP method selected */
1305
1439
  method: string;
1306
1440
  };
@@ -1330,41 +1464,41 @@ type SelectorResult = {
1330
1464
  * const proxy = createSelectorProxy<ApiSchema>();
1331
1465
  *
1332
1466
  * // Select an endpoint
1333
- * const endpoint = proxy.posts.$get;
1467
+ * const endpoint = proxy("posts").GET;
1334
1468
  *
1335
1469
  * // Extract path for cache operations
1336
- * const path = extractPathFromSelector(endpoint); // ['posts']
1337
- * const method = extractMethodFromSelector(endpoint); // '$get'
1470
+ * const path = extractPathFromSelector(endpoint); // 'posts'
1471
+ * const method = extractMethodFromSelector(endpoint); // 'GET'
1338
1472
  * ```
1339
1473
  *
1340
1474
  * @internal onCapture - Used internally by framework adapters
1341
1475
  */
1342
1476
  declare function createSelectorProxy<TSchema>(onCapture?: (result: SelectorResult) => void): TSchema;
1343
1477
  /**
1344
- * Extracts the path segments from a SelectorFunction.
1478
+ * Extracts the path from a SelectorFunction.
1345
1479
  *
1346
1480
  * @param fn - A SelectorFunction returned from `createSelectorProxy`
1347
- * @returns Array of path segments (e.g., ['posts', 'comments'])
1481
+ * @returns The path string (e.g., 'posts', 'posts/:id')
1348
1482
  *
1349
1483
  * @example
1350
1484
  * ```ts
1351
1485
  * const proxy = createSelectorProxy<ApiSchema>();
1352
- * const path = extractPathFromSelector(proxy.posts.comments.$get);
1353
- * // path = ['posts', 'comments']
1486
+ * const path = extractPathFromSelector(proxy("posts").GET);
1487
+ * // path = 'posts'
1354
1488
  * ```
1355
1489
  */
1356
- declare function extractPathFromSelector(fn: unknown): string[];
1490
+ declare function extractPathFromSelector(fn: unknown): string;
1357
1491
  /**
1358
1492
  * Extracts the HTTP method from a SelectorFunction.
1359
1493
  *
1360
1494
  * @param fn - A SelectorFunction returned from `createSelectorProxy`
1361
- * @returns The HTTP method string (e.g., '$get', '$post') or undefined
1495
+ * @returns The HTTP method string (e.g., 'GET', 'POST') or undefined
1362
1496
  *
1363
1497
  * @example
1364
1498
  * ```ts
1365
1499
  * const proxy = createSelectorProxy<ApiSchema>();
1366
- * const method = extractMethodFromSelector(proxy.posts.$post);
1367
- * // method = '$post'
1500
+ * const method = extractMethodFromSelector(proxy("posts").POST);
1501
+ * // method = 'POST'
1368
1502
  * ```
1369
1503
  */
1370
1504
  declare function extractMethodFromSelector(fn: unknown): string | undefined;
@@ -1463,4 +1597,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1463
1597
  };
1464
1598
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1465
1599
 
1466
- export { type AnyRequestOptions, 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 Endpoint, type EventEmitter, type ExtractBody, type ExtractData, type ExtractError, type ExtractFormData, type ExtractMethodDef, type ExtractMethodOptions, type ExtractQuery, type ExtractUrlEncoded, type FetchDirection, type FetchExecutor, HTTP_METHODS, type HasMethod, type HasQueryMethods, type HasRequiredOptions, 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 MethodFn, type MethodOptionsMap, type MiddlewareContext, type MiddlewareHandler, type MiddlewarePhase, type MutationOnlyClient, type NormalizeEndpoint, 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 QueryOnlyClient, type QuerySchemaHelper, type RefetchEvent, type RequestOptions, type ResolveInstanceApi, type ResolveResultTypes, type ResolveSchemaTypes, type ResolveTypes, type ResolverContext, type RetryConfig, type SchemaMethod, type SelectedEndpoint, type SelectorFunction, type SelectorResult, Spoosh, type SpooshClient, type SpooshConfig, type SpooshInstance, type SpooshMiddleware, type SpooshOptions, type SpooshOptionsExtra, type SpooshPlugin, type SpooshResponse, type StateManager, type StaticPathKeys, type TagOptions, applyMiddlewares, buildUrl, composeMiddlewares, containsFile, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, generateTags, isJsonBody, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveTags, setHeaders, sortObjectKeys };
1600
+ 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 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 };