@spoosh/core 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -65,25 +65,22 @@ const { data: newUser } = await api.users.$post({
65
65
  body: { name: "John", email: "john@example.com" },
66
66
  });
67
67
 
68
- // GET /api/users/123 (dynamic segment)
69
- const { data: user } = await api.users[123].$get();
68
+ // GET /api/users/123 (direct usage - simplest)
69
+ const { data: user } = await api.users(123).$get();
70
70
 
71
- // Type-safe dynamic params with function syntax (recommended - params is typed)
72
- const { data } = await api.users(":userId").$get({
73
- params: { userId: "123" },
74
- });
75
-
76
- // Alternative bracket syntaxes (less recommended):
77
- // api.users[":userId"].$get() - works but no type inference for params
78
- // api.users[userId].$get() - works with variable, no type inference
79
-
80
- // PUT /api/users/123
81
- const { data: updated } = await api.users[123].$put({
71
+ // PUT /api/users/123 (with variable)
72
+ const userId = 123;
73
+ const { data: updated } = await api.users(userId).$put({
82
74
  body: { name: "John Updated" },
83
75
  });
84
76
 
85
77
  // DELETE /api/users/123
86
- await api.users[123].$delete();
78
+ await api.users(123).$delete();
79
+
80
+ // Typed params (advanced - when you need explicit param names)
81
+ const { data } = await api.users(":userId").$get({
82
+ params: { userId: 123 },
83
+ });
87
84
 
88
85
  // POST with FormData
89
86
  const { data: uploaded } = await api.upload.$post({
@@ -124,7 +121,7 @@ const api = createClient<ApiSchema>({ baseUrl: process.env.API_URL! });
124
121
  const { data: posts } = await api.posts.$get();
125
122
 
126
123
  // Auto-generates next: { tags: ['users', 'users/123', 'users/123/posts'] }
127
- const { data: userPosts } = await api.users[123].posts.$get();
124
+ const { data: userPosts } = await api.users(123).posts.$get();
128
125
  ```
129
126
 
130
127
  This enables automatic cache invalidation with `revalidateTag()` in Next.js.
package/dist/index.d.mts CHANGED
@@ -787,7 +787,7 @@ type HasMethod<TSchema, TMethod extends SchemaMethod> = TSchema extends {
787
787
  } ? true : false;
788
788
  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;
789
789
 
790
- type ExtractParamName$1<S extends string> = S extends `:${infer P}` ? P : never;
790
+ type ExtractParamName$1<S> = S extends `:${infer P}` ? P : never;
791
791
  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>;
792
792
  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;
793
793
  type IsSpecialKey<K> = K extends SchemaMethod | "_" ? true : false;
@@ -801,7 +801,6 @@ type HttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamN
801
801
  [K in SchemaMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
802
802
  };
803
803
  type DynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = ExtractDynamicSchema<TSchema> extends never ? object : {
804
- [key: string]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
805
804
  [key: number]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
806
805
  /**
807
806
  * Dynamic path segment with typed param name.
@@ -809,27 +808,16 @@ type DynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TPara
809
808
  *
810
809
  * @example
811
810
  * ```ts
812
- * // Typed params: { userId: string | number }
811
+ * // With number
812
+ * await api.users(123).$get()
813
+ *
814
+ * // With typed params
813
815
  * const { data, params } = await api.users(':userId').$get({ params: { userId: 123 } })
814
816
  * ```
815
817
  */
816
- <TKey extends string>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
818
+ <TKey extends string | number>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
819
  };
818
- type DynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
819
- _: infer D;
820
- } ? {
821
- /**
822
- * Dynamic path segment placeholder for routes like `/posts/:id`.
823
- *
824
- * @example
825
- * ```ts
826
- * // Direct client usage
827
- * const { data } = await api.posts._.$get({ params: { id: 123 } })
828
- * ```
829
- */
830
- _: SpooshClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
831
- } : object;
832
- type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = HttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & DynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & DynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
820
+ type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = HttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & DynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
833
821
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
834
822
  };
835
823
 
@@ -852,29 +840,14 @@ type HasMutationMethods<TSchema> = TSchema extends object ? MutationMethod exten
852
840
  type QueryHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
853
841
  [K in QueryMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
854
842
  };
855
- type ExtractParamName<S extends string> = S extends `:${infer P}` ? P : never;
843
+ type ExtractParamName<S> = S extends `:${infer P}` ? P : never;
856
844
  type QueryDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
857
845
  _: infer D;
858
846
  } ? HasQueryMethods<D> extends true ? {
859
- [key: string]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
860
847
  [key: number]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
861
- <TKey extends string>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
848
+ <TKey extends string | number>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
862
849
  } : object : object;
863
- type QueryDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
864
- _: infer D;
865
- } ? HasQueryMethods<D> extends true ? {
866
- /**
867
- * Dynamic path segment placeholder for routes like `/posts/:id`.
868
- *
869
- * @example
870
- * ```ts
871
- * useRead((api) => api.posts[123].$get())
872
- * useRead((api) => api.posts(':id').$get({ params: { id: 123 } }))
873
- * ```
874
- */
875
- _: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
876
- } : object : object;
877
- type QueryOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = QueryHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & QueryDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & QueryDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
850
+ type QueryOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = QueryHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & QueryDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
878
851
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: QueryOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
879
852
  };
880
853
  type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
@@ -883,25 +856,10 @@ type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object,
883
856
  type MutationDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = TSchema extends {
884
857
  _: infer D;
885
858
  } ? HasMutationMethods<D> extends true ? {
886
- [key: string]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
887
859
  [key: number]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
888
- <TKey extends string>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
889
- } : object : object;
890
- type MutationDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never> = TSchema extends {
891
- _: infer D;
892
- } ? HasMutationMethods<D> extends true ? {
893
- /**
894
- * Dynamic path segment placeholder for routes like `/posts/:id`.
895
- *
896
- * @example
897
- * ```ts
898
- * const { trigger } = useWrite((api) => api.posts(':id').$delete)
899
- * trigger({ params: { id: 123 } })
900
- * ```
901
- */
902
- _: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
860
+ <TKey extends string | number>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
903
861
  } : object : object;
904
- type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
862
+ type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
905
863
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
906
864
  };
907
865
 
@@ -961,17 +919,6 @@ type QuerySchemaHelper<TSchema> = {
961
919
  } & (TSchema extends {
962
920
  _: infer D;
963
921
  } ? HasQueryMethods<D> extends true ? {
964
- /**
965
- * Dynamic path segment placeholder for routes like `/posts/:id`.
966
- *
967
- * @example
968
- * ```ts
969
- * // In plugin callback - reference the endpoint
970
- * myCallback: (api) => api.posts._.$get
971
- * ```
972
- */
973
- _: QuerySchemaHelper<D>;
974
- [key: string]: QuerySchemaHelper<D>;
975
922
  [key: number]: QuerySchemaHelper<D>;
976
923
  } : object : object);
977
924
 
@@ -1253,6 +1200,7 @@ declare function buildUrl(baseUrl: string, path: string[], query?: Record<string
1253
1200
  */
1254
1201
  declare function generateTags(path: string[]): string[];
1255
1202
 
1203
+ declare function containsFile(value: unknown): boolean;
1256
1204
  declare function isJsonBody(body: unknown): body is Record<string, unknown> | unknown[];
1257
1205
 
1258
1206
  /**
@@ -1519,4 +1467,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1519
1467
  };
1520
1468
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1521
1469
 
1522
- 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, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, generateTags, isJsonBody, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveTags, setHeaders, sortObjectKeys };
1470
+ 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 };
package/dist/index.d.ts CHANGED
@@ -787,7 +787,7 @@ type HasMethod<TSchema, TMethod extends SchemaMethod> = TSchema extends {
787
787
  } ? true : false;
788
788
  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;
789
789
 
790
- type ExtractParamName$1<S extends string> = S extends `:${infer P}` ? P : never;
790
+ type ExtractParamName$1<S> = S extends `:${infer P}` ? P : never;
791
791
  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>;
792
792
  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;
793
793
  type IsSpecialKey<K> = K extends SchemaMethod | "_" ? true : false;
@@ -801,7 +801,6 @@ type HttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamN
801
801
  [K in SchemaMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
802
802
  };
803
803
  type DynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = ExtractDynamicSchema<TSchema> extends never ? object : {
804
- [key: string]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
805
804
  [key: number]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
806
805
  /**
807
806
  * Dynamic path segment with typed param name.
@@ -809,27 +808,16 @@ type DynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TPara
809
808
  *
810
809
  * @example
811
810
  * ```ts
812
- * // Typed params: { userId: string | number }
811
+ * // With number
812
+ * await api.users(123).$get()
813
+ *
814
+ * // With typed params
813
815
  * const { data, params } = await api.users(':userId').$get({ params: { userId: 123 } })
814
816
  * ```
815
817
  */
816
- <TKey extends string>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
818
+ <TKey extends string | number>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
819
  };
818
- type DynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
819
- _: infer D;
820
- } ? {
821
- /**
822
- * Dynamic path segment placeholder for routes like `/posts/:id`.
823
- *
824
- * @example
825
- * ```ts
826
- * // Direct client usage
827
- * const { data } = await api.posts._.$get({ params: { id: 123 } })
828
- * ```
829
- */
830
- _: SpooshClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
831
- } : object;
832
- type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = HttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & DynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & DynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
820
+ type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = HttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & DynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
833
821
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
834
822
  };
835
823
 
@@ -852,29 +840,14 @@ type HasMutationMethods<TSchema> = TSchema extends object ? MutationMethod exten
852
840
  type QueryHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
853
841
  [K in QueryMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
854
842
  };
855
- type ExtractParamName<S extends string> = S extends `:${infer P}` ? P : never;
843
+ type ExtractParamName<S> = S extends `:${infer P}` ? P : never;
856
844
  type QueryDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
857
845
  _: infer D;
858
846
  } ? HasQueryMethods<D> extends true ? {
859
- [key: string]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
860
847
  [key: number]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
861
- <TKey extends string>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
848
+ <TKey extends string | number>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
862
849
  } : object : object;
863
- type QueryDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
864
- _: infer D;
865
- } ? HasQueryMethods<D> extends true ? {
866
- /**
867
- * Dynamic path segment placeholder for routes like `/posts/:id`.
868
- *
869
- * @example
870
- * ```ts
871
- * useRead((api) => api.posts[123].$get())
872
- * useRead((api) => api.posts(':id').$get({ params: { id: 123 } }))
873
- * ```
874
- */
875
- _: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
876
- } : object : object;
877
- type QueryOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = QueryHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & QueryDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & QueryDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
850
+ type QueryOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = QueryHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & QueryDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames, TRootSchema> & {
878
851
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: QueryOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
879
852
  };
880
853
  type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
@@ -883,25 +856,10 @@ type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object,
883
856
  type MutationDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = TSchema extends {
884
857
  _: infer D;
885
858
  } ? HasMutationMethods<D> extends true ? {
886
- [key: string]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
887
859
  [key: number]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
888
- <TKey extends string>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
889
- } : object : object;
890
- type MutationDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames extends string = never> = TSchema extends {
891
- _: infer D;
892
- } ? HasMutationMethods<D> extends true ? {
893
- /**
894
- * Dynamic path segment placeholder for routes like `/posts/:id`.
895
- *
896
- * @example
897
- * ```ts
898
- * const { trigger } = useWrite((api) => api.posts(':id').$delete)
899
- * trigger({ params: { id: 123 } })
900
- * ```
901
- */
902
- _: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
860
+ <TKey extends string | number>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
903
861
  } : object : object;
904
- type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicKey<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
862
+ type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
905
863
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
906
864
  };
907
865
 
@@ -961,17 +919,6 @@ type QuerySchemaHelper<TSchema> = {
961
919
  } & (TSchema extends {
962
920
  _: infer D;
963
921
  } ? HasQueryMethods<D> extends true ? {
964
- /**
965
- * Dynamic path segment placeholder for routes like `/posts/:id`.
966
- *
967
- * @example
968
- * ```ts
969
- * // In plugin callback - reference the endpoint
970
- * myCallback: (api) => api.posts._.$get
971
- * ```
972
- */
973
- _: QuerySchemaHelper<D>;
974
- [key: string]: QuerySchemaHelper<D>;
975
922
  [key: number]: QuerySchemaHelper<D>;
976
923
  } : object : object);
977
924
 
@@ -1253,6 +1200,7 @@ declare function buildUrl(baseUrl: string, path: string[], query?: Record<string
1253
1200
  */
1254
1201
  declare function generateTags(path: string[]): string[];
1255
1202
 
1203
+ declare function containsFile(value: unknown): boolean;
1256
1204
  declare function isJsonBody(body: unknown): body is Record<string, unknown> | unknown[];
1257
1205
 
1258
1206
  /**
@@ -1519,4 +1467,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1519
1467
  };
1520
1468
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1521
1469
 
1522
- 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, createClient, createEventEmitter, createInfiniteReadController, createInitialState, createMiddleware, createOperationController, createPluginExecutor, createPluginRegistry, createProxyHandler, createSelectorProxy, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, generateTags, isJsonBody, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveTags, setHeaders, sortObjectKeys };
1470
+ 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 };
package/dist/index.js CHANGED
@@ -25,6 +25,7 @@ __export(src_exports, {
25
25
  applyMiddlewares: () => applyMiddlewares,
26
26
  buildUrl: () => buildUrl,
27
27
  composeMiddlewares: () => composeMiddlewares,
28
+ containsFile: () => containsFile,
28
29
  createClient: () => createClient,
29
30
  createEventEmitter: () => createEventEmitter,
30
31
  createInfiniteReadController: () => createInfiniteReadController,
@@ -103,6 +104,14 @@ function generateTags(path) {
103
104
  }
104
105
 
105
106
  // src/utils/isJsonBody.ts
107
+ function containsFile(value) {
108
+ if (value instanceof File || value instanceof Blob) return true;
109
+ if (Array.isArray(value)) return value.some(containsFile);
110
+ if (value && typeof value === "object") {
111
+ return Object.values(value).some(containsFile);
112
+ }
113
+ return false;
114
+ }
106
115
  function isJsonBody(body) {
107
116
  if (body === null || body === void 0) return false;
108
117
  if (body instanceof FormData) return false;
@@ -111,7 +120,11 @@ function isJsonBody(body) {
111
120
  if (body instanceof URLSearchParams) return false;
112
121
  if (body instanceof ReadableStream) return false;
113
122
  if (typeof body === "string") return false;
114
- return typeof body === "object";
123
+ if (typeof body === "object") {
124
+ if (containsFile(body)) return false;
125
+ return true;
126
+ }
127
+ return false;
115
128
  }
116
129
 
117
130
  // src/utils/mergeHeaders.ts
@@ -370,6 +383,10 @@ async function executeCoreFetch(config) {
370
383
  if (headers) {
371
384
  fetchInit.headers = headers;
372
385
  }
386
+ } else if (containsFile(requestOptions.body)) {
387
+ fetchInit.body = objectToFormData(
388
+ requestOptions.body
389
+ );
373
390
  } else {
374
391
  fetchInit.body = requestOptions.body;
375
392
  }
package/dist/index.mjs CHANGED
@@ -49,6 +49,14 @@ function generateTags(path) {
49
49
  }
50
50
 
51
51
  // src/utils/isJsonBody.ts
52
+ function containsFile(value) {
53
+ if (value instanceof File || value instanceof Blob) return true;
54
+ if (Array.isArray(value)) return value.some(containsFile);
55
+ if (value && typeof value === "object") {
56
+ return Object.values(value).some(containsFile);
57
+ }
58
+ return false;
59
+ }
52
60
  function isJsonBody(body) {
53
61
  if (body === null || body === void 0) return false;
54
62
  if (body instanceof FormData) return false;
@@ -57,7 +65,11 @@ function isJsonBody(body) {
57
65
  if (body instanceof URLSearchParams) return false;
58
66
  if (body instanceof ReadableStream) return false;
59
67
  if (typeof body === "string") return false;
60
- return typeof body === "object";
68
+ if (typeof body === "object") {
69
+ if (containsFile(body)) return false;
70
+ return true;
71
+ }
72
+ return false;
61
73
  }
62
74
 
63
75
  // src/utils/mergeHeaders.ts
@@ -316,6 +328,10 @@ async function executeCoreFetch(config) {
316
328
  if (headers) {
317
329
  fetchInit.headers = headers;
318
330
  }
331
+ } else if (containsFile(requestOptions.body)) {
332
+ fetchInit.body = objectToFormData(
333
+ requestOptions.body
334
+ );
319
335
  } else {
320
336
  fetchInit.body = requestOptions.body;
321
337
  }
@@ -1576,6 +1592,7 @@ export {
1576
1592
  applyMiddlewares,
1577
1593
  buildUrl,
1578
1594
  composeMiddlewares,
1595
+ containsFile,
1579
1596
  createClient,
1580
1597
  createEventEmitter,
1581
1598
  createInfiniteReadController,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.2.1",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [