@spoosh/core 0.1.0-beta.0 → 0.2.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.mts CHANGED
@@ -176,6 +176,40 @@ type EventEmitter = {
176
176
  };
177
177
  declare function createEventEmitter(): EventEmitter;
178
178
 
179
+ type Subscriber = () => void;
180
+ type CacheEntryWithKey<TData = unknown, TError = unknown> = {
181
+ key: string;
182
+ entry: CacheEntry<TData, TError>;
183
+ };
184
+ declare function createInitialState<TData, TError>(): OperationState<TData, TError>;
185
+ type StateManager = {
186
+ createQueryKey: (params: {
187
+ path: string[];
188
+ method: string;
189
+ options?: unknown;
190
+ }) => string;
191
+ getCache: <TData, TError>(key: string) => CacheEntry<TData, TError> | undefined;
192
+ setCache: <TData, TError>(key: string, entry: Partial<CacheEntry<TData, TError>>) => void;
193
+ deleteCache: (key: string) => void;
194
+ subscribeCache: (key: string, callback: Subscriber) => () => void;
195
+ getCacheByTags: <TData>(tags: string[]) => CacheEntry<TData> | undefined;
196
+ getCacheEntriesByTags: <TData, TError>(tags: string[]) => CacheEntryWithKey<TData, TError>[];
197
+ getCacheEntriesBySelfTag: <TData, TError>(selfTag: string) => CacheEntryWithKey<TData, TError>[];
198
+ setPluginResult: (key: string, data: Record<string, unknown>) => void;
199
+ /** Mark all cache entries with matching tags as stale */
200
+ markStale: (tags: string[]) => void;
201
+ /** Get all cache entries */
202
+ getAllCacheEntries: <TData, TError>() => CacheEntryWithKey<TData, TError>[];
203
+ /** Get the number of cache entries */
204
+ getSize: () => number;
205
+ /** Set a pending promise for a query key (for deduplication) */
206
+ setPendingPromise: (key: string, promise: Promise<unknown> | undefined) => void;
207
+ /** Get a pending promise for a query key */
208
+ getPendingPromise: (key: string) => Promise<unknown> | undefined;
209
+ clear: () => void;
210
+ };
211
+ declare function createStateManager(): StateManager;
212
+
179
213
  type OperationType = "read" | "write" | "infiniteRead";
180
214
  type LifecyclePhase = "onMount" | "onUnmount" | "onUpdate";
