@void-snippets/react 0.1.6 → 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
@@ -1,58 +1,57 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { ResourceService } from '@void-snippets/client';
3
2
  import { ResourceAdapters, TQueryParams, TPagination, ResourceListResult } from '@void-snippets/core';
4
3
 
5
- type Capitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S;
6
- type AnyResourceService = ResourceService<never, // TId — input (get/update/delete param) → contravariant → never
7
- unknown, // TBase — output (list response item) → covariant → unknown
8
- unknown, // TDetail — output (single response item) → covariant → unknown
9
- never, // TCreate — input (create payload) → contravariant → never
10
- never, // TUpdate — input (update payload) → contravariant → never
11
- unknown, // TListRaw — output → covariant → unknown
12
- unknown>;
13
- type ServiceGenerics<S extends AnyResourceService> = S extends ResourceService<infer TId, infer TBase, infer TDetail, infer TCreate, infer TUpdate, infer TListRaw, infer TSingleRaw> ? {
14
- TId: TId;
15
- TBase: TBase;
16
- TDetail: TDetail;
17
- TCreate: TCreate;
18
- TUpdate: TUpdate;
19
- TListRaw: TListRaw;
20
- TSingleRaw: TSingleRaw;
21
- } : never;
22
- type Id<S extends AnyResourceService> = ServiceGenerics<S>["TId"];
23
- type Base<S extends AnyResourceService> = ServiceGenerics<S>["TBase"];
24
- type Detail<S extends AnyResourceService> = ServiceGenerics<S>["TDetail"];
25
- type Create<S extends AnyResourceService> = ServiceGenerics<S>["TCreate"];
26
- type Update<S extends AnyResourceService> = ServiceGenerics<S>["TUpdate"];
27
- type ListRaw<S extends AnyResourceService> = ServiceGenerics<S>["TListRaw"];
28
- type SingleRaw<S extends AnyResourceService> = ServiceGenerics<S>["TSingleRaw"];
4
+ type CapitalizeStr<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S;
5
+ interface WithResourceTypes {
6
+ readonly __types: {
7
+ id: unknown;
8
+ base: unknown;
9
+ detail: unknown;
10
+ create: unknown;
11
+ update: unknown;
12
+ listRaw: unknown;
13
+ singleRaw: unknown;
14
+ };
15
+ }
16
+ type Id<S extends WithResourceTypes> = S["__types"]["id"];
17
+ type Base<S extends WithResourceTypes> = S["__types"]["base"];
18
+ type Detail<S extends WithResourceTypes> = S["__types"]["detail"];
19
+ type Create<S extends WithResourceTypes> = S["__types"]["create"];
20
+ type Update<S extends WithResourceTypes> = S["__types"]["update"];
21
+ type ListRaw<S extends WithResourceTypes> = S["__types"]["listRaw"];
22
+ type SingleRaw<S extends WithResourceTypes> = S["__types"]["singleRaw"];
29
23
  type UseListReturn<K extends string, TBase> = {
30
24
  [P in K]: TBase[];
31
25
  } & {
32
26
  pagination: TPagination;
33
27
  } & {
34
- [P in `is${Capitalize<K>}Loading`]: boolean;
28
+ [P in `is${CapitalizeStr<K>}Loading`]: boolean;
35
29
  } & {
36
30
  [P in `${K}Error`]: Error | null;
37
31
  } & {
38
- [P in `invalidate${Capitalize<K>}`]: () => void;
32
+ [P in `invalidate${CapitalizeStr<K>}`]: () => void;
39
33
  };
40
34
  interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
41
35
  /**
42
- * Adapters let you map your API's raw response shapes to the library's
43
- * internal format. If your API matches the default shape
44
- * ({ data: { items, page, limit, totalPages, totalDocuments } })
45
- * you can omit this entirely.
36
+ * Adapters map your API's raw response shapes to the library's internal
37
+ * format. Omit this entirely if your API matches the default shape:
38
+ * List: { data: { items, page, limit, totalPages, totalDocuments } }
39
+ * Single: { data: <item> }
46
40
  *
47
41
  * @example
48
42
  * createResourceHooks("contacts", ContactsApis, {
49
43
  * adapters: {
50
44
  * fromList: (raw) => ({
51
45
  * items: raw.results,
52
- * pagination: { page: raw.page, limit: raw.perPage, totalPages: raw.pages, totalDocuments: raw.total },
46
+ * pagination: {
47
+ * page: raw.meta.page,
48
+ * limit: raw.meta.perPage,
49
+ * totalPages: raw.meta.lastPage,
50
+ * totalDocuments: raw.meta.total,
51
+ * },
53
52
  * }),
54
53
  * fromSingle: (raw) => raw.payload,
55
- * }
54
+ * },
56
55
  * })
57
56
  */
58
57
  adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;
@@ -64,11 +63,11 @@ interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
64
63
  }
65
64
  /**
66
65
  * Creates a set of TanStack Query hooks for a resource.
67
- * All types are fully inferred from the `apiService` instance — no need to
68
- * pass generics manually.
66
+ * All types are fully inferred from the `apiService` instance — no generics
67
+ * need to be passed manually.
69
68
  *
70
- * @param queryKeyPrefix - Used as the TanStack Query cache key prefix AND
71
- * to name the returned hook properties dynamically.
69
+ * @param queryKeyPrefix - TanStack Query cache key prefix and the base name
70
+ * for the returned hook properties.
72
71
  * e.g. "contacts" → { contacts, isContactsLoading, ... }
73
72
  * @param apiService - An instance of ResourceService (or a subclass).
74
73
  * @param options - Optional adapters and default params.
@@ -78,15 +77,15 @@ interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
78
77
  * import { createResourceHooks } from '@void-snippets/react';
79
78
  * import { ContactsApis } from './contacts.api';
80
79
  *
81
- * // No generics needed — everything is inferred from ContactsApis
80
+ * // No generics needed — all types are inferred from ContactsApis
82
81
  * export const contactHooks = createResourceHooks('contacts', ContactsApis);
83
82
  *
84
83
  * // In a component:
85
84
  * const { contacts, isContactsLoading } = contactHooks.useList();
86
- * const { data } = contactHooks.useGet(id); // data typed as Contact.WithCreatedBy
87
- * const { create, update, delete: deleteContact } = contactHooks.useMutations();
85
+ * const { data } = contactHooks.useGet(id); // data: Contact.WithCreatedBy
86
+ * const { create, update, delete: remove } = contactHooks.useMutations();
88
87
  */
