@modelence/react-query 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,214 @@
1
+ # @modelence/react-query
2
+
3
+ React Query utilities for Modelence method calls.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm i @modelence/react-query @tanstack/react-query
9
+ ```
10
+
11
+ ## Overview
12
+
13
+ This package provides `getQueryOptions` and `getMutationOptions` factory functions that can be used with TanStack Query's native `useQuery` and `useMutation` hooks. This approach, recommended by TanStack, gives you direct access to TanStack Query's full API while providing Modelence-specific query configurations.
14
+
15
+ ## Usage
16
+
17
+ ### Basic Query
18
+
19
+ ```tsx
20
+ import { useQuery } from '@tanstack/react-query';
21
+ import { getQueryOptions } from '@modelence/react-query';
22
+
23
+ function TodoList() {
24
+ const { data, isPending, error } = useQuery(
25
+ getQueryOptions('todo.getAll', { limit: 10 })
26
+ );
27
+
28
+ if (isPending) return <div>Loading...</div>;
29
+ if (error) return <div>Error: {error.message}</div>;
30
+
31
+ return (
32
+ <div>
33
+ {data?.map(todo => (
34
+ <div key={todo.id}>{todo.title}</div>
35
+ ))}
36
+ </div>
37
+ );
38
+ }
39
+ ```
40
+
41
+ ### Basic Mutation
42
+
43
+ ```tsx
44
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
45
+ import { getMutationOptions } from '@modelence/react-query';
46
+
47
+ function CreateTodo() {
48
+ const queryClient = useQueryClient();
49
+
50
+ const { mutate: createTodo, isPending } = useMutation({
51
+ ...getMutationOptions('todo.create'),
52
+ onSuccess: () => {
53
+ // Invalidate and refetch todos
54
+ queryClient.invalidateQueries({ queryKey: ['todo.getAll'] });
55
+ },
56
+ });
57
+
58
+ return (
59
+ <button
60
+ onClick={() => createTodo({ title: 'New Todo', completed: false })}
61
+ disabled={isPending}
62
+ >
63
+ {isPending ? 'Creating...' : 'Create Todo'}
64
+ </button>
65
+ );
66
+ }
67
+ ```
68
+
69
+ ### Advanced Usage
70
+
71
+ #### Query with Additional Options
72
+
73
+ ```tsx
74
+ import { useQuery } from '@tanstack/react-query';
75
+ import { getQueryOptions } from '@modelence/react-query';
76
+
77
+ function TodoDetail({ id }: { id: string }) {
78
+ const { data: todo } = useQuery({
79
+ ...getQueryOptions('todo.getById', { id }),
80
+ enabled: !!id, // Only run query if id exists
81
+ staleTime: 5 * 60 * 1000, // 5 minutes
82
+ refetchOnWindowFocus: false,
83
+ });
84
+
85
+ return <div>{todo?.title}</div>;
86
+ }
87
+ ```
88
+
89
+ #### Mutation with Default Args
90
+
91
+ ```tsx
92
+ import { useMutation } from '@tanstack/react-query';
93
+ import { getMutationOptions } from '@modelence/react-query';
94
+
95
+ function UpdateTodo({ todoId }: { todoId: string }) {
96
+ const { mutate: updateTodo } = useMutation({
97
+ ...getMutationOptions('todo.update', { id: todoId }), // Default args
98
+ onSuccess: (data) => {
99
+ console.log('Todo updated:', data);
100
+ },
101
+ });
102
+
103
+ return (
104
+ <button onClick={() => updateTodo({ title: 'Updated Title' })}>
105
+ Update Todo
106
+ </button>
107
+ );
108
+ }
109
+ ```
110
+
111
+ #### Manual Cache Operations
112
+
113
+ ```tsx
114
+ import { useQueryClient } from '@tanstack/react-query';
115
+ import { createQueryKey, getQueryOptions } from '@modelence/react-query';
116
+
117
+ function TodoActions() {
118
+ const queryClient = useQueryClient();
119
+
120
+ const refreshTodos = () => {
121
+ queryClient.invalidateQueries({
122
+ queryKey: createQueryKey('todo.getAll', { limit: 10 })
123
+ });
124
+ };
125
+
126
+ const prefetchTodo = (id: string) => {
127
+ queryClient.prefetchQuery({
128
+ ...getQueryOptions('todo.getById', { id }),
129
+ staleTime: 10 * 60 * 1000, // 10 minutes
130
+ });
131
+ };
132
+
133
+ return (
134
+ <div>
135
+ <button onClick={refreshTodos}>Refresh Todos</button>
136
+ <button onClick={() => prefetchTodo('123')}>Prefetch Todo</button>
137
+ </div>
138
+ );
139
+ }
140
+ ```
141
+
142
+ ## API Reference
143
+
144
+ ### `getQueryOptions<T>(methodName, args?)`
145
+
146
+ Creates a query configuration object for use with TanStack Query's `useQuery`.
147
+
148
+ **Parameters:**
149
+ - `methodName` (string): The Modelence method name (e.g., 'todo.getAll')
150
+ - `args` (object, optional): Arguments to pass to the method
151
+
152
+ **Returns:** Query configuration object with `queryKey` and `queryFn`
153
+
154
+ ### `getMutationOptions<T, TVariables>(methodName, defaultArgs?)`
155
+
156
+ Creates a mutation configuration object for use with TanStack Query's `useMutation`.
157
+
158
+ **Parameters:**
159
+ - `methodName` (string): The Modelence method name (e.g., 'todo.create')
160
+ - `defaultArgs` (object, optional): Default arguments merged with mutation variables
161
+
162
+ **Returns:** Mutation configuration object with `mutationFn`
163
+
164
+ ### `createQueryKey<T, U>(methodName, args?)`
165
+
166
+ Utility function to create typed query keys for manual cache operations.
167
+
168
+ **Parameters:**
169
+ - `methodName` (T): The method name
170
+ - `args` (U, optional): The arguments
171
+
172
+ **Returns:** Typed query key array
173
+
174
+ ## Migration from Modelence's useQuery/useMutation
175
+
176
+ ### Before
177
+
178
+ ```tsx
179
+ import { useQuery, useMutation } from 'modelence/client';
180
+
181
+ function TodoComponent() {
182
+ const { data, isFetching, error } = useQuery('todo.getAll');
183
+ const { mutate: createTodo } = useMutation('todo.create');
184
+
185
+ // ...
186
+ }
187
+ ```
188
+
189
+ ### After
190
+
191
+ ```tsx
192
+ import { useQuery, useMutation } from '@tanstack/react-query';
193
+ import { getQueryOptions, getMutationOptions } from '@modelence/react-query';
194
+
195
+ function TodoComponent() {
196
+ const { data, isPending: isFetching, error } = useQuery(
197
+ getQueryOptions('todo.getAll')
198
+ );
199
+ const { mutate: createTodo } = useMutation(
200
+ getMutationOptions('todo.create')
201
+ );
202
+
203
+ // ...
204
+ }
205
+ ```
206
+
207
+ ## Benefits
208
+
209
+ 1. **Full TanStack Query API**: Access to all TanStack Query features and options
210
+ 2. **Simple and Explicit**: Clear separation between Modelence configuration and TanStack Query options
211
+ 3. **Better TypeScript Support**: Improved type inference and safety
212
+ 4. **Familiar API**: Standard TanStack Query patterns that developers already know
213
+ 5. **Future-Proof**: Easy to adopt new TanStack Query features as they're released
214
+ 6. **Composability**: Easy to combine with other TanStack Query utilities
@@ -0,0 +1,97 @@
1
+ type Args = Record<string, unknown>;
2
+ /**
3
+ * Creates query options for use with TanStack Query's useQuery hook.
4
+ *
5
+ * @typeParam T - The expected return type of the query
6
+ * @param methodName - The name of the method to query
7
+ * @param args - Optional arguments to pass to the method
8
+ * @returns Query options object for TanStack Query's useQuery
9
+ *
10
+ * @example
11
+ * ```tsx
12
+ * import { useQuery } from '@tanstack/react-query';
13
+ * import { modelenceQuery } from '@modelence/react-query';
14
+ *
15
+ * function MyComponent() {
16
+ * // Basic usage
17
+ * const { data } = useQuery(modelenceQuery('todo.getAll'));
18
+ *
19
+ * // With additional options
20
+ * const { data: todo } = useQuery({
21
+ * ...modelenceQuery('todo.getById', { id: '123' }),
22
+ * enabled: !!id,
23
+ * staleTime: 5 * 60 * 1000,
24
+ * });
25
+ *
26
+ * return <div>{data?.name}</div>;
27
+ * }
28
+ * ```
29
+ */
30
+ declare function modelenceQuery<T = unknown>(methodName: string, args?: Args): {
31
+ queryKey: (string | Args)[];
32
+ queryFn: () => Promise<T>;
33
+ };
34
+ /**
35
+ * Creates mutation options for use with TanStack Query's useMutation hook.
36
+ *
37
+ * @typeParam T - The expected return type of the mutation
38
+ * @param methodName - The name of the method to mutate
39
+ * @param defaultArgs - Optional default arguments to merge with mutation variables
40
+ * @returns Mutation options object for TanStack Query's useMutation
41
+ *
42
+ * @example
43
+ * ```tsx
44
+ * import { useMutation, useQueryClient } from '@tanstack/react-query';
45
+ * import { modelenceMutation } from '@modelence/react-query';
46
+ *
47
+ * function MyComponent() {
48
+ * const queryClient = useQueryClient();
49
+ *
50
+ * // Basic usage
51
+ * const { mutate } = useMutation(modelenceMutation('todos.create'));
52
+ *
53
+ * // With additional options
54
+ * const { mutate: updateTodo } = useMutation({
55
+ * ...modelenceMutation('todos.update'),
56
+ * onSuccess: () => {
57
+ * queryClient.invalidateQueries({ queryKey: ['todos.getAll'] });
58
+ * },
59
+ * });
60
+ *
61
+ * return <button onClick={() => mutate({ title: 'New Todo' })}>Create</button>;
62
+ * }
63
+ * ```
64
+ */
65
+ declare function modelenceMutation<T = unknown>(methodName: string, defaultArgs?: Args): {
66
+ mutationFn: (variables?: Args) => Promise<T>;
67
+ };
68
+ /**
69
+ * Type helper for creating properly typed query keys
70
+ */
71
+ type ModelenceQueryKey<T extends string, U extends Args = Args> = readonly [T, U];
72
+ /**
73
+ * Utility function to create query keys for manual cache operations
74
+ *
75
+ * @param methodName - The method name
76
+ * @param args - The arguments
77
+ * @returns Typed query key
78
+ *
79
+ * @example
80
+ * ```tsx
81
+ * import { useQueryClient } from '@tanstack/react-query';
82
+ * import { createQueryKey } from '@modelence/react-query';
83
+ *
84
+ * function TodoActions() {
85
+ * const queryClient = useQueryClient();
86
+ *
87
+ * const refreshTodos = () => {
88
+ * queryClient.invalidateQueries({
89
+ * queryKey: createQueryKey('todo.getAll', { limit: 10 })
90
+ * });
91
+ * };
92
+ * }
93
+ * ```
94
+ */
95
+ declare function createQueryKey<T extends string, U extends Args = Args>(methodName: T, args?: U): ModelenceQueryKey<T, U>;
96
+
97
+ export { type ModelenceQueryKey, createQueryKey, modelenceMutation, modelenceQuery };
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ import {callMethod}from'modelence/client';function s(e,n={}){return {queryKey:[e,n],queryFn:()=>callMethod(e,n)}}function u(e,n={}){return {mutationFn:(t={})=>callMethod(e,{...n,...t})}}function y(e,n={}){return [e,n]}export{y as createQueryKey,u as modelenceMutation,s as modelenceQuery};//# sourceMappingURL=index.js.map
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["modelenceQuery","methodName","args","callMethod","modelenceMutation","defaultArgs","variables","createQueryKey"],"mappings":"0CAgCO,SAASA,CACdC,CAAAA,CAAAA,CACAC,CAAa,CAAA,EACb,CAAA,CACA,OAAO,CACL,QAAU,CAAA,CAACD,CAAYC,CAAAA,CAAI,CAC3B,CAAA,OAAA,CAAS,IAAMC,UAAAA,CAAcF,CAAYC,CAAAA,CAAI,CAC/C,CACF,CAiCO,SAASE,CACdH,CAAAA,CAAAA,CACAI,CAAoB,CAAA,EACpB,CAAA,CACA,OAAO,CACL,UAAY,CAAA,CAACC,CAAkB,CAAA,EAAOH,GAAAA,UAAAA,CAAcF,CAAY,CAAA,CAAE,GAAGI,CAAAA,CAAa,GAAGC,CAAU,CAAC,CAClG,CACF,CA8BO,SAASC,CAAAA,CACdN,CACAC,CAAAA,CAAAA,CAAU,EAAC,CACc,CACzB,OAAO,CAACD,CAAAA,CAAYC,CAAI,CAC1B","file":"index.js","sourcesContent":["import { callMethod } from 'modelence/client';\n\ntype Args = Record<string, unknown>;\n\n/**\n * Creates query options for use with TanStack Query's useQuery hook.\n * \n * @typeParam T - The expected return type of the query\n * @param methodName - The name of the method to query\n * @param args - Optional arguments to pass to the method\n * @returns Query options object for TanStack Query's useQuery\n * \n * @example\n * ```tsx\n * import { useQuery } from '@tanstack/react-query';\n * import { modelenceQuery } from '@modelence/react-query';\n * \n * function MyComponent() {\n * // Basic usage\n * const { data } = useQuery(modelenceQuery('todo.getAll'));\n * \n * // With additional options\n * const { data: todo } = useQuery({\n * ...modelenceQuery('todo.getById', { id: '123' }),\n * enabled: !!id,\n * staleTime: 5 * 60 * 1000,\n * });\n * \n * return <div>{data?.name}</div>;\n * }\n * ```\n */\nexport function modelenceQuery<T = unknown>(\n methodName: string, \n args: Args = {}\n) {\n return {\n queryKey: [methodName, args],\n queryFn: () => callMethod<T>(methodName, args),\n };\n}\n\n/**\n * Creates mutation options for use with TanStack Query's useMutation hook.\n * \n * @typeParam T - The expected return type of the mutation\n * @param methodName - The name of the method to mutate\n * @param defaultArgs - Optional default arguments to merge with mutation variables\n * @returns Mutation options object for TanStack Query's useMutation\n * \n * @example\n * ```tsx\n * import { useMutation, useQueryClient } from '@tanstack/react-query';\n * import { modelenceMutation } from '@modelence/react-query';\n * \n * function MyComponent() {\n * const queryClient = useQueryClient();\n * \n * // Basic usage\n * const { mutate } = useMutation(modelenceMutation('todos.create'));\n * \n * // With additional options\n * const { mutate: updateTodo } = useMutation({\n * ...modelenceMutation('todos.update'),\n * onSuccess: () => {\n * queryClient.invalidateQueries({ queryKey: ['todos.getAll'] });\n * },\n * });\n * \n * return <button onClick={() => mutate({ title: 'New Todo' })}>Create</button>;\n * }\n * ```\n */\nexport function modelenceMutation<T = unknown>(\n methodName: string, \n defaultArgs: Args = {}\n) {\n return {\n mutationFn: (variables: Args = {}) => callMethod<T>(methodName, { ...defaultArgs, ...variables }),\n };\n}\n\n/**\n * Type helper for creating properly typed query keys\n */\nexport type ModelenceQueryKey<T extends string, U extends Args = Args> = readonly [T, U];\n\n/**\n * Utility function to create query keys for manual cache operations\n * \n * @param methodName - The method name\n * @param args - The arguments\n * @returns Typed query key\n * \n * @example\n * ```tsx\n * import { useQueryClient } from '@tanstack/react-query';\n * import { createQueryKey } from '@modelence/react-query';\n * \n * function TodoActions() {\n * const queryClient = useQueryClient();\n * \n * const refreshTodos = () => {\n * queryClient.invalidateQueries({ \n * queryKey: createQueryKey('todo.getAll', { limit: 10 }) \n * });\n * };\n * }\n * ```\n */\nexport function createQueryKey<T extends string, U extends Args = Args>(\n methodName: T,\n args: U = {} as U\n): ModelenceQueryKey<T, U> {\n return [methodName, args] as const;\n}\n"]}
@@ -0,0 +1,249 @@
1
+ import React from 'react';
2
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
3
+ import { getQueryOptions, getMutationOptions, createQueryKey } from '@modelence/react-query';
4
+
5
+ interface Todo {
6
+ id: string;
7
+ title: string;
8
+ completed: boolean;
9
+ }
10
+
11
+ // Example 1: Basic query usage
12
+ function TodoList() {
13
+ const { data: todos, isPending, error } = useQuery<Todo[]>(
14
+ getQueryOptions('todo.getAll', { limit: 10 })
15
+ );
16
+
17
+ if (isPending) return <div>Loading todos...</div>;
18
+ if (error) return <div>Error: {error.message}</div>;
19
+
20
+ return (
21
+ <div>
22
+ <h2>Todos</h2>
23
+ {todos?.map((todo) => (
24
+ <div key={todo.id}>
25
+ <h3>{todo.title}</h3>
26
+ <p>{todo.completed ? '✅' : '⏳'}</p>
27
+ </div>
28
+ ))}
29
+ </div>
30
+ );
31
+ }
32
+
33
+ // Example 2: Query with options and enabled condition
34
+ function TodoDetail({ todoId }: { todoId: string | null }) {
35
+ const { data: todo, isPending } = useQuery<Todo>({
36
+ ...getQueryOptions('todo.getById', { id: todoId }),
37
+ enabled: !!todoId, // Only run when todoId exists
38
+ staleTime: 5 * 60 * 1000, // 5 minutes
39
+ retry: 3,
40
+ });
41
+
42
+ if (!todoId) return <div>Select a todo</div>;
43
+ if (isPending) return <div>Loading todo...</div>;
44
+
45
+ return (
46
+ <div>
47
+ <h3>{todo?.title}</h3>
48
+ <p>Status: {todo?.completed ? 'Completed' : 'Pending'}</p>
49
+ </div>
50
+ );
51
+ }
52
+
53
+ // Example 3: Basic mutation
54
+ function CreateTodo() {
55
+ const queryClient = useQueryClient();
56
+
57
+ const { mutate: createTodo, isPending, error } = useMutation<Todo, Error, { title: string; completed: boolean }>({
58
+ ...getMutationOptions('todo.create'),
59
+ onSuccess: () => {
60
+ // Invalidate and refetch all todo queries
61
+ queryClient.invalidateQueries({ queryKey: ['todo.getAll'] });
62
+ },
63
+ onError: (error) => {
64
+ console.error('Failed to create todo:', error);
65
+ },
66
+ });
67
+
68
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
69
+ e.preventDefault();
70
+ const formData = new FormData(e.currentTarget);
71
+ const title = formData.get('title') as string;
72
+
73
+ createTodo({ title, completed: false });
74
+ e.currentTarget.reset();
75
+ };
76
+
77
+ return (
78
+ <form onSubmit={handleSubmit}>
79
+ <input
80
+ name="title"
81
+ placeholder="Enter todo title"
82
+ required
83
+ disabled={isPending}
84
+ />
85
+ <button type="submit" disabled={isPending}>
86
+ {isPending ? 'Creating...' : 'Create Todo'}
87
+ </button>
88
+ {error && <p style={{ color: 'red' }}>Error: {error.message}</p>}
89
+ </form>
90
+ );
91
+ }
92
+
93
+ // Example 4: Mutation with default args
94
+ function UpdateTodo({ todoId }: { todoId: string }) {
95
+ const queryClient = useQueryClient();
96
+
97
+ const { mutate: updateTodo, isPending } = useMutation<Todo, Error, { completed: boolean }>({
98
+ ...getMutationOptions('todo.update', { id: todoId }), // Default id
99
+ onSuccess: () => {
100
+ // Invalidate specific todo and list
101
+ queryClient.invalidateQueries({ queryKey: ['todo.getById'] });
102
+ queryClient.invalidateQueries({ queryKey: ['todo.getAll'] });
103
+ },
104
+ });
105
+
106
+ const toggleComplete = () => {
107
+ // The id is already provided in defaultArgs, so we only need the fields to update
108
+ updateTodo({ completed: true });
109
+ };
110
+
111
+ return (
112
+ <button onClick={toggleComplete} disabled={isPending}>
113
+ {isPending ? 'Updating...' : 'Mark Complete'}
114
+ </button>
115
+ );
116
+ }
117
+
118
+ // Example 5: Manual cache operations
119
+ function TodoActions() {
120
+ const queryClient = useQueryClient();
121
+
122
+ const refreshTodos = () => {
123
+ queryClient.invalidateQueries({
124
+ queryKey: createQueryKey('todo.getAll', { limit: 10 })
125
+ });
126
+ };
127
+
128
+ const prefetchTodo = (id: string) => {
129
+ queryClient.prefetchQuery<Todo>({
130
+ ...getQueryOptions('todo.getById', { id }),
131
+ staleTime: 10 * 60 * 1000, // 10 minutes
132
+ });
133
+ };
134
+
135
+ const setTodoData = (id: string, todo: Todo) => {
136
+ queryClient.setQueryData(
137
+ createQueryKey('todo.getById', { id }),
138
+ todo
139
+ );
140
+ };
141
+
142
+ return (
143
+ <div>
144
+ <button onClick={refreshTodos}>Refresh Todos</button>
145
+ <button onClick={() => prefetchTodo('123')}>Prefetch Todo 123</button>
146
+ <button onClick={() => setTodoData('123', { id: '123', title: 'Test', completed: false })}>
147
+ Set Todo Data
148
+ </button>
149
+ </div>
150
+ );
151
+ }
152
+
153
+ // Example 6: Advanced usage with optimistic updates
154
+ function OptimisticTodo({ todoId }: { todoId: string }) {
155
+ const queryClient = useQueryClient();
156
+
157
+ const { mutate: updateTodo } = useMutation<
158
+ Todo,
159
+ Error,
160
+ { id: string; completed: boolean },
161
+ { previousTodo: Todo | undefined }
162
+ >({
163
+ ...getMutationOptions('todo.update'),
164
+ onMutate: async (variables) => {
165
+ // Cancel outgoing refetches (so they don't overwrite our optimistic update)
166
+ await queryClient.cancelQueries({
167
+ queryKey: createQueryKey('todo.getById', { id: todoId })
168
+ });
169
+
170
+ // Snapshot the previous value
171
+ const previousTodo = queryClient.getQueryData<Todo>(
172
+ createQueryKey('todo.getById', { id: todoId })
173
+ );
174
+
175
+ // Optimistically update to the new value
176
+ queryClient.setQueryData(
177
+ createQueryKey('todo.getById', { id: todoId }),
178
+ (old: Todo | undefined) => old ? { ...old, ...variables } : undefined
179
+ );
180
+
181
+ // Return a context object with the snapshotted value
182
+ return { previousTodo };
183
+ },
184
+ onError: (_err, _variables, context) => {
185
+ // If the mutation fails, use the context to roll back
186
+ if (context?.previousTodo) {
187
+ queryClient.setQueryData(
188
+ createQueryKey('todo.getById', { id: todoId }),
189
+ context.previousTodo
190
+ );
191
+ }
192
+ },
193
+ onSettled: () => {
194
+ // Always refetch after error or success
195
+ queryClient.invalidateQueries({
196
+ queryKey: createQueryKey('todo.getById', { id: todoId })
197
+ });
198
+ },
199
+ });
200
+
201
+ return (
202
+ <button onClick={() => updateTodo({ id: todoId, completed: true })}>
203
+ Update with Optimistic UI
204
+ </button>
205
+ );
206
+ }
207
+
208
+ // Main app component showcasing all examples
209
+ export default function App() {
210
+ const [selectedTodoId, setSelectedTodoId] = React.useState<string | null>(null);
211
+
212
+ return (
213
+ <div style={{ padding: '20px', maxWidth: '800px', margin: '0 auto' }}>
214
+ <h1>@modelence/react-query Examples</h1>
215
+
216
+ <section>
217
+ <h2>Create Todo</h2>
218
+ <CreateTodo />
219
+ </section>
220
+
221
+ <section>
222
+ <h2>Todo List</h2>
223
+ <TodoList />
224
+ </section>
225
+
226
+ <section>
227
+ <h2>Todo Detail</h2>
228
+ <input
229
+ placeholder="Enter todo ID"
230
+ onChange={(e) => setSelectedTodoId(e.target.value || null)}
231
+ />
232
+ <TodoDetail todoId={selectedTodoId} />
233
+ </section>
234
+
235
+ {selectedTodoId && (
236
+ <section>
237
+ <h2>Todo Actions</h2>
238
+ <UpdateTodo todoId={selectedTodoId} />
239
+ <OptimisticTodo todoId={selectedTodoId} />
240
+ </section>
241
+ )}
242
+
243
+ <section>
244
+ <h2>Cache Actions</h2>
245
+ <TodoActions />
246
+ </section>
247
+ </div>
248
+ );
249
+ }
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "type": "module",
3
+ "name": "@modelence/react-query",
4
+ "version": "1.0.0",
5
+ "description": "React Query utilities for Modelence",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist/index.js",
9
+ "types": "./dist/index.d.ts"
10
+ }
11
+ },
12
+ "main": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "scripts": {
15
+ "build": "tsup",
16
+ "dev": "tsup --watch",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "author": "Modelence",
20
+ "license": "SEE LICENSE IN LICENSE",
21
+ "peerDependencies": {
22
+ "@tanstack/react-query": ">=5.0.0",
23
+ "react": ">=18.0.0"
24
+ },
25
+ "dependencies": {
26
+ "modelence": "^0.5.0"
27
+ },
28
+ "devDependencies": {
29
+ "@tanstack/react-query": "^5.76.2",
30
+ "@types/react": "^19.0.0",
31
+ "react": "^19.0.0",
32
+ "tsup": "^8.3.6",
33
+ "typescript": "^5.7.2"
34
+ }
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,116 @@
1
+ import { callMethod } from 'modelence/client';
2
+
3
+ type Args = Record<string, unknown>;
4
+
5
+ /**
6
+ * Creates query options for use with TanStack Query's useQuery hook.
7
+ *
8
+ * @typeParam T - The expected return type of the query
9
+ * @param methodName - The name of the method to query
10
+ * @param args - Optional arguments to pass to the method
11
+ * @returns Query options object for TanStack Query's useQuery
12
+ *
13
+ * @example
14
+ * ```tsx
15
+ * import { useQuery } from '@tanstack/react-query';
16
+ * import { modelenceQuery } from '@modelence/react-query';
17
+ *
18
+ * function MyComponent() {
19
+ * // Basic usage
20
+ * const { data } = useQuery(modelenceQuery('todo.getAll'));
21
+ *
22
+ * // With additional options
23
+ * const { data: todo } = useQuery({
24
+ * ...modelenceQuery('todo.getById', { id: '123' }),
25
+ * enabled: !!id,
26
+ * staleTime: 5 * 60 * 1000,
27
+ * });
28
+ *
29
+ * return <div>{data?.name}</div>;
30
+ * }
31
+ * ```
32
+ */
33
+ export function modelenceQuery<T = unknown>(
34
+ methodName: string,
35
+ args: Args = {}
36
+ ) {
37
+ return {
38
+ queryKey: [methodName, args],
39
+ queryFn: () => callMethod<T>(methodName, args),
40
+ };
41
+ }
42
+
43
+ /**
44
+ * Creates mutation options for use with TanStack Query's useMutation hook.
45
+ *
46
+ * @typeParam T - The expected return type of the mutation
47
+ * @param methodName - The name of the method to mutate
48
+ * @param defaultArgs - Optional default arguments to merge with mutation variables
49
+ * @returns Mutation options object for TanStack Query's useMutation
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * import { useMutation, useQueryClient } from '@tanstack/react-query';
54
+ * import { modelenceMutation } from '@modelence/react-query';
55
+ *
56
+ * function MyComponent() {
57
+ * const queryClient = useQueryClient();
58
+ *
59
+ * // Basic usage
60
+ * const { mutate } = useMutation(modelenceMutation('todos.create'));
61
+ *
62
+ * // With additional options
63
+ * const { mutate: updateTodo } = useMutation({
64
+ * ...modelenceMutation('todos.update'),
65
+ * onSuccess: () => {
66
+ * queryClient.invalidateQueries({ queryKey: ['todos.getAll'] });
67
+ * },
68
+ * });
69
+ *
70
+ * return <button onClick={() => mutate({ title: 'New Todo' })}>Create</button>;
71
+ * }
72
+ * ```
73
+ */
74
+ export function modelenceMutation<T = unknown>(
75
+ methodName: string,
76
+ defaultArgs: Args = {}
77
+ ) {
78
+ return {
79
+ mutationFn: (variables: Args = {}) => callMethod<T>(methodName, { ...defaultArgs, ...variables }),
80
+ };
81
+ }
82
+
83
+ /**
84
+ * Type helper for creating properly typed query keys
85
+ */
86
+ export type ModelenceQueryKey<T extends string, U extends Args = Args> = readonly [T, U];
87
+
88
+ /**
89
+ * Utility function to create query keys for manual cache operations
90
+ *
91
+ * @param methodName - The method name
92
+ * @param args - The arguments
93
+ * @returns Typed query key
94
+ *
95
+ * @example
96
+ * ```tsx
97
+ * import { useQueryClient } from '@tanstack/react-query';
98
+ * import { createQueryKey } from '@modelence/react-query';
99
+ *
100
+ * function TodoActions() {
101
+ * const queryClient = useQueryClient();
102
+ *
103
+ * const refreshTodos = () => {
104
+ * queryClient.invalidateQueries({
105
+ * queryKey: createQueryKey('todo.getAll', { limit: 10 })
106
+ * });
107
+ * };
108
+ * }
109
+ * ```
110
+ */
111
+ export function createQueryKey<T extends string, U extends Args = Args>(
112
+ methodName: T,
113
+ args: U = {} as U
114
+ ): ModelenceQueryKey<T, U> {
115
+ return [methodName, args] as const;
116
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "lib": ["ES2020", "DOM"],
5
+ "module": "ESNext",
6
+ "moduleResolution": "Bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "esModuleInterop": true,
9
+ "allowJs": true,
10
+ "strict": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "skipLibCheck": true,
13
+ "declaration": true,
14
+ "outDir": "dist",
15
+ "jsx": "react-jsx"
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,28 @@
1
+ import { defineConfig } from 'tsup'
2
+
3
+ export default defineConfig((options) => ({
4
+ entry: ['src/index.ts'],
5
+ format: ['esm'],
6
+ dts: {
7
+ resolve: true,
8
+ entry: {
9
+ index: 'src/index.ts'
10
+ }
11
+ },
12
+ splitting: true,
13
+ clean: true,
14
+ outDir: 'dist',
15
+ sourcemap: true,
16
+ minify: !options.watch,
17
+ treeshake: !options.watch,
18
+ jsx: true,
19
+ esbuildOptions: (options) => {
20
+ options.resolveExtensions = ['.ts', '.js', '.tsx', '.jsx']
21
+ return options
22
+ },
23
+ external: [
24
+ 'react',
25
+ '@tanstack/react-query',
26
+ 'modelence'
27
+ ]
28
+ }));