@spoosh/core 0.2.2 → 0.4.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
@@ -36,7 +36,10 @@ type ApiSchema = {
36
36
  $post: Endpoint<{ data: { url: string }; formData: { file: File } }>;
37
37
  };
38
38
  payments: {
39
- $post: Endpoint<{ data: { id: string }; urlEncoded: { amount: number; currency: string } }>;
39
+ $post: Endpoint<{
40
+ data: { id: string };
41
+ urlEncoded: { amount: number; currency: string };
42
+ }>;
40
43
  };
41
44
  };
42
45
  ```
@@ -65,25 +68,22 @@ const { data: newUser } = await api.users.$post({
65
68
  body: { name: "John", email: "john@example.com" },
66
69
  });
67
70
 
68
- // GET /api/users/123 (dynamic segment)
69
- const { data: user } = await api.users[123].$get();
71
+ // GET /api/users/123 (direct usage - simplest)
72
+ const { data: user } = await api.users(123).$get();
70
73
 
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({
74
+ // PUT /api/users/123 (with variable)
75
+ const userId = 123;
76
+ const { data: updated } = await api.users(userId).$put({
82
77
  body: { name: "John Updated" },
83
78
  });
84
79
 
85
80
  // DELETE /api/users/123
86
- await api.users[123].$delete();
81
+ await api.users(123).$delete();
82
+
83
+ // Typed params (advanced - when you need explicit param names)
84
+ const { data } = await api.users(":userId").$get({
85
+ params: { userId: 123 },
86
+ });
87
87
 
88
88
  // POST with FormData
89
89
  const { data: uploaded } = await api.upload.$post({
@@ -124,7 +124,7 @@ const api = createClient<ApiSchema>({ baseUrl: process.env.API_URL! });
124
124
  const { data: posts } = await api.posts.$get();
125
125
 
126
126
  // Auto-generates next: { tags: ['users', 'users/123', 'users/123/posts'] }
127
- const { data: userPosts } = await api.users[123].posts.$get();
127
+ const { data: userPosts } = await api.users(123).posts.$get();
128
128
  ```
129
129
 
130
130
  This enables automatic cache invalidation with `revalidateTag()` in Next.js.
@@ -176,16 +176,16 @@ const updatedContext = await applyMiddlewares(context, middlewares, "before");
176
176
 
177
177
  ## Schema Types
178
178
 
179
- | Type | Description | Example |
180
- | --------------------------------------- | ----------------------------- | --------------------------------------------------------------------- |
181
- | `Endpoint<{ data }>` | Endpoint with data only | `$get: Endpoint<{ data: User[] }>` |
182
- | `Endpoint<{ data; body }>` | Endpoint with JSON body | `$post: Endpoint<{ data: User; body: CreateUserBody }>` |
183
- | `Endpoint<{ data; query }>` | Endpoint with query params | `$get: Endpoint<{ data: User[]; query: { page: number } }>` |
184
- | `Endpoint<{ data; formData }>` | Endpoint with multipart form | `$post: Endpoint<{ data: Result; formData: { file: File } }>` |
185
- | `Endpoint<{ data; urlEncoded }>` | Endpoint with URL-encoded | `$post: Endpoint<{ data: Result; urlEncoded: { amount: number } }>` |
186
- | `Endpoint<{ data; error }>` | Endpoint with typed error | `$get: Endpoint<{ data: User; error: ApiError }>` |
187
- | `void` | No response body | `$delete: void` |
188
- | `_` | Dynamic path segment | `users: { _: { $get: Endpoint<{ data: User }> } }` |
179
+ | Type | Description | Example |
180
+ | -------------------------------- | ---------------------------- | ------------------------------------------------------------------- |
181
+ | `Endpoint<{ data }>` | Endpoint with data only | `$get: Endpoint<{ data: User[] }>` |
182
+ | `Endpoint<{ data; body }>` | Endpoint with JSON body | `$post: Endpoint<{ data: User; body: CreateUserBody }>` |
183
+ | `Endpoint<{ data; query }>` | Endpoint with query params | `$get: Endpoint<{ data: User[]; query: { page: number } }>` |
184
+ | `Endpoint<{ data; formData }>` | Endpoint with multipart form | `$post: Endpoint<{ data: Result; formData: { file: File } }>` |
185
+ | `Endpoint<{ data; urlEncoded }>` | Endpoint with URL-encoded | `$post: Endpoint<{ data: Result; urlEncoded: { amount: number } }>` |
186
+ | `Endpoint<{ data; error }>` | Endpoint with typed error | `$get: Endpoint<{ data: User; error: ApiError }>` |
187
+ | `void` | No response body | `$delete: void` |
188
+ | `_` | Dynamic path segment | `users: { _: { $get: Endpoint<{ data: User }> } }` |
189
189
 