181
215
  type OperationState<TData = unknown, TError = unknown> = {
@@ -536,40 +570,6 @@ type InstanceApiContext<TApi = unknown> = {
536
570
  pluginExecutor: InstancePluginExecutor;
537
571
  };
538
572
 
539
- type Subscriber = () => void;
540
- type CacheEntryWithKey<TData = unknown, TError = unknown> = {
541
- key: string;
542
- entry: CacheEntry<TData, TError>;
543
- };
544
- declare function createInitialState<TData, TError>(): OperationState<TData, TError>;
545
- type StateManager = {
546
- createQueryKey: (params: {
547
- path: string[];
548
- method: string;
549
- options?: unknown;
550
- }) => string;
551
- getCache: <TData, TError>(key: string) => CacheEntry<TData, TError> | undefined;
552
- setCache: <TData, TError>(key: string, entry: Partial<CacheEntry<TData, TError>>) => void;
553
- deleteCache: (key: string) => void;
554
- subscribeCache: (key: string, callback: Subscriber) => () => void;
555
- getCacheByTags: <TData>(tags: string[]) => CacheEntry<TData> | undefined;
556
- getCacheEntriesByTags: <TData, TError>(tags: string[]) => CacheEntryWithKey<TData, TError>[];
557
- getCacheEntriesBySelfTag: <TData, TError>(selfTag: string) => CacheEntryWithKey<TData, TError>[];
558
- setPluginResult: (key: string, data: Record<string, unknown>) => void;
559
- /** Mark all cache entries with matching tags as stale */
560
- markStale: (tags: string[]) => void;
561
- /** Get all cache entries */
562
- getAllCacheEntries: <TData, TError>() => CacheEntryWithKey<TData, TError>[];
563
- /** Get the number of cache entries */
564
- getSize: () => number;
565
- /** Set a pending promise for a query key (for deduplication) */
566
- setPendingPromise: (key: string, promise: Promise<unknown> | undefined) => void;
567
- /** Get a pending promise for a query key */
568
- getPendingPromise: (key: string) => Promise<unknown> | undefined;
569
- clear: () => void;
570
- };
571
- declare function createStateManager(): StateManager;
572
-
573
573
  type PluginExecutor = {
574
574
  /** Execute lifecycle hooks for onMount or onUnmount */
575
575
  executeLifecycle: <TData, TError>(phase: "onMount" | "onUnmount", operationType: OperationType, context: PluginContext<TData, TError>) => Promise<void>;
@@ -583,6 +583,117 @@ type PluginExecutor = {
583
583
  };
584
584
  declare function createPluginExecutor(initialPlugins?: SpooshPlugin[]): PluginExecutor;
585
585
 
586
+ /**
587
+ * Resolves plugin option types based on the full context.
588
+ *
589
+ * This is the single entry point for all type resolution. It receives
590
+ * the full ResolverContext containing schema, data, error, and input types,
591
+ * and resolves each option key accordingly.
592
+ *
593
+ * Plugins extend PluginResolvers via declaration merging to add their own
594
+ * resolved option types.
595
+ *
596
+ * @example
597
+ * ```ts
598
+ * // In your plugin's types.ts:
599
+ * declare module "@spoosh/core" {
600
+ * interface PluginResolvers<TContext> {
601
+ * myOption: MyResolvedType<TContext["data"]> | undefined;
602
+ * }
603
+ * }
604
+ *
605
+ * // Type resolution:
606
+ * type ResolvedOptions = ResolveTypes<
607
+ * MergePluginOptions<TPlugins>["read"],
608
+ * {
609
+ * schema: ApiSchema;
610
+ * data: Post[];
611
+ * error: Error;
612
+ * input: { query: { page: number }; body: never; params: never; formData: never };
613
+ * }
614
+ * >;
615
+ * ```
616
+ */
617
+ type ResolveTypes<TOptions, TContext extends ResolverContext> = {
618
+ [K in keyof TOptions]: K extends keyof PluginResolvers<TContext> ? PluginResolvers<TContext>[K] : TOptions[K] extends DataAwareCallback<infer R, unknown, unknown> | undefined ? DataAwareCallback<R, TContext["data"], TContext["error"]> | undefined : TOptions[K] extends DataAwareTransform<unknown, unknown> | undefined ? DataAwareTransform<TContext["data"], TContext["error"]> | undefined : TOptions[K];
619
+ };
620
+ /**
621
+ * Resolves schema-aware types in plugin options.
622
+ * This is a simplified resolver for write operations that only need schema context.
623
+ */
624
+ type ResolveSchemaTypes<TOptions, TSchema> = ResolveTypes<TOptions, ResolverContext<TSchema>>;
625
+ /**
626
+ * Resolves plugin result types based on the options passed to the hook.
627
+ *
628
+ * This allows plugins to infer result types from the options. For example,
629
+ * the transform plugin can infer `transformedData` type from the response
630
+ * transformer's return type.
631
+ *
632
+ * @example
633
+ * ```ts
634
+ * // Usage in hooks:
635
+ * type ResolvedResults = ResolveResultTypes<PluginResults["read"], TReadOpts>;
636
+ * // If TReadOpts has { transform: { response: (d) => { count: number } } }
637
+ * // Then transformedData will be { count: number } | undefined
638
+ * ```
639
+ */
640
+ type ResolveResultTypes<TResults, TOptions> = TResults & PluginResultResolvers<TOptions>;
641
+ /**
642
+ * Resolves instance API types with schema awareness.
643
+ * Maps each key in TInstanceApi to its resolved type from resolvers.
644
+ *
645
+ * Plugins extend InstanceApiResolvers via declaration merging to add their own
646
+ * resolved instance API types.
647
+ *
648
+ * @example
649
+ * ```ts
650
+ * // In your plugin's types.ts:
651
+ * declare module "@spoosh/core" {
652
+ * interface InstanceApiResolvers<TSchema> {
653
+ * prefetch: PrefetchFn<TSchema>;
654
+ * }
655
+ * }
656
+ * ```
657
+ */
658
+ type ResolveInstanceApi<TInstanceApi, TSchema, TReadOptions = object> = {
659
+ [K in keyof TInstanceApi]: K extends keyof InstanceApiResolvers<TSchema> ? InstanceApiResolvers<TSchema>[K] : TInstanceApi[K];
660
+ };
661
+
662
+ type ExtractReadOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
663
+ readOptions: infer R;
664
+ } ? R : object : object;
665
+ type ExtractWriteOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
666
+ writeOptions: infer W;
667
+ } ? W : object : object;
668
+ type ExtractInfiniteReadOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
669
+ infiniteReadOptions: infer I;
670
+ } ? I : object : object;
671
+ type ExtractReadResult<T> = T extends SpooshPlugin<infer Types> ? Types extends {
672
+ readResult: infer R;
673
+ } ? R : object : object;
674
+ type ExtractWriteResult<T> = T extends SpooshPlugin<infer Types> ? Types extends {
675
+ writeResult: infer W;
676
+ } ? W : object : object;
677
+ type ExtractInstanceApi<T> = T extends SpooshPlugin<infer Types> ? Types extends {
678
+ instanceApi: infer A;
679
+ } ? A : object : object;
680
+ type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
681
+ type MergePluginOptions<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
682
+ read: UnionToIntersection<ExtractReadOptions<TPlugins[number]>>;
683
+ write: UnionToIntersection<ExtractWriteOptions<TPlugins[number]>>;
684
+ infiniteRead: UnionToIntersection<ExtractInfiniteReadOptions<TPlugins[number]>>;
685
+ };
686
+ type MergePluginResults<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
687
+ read: UnionToIntersection<ExtractReadResult<TPlugins[number]>>;
688
+ write: UnionToIntersection<ExtractWriteResult<TPlugins[number]>>;
689
+ };
690
+ type MergePluginInstanceApi<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[], TSchema = unknown> = ResolveInstanceApi<UnionToIntersection<ExtractInstanceApi<TPlugins[number]>>, TSchema, MergePluginOptions<TPlugins>["read"]>;
691
+ type PluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]> = {
692
+ plugins: TPlugins;
693
+ _options: MergePluginOptions<TPlugins>;
694
+ };
695
+ declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]>(plugins: [...TPlugins]): PluginRegistry<TPlugins>;
696
+
586
697
  declare const EndpointBrand: unique symbol;