89
- declare function createResourceHooks<K extends string, S extends AnyResourceService>(queryKeyPrefix: K, apiService: S, options?: CreateResourceHooksOptions<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>): {
88
+ declare function createResourceHooks<K extends string, S extends WithResourceTypes>(queryKeyPrefix: K, apiService: S, options?: CreateResourceHooksOptions<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>): {
90
89
  useList: (params?: TQueryParams) => UseListReturn<K, Base<S>>;
91
90
  useGet: (id: Id<S>, staleTime?: number) => {
92
91
  data: _tanstack_react_query.NoInfer<Detail<S>> | undefined;
package/dist/index.d.ts CHANGED
@@ -1,58 +1,57 @@
1
1
  import * as _tanstack_react_query from '@tanstack/react-query';
2
- import { ResourceService } from '@void-snippets/client';
3
2
  import { ResourceAdapters, TQueryParams, TPagination, ResourceListResult } from '@void-snippets/core';
4
3
 
5
- type Capitalize<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S;
6
- type AnyResourceService = ResourceService<never, // TId — input (get/update/delete param) → contravariant → never
7
- unknown, // TBase — output (list response item) → covariant → unknown
8
- unknown, // TDetail — output (single response item) → covariant → unknown
9
- never, // TCreate — input (create payload) → contravariant → never
10
- never, // TUpdate — input (update payload) → contravariant → never
11
- unknown, // TListRaw — output → covariant → unknown
12
- unknown>;
13
- type ServiceGenerics<S extends AnyResourceService> = S extends ResourceService<infer TId, infer TBase, infer TDetail, infer TCreate, infer TUpdate, infer TListRaw, infer TSingleRaw> ? {
14
- TId: TId;
15
- TBase: TBase;
16
- TDetail: TDetail;
17
- TCreate: TCreate;
18
- TUpdate: TUpdate;
19
- TListRaw: TListRaw;
20
- TSingleRaw: TSingleRaw;
21
- } : never;
22
- type Id<S extends AnyResourceService> = ServiceGenerics<S>["TId"];
23
- type Base<S extends AnyResourceService> = ServiceGenerics<S>["TBase"];
24
- type Detail<S extends AnyResourceService> = ServiceGenerics<S>["TDetail"];
25
- type Create<S extends AnyResourceService> = ServiceGenerics<S>["TCreate"];
26
- type Update<S extends AnyResourceService> = ServiceGenerics<S>["TUpdate"];
27
- type ListRaw<S extends AnyResourceService> = ServiceGenerics<S>["TListRaw"];
28
- type SingleRaw<S extends AnyResourceService> = ServiceGenerics<S>["TSingleRaw"];
4
+ type CapitalizeStr<S extends string> = S extends `${infer F}${infer Rest}` ? `${Uppercase<F>}${Rest}` : S;
5
+ interface WithResourceTypes {
6
+ readonly __types: {
7
+ id: unknown;
8
+ base: unknown;
9
+ detail: unknown;
10
+ create: unknown;
11
+ update: unknown;
12
+ listRaw: unknown;
13
+ singleRaw: unknown;
14
+ };
15
+ }
16
+ type Id<S extends WithResourceTypes> = S["__types"]["id"];
17
+ type Base<S extends WithResourceTypes> = S["__types"]["base"];
18
+ type Detail<S extends WithResourceTypes> = S["__types"]["detail"];
19
+ type Create<S extends WithResourceTypes> = S["__types"]["create"];
20
+ type Update<S extends WithResourceTypes> = S["__types"]["update"];
21
+ type ListRaw<S extends WithResourceTypes> = S["__types"]["listRaw"];
22
+ type SingleRaw<S extends WithResourceTypes> = S["__types"]["singleRaw"];
29
23
  type UseListReturn<K extends string, TBase> = {
30
24
  [P in K]: TBase[];
31
25
  } & {
32
26
  pagination: TPagination;
33
27
  } & {
34
- [P in `is${Capitalize<K>}Loading`]: boolean;
28
+ [P in `is${CapitalizeStr<K>}Loading`]: boolean;
35
29
  } & {
36
30
  [P in `${K}Error`]: Error | null;
37
31
  } & {
38
- [P in `invalidate${Capitalize<K>}`]: () => void;
32
+ [P in `invalidate${CapitalizeStr<K>}`]: () => void;
39
33
  };
40
34
  interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
41
35
  /**
42
- * Adapters let you map your API's raw response shapes to the library's
43
- * internal format. If your API matches the default shape
44
- * ({ data: { items, page, limit, totalPages, totalDocuments } })
45
- * you can omit this entirely.
36
+ * Adapters map your API's raw response shapes to the library's internal
37
+ * format. Omit this entirely if your API matches the default shape:
38
+ * List: { data: { items, page, limit, totalPages, totalDocuments } }
39
+ * Single: { data: <item> }
46
40
  *
47
41
  * @example
48
42
  * createResourceHooks("contacts", ContactsApis, {
49
43
  * adapters: {
50
44
  * fromList: (raw) => ({
51
45
  * items: raw.results,
52
- * pagination: { page: raw.page, limit: raw.perPage, totalPages: raw.pages, totalDocuments: raw.total },
46
+ * pagination: {
47
+ * page: raw.meta.page,
48
+ * limit: raw.meta.perPage,
49
+ * totalPages: raw.meta.lastPage,
50
+ * totalDocuments: raw.meta.total,
51
+ * },
53
52
  * }),
54
53
  * fromSingle: (raw) => raw.payload,
55
- * }
54
+ * },
56
55
  * })
57
56
  */
58
57
  adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;
@@ -64,11 +63,11 @@ interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
64
63
  }
65
64
  /**
66
65
  * Creates a set of TanStack Query hooks for a resource.
67
- * All types are fully inferred from the `apiService` instance — no need to
68
- * pass generics manually.
66
+ * All types are fully inferred from the `apiService` instance — no generics
67
+ * need to be passed manually.
69
68
  *
70
- * @param queryKeyPrefix - Used as the TanStack Query cache key prefix AND
71
- * to name the returned hook properties dynamically.
69
+ * @param queryKeyPrefix - TanStack Query cache key prefix and the base name
70
+ * for the returned hook properties.
72
71
  * e.g. "contacts" → { contacts, isContactsLoading, ... }
73
72
  * @param apiService - An instance of ResourceService (or a subclass).
74
73
  * @param options - Optional adapters and default params.
@@ -78,15 +77,15 @@ interface CreateResourceHooksOptions<TListRaw, TBase, TSingleRaw, TDetail> {
78
77
  * import { createResourceHooks } from '@void-snippets/react';
79
78
  * import { ContactsApis } from './contacts.api';
80
79
  *
81
- * // No generics needed — everything is inferred from ContactsApis
80
+ * // No generics needed — all types are inferred from ContactsApis
82
81
  * export const contactHooks = createResourceHooks('contacts', ContactsApis);
83
82
  *
84
83
  * // In a component:
85
84
  * const { contacts, isContactsLoading } = contactHooks.useList();
86
- * const { data } = contactHooks.useGet(id); // data typed as Contact.WithCreatedBy
87
- * const { create, update, delete: deleteContact } = contactHooks.useMutations();
85
+ * const { data } = contactHooks.useGet(id); // data: Contact.WithCreatedBy
86
+ * const { create, update, delete: remove } = contactHooks.useMutations();
88
87
  */
89
- declare function createResourceHooks<K extends string, S extends AnyResourceService>(queryKeyPrefix: K, apiService: S, options?: CreateResourceHooksOptions<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>): {
88
+ declare function createResourceHooks<K extends string, S extends WithResourceTypes>(queryKeyPrefix: K, apiService: S, options?: CreateResourceHooksOptions<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>): {
90
89
  useList: (params?: TQueryParams) => UseListReturn<K, Base<S>>;
91
90
  useGet: (id: Id<S>, staleTime?: number) => {
92
91
  data: _tanstack_react_query.NoInfer<Detail<S>> | undefined;
package/dist/index.js CHANGED
@@ -32,6 +32,7 @@ var DEFAULT_PAGINATION = {
32
32
  limit: 10
33
33
  };
34
34
  function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
35
+ const service = apiService;
35
36
  const {
36
37
  adapters = (0, import_core.createDefaultAdapters)(),
37
38
  defaultParams = DEFAULT_PAGINATION
@@ -49,7 +50,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
49
50
  const query = (0, import_react_query.useQuery)({
50
51
  queryKey,
51
52
  queryFn: async () => {
52
- const raw = await apiService.list(params);
53
+ const raw = await service.list(params);
53
54
  return adapters.fromList(raw);
54
55
  }
55
56
  });
@@ -75,7 +76,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
75
76
  const query = (0, import_react_query.useQuery)({
76
77
  queryKey: [queryKeyPrefix, id],
77
78
  queryFn: async () => {
78
- const raw = await apiService.get(id);
79
+ const raw = await service.get(id);
79
80
  return adapters.fromSingle(raw);
80
81
  },
81
82
  enabled: id !== void 0 && id !== null && id !== "",
@@ -96,21 +97,21 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
96
97
  const invalidate = () => queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });
97
98
  const createMutation = (0, import_react_query.useMutation)({
98
99
  mutationFn: async (payload) => {
99
- const raw = await apiService.create(payload);
100
+ const raw = await service.create(payload);
100
101
  return adapters.fromSingle(raw);
101
102
  },
102
103
  onSuccess: invalidate
103
104
  });
104
105
  const updateMutation = (0, import_react_query.useMutation)({
105
106
  mutationFn: async ({ _id, payload }) => {
106
- const raw = await apiService.update(_id, payload);
107
+ const raw = await service.update(_id, payload);
107
108
  return adapters.fromSingle(raw);
108
109
  },
109
110
  onSuccess: invalidate
110
111
  });
111
112
  const deleteMutation = (0, import_react_query.useMutation)({
112
113
  mutationFn: async (_id) => {
113
- const raw = await apiService.delete(_id);
114
+ const raw = await service.delete(_id);
114
115
  return adapters.fromSingle(raw);
115
116
  },
116
117
  onSuccess: invalidate
@@ -129,7 +130,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
129
130
  queryKey: [queryKeyPrefix, "INFINITE", params],
130
131
  queryFn: async ({ pageParam }) => {
131
132
  var _a;
132
- const raw = await apiService.list({
133
+ const raw = await service.list({
133
134
  ...params,
134
135
  page: pageParam,
135
136
  limit: (_a = params.limit) != null ? _a : 20
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/hooks/createResourceHooks.ts"],"sourcesContent":["export { createResourceHooks } from \"./hooks/createResourceHooks\";\nexport type {\n UseListReturn,\n CreateResourceHooksOptions,\n} from \"./hooks/createResourceHooks\";\n","import {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from \"@tanstack/react-query\";\nimport type { ResourceService } from \"@void-snippets/client\";\nimport type {\n ResourceAdapters,\n ResourceListResult,\n TPagination,\n TQueryParams,\n TDefaultPaginatedResponse,\n TDefaultSingleResponse,\n} from \"@void-snippets/core\";\nimport { createDefaultAdapters } from \"@void-snippets/core\";\n\n// ============================================================================\n// TYPE HELPERS\n// ============================================================================\n\ntype Capitalize<S extends string> = S extends `${infer F}${infer Rest}`\n ? `${Uppercase<F>}${Rest}`\n : S;\n\nconst DEFAULT_PAGINATION: Required<TQueryParams> = {\n page: 1,\n limit: 10,\n};\n\n// ============================================================================\n// SERVICE GENERIC EXTRACTOR\n// Instead of listing all 7 generics explicitly in createResourceHooks,\n// we capture the whole service as S and extract its generics here.\n// This is what enables full inference across package boundaries — TypeScript\n// only needs to match one type (S) rather than 7 simultaneously.\n//\n// AnyResourceService uses `unknown` (not `any`) as the upper bound —\n// unknown forces proper type narrowing downstream while remaining safe.\n// ============================================================================\n\ntype AnyResourceService = ResourceService<\n never, // TId — input (get/update/delete param) → contravariant → never\n unknown, // TBase — output (list response item) → covariant → unknown\n unknown, // TDetail — output (single response item) → covariant → unknown\n never, // TCreate — input (create payload) → contravariant → never\n never, // TUpdate — input (update payload) → contravariant → never\n unknown, // TListRaw — output → covariant → unknown\n unknown // TSingleRaw — output → covariant → unknown\n>;\n\ntype ServiceGenerics<S extends AnyResourceService> =\n S extends ResourceService<\n infer TId,\n infer TBase,\n infer TDetail,\n infer TCreate,\n infer TUpdate,\n infer TListRaw,\n infer TSingleRaw\n >\n ? {\n TId: TId;\n TBase: TBase;\n TDetail: TDetail;\n TCreate: TCreate;\n TUpdate: TUpdate;\n TListRaw: TListRaw;\n TSingleRaw: TSingleRaw;\n }\n : never;\n\n// Convenience aliases so usage inside the function body stays readable\ntype Id<S extends AnyResourceService> = ServiceGenerics<S>[\"TId\"];\ntype Base<S extends AnyResourceService> = ServiceGenerics<S>[\"TBase\"];\ntype Detail<S extends AnyResourceService> = ServiceGenerics<S>[\"TDetail\"];\ntype Create<S extends AnyResourceService> = ServiceGenerics<S>[\"TCreate\"];\ntype Update<S extends AnyResourceService> = ServiceGenerics<S>[\"TUpdate\"];\ntype ListRaw<S extends AnyResourceService> = ServiceGenerics<S>[\"TListRaw\"];\ntype SingleRaw<S extends AnyResourceService> = ServiceGenerics<S>[\"TSingleRaw\"];\n\n// ============================================================================\n// RETURN TYPE — useList\n// Dynamically named keys based on the queryKeyPrefix (e.g. \"contacts\"):\n// { contacts: TBase[], isContactsLoading: boolean, contactsError: Error | null, invalidateContacts: () => void }\n// ============================================================================\n\nexport type UseListReturn<K extends string, TBase> = {\n [P in K]: TBase[];\n} & {\n pagination: TPagination;\n} & {\n [P in `is${Capitalize<K>}Loading`]: boolean;\n} & {\n [P in `${K}Error`]: Error | null;\n} & {\n [P in `invalidate${Capitalize<K>}`]: () => void;\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport interface CreateResourceHooksOptions<\n TListRaw,\n TBase,\n TSingleRaw,\n TDetail,\n> {\n /**\n * Adapters let you map your API's raw response shapes to the library's\n * internal format. If your API matches the default shape\n * ({ data: { items, page, limit, totalPages, totalDocuments } })\n * you can omit this entirely.\n *\n * @example\n * createResourceHooks(\"contacts\", ContactsApis, {\n * adapters: {\n * fromList: (raw) => ({\n * items: raw.results,\n * pagination: { page: raw.page, limit: raw.perPage, totalPages: raw.pages, totalDocuments: raw.total },\n * }),\n * fromSingle: (raw) => raw.payload,\n * }\n * })\n */\n adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;\n\n /**\n * Default params passed to useList and useInfinite when none are provided.\n * @default { page: 1, limit: 10 }\n */\n defaultParams?: TQueryParams;\n}\n\n// ============================================================================\n// FACTORY\n// ============================================================================\n\n/**\n * Creates a set of TanStack Query hooks for a resource.\n * All types are fully inferred from the `apiService` instance — no need to\n * pass generics manually.\n *\n * @param queryKeyPrefix - Used as the TanStack Query cache key prefix AND\n * to name the returned hook properties dynamically.\n * e.g. \"contacts\" → { contacts, isContactsLoading, ... }\n * @param apiService - An instance of ResourceService (or a subclass).\n * @param options - Optional adapters and default params.\n *\n * @example\n * // contacts.hooks.ts\n * import { createResourceHooks } from '@void-snippets/react';\n * import { ContactsApis } from './contacts.api';\n *\n * // No generics needed — everything is inferred from ContactsApis\n * export const contactHooks = createResourceHooks('contacts', ContactsApis);\n *\n * // In a component:\n * const { contacts, isContactsLoading } = contactHooks.useList();\n * const { data } = contactHooks.useGet(id); // data typed as Contact.WithCreatedBy\n * const { create, update, delete: deleteContact } = contactHooks.useMutations();\n */\nexport function createResourceHooks<\n K extends string,\n S extends AnyResourceService,\n>(\n queryKeyPrefix: K,\n apiService: S,\n options: CreateResourceHooksOptions<\n ListRaw<S>,\n Base<S>,\n SingleRaw<S>,\n Detail<S>\n > = {},\n) {\n const {\n adapters = createDefaultAdapters<Base<S>, Detail<S>>() as ResourceAdapters<\n TDefaultPaginatedResponse<Base<S>>,\n Base<S>,\n TDefaultSingleResponse<Detail<S>>,\n Detail<S>\n > as ResourceAdapters<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>,\n defaultParams = DEFAULT_PAGINATION,\n } = options;\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n const capPrefix = capitalize(queryKeyPrefix);\n\n return {\n // -------------------------------------------------------------------------\n // useList — paginated list with invalidation helper\n // -------------------------------------------------------------------------\n useList: (\n params: TQueryParams = defaultParams,\n ): UseListReturn<K, Base<S>> => {\n const queryClient = useQueryClient();\n const queryKey = [queryKeyPrefix, params];\n\n const query = useQuery<ResourceListResult<Base<S>>, Error>({\n queryKey,\n queryFn: async () => {\n const raw = await apiService.list(params);\n return adapters.fromList(raw as ListRaw<S>);\n },\n });\n\n const items = query.data?.items ?? [];\n const pagination: TPagination = query.data?.pagination ?? {\n page: 1,\n limit: defaultParams.limit ?? 10,\n totalPages: 0,\n totalDocuments: 0,\n };\n\n return {\n [queryKeyPrefix]: items,\n pagination,\n [`is${capPrefix}Loading`]: query.isLoading || query.isFetching,\n [`${queryKeyPrefix}Error`]: query.error,\n [`invalidate${capPrefix}`]: () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] }),\n } as UseListReturn<K, Base<S>>;\n },\n\n // -------------------------------------------------------------------------\n // useGet — single item by id\n // -------------------------------------------------------------------------\n useGet: (id: Id<S>, staleTime = 30_000) => {\n const query = useQuery<Detail<S>, Error>({\n queryKey: [queryKeyPrefix, id],\n queryFn: async () => {\n const raw = await apiService.get(id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n enabled: id !== undefined && id !== null && id !== \"\",\n staleTime,\n });\n\n return {\n data: query.data,\n isLoading: query.isLoading || query.isFetching,\n error: query.error,\n refetch: query.refetch,\n };\n },\n\n // -------------------------------------------------------------------------\n // useMutations — create / update / delete with auto-invalidation\n // -------------------------------------------------------------------------\n useMutations: () => {\n const queryClient = useQueryClient();\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });\n\n const createMutation = useMutation<Detail<S>, Error, Create<S>>({\n mutationFn: async (payload) => {\n const raw = await apiService.create(payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const updateMutation = useMutation<\n Detail<S>,\n Error,\n { _id: Id<S>; payload: Update<S> }\n >({\n mutationFn: async ({ _id, payload }) => {\n const raw = await apiService.update(_id, payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const deleteMutation = useMutation<Detail<S>, Error, Id<S>>({\n mutationFn: async (_id) => {\n const raw = await apiService.delete(_id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n return {\n create: createMutation,\n update: updateMutation,\n delete: deleteMutation,\n };\n },\n\n // -------------------------------------------------------------------------\n // useInfinite — infinite scroll / load more\n // -------------------------------------------------------------------------\n useInfinite: (params: TQueryParams = defaultParams) => {\n return useInfiniteQuery<ResourceListResult<Base<S>>, Error>({\n queryKey: [queryKeyPrefix, \"INFINITE\", params],\n queryFn: async ({ pageParam }) => {\n const raw = await apiService.list({\n ...params,\n page: pageParam as number,\n limit: params.limit ?? 20,\n });\n return adapters.fromList(raw as ListRaw<S>);\n },\n getNextPageParam: (lastPage) => {\n const { page, totalPages } = lastPage.pagination;\n return page < totalPages ? page + 1 : undefined;\n },\n initialPageParam: 1,\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/hooks/createResourceHooks.ts"],"sourcesContent":["export { createResourceHooks } from \"./hooks/createResourceHooks\";\nexport type {\n UseListReturn,\n CreateResourceHooksOptions,\n} from \"./hooks/createResourceHooks\";\n","import {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from \"@tanstack/react-query\";\nimport type { ResourceService } from \"@void-snippets/client\";\nimport type {\n ResourceAdapters,\n ResourceListResult,\n TPagination,\n TQueryParams,\n TDefaultPaginatedResponse,\n TDefaultSingleResponse,\n} from \"@void-snippets/core\";\nimport { createDefaultAdapters } from \"@void-snippets/core\";\n\n// ============================================================================\n// TYPE HELPERS\n// ============================================================================\n\ntype CapitalizeStr<S extends string> = S extends `${infer F}${infer Rest}`\n ? `${Uppercase<F>}${Rest}`\n : S;\n\nconst DEFAULT_PAGINATION: TQueryParams = {\n page: 1,\n limit: 10,\n};\n\n// ============================================================================\n// PHANTOM TYPE CONSTRAINT\n//\n// We constrain S only on the __types phantom property (which is readonly,\n// i.e. covariant) so that ResourceService<string, ...> is assignable to\n// WithResourceTypes. This avoids the variance issue that arises when\n// constraining against method parameter types (contravariant positions).\n//\n// Inside the function we cast apiService once to a fully-typed ResourceService\n// using the phantom generics — this is safe because S IS a ResourceService.\n// ============================================================================\n\ninterface WithResourceTypes {\n readonly __types: {\n id: unknown;\n base: unknown;\n detail: unknown;\n create: unknown;\n update: unknown;\n listRaw: unknown;\n singleRaw: unknown;\n };\n}\n\n// Aliases for reading phantom types from S — keeps the function body readable\ntype Id<S extends WithResourceTypes> = S[\"__types\"][\"id\"];\ntype Base<S extends WithResourceTypes> = S[\"__types\"][\"base\"];\ntype Detail<S extends WithResourceTypes> = S[\"__types\"][\"detail\"];\ntype Create<S extends WithResourceTypes> = S[\"__types\"][\"create\"];\ntype Update<S extends WithResourceTypes> = S[\"__types\"][\"update\"];\ntype ListRaw<S extends WithResourceTypes> = S[\"__types\"][\"listRaw\"];\ntype SingleRaw<S extends WithResourceTypes> = S[\"__types\"][\"singleRaw\"];\n\n// ============================================================================\n// RETURN TYPE — useList\n// Dynamically named keys based on the queryKeyPrefix (e.g. \"contacts\"):\n// { contacts: TBase[], isContactsLoading: boolean, contactsError: Error | null, invalidateContacts: () => void }\n// ============================================================================\n\nexport type UseListReturn<K extends string, TBase> = {\n [P in K]: TBase[];\n} & {\n pagination: TPagination;\n} & {\n [P in `is${CapitalizeStr<K>}Loading`]: boolean;\n} & {\n [P in `${K}Error`]: Error | null;\n} & {\n [P in `invalidate${CapitalizeStr<K>}`]: () => void;\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport interface CreateResourceHooksOptions<\n TListRaw,\n TBase,\n TSingleRaw,\n TDetail,\n> {\n /**\n * Adapters map your API's raw response shapes to the library's internal\n * format. Omit this entirely if your API matches the default shape:\n * List: { data: { items, page, limit, totalPages, totalDocuments } }\n * Single: { data: <item> }\n *\n * @example\n * createResourceHooks(\"contacts\", ContactsApis, {\n * adapters: {\n * fromList: (raw) => ({\n * items: raw.results,\n * pagination: {\n * page: raw.meta.page,\n * limit: raw.meta.perPage,\n * totalPages: raw.meta.lastPage,\n * totalDocuments: raw.meta.total,\n * },\n * }),\n * fromSingle: (raw) => raw.payload,\n * },\n * })\n */\n adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;\n\n /**\n * Default params passed to useList and useInfinite when none are provided.\n * @default { page: 1, limit: 10 }\n */\n defaultParams?: TQueryParams;\n}\n\n// ============================================================================\n// FACTORY\n// ============================================================================\n\n/**\n * Creates a set of TanStack Query hooks for a resource.\n * All types are fully inferred from the `apiService` instance — no generics\n * need to be passed manually.\n *\n * @param queryKeyPrefix - TanStack Query cache key prefix and the base name\n * for the returned hook properties.\n * e.g. \"contacts\" → { contacts, isContactsLoading, ... }\n * @param apiService - An instance of ResourceService (or a subclass).\n * @param options - Optional adapters and default params.\n *\n * @example\n * // contacts.hooks.ts\n * import { createResourceHooks } from '@void-snippets/react';\n * import { ContactsApis } from './contacts.api';\n *\n * // No generics needed — all types are inferred from ContactsApis\n * export const contactHooks = createResourceHooks('contacts', ContactsApis);\n *\n * // In a component:\n * const { contacts, isContactsLoading } = contactHooks.useList();\n * const { data } = contactHooks.useGet(id); // data: Contact.WithCreatedBy\n * const { create, update, delete: remove } = contactHooks.useMutations();\n */\nexport function createResourceHooks<\n K extends string,\n S extends WithResourceTypes,\n>(\n queryKeyPrefix: K,\n apiService: S,\n options: CreateResourceHooksOptions<\n ListRaw<S>,\n Base<S>,\n SingleRaw<S>,\n Detail<S>\n > = {},\n) {\n // One safe cast: S is guaranteed to be a ResourceService subclass because\n // __types is only present on ResourceService. Casting here lets us call\n // the service methods with the correct types extracted from the phantom.\n const service = apiService as unknown as ResourceService<\n Id<S>,\n Base<S>,\n Detail<S>,\n Create<S>,\n Update<S>,\n ListRaw<S>,\n SingleRaw<S>\n >;\n\n const {\n adapters = createDefaultAdapters<Base<S>, Detail<S>>() as ResourceAdapters<\n TDefaultPaginatedResponse<Base<S>>,\n Base<S>,\n TDefaultSingleResponse<Detail<S>>,\n Detail<S>\n > as ResourceAdapters<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>,\n defaultParams = DEFAULT_PAGINATION,\n } = options;\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n const capPrefix = capitalize(queryKeyPrefix);\n\n return {\n // -------------------------------------------------------------------------\n // useList — paginated list with invalidation helper\n // -------------------------------------------------------------------------\n useList: (\n params: TQueryParams = defaultParams,\n ): UseListReturn<K, Base<S>> => {\n const queryClient = useQueryClient();\n const queryKey = [queryKeyPrefix, params];\n\n const query = useQuery<ResourceListResult<Base<S>>, Error>({\n queryKey,\n queryFn: async () => {\n const raw = await service.list(params);\n return adapters.fromList(raw as ListRaw<S>);\n },\n });\n\n const items = query.data?.items ?? [];\n const pagination: TPagination = query.data?.pagination ?? {\n page: 1,\n limit: defaultParams.limit ?? 10,\n totalPages: 0,\n totalDocuments: 0,\n };\n\n return {\n [queryKeyPrefix]: items,\n pagination,\n [`is${capPrefix}Loading`]: query.isLoading || query.isFetching,\n [`${queryKeyPrefix}Error`]: query.error,\n [`invalidate${capPrefix}`]: () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] }),\n } as UseListReturn<K, Base<S>>;\n },\n\n // -------------------------------------------------------------------------\n // useGet — single item by id\n // -------------------------------------------------------------------------\n useGet: (id: Id<S>, staleTime = 30_000) => {\n const query = useQuery<Detail<S>, Error>({\n queryKey: [queryKeyPrefix, id],\n queryFn: async () => {\n const raw = await service.get(id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n enabled: id !== undefined && id !== null && id !== \"\",\n staleTime,\n });\n\n return {\n data: query.data,\n isLoading: query.isLoading || query.isFetching,\n error: query.error,\n refetch: query.refetch,\n };\n },\n\n // -------------------------------------------------------------------------\n // useMutations — create / update / delete with auto-invalidation\n // -------------------------------------------------------------------------\n useMutations: () => {\n const queryClient = useQueryClient();\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });\n\n const createMutation = useMutation<Detail<S>, Error, Create<S>>({\n mutationFn: async (payload) => {\n const raw = await service.create(payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const updateMutation = useMutation<\n Detail<S>,\n Error,\n { _id: Id<S>; payload: Update<S> }\n >({\n mutationFn: async ({ _id, payload }) => {\n const raw = await service.update(_id, payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const deleteMutation = useMutation<Detail<S>, Error, Id<S>>({\n mutationFn: async (_id) => {\n const raw = await service.delete(_id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n return {\n create: createMutation,\n update: updateMutation,\n delete: deleteMutation,\n };\n },\n\n // -------------------------------------------------------------------------\n // useInfinite — infinite scroll / load more\n // -------------------------------------------------------------------------\n useInfinite: (params: TQueryParams = defaultParams) => {\n return useInfiniteQuery<ResourceListResult<Base<S>>, Error>({\n queryKey: [queryKeyPrefix, \"INFINITE\", params],\n queryFn: async ({ pageParam }) => {\n const raw = await service.list({\n ...params,\n page: pageParam as number,\n limit: params.limit ?? 20,\n });\n return adapters.fromList(raw as ListRaw<S>);\n },\n getNextPageParam: (lastPage) => {\n const { page, totalPages } = lastPage.pagination;\n return page < totalPages ? page + 1 : undefined;\n },\n initialPageParam: 1,\n });\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,yBAKO;AAUP,kBAAsC;AAUtC,IAAM,qBAAmC;AAAA,EACvC,MAAM;AAAA,EACN,OAAO;AACT;AA0HO,SAAS,oBAId,gBACA,YACA,UAKI,CAAC,GACL;AAIA,QAAM,UAAU;AAUhB,QAAM;AAAA,IACJ,eAAW,mCAA0C;AAAA,IAMrD,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,CAAC,QAClB,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAC3C,QAAM,YAAY,WAAW,cAAc;AAE3C,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,SAAS,CACP,SAAuB,kBACO;AApMpC;AAqMM,YAAM,kBAAc,mCAAe;AACnC,YAAM,WAAW,CAAC,gBAAgB,MAAM;AAExC,YAAM,YAAQ,6BAA6C;AAAA,QACzD;AAAA,QACA,SAAS,YAAY;AACnB,gBAAM,MAAM,MAAM,QAAQ,KAAK,MAAM;AACrC,iBAAO,SAAS,SAAS,GAAiB;AAAA,QAC5C;AAAA,MACF,CAAC;AAED,YAAM,SAAQ,iBAAM,SAAN,mBAAY,UAAZ,YAAqB,CAAC;AACpC,YAAM,cAA0B,iBAAM,SAAN,mBAAY,eAAZ,YAA0B;AAAA,QACxD,MAAM;AAAA,QACN,QAAO,mBAAc,UAAd,YAAuB;AAAA,QAC9B,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAClB;AAEA,aAAO;AAAA,QACL,CAAC,cAAc,GAAG;AAAA,QAClB;AAAA,QACA,CAAC,KAAK,SAAS,SAAS,GAAG,MAAM,aAAa,MAAM;AAAA,QACpD,CAAC,GAAG,cAAc,OAAO,GAAG,MAAM;AAAA,QAClC,CAAC,aAAa,SAAS,EAAE,GAAG,MAC1B,YAAY,kBAAkB,EAAE,UAAU,CAAC,cAAc,EAAE,CAAC;AAAA,MAChE;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,QAAQ,CAAC,IAAW,YAAY,QAAW;AACzC,YAAM,YAAQ,6BAA2B;AAAA,QACvC,UAAU,CAAC,gBAAgB,EAAE;AAAA,QAC7B,SAAS,YAAY;AACnB,gBAAM,MAAM,MAAM,QAAQ,IAAI,EAAE;AAChC,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,SAAS,OAAO,UAAa,OAAO,QAAQ,OAAO;AAAA,QACnD;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,MAAM,MAAM;AAAA,QACZ,WAAW,MAAM,aAAa,MAAM;AAAA,QACpC,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,cAAc,MAAM;AAClB,YAAM,kBAAc,mCAAe;AACnC,YAAM,aAAa,MACjB,YAAY,kBAAkB,EAAE,UAAU,CAAC,cAAc,EAAE,CAAC;AAE9D,YAAM,qBAAiB,gCAAyC;AAAA,QAC9D,YAAY,OAAO,YAAY;AAC7B,gBAAM,MAAM,MAAM,QAAQ,OAAO,OAAO;AACxC,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,YAAM,qBAAiB,gCAIrB;AAAA,QACA,YAAY,OAAO,EAAE,KAAK,QAAQ,MAAM;AACtC,gBAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,OAAO;AAC7C,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,YAAM,qBAAiB,gCAAqC;AAAA,QAC1D,YAAY,OAAO,QAAQ;AACzB,gBAAM,MAAM,MAAM,QAAQ,OAAO,GAAG;AACpC,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,aAAa,CAAC,SAAuB,kBAAkB;AACrD,iBAAO,qCAAqD;AAAA,QAC1D,UAAU,CAAC,gBAAgB,YAAY,MAAM;AAAA,QAC7C,SAAS,OAAO,EAAE,UAAU,MAAM;AAzS1C;AA0SU,gBAAM,MAAM,MAAM,QAAQ,KAAK;AAAA,YAC7B,GAAG;AAAA,YACH,MAAM;AAAA,YACN,QAAO,YAAO,UAAP,YAAgB;AAAA,UACzB,CAAC;AACD,iBAAO,SAAS,SAAS,GAAiB;AAAA,QAC5C;AAAA,QACA,kBAAkB,CAAC,aAAa;AAC9B,gBAAM,EAAE,MAAM,WAAW,IAAI,SAAS;AACtC,iBAAO,OAAO,aAAa,OAAO,IAAI;AAAA,QACxC;AAAA,QACA,kBAAkB;AAAA,MACpB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
package/dist/index.mjs CHANGED
@@ -11,6 +11,7 @@ var DEFAULT_PAGINATION = {
11
11
  limit: 10
12
12
  };
13
13
  function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
14
+ const service = apiService;
14
15
  const {
15
16
  adapters = createDefaultAdapters(),
16
17
  defaultParams = DEFAULT_PAGINATION
@@ -28,7 +29,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
28
29
  const query = useQuery({
29
30
  queryKey,
30
31
  queryFn: async () => {
31
- const raw = await apiService.list(params);
32
+ const raw = await service.list(params);
32
33
  return adapters.fromList(raw);
33
34
  }
34
35
  });
@@ -54,7 +55,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
54
55
  const query = useQuery({
55
56
  queryKey: [queryKeyPrefix, id],
56
57
  queryFn: async () => {
57
- const raw = await apiService.get(id);
58
+ const raw = await service.get(id);
58
59
  return adapters.fromSingle(raw);
59
60
  },
60
61
  enabled: id !== void 0 && id !== null && id !== "",
@@ -75,21 +76,21 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
75
76
  const invalidate = () => queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });
76
77
  const createMutation = useMutation({
77
78
  mutationFn: async (payload) => {
78
- const raw = await apiService.create(payload);
79
+ const raw = await service.create(payload);
79
80
  return adapters.fromSingle(raw);
80
81
  },
81
82
  onSuccess: invalidate
82
83
  });
83
84
  const updateMutation = useMutation({
84
85
  mutationFn: async ({ _id, payload }) => {
85
- const raw = await apiService.update(_id, payload);
86
+ const raw = await service.update(_id, payload);
86
87
  return adapters.fromSingle(raw);
87
88
  },
88
89
  onSuccess: invalidate
89
90
  });
90
91
  const deleteMutation = useMutation({
91
92
  mutationFn: async (_id) => {
92
- const raw = await apiService.delete(_id);
93
+ const raw = await service.delete(_id);
93
94
  return adapters.fromSingle(raw);
94
95
  },
95
96
  onSuccess: invalidate
@@ -108,7 +109,7 @@ function createResourceHooks(queryKeyPrefix, apiService, options = {}) {
108
109
  queryKey: [queryKeyPrefix, "INFINITE", params],
109
110
  queryFn: async ({ pageParam }) => {
110
111
  var _a;
111
- const raw = await apiService.list({
112
+ const raw = await service.list({
112
113
  ...params,
113
114
  page: pageParam,
114
115
  limit: (_a = params.limit) != null ? _a : 20
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/hooks/createResourceHooks.ts"],"sourcesContent":["import {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from \"@tanstack/react-query\";\nimport type { ResourceService } from \"@void-snippets/client\";\nimport type {\n ResourceAdapters,\n ResourceListResult,\n TPagination,\n TQueryParams,\n TDefaultPaginatedResponse,\n TDefaultSingleResponse,\n} from \"@void-snippets/core\";\nimport { createDefaultAdapters } from \"@void-snippets/core\";\n\n// ============================================================================\n// TYPE HELPERS\n// ============================================================================\n\ntype Capitalize<S extends string> = S extends `${infer F}${infer Rest}`\n ? `${Uppercase<F>}${Rest}`\n : S;\n\nconst DEFAULT_PAGINATION: Required<TQueryParams> = {\n page: 1,\n limit: 10,\n};\n\n// ============================================================================\n// SERVICE GENERIC EXTRACTOR\n// Instead of listing all 7 generics explicitly in createResourceHooks,\n// we capture the whole service as S and extract its generics here.\n// This is what enables full inference across package boundaries — TypeScript\n// only needs to match one type (S) rather than 7 simultaneously.\n//\n// AnyResourceService uses `unknown` (not `any`) as the upper bound —\n// unknown forces proper type narrowing downstream while remaining safe.\n// ============================================================================\n\ntype AnyResourceService = ResourceService<\n never, // TId — input (get/update/delete param) → contravariant → never\n unknown, // TBase — output (list response item) → covariant → unknown\n unknown, // TDetail — output (single response item) → covariant → unknown\n never, // TCreate — input (create payload) → contravariant → never\n never, // TUpdate — input (update payload) → contravariant → never\n unknown, // TListRaw — output → covariant → unknown\n unknown // TSingleRaw — output → covariant → unknown\n>;\n\ntype ServiceGenerics<S extends AnyResourceService> =\n S extends ResourceService<\n infer TId,\n infer TBase,\n infer TDetail,\n infer TCreate,\n infer TUpdate,\n infer TListRaw,\n infer TSingleRaw\n >\n ? {\n TId: TId;\n TBase: TBase;\n TDetail: TDetail;\n TCreate: TCreate;\n TUpdate: TUpdate;\n TListRaw: TListRaw;\n TSingleRaw: TSingleRaw;\n }\n : never;\n\n// Convenience aliases so usage inside the function body stays readable\ntype Id<S extends AnyResourceService> = ServiceGenerics<S>[\"TId\"];\ntype Base<S extends AnyResourceService> = ServiceGenerics<S>[\"TBase\"];\ntype Detail<S extends AnyResourceService> = ServiceGenerics<S>[\"TDetail\"];\ntype Create<S extends AnyResourceService> = ServiceGenerics<S>[\"TCreate\"];\ntype Update<S extends AnyResourceService> = ServiceGenerics<S>[\"TUpdate\"];\ntype ListRaw<S extends AnyResourceService> = ServiceGenerics<S>[\"TListRaw\"];\ntype SingleRaw<S extends AnyResourceService> = ServiceGenerics<S>[\"TSingleRaw\"];\n\n// ============================================================================\n// RETURN TYPE — useList\n// Dynamically named keys based on the queryKeyPrefix (e.g. \"contacts\"):\n// { contacts: TBase[], isContactsLoading: boolean, contactsError: Error | null, invalidateContacts: () => void }\n// ============================================================================\n\nexport type UseListReturn<K extends string, TBase> = {\n [P in K]: TBase[];\n} & {\n pagination: TPagination;\n} & {\n [P in `is${Capitalize<K>}Loading`]: boolean;\n} & {\n [P in `${K}Error`]: Error | null;\n} & {\n [P in `invalidate${Capitalize<K>}`]: () => void;\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport interface CreateResourceHooksOptions<\n TListRaw,\n TBase,\n TSingleRaw,\n TDetail,\n> {\n /**\n * Adapters let you map your API's raw response shapes to the library's\n * internal format. If your API matches the default shape\n * ({ data: { items, page, limit, totalPages, totalDocuments } })\n * you can omit this entirely.\n *\n * @example\n * createResourceHooks(\"contacts\", ContactsApis, {\n * adapters: {\n * fromList: (raw) => ({\n * items: raw.results,\n * pagination: { page: raw.page, limit: raw.perPage, totalPages: raw.pages, totalDocuments: raw.total },\n * }),\n * fromSingle: (raw) => raw.payload,\n * }\n * })\n */\n adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;\n\n /**\n * Default params passed to useList and useInfinite when none are provided.\n * @default { page: 1, limit: 10 }\n */\n defaultParams?: TQueryParams;\n}\n\n// ============================================================================\n// FACTORY\n// ============================================================================\n\n/**\n * Creates a set of TanStack Query hooks for a resource.\n * All types are fully inferred from the `apiService` instance — no need to\n * pass generics manually.\n *\n * @param queryKeyPrefix - Used as the TanStack Query cache key prefix AND\n * to name the returned hook properties dynamically.\n * e.g. \"contacts\" → { contacts, isContactsLoading, ... }\n * @param apiService - An instance of ResourceService (or a subclass).\n * @param options - Optional adapters and default params.\n *\n * @example\n * // contacts.hooks.ts\n * import { createResourceHooks } from '@void-snippets/react';\n * import { ContactsApis } from './contacts.api';\n *\n * // No generics needed — everything is inferred from ContactsApis\n * export const contactHooks = createResourceHooks('contacts', ContactsApis);\n *\n * // In a component:\n * const { contacts, isContactsLoading } = contactHooks.useList();\n * const { data } = contactHooks.useGet(id); // data typed as Contact.WithCreatedBy\n * const { create, update, delete: deleteContact } = contactHooks.useMutations();\n */\nexport function createResourceHooks<\n K extends string,\n S extends AnyResourceService,\n>(\n queryKeyPrefix: K,\n apiService: S,\n options: CreateResourceHooksOptions<\n ListRaw<S>,\n Base<S>,\n SingleRaw<S>,\n Detail<S>\n > = {},\n) {\n const {\n adapters = createDefaultAdapters<Base<S>, Detail<S>>() as ResourceAdapters<\n TDefaultPaginatedResponse<Base<S>>,\n Base<S>,\n TDefaultSingleResponse<Detail<S>>,\n Detail<S>\n > as ResourceAdapters<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>,\n defaultParams = DEFAULT_PAGINATION,\n } = options;\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n const capPrefix = capitalize(queryKeyPrefix);\n\n return {\n // -------------------------------------------------------------------------\n // useList — paginated list with invalidation helper\n // -------------------------------------------------------------------------\n useList: (\n params: TQueryParams = defaultParams,\n ): UseListReturn<K, Base<S>> => {\n const queryClient = useQueryClient();\n const queryKey = [queryKeyPrefix, params];\n\n const query = useQuery<ResourceListResult<Base<S>>, Error>({\n queryKey,\n queryFn: async () => {\n const raw = await apiService.list(params);\n return adapters.fromList(raw as ListRaw<S>);\n },\n });\n\n const items = query.data?.items ?? [];\n const pagination: TPagination = query.data?.pagination ?? {\n page: 1,\n limit: defaultParams.limit ?? 10,\n totalPages: 0,\n totalDocuments: 0,\n };\n\n return {\n [queryKeyPrefix]: items,\n pagination,\n [`is${capPrefix}Loading`]: query.isLoading || query.isFetching,\n [`${queryKeyPrefix}Error`]: query.error,\n [`invalidate${capPrefix}`]: () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] }),\n } as UseListReturn<K, Base<S>>;\n },\n\n // -------------------------------------------------------------------------\n // useGet — single item by id\n // -------------------------------------------------------------------------\n useGet: (id: Id<S>, staleTime = 30_000) => {\n const query = useQuery<Detail<S>, Error>({\n queryKey: [queryKeyPrefix, id],\n queryFn: async () => {\n const raw = await apiService.get(id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n enabled: id !== undefined && id !== null && id !== \"\",\n staleTime,\n });\n\n return {\n data: query.data,\n isLoading: query.isLoading || query.isFetching,\n error: query.error,\n refetch: query.refetch,\n };\n },\n\n // -------------------------------------------------------------------------\n // useMutations — create / update / delete with auto-invalidation\n // -------------------------------------------------------------------------\n useMutations: () => {\n const queryClient = useQueryClient();\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });\n\n const createMutation = useMutation<Detail<S>, Error, Create<S>>({\n mutationFn: async (payload) => {\n const raw = await apiService.create(payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const updateMutation = useMutation<\n Detail<S>,\n Error,\n { _id: Id<S>; payload: Update<S> }\n >({\n mutationFn: async ({ _id, payload }) => {\n const raw = await apiService.update(_id, payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const deleteMutation = useMutation<Detail<S>, Error, Id<S>>({\n mutationFn: async (_id) => {\n const raw = await apiService.delete(_id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n return {\n create: createMutation,\n update: updateMutation,\n delete: deleteMutation,\n };\n },\n\n // -------------------------------------------------------------------------\n // useInfinite — infinite scroll / load more\n // -------------------------------------------------------------------------\n useInfinite: (params: TQueryParams = defaultParams) => {\n return useInfiniteQuery<ResourceListResult<Base<S>>, Error>({\n queryKey: [queryKeyPrefix, \"INFINITE\", params],\n queryFn: async ({ pageParam }) => {\n const raw = await apiService.list({\n ...params,\n page: pageParam as number,\n limit: params.limit ?? 20,\n });\n return adapters.fromList(raw as ListRaw<S>);\n },\n getNextPageParam: (lastPage) => {\n const { page, totalPages } = lastPage.pagination;\n return page < totalPages ? page + 1 : undefined;\n },\n initialPageParam: 1,\n });\n },\n };\n}\n"],"mappings":";AAAA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AAUP,SAAS,6BAA6B;AAUtC,IAAM,qBAA6C;AAAA,EACjD,MAAM;AAAA,EACN,OAAO;AACT;AAuIO,SAAS,oBAId,gBACA,YACA,UAKI,CAAC,GACL;AACA,QAAM;AAAA,IACJ,WAAW,sBAA0C;AAAA,IAMrD,gBAAgB;AAAA,EAClB,IAAI;AAEJ,QAAM,aAAa,CAAC,QAClB,IAAI,OAAO,CAAC,EAAE,YAAY,IAAI,IAAI,MAAM,CAAC;AAC3C,QAAM,YAAY,WAAW,cAAc;AAE3C,SAAO;AAAA;AAAA;AAAA;AAAA,IAIL,SAAS,CACP,SAAuB,kBACO;AApMpC;AAqMM,YAAM,cAAc,eAAe;AACnC,YAAM,WAAW,CAAC,gBAAgB,MAAM;AAExC,YAAM,QAAQ,SAA6C;AAAA,QACzD;AAAA,QACA,SAAS,YAAY;AACnB,gBAAM,MAAM,MAAM,WAAW,KAAK,MAAM;AACxC,iBAAO,SAAS,SAAS,GAAiB;AAAA,QAC5C;AAAA,MACF,CAAC;AAED,YAAM,SAAQ,iBAAM,SAAN,mBAAY,UAAZ,YAAqB,CAAC;AACpC,YAAM,cAA0B,iBAAM,SAAN,mBAAY,eAAZ,YAA0B;AAAA,QACxD,MAAM;AAAA,QACN,QAAO,mBAAc,UAAd,YAAuB;AAAA,QAC9B,YAAY;AAAA,QACZ,gBAAgB;AAAA,MAClB;AAEA,aAAO;AAAA,QACL,CAAC,cAAc,GAAG;AAAA,QAClB;AAAA,QACA,CAAC,KAAK,SAAS,SAAS,GAAG,MAAM,aAAa,MAAM;AAAA,QACpD,CAAC,GAAG,cAAc,OAAO,GAAG,MAAM;AAAA,QAClC,CAAC,aAAa,SAAS,EAAE,GAAG,MAC1B,YAAY,kBAAkB,EAAE,UAAU,CAAC,cAAc,EAAE,CAAC;AAAA,MAChE;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,QAAQ,CAAC,IAAW,YAAY,QAAW;AACzC,YAAM,QAAQ,SAA2B;AAAA,QACvC,UAAU,CAAC,gBAAgB,EAAE;AAAA,QAC7B,SAAS,YAAY;AACnB,gBAAM,MAAM,MAAM,WAAW,IAAI,EAAE;AACnC,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,SAAS,OAAO,UAAa,OAAO,QAAQ,OAAO;AAAA,QACnD;AAAA,MACF,CAAC;AAED,aAAO;AAAA,QACL,MAAM,MAAM;AAAA,QACZ,WAAW,MAAM,aAAa,MAAM;AAAA,QACpC,OAAO,MAAM;AAAA,QACb,SAAS,MAAM;AAAA,MACjB;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,cAAc,MAAM;AAClB,YAAM,cAAc,eAAe;AACnC,YAAM,aAAa,MACjB,YAAY,kBAAkB,EAAE,UAAU,CAAC,cAAc,EAAE,CAAC;AAE9D,YAAM,iBAAiB,YAAyC;AAAA,QAC9D,YAAY,OAAO,YAAY;AAC7B,gBAAM,MAAM,MAAM,WAAW,OAAO,OAAO;AAC3C,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,YAAM,iBAAiB,YAIrB;AAAA,QACA,YAAY,OAAO,EAAE,KAAK,QAAQ,MAAM;AACtC,gBAAM,MAAM,MAAM,WAAW,OAAO,KAAK,OAAO;AAChD,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,YAAM,iBAAiB,YAAqC;AAAA,QAC1D,YAAY,OAAO,QAAQ;AACzB,gBAAM,MAAM,MAAM,WAAW,OAAO,GAAG;AACvC,iBAAO,SAAS,WAAW,GAAmB;AAAA,QAChD;AAAA,QACA,WAAW;AAAA,MACb,CAAC;AAED,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ;AAAA,MACV;AAAA,IACF;AAAA;AAAA;AAAA;AAAA,IAKA,aAAa,CAAC,SAAuB,kBAAkB;AACrD,aAAO,iBAAqD;AAAA,QAC1D,UAAU,CAAC,gBAAgB,YAAY,MAAM;AAAA,QAC7C,SAAS,OAAO,EAAE,UAAU,MAAM;AAzS1C;AA0SU,gBAAM,MAAM,MAAM,WAAW,KAAK;AAAA,YAChC,GAAG;AAAA,YACH,MAAM;AAAA,YACN,QAAO,YAAO,UAAP,YAAgB;AAAA,UACzB,CAAC;AACD,iBAAO,SAAS,SAAS,GAAiB;AAAA,QAC5C;AAAA,QACA,kBAAkB,CAAC,aAAa;AAC9B,gBAAM,EAAE,MAAM,WAAW,IAAI,SAAS;AACtC,iBAAO,OAAO,aAAa,OAAO,IAAI;AAAA,QACxC;AAAA,QACA,kBAAkB;AAAA,MACpB,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../src/hooks/createResourceHooks.ts"],"sourcesContent":["import {\n useInfiniteQuery,\n useMutation,\n useQuery,\n useQueryClient,\n} from \"@tanstack/react-query\";\nimport type { ResourceService } from \"@void-snippets/client\";\nimport type {\n ResourceAdapters,\n ResourceListResult,\n TPagination,\n TQueryParams,\n TDefaultPaginatedResponse,\n TDefaultSingleResponse,\n} from \"@void-snippets/core\";\nimport { createDefaultAdapters } from \"@void-snippets/core\";\n\n// ============================================================================\n// TYPE HELPERS\n// ============================================================================\n\ntype CapitalizeStr<S extends string> = S extends `${infer F}${infer Rest}`\n ? `${Uppercase<F>}${Rest}`\n : S;\n\nconst DEFAULT_PAGINATION: TQueryParams = {\n page: 1,\n limit: 10,\n};\n\n// ============================================================================\n// PHANTOM TYPE CONSTRAINT\n//\n// We constrain S only on the __types phantom property (which is readonly,\n// i.e. covariant) so that ResourceService<string, ...> is assignable to\n// WithResourceTypes. This avoids the variance issue that arises when\n// constraining against method parameter types (contravariant positions).\n//\n// Inside the function we cast apiService once to a fully-typed ResourceService\n// using the phantom generics — this is safe because S IS a ResourceService.\n// ============================================================================\n\ninterface WithResourceTypes {\n readonly __types: {\n id: unknown;\n base: unknown;\n detail: unknown;\n create: unknown;\n update: unknown;\n listRaw: unknown;\n singleRaw: unknown;\n };\n}\n\n// Aliases for reading phantom types from S — keeps the function body readable\ntype Id<S extends WithResourceTypes> = S[\"__types\"][\"id\"];\ntype Base<S extends WithResourceTypes> = S[\"__types\"][\"base\"];\ntype Detail<S extends WithResourceTypes> = S[\"__types\"][\"detail\"];\ntype Create<S extends WithResourceTypes> = S[\"__types\"][\"create\"];\ntype Update<S extends WithResourceTypes> = S[\"__types\"][\"update\"];\ntype ListRaw<S extends WithResourceTypes> = S[\"__types\"][\"listRaw\"];\ntype SingleRaw<S extends WithResourceTypes> = S[\"__types\"][\"singleRaw\"];\n\n// ============================================================================\n// RETURN TYPE — useList\n// Dynamically named keys based on the queryKeyPrefix (e.g. \"contacts\"):\n// { contacts: TBase[], isContactsLoading: boolean, contactsError: Error | null, invalidateContacts: () => void }\n// ============================================================================\n\nexport type UseListReturn<K extends string, TBase> = {\n [P in K]: TBase[];\n} & {\n pagination: TPagination;\n} & {\n [P in `is${CapitalizeStr<K>}Loading`]: boolean;\n} & {\n [P in `${K}Error`]: Error | null;\n} & {\n [P in `invalidate${CapitalizeStr<K>}`]: () => void;\n};\n\n// ============================================================================\n// OPTIONS\n// ============================================================================\n\nexport interface CreateResourceHooksOptions<\n TListRaw,\n TBase,\n TSingleRaw,\n TDetail,\n> {\n /**\n * Adapters map your API's raw response shapes to the library's internal\n * format. Omit this entirely if your API matches the default shape:\n * List: { data: { items, page, limit, totalPages, totalDocuments } }\n * Single: { data: <item> }\n *\n * @example\n * createResourceHooks(\"contacts\", ContactsApis, {\n * adapters: {\n * fromList: (raw) => ({\n * items: raw.results,\n * pagination: {\n * page: raw.meta.page,\n * limit: raw.meta.perPage,\n * totalPages: raw.meta.lastPage,\n * totalDocuments: raw.meta.total,\n * },\n * }),\n * fromSingle: (raw) => raw.payload,\n * },\n * })\n */\n adapters?: ResourceAdapters<TListRaw, TBase, TSingleRaw, TDetail>;\n\n /**\n * Default params passed to useList and useInfinite when none are provided.\n * @default { page: 1, limit: 10 }\n */\n defaultParams?: TQueryParams;\n}\n\n// ============================================================================\n// FACTORY\n// ============================================================================\n\n/**\n * Creates a set of TanStack Query hooks for a resource.\n * All types are fully inferred from the `apiService` instance — no generics\n * need to be passed manually.\n *\n * @param queryKeyPrefix - TanStack Query cache key prefix and the base name\n * for the returned hook properties.\n * e.g. \"contacts\" → { contacts, isContactsLoading, ... }\n * @param apiService - An instance of ResourceService (or a subclass).\n * @param options - Optional adapters and default params.\n *\n * @example\n * // contacts.hooks.ts\n * import { createResourceHooks } from '@void-snippets/react';\n * import { ContactsApis } from './contacts.api';\n *\n * // No generics needed — all types are inferred from ContactsApis\n * export const contactHooks = createResourceHooks('contacts', ContactsApis);\n *\n * // In a component:\n * const { contacts, isContactsLoading } = contactHooks.useList();\n * const { data } = contactHooks.useGet(id); // data: Contact.WithCreatedBy\n * const { create, update, delete: remove } = contactHooks.useMutations();\n */\nexport function createResourceHooks<\n K extends string,\n S extends WithResourceTypes,\n>(\n queryKeyPrefix: K,\n apiService: S,\n options: CreateResourceHooksOptions<\n ListRaw<S>,\n Base<S>,\n SingleRaw<S>,\n Detail<S>\n > = {},\n) {\n // One safe cast: S is guaranteed to be a ResourceService subclass because\n // __types is only present on ResourceService. Casting here lets us call\n // the service methods with the correct types extracted from the phantom.\n const service = apiService as unknown as ResourceService<\n Id<S>,\n Base<S>,\n Detail<S>,\n Create<S>,\n Update<S>,\n ListRaw<S>,\n SingleRaw<S>\n >;\n\n const {\n adapters = createDefaultAdapters<Base<S>, Detail<S>>() as ResourceAdapters<\n TDefaultPaginatedResponse<Base<S>>,\n Base<S>,\n TDefaultSingleResponse<Detail<S>>,\n Detail<S>\n > as ResourceAdapters<ListRaw<S>, Base<S>, SingleRaw<S>, Detail<S>>,\n defaultParams = DEFAULT_PAGINATION,\n } = options;\n\n const capitalize = (str: string) =>\n str.charAt(0).toUpperCase() + str.slice(1);\n const capPrefix = capitalize(queryKeyPrefix);\n\n return {\n // -------------------------------------------------------------------------\n // useList — paginated list with invalidation helper\n // -------------------------------------------------------------------------\n useList: (\n params: TQueryParams = defaultParams,\n ): UseListReturn<K, Base<S>> => {\n const queryClient = useQueryClient();\n const queryKey = [queryKeyPrefix, params];\n\n const query = useQuery<ResourceListResult<Base<S>>, Error>({\n queryKey,\n queryFn: async () => {\n const raw = await service.list(params);\n return adapters.fromList(raw as ListRaw<S>);\n },\n });\n\n const items = query.data?.items ?? [];\n const pagination: TPagination = query.data?.pagination ?? {\n page: 1,\n limit: defaultParams.limit ?? 10,\n totalPages: 0,\n totalDocuments: 0,\n };\n\n return {\n [queryKeyPrefix]: items,\n pagination,\n [`is${capPrefix}Loading`]: query.isLoading || query.isFetching,\n [`${queryKeyPrefix}Error`]: query.error,\n [`invalidate${capPrefix}`]: () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] }),\n } as UseListReturn<K, Base<S>>;\n },\n\n // -------------------------------------------------------------------------\n // useGet — single item by id\n // -------------------------------------------------------------------------\n useGet: (id: Id<S>, staleTime = 30_000) => {\n const query = useQuery<Detail<S>, Error>({\n queryKey: [queryKeyPrefix, id],\n queryFn: async () => {\n const raw = await service.get(id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n enabled: id !== undefined && id !== null && id !== \"\",\n staleTime,\n });\n\n return {\n data: query.data,\n isLoading: query.isLoading || query.isFetching,\n error: query.error,\n refetch: query.refetch,\n };\n },\n\n // -------------------------------------------------------------------------\n // useMutations — create / update / delete with auto-invalidation\n // -------------------------------------------------------------------------\n useMutations: () => {\n const queryClient = useQueryClient();\n const invalidate = () =>\n queryClient.invalidateQueries({ queryKey: [queryKeyPrefix] });\n\n const createMutation = useMutation<Detail<S>, Error, Create<S>>({\n mutationFn: async (payload) => {\n const raw = await service.create(payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const updateMutation = useMutation<\n Detail<S>,\n Error,\n { _id: Id<S>; payload: Update<S> }\n >({\n mutationFn: async ({ _id, payload }) => {\n const raw = await service.update(_id, payload);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n const deleteMutation = useMutation<Detail<S>, Error, Id<S>>({\n mutationFn: async (_id) => {\n const raw = await service.delete(_id);\n return adapters.fromSingle(raw as SingleRaw<S>);\n },\n onSuccess: invalidate,\n });\n\n return {\n create: createMutation,\n update: updateMutation,\n delete: deleteMutation,\n };\n },\n\n // -------------------------------------------------------------------------\n // useInfinite — infinite scroll / load more\n // -------------------------------------------------------------------------\n useInfinite: (params: TQueryParams = defaultParams) => {\n return useInfiniteQuery<ResourceListResult<Base<S>>, Error>({\n queryKey: [queryKeyPrefix, \"INFINITE\", params],\n queryFn: async ({ pageParam }) => {\n const raw = await service.list({\n ...params,\n page: pageParam as number,\n limit: params.limit ?? 20,\n });\n return adapters.fromList(raw as ListRaw<S>);\n },\n getNextPageParam: (lastPage) => {\n const { page, totalPages } = lastPage.pagination;\n return page < totalPages ? page + 1 : undefined;\n },\n initialPageParam: 1,\n });\n },\n };\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@void-snippets/react",
3
- "version": "0.1.6",
3
+ "version": "0.2.0",
4
4
  "description": "TanStack Query resource hooks factory for React",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -26,11 +26,11 @@
26
26
  "license": "MIT",
27
27
  "peerDependencies": {
28
28
  "react": ">=17.0.0",
29
- "@void-snippets/client": "0.1.3"
29
+ "@void-snippets/client": ">=0.1.0"
30
30
  },
31
31
  "dependencies": {
32
32
  "@tanstack/react-query": "^5.0.0",
33
- "@void-snippets/core": "0.1.3"
33
+ "@void-snippets/core": "0.2.0"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/react": "^18.0.0",