190
190
  ## API Reference
191
191
 
@@ -209,11 +209,8 @@ import { cachePlugin } from "@spoosh/plugin-cache";
209
209
  import { retryPlugin } from "@spoosh/plugin-retry";
210
210
 
211
211
  const client = new Spoosh<ApiSchema, Error>("/api", {
212
- headers: { Authorization: "Bearer token" }
213
- }).use([
214
- cachePlugin({ staleTime: 5000 }),
215
- retryPlugin({ retries: 3 })
216
- ]);
212
+ headers: { Authorization: "Bearer token" },
213
+ }).use([cachePlugin({ staleTime: 5000 }), retryPlugin({ retries: 3 })]);
217
214
 
218
215
  const { api } = client;
219
216
  const { data } = await api.users.$get();
@@ -221,26 +218,25 @@ const { data } = await api.users.$get();
221
218
 
222
219
  **Constructor Parameters:**
223
220
 
224
- | Parameter | Type | Description |
225
- | ---------------- | --------------- | --------------------------------------- |
226
- | `baseUrl` | `string` | Base URL for all API requests |
227
- | `defaultOptions` | `RequestInit` | (Optional) Default fetch options |
221
+ | Parameter | Type | Description |
222
+ | ---------------- | ------------- | -------------------------------- |
223
+ | `baseUrl` | `string` | Base URL for all API requests |
224
+ | `defaultOptions` | `RequestInit` | (Optional) Default fetch options |
228
225
 
229
226
  **Methods:**
230
227
 
231
- | Method | Description |
232
- | ------ | ----------- |
228
+ | Method | Description |
229
+ | --------------- | --------------------------------------------------------------------- |
233
230
  | `.use(plugins)` | Add plugins to the client. Returns a new instance with updated types. |
234
231
 
235
232
  **Properties:**
236
233
 
237
- | Property | Description |
238
- | -------- | ----------- |
239
- | `.api` | Type-safe API client for making requests |
240
- | `.stateManager` | Cache and state management |
241
- | `.eventEmitter` | Event system for refetch/invalidation |
242
- | `.pluginExecutor` | Plugin lifecycle management |
243
-
234
+ | Property | Description |
235
+ | ----------------- | ---------------------------------------- |
236
+ | `.api` | Type-safe API client for making requests |
237
+ | `.stateManager` | Cache and state management |
238
+ | `.eventEmitter` | Event system for refetch/invalidation |
239
+ | `.pluginExecutor` | Plugin lifecycle management |
244
240
 
245
241
  ## Creating Plugins
246
242
 
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,35 +801,22 @@ 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
- [key: number]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
806
804
  /**
807
805
  * Dynamic path segment with typed param name.
808
806
  * Use `:paramName` format to get typed params in the response.
809
807
  *
810
808
  * @example
811
809
  * ```ts
812
- * // Typed params: { userId: string | number }
810
+ * // With number
811
+ * await api.users(123).$get()
812
+ *
813
+ * // With typed params
813
814
  * const { data, params } = await api.users(':userId').$get({ params: { userId: 123 } })
814
815
  * ```
815
816
  */