587
698
  /**
588
699
  * Define an API endpoint with its data, request options, and error types.
@@ -722,72 +833,6 @@ type SpooshClient<TSchema, TDefaultError = unknown, TOptionsMap = object, TParam
722
833
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : K]: SpooshClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames, TRootSchema>;
723
834
  };
724
835
 
725
- type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
726
- interface SpooshConfig<TPlugins extends PluginArray = PluginArray> {
727
- baseUrl: string;
728
- defaultOptions?: SpooshOptions;
729
- plugins?: TPlugins;
730
- }
731
- type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends PluginArray = PluginArray> = {
732
- api: SpooshClient<TSchema, TDefaultError, CoreRequestOptionsBase>;
733
- stateManager: StateManager;
734
- eventEmitter: EventEmitter;
735
- pluginExecutor: PluginExecutor;
736
- config: {
737
- baseUrl: string;
738
- defaultOptions: SpooshOptions;
739
- };
740
- _types: {
741
- schema: TSchema;
742
- defaultError: TDefaultError;
743
- plugins: TPlugins;
744
- };
745
- };
746
-
747
- declare function createSpoosh<TSchema = unknown, TDefaultError = unknown, const TPlugins extends PluginArray = PluginArray>(config: SpooshConfig<TPlugins>): SpooshInstance<TSchema, TDefaultError, TPlugins>;
748
-
749
- type SpooshClientConfig = {
750
- baseUrl: string;
751
- defaultOptions?: SpooshOptions;
752
- middlewares?: SpooshMiddleware[];
753
- };
754
- /**
755
- * Creates a lightweight type-safe API client for vanilla JavaScript/TypeScript usage.
756
- *
757
- * This is a simpler alternative to `createSpoosh` for users who don't need
758
- * the full plugin system, state management, or React integration.
759
- *
760
- * @param config - Client configuration
761
- * @returns Type-safe API client
762
- *
763
- * @example
764
- * ```ts
765
- * type ApiSchema = {
766
- * posts: {
767
- * $get: Endpoint<Post[]>;
768
- * $post: Endpoint<Post, CreatePostBody>;
769
- * _: {
770
- * $get: Endpoint<Post>;
771
- * $delete: Endpoint<void>;
772
- * };
773
- * };
774
- * };
775
- *
776
- * type ApiError = {
777
- * message: string;
778
- * }
779
- *
780
- * const api = createClient<ApiSchema, ApiError>({
781
- * baseUrl: "/api",
782
- * });
783
- *
784
- * // Type-safe API calls
785
- * const { data } = await api.posts.$get();
786
- * const { data: post } = await api.posts[123].$get();
787
- * ```
788
- */
789
- declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
790
-
791
836
  type QueryMethod = "$get";
792
837
  type MutationMethod = "$post" | "$put" | "$patch" | "$delete";
793
838
  type HasQueryMethods<TSchema> = TSchema extends object ? "$get" extends keyof TSchema ? true : TSchema extends {
@@ -860,13 +905,353 @@ type MutationOnlyClient<TSchema, TDefaultError = unknown, TOptionsMap = object,
860
905
  [K in keyof StaticPathKeys<TSchema> as K extends SchemaMethod ? never : HasMutationMethods<TSchema[K]> extends true ? K : never]: MutationOnlyClient<TSchema[K], TDefaultError, TOptionsMap, TParamNames>;
861
906
  };
862
907
 
863
- declare function buildUrl(baseUrl: string, path: string[], query?: Record<string, string | number | boolean | undefined>): string;
864
-
908
+ type ExtractEndpointData<T> = T extends {
909
+ data: infer D;
910
+ } ? D : T extends void ? void : T;
911
+ type ExtractEndpointRequestOptions<T> = {
912
+ [K in Extract<keyof T, "query" | "body" | "params">]?: T[K];
913
+ };
914
+ type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promise<SpooshResponse<ExtractEndpointData<T>, unknown, ExtractEndpointRequestOptions<T>>>;
865
915
  /**
866
- * Generate cache tags from URL path segments.
867
- * e.g., ['posts', '1'] → ['posts', 'posts/1']
868
- */
869
- declare function generateTags(path: string[]): string[];
916
+ * Schema navigation helper for plugins that need type-safe API schema access.
917
+ *
918
+ * This type transforms the API schema into a navigable structure where:
919
+ * - Static path segments become nested properties
920
+ * - Dynamic segments (`_`) become index signatures
921
+ * - `$get` endpoints become callable method types
922
+ *
923
+ * Use this in plugin option types that need to reference API endpoints:
924
+ *
925
+ * @example
926
+ * ```ts
927
+ * // Define your plugin's callback type
928
+ * type MyCallbackFn<TSchema = unknown> = (
929
+ * api: QuerySchemaHelper<TSchema>
930
+ * ) => unknown;
931
+ *
932
+ * // Usage in plugin options
933
+ * interface MyPluginWriteOptions {
934
+ * myCallback?: MyCallbackFn<unknown>;
935
+ * }
936
+ *
937
+ * // Register for schema resolution
938
+ * declare module '@spoosh/core' {
939
+ * interface SchemaResolvers<TSchema> {
940
+ * myCallback: MyCallbackFn<TSchema> | undefined;
941
+ * }
942
+ * }
943
+ * ```
944
+ *
945
+ * @example
946
+ * ```ts
947
+ * // User's code - paths are type-checked!
948
+ * trigger({
949
+ * myCallback: (api) => [
950
+ * api.posts.$get, // ✓ Valid
951
+ * api.users[1].$get, // ✓ Dynamic segment
952
+ * api.nonexistent.$get, // ✗ Type error
953
+ * ],
954
+ * });
955
+ * ```
956
+ */
957
+ type QuerySchemaHelper<TSchema> = {
958
+ [K in keyof TSchema as K extends SchemaMethod | "_" ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: K extends keyof TSchema ? QuerySchemaHelper<TSchema[K]> : never;
959
+ } & {
960
+ [K in "$get" as K extends keyof TSchema ? K : never]: K extends keyof TSchema ? EndpointToMethod<TSchema[K]> : never;
961
+ } & (TSchema extends {
962
+ _: infer D;
963
+ } ? 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>;
976
+ } : object : object);
977
+
978
+ type PluginArray = readonly SpooshPlugin<PluginTypeConfig>[];
979
+ interface SpooshConfig<TPlugins extends PluginArray = PluginArray> {
980
+ baseUrl: string;
981
+ defaultOptions?: SpooshOptions;
982
+ plugins?: TPlugins;
983
+ }
984
+ type SpooshInstance<TSchema = unknown, TDefaultError = unknown, TPlugins extends PluginArray = PluginArray> = {
985
+ api: SpooshClient<TSchema, TDefaultError, CoreRequestOptionsBase>;
986
+ stateManager: StateManager;
987
+ eventEmitter: EventEmitter;
988
+ pluginExecutor: PluginExecutor;
989
+ config: {
990
+ baseUrl: string;
991
+ defaultOptions: SpooshOptions;
992
+ };
993
+ _types: {
994
+ schema: TSchema;
995
+ defaultError: TDefaultError;
996
+ plugins: TPlugins;
997
+ };
998
+ };
999
+
1000
+ /**
1001
+ * Class-based builder for creating Spoosh instances with type-safe plugin inference.
1002
+ *
1003
+ * @template TSchema - The API schema type defining endpoints and their types
1004
+ * @template TError - Default error type for all requests (defaults to unknown)
1005
+ * @template TPlugins - A const tuple of plugin instances for type inference (defaults to empty array)
1006
+ *
1007
+ * @example Basic usage
1008
+ * ```ts
1009
+ * const client = new Spoosh<ApiSchema, Error>('/api')
1010
+ * .use([cachePlugin(), retryPlugin()]);
1011
+ *
1012
+ * const { api } = client;
1013
+ * const response = await api.posts.$get();
1014
+ * ```
1015
+ *
1016
+ * @example With default options
1017
+ * ```ts
1018
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
1019
+ * headers: { 'Authorization': 'Bearer token' }
1020
+ * }).use([cachePlugin()]);
1021
+ * ```
1022
+ *
1023
+ * @example With React hooks
1024
+ * ```ts
1025
+ * import { createReactSpoosh } from '@spoosh/react';
1026
+ *
1027
+ * const client = new Spoosh<ApiSchema, Error>('/api')
1028
+ * .use([cachePlugin(), retryPlugin()]);
1029
+ *
1030
+ * const { useRead, useWrite } = createReactSpoosh(client);
1031
+ * ```
1032
+ *
1033
+ * @since 0.1.0
1034
+ */
1035
+ declare class Spoosh<TSchema = unknown, TError = unknown, TPlugins extends PluginArray = []> {
1036
+ private baseUrl;
1037
+ private defaultOptions;
1038
+ private _plugins;
1039
+ /**
1040
+ * Creates a new Spoosh instance.
1041
+ *
1042
+ * @param baseUrl - The base URL for all API requests (e.g., '/api' or 'https://api.example.com')
1043
+ * @param defaultOptions - Optional default options applied to all requests (headers, credentials, etc.)
1044
+ * @param plugins - Internal parameter used by the `.use()` method. Do not pass directly.
1045
+ *
1046
+ * @example
1047
+ * ```ts
1048
+ * // Simple usage
1049
+ * const client = new Spoosh<ApiSchema, Error>('/api');
1050
+ *
1051
+ * // With default headers
1052
+ * const client = new Spoosh<ApiSchema, Error>('/api', {
1053
+ * headers: { 'X-API-Key': 'secret' }
1054
+ * });
1055
+ * ```
1056
+ */
1057
+ constructor(baseUrl: string, defaultOptions?: SpooshOptions, plugins?: TPlugins);
1058
+ /**
1059
+ * Adds plugins to the Spoosh instance.
1060
+ *
1061
+ * Returns a **new** Spoosh instance with updated plugin types (immutable pattern).
1062
+ * Each call to `.use()` replaces the previous plugins rather than adding to them.
1063
+ *
1064
+ * @template TNewPlugins - The const tuple type of the new plugins array
1065
+ * @param plugins - Array of plugin instances to use
1066
+ * @returns A new Spoosh instance with the specified plugins
1067
+ *
1068
+ * @example Single use() call
1069
+ * ```ts
1070
+ * const client = new Spoosh<Schema, Error>('/api')
1071
+ * .use([cachePlugin(), retryPlugin(), debouncePlugin()]);
1072
+ * ```
1073
+ *
1074
+ * @example Chaining use() calls (replaces plugins)
1075
+ * ```ts
1076
+ * const client1 = new Spoosh<Schema, Error>('/api')
1077
+ * .use([cachePlugin()]);
1078
+ *
1079
+ * // This replaces cachePlugin with retryPlugin
1080
+ * const client2 = client1.use([retryPlugin()]);
1081
+ * ```
1082
+ *
1083
+ * @example With plugin configuration
1084
+ * ```ts
1085
+ * const client = new Spoosh<Schema, Error>('/api').use([
1086
+ * cachePlugin({ staleTime: 5000 }),
1087
+ * retryPlugin({ retries: 3, retryDelay: 1000 }),
1088
+ * prefetchPlugin(),
1089
+ * ]);
1090
+ * ```
1091
+ */
1092
+ use<const TNewPlugins extends PluginArray>(plugins: TNewPlugins): Spoosh<TSchema, TError, TNewPlugins>;
1093
+ /**
1094
+ * Cached instance of the underlying SpooshInstance.
1095
+ * Created lazily on first property access.
1096
+ * @private
1097
+ */
1098
+ private _instance?;
1099
+ /**
1100
+ * Gets or creates the underlying SpooshInstance.
1101
+ * Uses lazy initialization for optimal performance.
1102
+ * @private
1103
+ */
1104
+ private getInstance;
1105
+ /**
1106
+ * The type-safe API client for making requests.
1107
+ *
1108
+ * Provides a proxy-based interface for accessing endpoints defined in your schema.
1109
+ *
1110
+ * @example
1111
+ * ```ts
1112
+ * const client = new Spoosh<ApiSchema, Error>('/api').use([...]);
1113
+ * const { api } = client;
1114
+ *
1115
+ * // GET request
1116
+ * const { data } = await api.posts.$get();
1117
+ *
1118
+ * // POST request with body
1119
+ * const { data } = await api.posts.$post({ body: { title: 'Hello' } });
1120
+ *
1121
+ * // Dynamic path parameters
1122
+ * const { data } = await api.posts[postId].$get();
1123
+ * ```
1124
+ */
1125
+ get api(): SpooshClient<TSchema, TError, CoreRequestOptionsBase>;
1126
+ /**
1127
+ * State manager for cache and state operations.
1128
+ *
1129
+ * Provides methods for managing cached data, invalidating entries, and retrieving state.
1130
+ *
1131
+ * @example
1132
+ * ```ts
1133
+ * const { stateManager } = client;
1134
+ *
1135
+ * // Get cached data
1136
+ * const cache = stateManager.getCache('posts.$get');
1137
+ *
1138
+ * // Invalidate cache by tag
1139
+ * stateManager.invalidate(['posts']);
1140
+ *
1141
+ * // Clear all cache
1142
+ * stateManager.clearCache();
1143
+ * ```
1144
+ */
1145
+ get stateManager(): StateManager;
1146
+ /**
1147
+ * Event emitter for subscribing to refetch and invalidation events.
1148
+ *
1149
+ * Used internally by plugins and hooks to trigger re-fetches.
1150
+ *
1151
+ * @example
1152
+ * ```ts
1153
+ * const { eventEmitter } = client;
1154
+ *
1155
+ * // Subscribe to refetch events
1156
+ * eventEmitter.on('refetch', ({ queryKey, reason }) => {
1157
+ * console.log(`Refetching ${queryKey} due to ${reason}`);
1158
+ * });
1159
+ * ```
1160
+ */
1161
+ get eventEmitter(): EventEmitter;
1162
+ /**
1163
+ * Plugin executor that manages plugin lifecycle and middleware.
1164
+ *
1165
+ * Provides access to registered plugins and their execution context.
1166
+ *
1167
+ * @example
1168
+ * ```ts
1169
+ * const { pluginExecutor } = client;
1170
+ *
1171
+ * // Get all registered plugins
1172
+ * const plugins = pluginExecutor.getPlugins();
1173
+ *
1174
+ * // Check if a plugin is registered
1175
+ * const hasCache = plugins.some(p => p.name === 'cache');
1176
+ * ```
1177
+ */
1178
+ get pluginExecutor(): PluginExecutor;
1179
+ /**
1180
+ * Configuration object containing baseUrl and defaultOptions.
1181
+ *
1182
+ * @example
1183
+ * ```ts
1184
+ * const { config } = client;
1185
+ * console.log(config.baseUrl); // '/api'
1186
+ * console.log(config.defaultOptions); // { headers: {...} }
1187
+ * ```
1188
+ */
1189
+ get config(): {
1190
+ baseUrl: string;
1191
+ defaultOptions: SpooshOptions;
1192
+ };
1193
+ /**
1194
+ * Type information carrier for generic type inference.
1195
+ * Used internally by TypeScript for type resolution.
1196
+ *
1197
+ * @internal
1198
+ */
1199
+ get _types(): {
1200
+ schema: TSchema;
1201
+ defaultError: TError;
1202
+ plugins: TPlugins;
1203
+ };
1204
+ }
1205
+
1206
+ type SpooshClientConfig = {
1207
+ baseUrl: string;
1208
+ defaultOptions?: SpooshOptions;
1209
+ middlewares?: SpooshMiddleware[];
1210
+ };
1211
+ /**
1212
+ * Creates a lightweight type-safe API client for vanilla JavaScript/TypeScript usage.
1213
+ *
1214
+ * This is a simpler alternative to `Spoosh` for users who don't need
1215
+ * the full plugin system, state management, or React integration.
1216
+ *
1217
+ * @param config - Client configuration
1218
+ * @returns Type-safe API client
1219
+ *
1220
+ * @example
1221
+ * ```ts
1222
+ * type ApiSchema = {
1223
+ * posts: {
1224
+ * $get: Endpoint<Post[]>;
1225
+ * $post: Endpoint<Post, CreatePostBody>;
1226
+ * _: {
1227
+ * $get: Endpoint<Post>;
1228
+ * $delete: Endpoint<void>;
1229
+ * };
1230
+ * };
1231
+ * };
1232
+ *
1233
+ * type ApiError = {
1234
+ * message: string;
1235
+ * }
1236
+ *
1237
+ * const api = createClient<ApiSchema, ApiError>({
1238
+ * baseUrl: "/api",
1239
+ * });
1240
+ *
1241
+ * // Type-safe API calls
1242
+ * const { data } = await api.posts.$get();
1243
+ * const { data: post } = await api.posts[123].$get();
1244
+ * ```
1245
+ */
1246
+ declare function createClient<TSchema, TDefaultError = unknown>(config: SpooshClientConfig): SpooshClient<TSchema, TDefaultError>;
1247
+
1248
+ declare function buildUrl(baseUrl: string, path: string[], query?: Record<string, string | number | boolean | undefined>): string;
1249
+
1250
+ /**
1251
+ * Generate cache tags from URL path segments.
1252
+ * e.g., ['posts', '1'] → ['posts', 'posts/1']
1253
+ */
1254
+ declare function generateTags(path: string[]): string[];
870
1255
 