816
- <TKey extends string>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
+ <TKey extends string | number>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
818
  };
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> & {
819
+ 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
820
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
834
821
  };
835
822
 
@@ -852,29 +839,13 @@ type HasMutationMethods<TSchema> = TSchema extends object ? MutationMethod exten
852
839
  type QueryHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
853
840
  [K in QueryMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
854
841
  };
855
- type ExtractParamName<S extends string> = S extends `:${infer P}` ? P : never;
842
+ type ExtractParamName<S> = S extends `:${infer P}` ? P : never;
856
843
  type QueryDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
857
844
  _: infer D;
858
845
  } ? HasQueryMethods<D> extends true ? {
859
- [key: string]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
860
- [key: number]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
861
- <TKey extends string>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
846
+ <TKey extends string | number>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
862
847
  } : 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> & {
848
+ 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
849
  [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
850
  };
880
851
  type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
@@ -883,25 +854,9 @@ type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object,
883
854
  type MutationDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = TSchema extends {
884
855
  _: infer D;
885
856
  } ? HasMutationMethods<D> extends true ? {
886
- [key: string]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
887
- [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>;
857
+ <TKey extends string | number>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
903
858
  } : 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> & {
859
+ type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
905
860
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
906
861
  };
907
862
 
@@ -948,7 +903,7 @@ type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promi
948
903
  * trigger({
949
904
  * myCallback: (api) => [
950
905
  * api.posts.$get, // ✓ Valid
951
- * api.users[1].$get, // ✓ Dynamic segment
906
+ * api.users(1).$get, // ✓ Dynamic segment
952
907
  * api.nonexistent.$get, // ✗ Type error
953
908
  * ],
954
909
  * });
@@ -961,18 +916,7 @@ type QuerySchemaHelper<TSchema> = {
961
916
  } & (TSchema extends {
962
917
  _: infer D;
963
918
  } ? 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
- [key: number]: QuerySchemaHelper<D>;
919
+ <TKey extends string | number>(key: TKey): QuerySchemaHelper<D>;
976
920
  } : object : object);
977
921
 
978
922
  type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
@@ -1119,7 +1063,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1119
1063
  * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
1120
1064
  *
1121
1065
  * // Dynamic path parameters
1122
- * const { data } = await api.posts[postId].$get();
1066
+ * const { data } = await api.posts(postId).$get();
1123
1067
  * ```
1124
1068
  */
1125
1069
  get api(): SpooshClient<TSchema, TError, CoreRequestOptionsBase>;
@@ -1240,7 +1184,7 @@ type SpooshClientConfig = {
1240
1184
  *
1241
1185
  * // Type-safe API calls
1242
1186
  * const { data } = await api.posts.$get();
1243
- * const { data: post } = await api.posts[123].$get();
1187
+ * const { data: post } = await api.posts(123).$get();
1244
1188
  * ```
1245
1189
  */
1246
1190
  declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
@@ -1313,9 +1257,9 @@ type ProxyHandlerConfig<TOptions = SpooshOptions> = {
1313
1257
  * await api.posts.$get();
1314
1258
  *
1315
1259
  * // Dynamic segments via function call:
1316
- * // api.posts[123].$get() or api.posts('123').$get()
1260
+ * // api.posts(123).$get() or api.posts('123').$get()
1317
1261
  * // Executes: GET /api/posts/123
1318
- * await api.posts[123].$get();
1262
+ * await api.posts(123).$get();
1319
1263
  * ```
1320
1264
  */
1321
1265
  declare function createProxyHandler<TSchema extends object, TOptions = SpooshOptions>(config: ProxyHandlerConfig<TOptions>): TSchema;
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,35 +801,22 @@ 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
- [key: number]: SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
806
804
  /**
807
805
  * Dynamic path segment with typed param name.
808
806
  * Use `:paramName` format to get typed params in the response.
809
807
  *
810
808
  * @example
811
809
  * ```ts
812
- * // Typed params: { userId: string | number }
810
+ * // With number
811
+ * await api.users(123).$get()
812
+ *
813
+ * // With typed params
813
814
  * const { data, params } = await api.users(':userId').$get({ params: { userId: 123 } })
814
815
  * ```
815
816
  */
816
- <TKey extends string>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
+ <TKey extends string | number>(key: TKey): SpooshClient<ExtractDynamicSchema<TSchema>, TDefaultError, TOptionsMap, TParamNames | ExtractParamName$1<TKey>, TRootSchema>;
817
818
  };
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> & {
819
+ 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
820
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
834
821
  };
835
822
 
@@ -852,29 +839,13 @@ type HasMutationMethods<TSchema> = TSchema extends object ? MutationMethod exten
852
839
  type QueryHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
853
840
  [K in QueryMethod as K extends keyof TSchema ? K : never]: MethodFn<TSchema, K, TDefaultError, TOptionsMap, TParamNames>;
854
841
  };
855
- type ExtractParamName<S extends string> = S extends `:${infer P}` ? P : never;
842
+ type ExtractParamName<S> = S extends `:${infer P}` ? P : never;
856
843
  type QueryDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never, TRootSchema = TSchema> = TSchema extends {
857
844
  _: infer D;
858
845
  } ? HasQueryMethods<D> extends true ? {
859
- [key: string]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
860
- [key: number]: QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string, TRootSchema>;
861
- <TKey extends string>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
846
+ <TKey extends string | number>(key: TKey): QueryOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>, TRootSchema>;
862
847
  } : 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> & {
848
+ 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
849
  [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
850
  };
880
851
  type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = {
@@ -883,25 +854,9 @@ type MutationHttpMethods<TSchema, TDefaultError = unknown, TOptionsMap = object,
883
854
  type MutationDynamicAccess<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = TSchema extends {
884
855
  _: infer D;
885
856
  } ? HasMutationMethods<D> extends true ? {
886
- [key: string]: MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | string>;
887
- [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>;
857
+ <TKey extends string | number>(key: TKey): MutationOnlyClient<D, TDefaultError, TOptionsMap, TParamNames | ExtractParamName<TKey>>;
903
858
  } : 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> & {
859
+ type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParamNames extends string = never> = MutationHttpMethods<TSchema, TDefaultError, TOptionsMap, TParamNames> & MutationDynamicAccess<TSchema, TDefaultError, TOptionsMap, TParamNames> & {
905
860
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
906
861
  };
907
862
 
@@ -948,7 +903,7 @@ type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promi
948
903
  * trigger({
949
904
  * myCallback: (api) => [
950
905
  * api.posts.$get, // ✓ Valid
951
- * api.users[1].$get, // ✓ Dynamic segment
906
+ * api.users(1).$get, // ✓ Dynamic segment
952
907
  * api.nonexistent.$get, // ✗ Type error
953
908
  * ],
954
909
  * });
@@ -961,18 +916,7 @@ type QuerySchemaHelper<TSchema> = {
961
916
  } & (TSchema extends {
962
917
  _: infer D;
963
918
  } ? 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
- [key: number]: QuerySchemaHelper<D>;
919
+ <TKey extends string | number>(key: TKey): QuerySchemaHelper<D>;
976
920
  } : object : object);
977
921
 
978
922
  type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
@@ -1119,7 +1063,7 @@ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends Plugi
1119
1063
  * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
1120
1064
  *
1121
1065
  * // Dynamic path parameters
1122
- * const { data } = await api.posts[postId].$get();
1066
+ * const { data } = await api.posts(postId).$get();
1123
1067
  * ```
1124
1068
  */
1125
1069
  get api(): SpooshClient<TSchema, TError, CoreRequestOptionsBase>;
@@ -1240,7 +1184,7 @@ type SpooshClientConfig = {
1240
1184
  *
1241
1185
  * // Type-safe API calls
1242
1186
  * const { data } = await api.posts.$get();
1243
- * const { data: post } = await api.posts[123].$get();
1187
+ * const { data: post } = await api.posts(123).$get();
1244
1188
  * ```
1245
1189
  */
1246
1190
  declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
@@ -1313,9 +1257,9 @@ type ProxyHandlerConfig<TOptions = SpooshOptions> = {
1313
1257
  * await api.posts.$get();
1314
1258
  *
1315
1259
  * // Dynamic segments via function call:
1316
- * // api.posts[123].$get() or api.posts('123').$get()
1260
+ * // api.posts(123).$get() or api.posts('123').$get()
1317
1261
  * // Executes: GET /api/posts/123
1318
- * await api.posts[123].$get();
1262
+ * await api.posts(123).$get();
1319
1263
  * ```
1320
1264
  */
1321
1265
  declare function createProxyHandler<TSchema extends object, TOptions = SpooshOptions>(config: ProxyHandlerConfig<TOptions>): TSchema;
package/dist/index.js CHANGED
@@ -476,11 +476,9 @@ function createProxyHandler(config) {
476
476
  nextTags
477
477
  });
478
478
  },
479
- // Handles function call syntax for dynamic segments: api.posts("123"), api.users(userId)
480
- // Q. Why allow this syntax?
481
- // A. To support dynamic type inference in frameworks where property access with variables is not possible.
482
- // Eg. api.posts[":id"].$get() <-- TypeScript sees this as bracket notation with a string literal, can't infer param types
483
- // But api.posts(":id").$get() <-- TypeScript can capture ":id" as a template literal type, enabling params: { id: string } inference
479
+ // Handles function call syntax for dynamic segments: api.posts(123), api.posts(":id"), api.users(userId)
480
+ // This is the only way to access dynamic segments in Spoosh.
481
+ // The function call syntax allows TypeScript to capture the literal type, enabling params: { id: string } inference.
484
482
  apply(_target, _thisArg, args) {
485
483
  const [segment] = args;
486
484
  return createProxyHandler({
@@ -989,7 +987,7 @@ var Spoosh = class _Spoosh {
989
987
  * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
990
988
  *
991
989
  * // Dynamic path parameters
992
- * const { data } = await api.posts[postId].$get();
990
+ * const { data } = await api.posts(postId).$get();
993
991
  * ```
994
992
  */
995
993
  get api() {
package/dist/index.mjs CHANGED
@@ -421,11 +421,9 @@ function createProxyHandler(config) {
421
421
  nextTags
422
422
  });
423
423
  },
424
- // Handles function call syntax for dynamic segments: api.posts("123"), api.users(userId)
425
- // Q. Why allow this syntax?
426
- // A. To support dynamic type inference in frameworks where property access with variables is not possible.
427
- // Eg. api.posts[":id"].$get() <-- TypeScript sees this as bracket notation with a string literal, can't infer param types
428
- // But api.posts(":id").$get() <-- TypeScript can capture ":id" as a template literal type, enabling params: { id: string } inference
424
+ // Handles function call syntax for dynamic segments: api.posts(123), api.posts(":id"), api.users(userId)
425
+ // This is the only way to access dynamic segments in Spoosh.
426
+ // The function call syntax allows TypeScript to capture the literal type, enabling params: { id: string } inference.
429
427
  apply(_target, _thisArg, args) {
430
428
  const [segment] = args;
431
429
  return createProxyHandler({
@@ -934,7 +932,7 @@ var Spoosh = class _Spoosh {
934
932
  * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
935
933
  *
936
934
  * // Dynamic path parameters
937
- * const { data } = await api.posts[postId].$get();
935
+ * const { data } = await api.posts(postId).$get();
938
936
  * ```
939
937
  */
940
938
  get api() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spoosh/core",
3
- "version": "0.2.2",
3
+ "version": "0.4.0",
4
4
  "license": "MIT",
5
5
  "description": "Type-safe API client with plugin middleware system",
6
6
  "keywords": [