871
1256
  declare function isJsonBody(body: unknown): body is Record<string, unknown> | unknown[];
872
1257
 
@@ -1046,187 +1431,6 @@ declare function createMiddleware<TData = unknown, TError = unknown>(name: strin
1046
1431
  declare function applyMiddlewares<TData = unknown, TError = unknown>(context: MiddlewareContext<TData, TError>, middlewares: SpooshMiddleware<TData, TError>[], phase: MiddlewarePhase): Promise<MiddlewareContext<TData, TError>>;
1047
1432
  declare function composeMiddlewares<TData = unknown, TError = unknown>(...middlewareLists: (SpooshMiddleware<TData, TError>[] | undefined)[]): SpooshMiddleware<TData, TError>[];
1048
1433
 
1049
- /**
1050
- * Resolves plugin option types based on the full context.
1051
- *
1052
- * This is the single entry point for all type resolution. It receives
1053
- * the full ResolverContext containing schema, data, error, and input types,
1054
- * and resolves each option key accordingly.
1055
- *
1056
- * Plugins extend PluginResolvers via declaration merging to add their own
1057
- * resolved option types.
1058
- *
1059
- * @example
1060
- * ```ts
1061
- * // In your plugin's types.ts:
1062
- * declare module "@spoosh/core" {
1063
- * interface PluginResolvers<TContext> {
1064
- * myOption: MyResolvedType<TContext["data"]> | undefined;
1065
- * }
1066
- * }
1067
- *
1068
- * // Type resolution:
1069
- * type ResolvedOptions = ResolveTypes<
1070
- * MergePluginOptions<TPlugins>["read"],
1071
- * {
1072
- * schema: ApiSchema;
1073
- * data: Post[];
1074
- * error: Error;
1075
- * input: { query: { page: number }; body: never; params: never; formData: never };
1076
- * }
1077
- * >;
1078
- * ```
1079
- */
1080
- type ResolveTypes<TOptions, TContext extends ResolverContext> = {
1081
- [K in keyof TOptions]: K extends keyof PluginResolvers<TContext> ? PluginResolvers<TContext>[K] : TOptions[K] extends DataAwareCallback<infer R, unknown, unknown> | undefined ? DataAwareCallback<R, TContext["data"], TContext["error"]> | undefined : TOptions[K] extends DataAwareTransform<unknown, unknown> | undefined ? DataAwareTransform<TContext["data"], TContext["error"]> | undefined : TOptions[K];
1082
- };
1083
- /**
1084
- * Resolves schema-aware types in plugin options.
1085
- * This is a simplified resolver for write operations that only need schema context.
1086
- */
1087
- type ResolveSchemaTypes<TOptions, TSchema> = ResolveTypes<TOptions, ResolverContext<TSchema>>;
1088
- /**
1089
- * Resolves plugin result types based on the options passed to the hook.
1090
- *
1091
- * This allows plugins to infer result types from the options. For example,
1092
- * the transform plugin can infer `transformedData` type from the response
1093
- * transformer's return type.
1094
- *
1095
- * @example
1096
- * ```ts
1097
- * // Usage in hooks:
1098
- * type ResolvedResults = ResolveResultTypes<PluginResults["read"], TReadOpts>;
1099
- * // If TReadOpts has { transform: { response: (d) => { count: number } } }
1100
- * // Then transformedData will be { count: number } | undefined
1101
- * ```
1102
- */
1103
- type ResolveResultTypes<TResults, TOptions> = TResults & PluginResultResolvers<TOptions>;
1104
- /**
1105
- * Resolves instance API types with schema awareness.
1106
- * Maps each key in TInstanceApi to its resolved type from resolvers.
1107
- *
1108
- * Plugins extend InstanceApiResolvers via declaration merging to add their own
1109
- * resolved instance API types.
1110
- *
1111
- * @example
1112
- * ```ts
1113
- * // In your plugin's types.ts:
1114
- * declare module "@spoosh/core" {
1115
- * interface InstanceApiResolvers<TSchema> {
1116
- * prefetch: PrefetchFn<TSchema>;
1117
- * }
1118
- * }
1119
- * ```
1120
- */
1121
- type ResolveInstanceApi<TInstanceApi, TSchema, TReadOptions = object> = {
1122
- [K in keyof TInstanceApi]: K extends keyof InstanceApiResolvers<TSchema> ? InstanceApiResolvers<TSchema>[K] : TInstanceApi[K];
1123
- };
1124
-
1125
- type ExtractReadOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1126
- readOptions: infer R;
1127
- } ? R : object : object;
1128
- type ExtractWriteOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1129
- writeOptions: infer W;
1130
- } ? W : object : object;
1131
- type ExtractInfiniteReadOptions<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1132
- infiniteReadOptions: infer I;
1133
- } ? I : object : object;
1134
- type ExtractReadResult<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1135
- readResult: infer R;
1136
- } ? R : object : object;
1137
- type ExtractWriteResult<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1138
- writeResult: infer W;
1139
- } ? W : object : object;
1140
- type ExtractInstanceApi<T> = T extends SpooshPlugin<infer Types> ? Types extends {
1141
- instanceApi: infer A;
1142
- } ? A : object : object;
1143
- type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
1144
- type MergePluginOptions<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
1145
- read: UnionToIntersection<ExtractReadOptions<TPlugins[number]>>;
1146
- write: UnionToIntersection<ExtractWriteOptions<TPlugins[number]>>;
1147
- infiniteRead: UnionToIntersection<ExtractInfiniteReadOptions<TPlugins[number]>>;
1148
- };
1149
- type MergePluginResults<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[]> = {
1150
- read: UnionToIntersection<ExtractReadResult<TPlugins[number]>>;
1151
- write: UnionToIntersection<ExtractWriteResult<TPlugins[number]>>;
1152
- };
1153
- type MergePluginInstanceApi<TPlugins extends readonly SpooshPlugin<PluginTypeConfig>[], TSchema = unknown> = ResolveInstanceApi<UnionToIntersection<ExtractInstanceApi<TPlugins[number]>>, TSchema, MergePluginOptions<TPlugins>["read"]>;
1154
- type PluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]> = {
1155
- plugins: TPlugins;
1156
- _options: MergePluginOptions<TPlugins>;
1157
- };
1158
- declare function createPluginRegistry<TPlugins extends SpooshPlugin<PluginTypeConfig>[]>(plugins: [...TPlugins]): PluginRegistry<TPlugins>;
1159
-
1160
- type ExtractEndpointData<T> = T extends {
1161
- data: infer D;
1162
- } ? D : T extends void ? void : T;
1163
- type ExtractEndpointRequestOptions<T> = {
1164
- [K in Extract<keyof T, "query" | "body" | "params">]?: T[K];
1165
- };
1166
- type EndpointToMethod<T> = (options?: ExtractEndpointRequestOptions<T>) => Promise<SpooshResponse<ExtractEndpointData<T>, unknown, ExtractEndpointRequestOptions<T>>>;
1167
- /**
1168
- * Schema navigation helper for plugins that need type-safe API schema access.
1169
- *
1170
- * This type transforms the API schema into a navigable structure where:
1171
- * - Static path segments become nested properties
1172
- * - Dynamic segments (`_`) become index signatures
1173
- * - `$get` endpoints become callable method types
1174
- *
1175
- * Use this in plugin option types that need to reference API endpoints:
1176
- *
1177
- * @example
1178
- * ```ts
1179
- * // Define your plugin's callback type
1180
- * type MyCallbackFn<TSchema = unknown> = (
1181
- * api: QuerySchemaHelper<TSchema>
1182
- * ) => unknown;
1183
- *
1184
- * // Usage in plugin options
1185
- * interface MyPluginWriteOptions {
1186
- * myCallback?: MyCallbackFn<unknown>;
1187
- * }
1188
- *
1189
- * // Register for schema resolution
1190
- * declare module '@spoosh/core' {
1191
- * interface SchemaResolvers<TSchema> {
1192
- * myCallback: MyCallbackFn<TSchema> | undefined;
1193
- * }
1194
- * }
1195
- * ```
1196
- *
1197
- * @example
1198
- * ```ts
1199
- * // User's code - paths are type-checked!
1200
- * trigger({
1201
- * myCallback: (api) => [
1202
- * api.posts.$get, // ✓ Valid
1203
- * api.users[1].$get, // ✓ Dynamic segment
1204
- * api.nonexistent.$get, // ✗ Type error
1205
- * ],
1206
- * });
1207
- * ```
1208
- */
1209
- type QuerySchemaHelper<TSchema> = {
1210
- [K in keyof TSchema as K extends SchemaMethod | "_" ? never : HasQueryMethods<TSchema[K]> extends true ? K : never]: K extends keyof TSchema ? QuerySchemaHelper<TSchema[K]> : never;
1211
- } & {
1212
- [K in "$get" as K extends keyof TSchema ? K : never]: K extends keyof TSchema ? EndpointToMethod<TSchema[K]> : never;
1213
- } & (TSchema extends {
1214
- _: infer D;
1215
- } ? HasQueryMethods<D> extends true ? {
1216
- /**
1217
- * Dynamic path segment placeholder for routes like `/posts/:id`.
1218
- *
1219
- * @example
1220
- * ```ts
1221
- * // In plugin callback - reference the endpoint
1222
- * myCallback: (api) => api.posts._.$get
1223
- * ```
1224
- */
1225
- _: QuerySchemaHelper<D>;
1226
- [key: string]: QuerySchemaHelper<D>;
1227
- [key: number]: QuerySchemaHelper<D>;
1228
- } : object : object);
1229
-
1230
1434
  type ExecuteOptions = {
1231
1435
  force?: boolean;
1232
1436
  };
@@ -1315,4 +1519,4 @@ type CreateInfiniteReadOptions<TData, TItem, TError, TRequest> = {
1315
1519
  };
1316
1520
  declare function createInfiniteReadController<TData, TItem, TError, TRequest extends InfiniteRequestOptions = InfiniteRequestOptions>(options: CreateInfiniteReadOptions<TData, TItem, TError, TRequest>): InfiniteReadController<TData, TItem, TError>;
1317
1521
 
1318
- 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, 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, createSpoosh, createStateManager, executeFetch, extractMethodFromSelector, extractPathFromSelector, generateTags, isJsonBody, mergeHeaders, objectToFormData, objectToUrlEncoded, resolveHeadersToRecord, resolvePath, resolveTags, setHeaders, sortObjectKeys };
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 };