@modelence/react-query 1.2.1 → 1.2.2
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.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.ts +14 -4
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {hashKey}from'@tanstack/react-query';import {startWebsockets,callMethod,subscribeLiveQuery}from'modelence/client';var
|
|
1
|
+
import {hashKey}from'@tanstack/react-query';import {startWebsockets,callMethod,subscribeLiveQuery}from'modelence/client';var i=null,b=null,o=new Map,d=class{connect(e){if(i)throw new Error("ModelenceQueryClient can only be connected to one QueryClient");b&&(b(),o.forEach(n=>n.unsubscribe()),o.clear()),startWebsockets(),i=e,b=e.getQueryCache().subscribe(n=>{if(n.type==="removed"){let t=hashKey(n.query.queryKey),c=o.get(t);if(c){if(c.resolvers.size>0){let l=new Error("Query was removed from cache");c.resolvers.forEach(u=>u.reject(l)),c.resolvers.clear();}c.unsubscribe(),o.delete(t);}}});}};function h(r,e={}){return {queryKey:[r,e],queryFn:()=>callMethod(r,e)}}function x(r,e={}){let n=["live",r,e],t=hashKey(n);return {queryKey:n,queryFn:()=>new Promise((c,l)=>{if(!i){let a=new Error("ModelenceQueryClient must be connected before using modelenceLiveQuery()");console.error("[Modelence]",a.message),l(a);return}let u=o.get(t);u||(u={unsubscribe:subscribeLiveQuery(r,e,y=>{let s=o.get(t);s?.resolvers.size&&(s.resolvers.forEach(f=>f.resolve(y)),s.resolvers.clear()),i&&i.setQueryData(n,y);},y=>{let s=o.get(t);s&&(s.resolvers.size&&(s.resolvers.forEach(f=>f.reject(new Error(y))),s.resolvers.clear()),s.unsubscribe(),o.delete(t));}),resolvers:new Set},o.set(t,u)),u.resolvers.add({resolve:c,reject:l});}),staleTime:1/0,refetchOnWindowFocus:false,refetchOnMount:false,refetchOnReconnect:false,gcTime:0}}function E(r,e={}){return {mutationFn:(n={})=>callMethod(r,{...e,...n})}}function A(r,e={}){return [r,e]}export{d as ModelenceQueryClient,A as createQueryKey,x as modelenceLiveQuery,E as modelenceMutation,h as modelenceQuery};//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":["queryClientRef","cacheUnsubscribe","subscriptions","ModelenceQueryClient","queryClient","sub","startWebsockets","event","subscriptionKey","hashKey","modelenceQuery","methodName","args","callMethod","modelenceLiveQuery","queryKey","resolve","reject","error","subscribeLiveQuery","data","currentSub","r","modelenceMutation","defaultArgs","variables","createQueryKey"],"mappings":"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","file":"index.js","sourcesContent":["import { QueryClient, hashKey } from '@tanstack/react-query';\nimport { callMethod, subscribeLiveQuery, startWebsockets } from 'modelence/client';\n\ntype Args = Record<string, unknown>;\n\ninterface Subscription {\n unsubscribe: () => void;\n resolvers: Set<{\n resolve: (data: unknown) => void;\n reject: (error: Error) => void;\n }>;\n}\n\nlet queryClientRef: QueryClient | null = null;\nlet cacheUnsubscribe: (() => void) | null = null;\nconst subscriptions = new Map<string, Subscription>();\n\n/**\n * Client for managing Modelence live queries with TanStack Query.\n * Create one instance and connect it to your QueryClient.\n * \n * @example\n * ```tsx\n * import { QueryClient, QueryClientProvider } from '@tanstack/react-query';\n * import { ModelenceQueryClient } from '@modelence/react-query';\n * \n * const queryClient = new QueryClient();\n * new ModelenceQueryClient().connect(queryClient);\n * \n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <YourApp />\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport class ModelenceQueryClient {\n /**\n * Connects to a TanStack Query QueryClient.\n * This enables live query subscriptions and cache updates.\n */\n connect(queryClient: QueryClient) {\n // Only support one query client at a time\n if (queryClientRef) {\n throw new Error('ModelenceQueryClient can only be connected to one QueryClient');\n }\n\n if (cacheUnsubscribe) {\n cacheUnsubscribe();\n subscriptions.forEach((sub) => sub.unsubscribe());\n subscriptions.clear();\n }\n\n startWebsockets();\n\n queryClientRef = queryClient;\n\n cacheUnsubscribe = queryClient.getQueryCache().subscribe((event) => {\n if (event.type === 'removed') {\n const subscriptionKey = hashKey(event.query.queryKey);\n const sub = subscriptions.get(subscriptionKey);\n if (sub?.resolvers.size === 0) {\n sub.unsubscribe();\n subscriptions.delete(subscriptionKey);\n }\n }\n });\n }\n}\n\n/**\n * Creates query options for use with TanStack Query's useQuery hook.\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 * \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 */\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 query options for live queries with TanStack Query's useQuery hook.\n * Data will be updated in real-time when underlying data changes.\n * \n * Requires ModelenceQueryClient to be connected to your QueryClient.\n * \n * @example\n * ```tsx\n * import { useQuery } from '@tanstack/react-query';\n * import { modelenceLiveQuery } from '@modelence/react-query';\n * \n * function TodoList() {\n * // Subscribe to live updates - data refreshes automatically when todos change\n * const { data: todos } = useQuery(modelenceLiveQuery('todo.getAll', { userId }));\n * \n * return (\n * <ul>\n * {todos?.map(todo => <li key={todo._id}>{todo.title}</li>)}\n * </ul>\n * );\n * }\n * ```\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 */\nexport function modelenceLiveQuery<T = unknown>(\n methodName: string, \n args: Args = {}\n) {\n const queryKey = ['live', methodName, args] as const;\n const subscriptionKey = hashKey(queryKey);\n\n return {\n queryKey,\n queryFn: () => new Promise<T>((resolve, reject) => {\n if (!queryClientRef) {\n const error = new Error('ModelenceQueryClient must be connected before using modelenceLiveQuery()');\n console.error('[Modelence]', error.message);\n reject(error);\n return;\n }\n\n let sub = subscriptions.get(subscriptionKey);\n\n if (!sub) {\n const unsubscribe = subscribeLiveQuery<T>(\n methodName,\n args,\n (data) => {\n const currentSub = subscriptions.get(subscriptionKey);\n \n if (currentSub?.resolvers.size) {\n currentSub.resolvers.forEach((r) => r.resolve(data));\n currentSub.resolvers.clear();\n }\n\n if (queryClientRef) {\n queryClientRef.setQueryData(queryKey, data);\n }\n },\n (error) => {\n const currentSub = subscriptions.get(subscriptionKey);\n if (currentSub?.resolvers.size) {\n currentSub.resolvers.forEach((r) => r.reject(new Error(error)));\n currentSub.resolvers.clear();\n }\n }\n );\n\n sub = { unsubscribe, resolvers: new Set() };\n subscriptions.set(subscriptionKey, sub);\n }\n\n sub.resolvers.add({\n resolve: resolve as (data: unknown) => void,\n reject,\n });\n }),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n refetchOnMount: false,\n refetchOnReconnect: false,\n gcTime: 0,\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"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":["queryClientRef","cacheUnsubscribe","subscriptions","ModelenceQueryClient","queryClient","sub","startWebsockets","event","subscriptionKey","hashKey","cancelError","r","modelenceQuery","methodName","args","callMethod","modelenceLiveQuery","queryKey","resolve","reject","error","subscribeLiveQuery","data","currentSub","modelenceMutation","defaultArgs","variables","createQueryKey"],"mappings":"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","file":"index.js","sourcesContent":["import { QueryClient, hashKey } from '@tanstack/react-query';\nimport { callMethod, subscribeLiveQuery, startWebsockets } from 'modelence/client';\n\ntype Args = Record<string, unknown>;\n\ninterface Subscription {\n unsubscribe: () => void;\n resolvers: Set<{\n resolve: (data: unknown) => void;\n reject: (error: Error) => void;\n }>;\n}\n\nlet queryClientRef: QueryClient | null = null;\nlet cacheUnsubscribe: (() => void) | null = null;\nconst subscriptions = new Map<string, Subscription>();\n\n/**\n * Client for managing Modelence live queries with TanStack Query.\n * Create one instance and connect it to your QueryClient.\n * \n * @example\n * ```tsx\n * import { QueryClient, QueryClientProvider } from '@tanstack/react-query';\n * import { ModelenceQueryClient } from '@modelence/react-query';\n * \n * const queryClient = new QueryClient();\n * new ModelenceQueryClient().connect(queryClient);\n * \n * function App() {\n * return (\n * <QueryClientProvider client={queryClient}>\n * <YourApp />\n * </QueryClientProvider>\n * );\n * }\n * ```\n */\nexport class ModelenceQueryClient {\n /**\n * Connects to a TanStack Query QueryClient.\n * This enables live query subscriptions and cache updates.\n */\n connect(queryClient: QueryClient) {\n // Only support one query client at a time\n if (queryClientRef) {\n throw new Error('ModelenceQueryClient can only be connected to one QueryClient');\n }\n\n if (cacheUnsubscribe) {\n cacheUnsubscribe();\n subscriptions.forEach((sub) => sub.unsubscribe());\n subscriptions.clear();\n }\n\n startWebsockets();\n\n queryClientRef = queryClient;\n\n cacheUnsubscribe = queryClient.getQueryCache().subscribe((event) => {\n if (event.type === 'removed') {\n const subscriptionKey = hashKey(event.query.queryKey);\n const sub = subscriptions.get(subscriptionKey);\n if (sub) {\n // Reject any pending resolvers since the query was removed\n if (sub.resolvers.size > 0) {\n const cancelError = new Error('Query was removed from cache');\n sub.resolvers.forEach((r) => r.reject(cancelError));\n sub.resolvers.clear();\n }\n sub.unsubscribe();\n subscriptions.delete(subscriptionKey);\n }\n }\n });\n }\n}\n\n/**\n * Creates query options for use with TanStack Query's useQuery hook.\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 * \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 */\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 query options for live queries with TanStack Query's useQuery hook.\n * Data will be updated in real-time when underlying data changes.\n * \n * Requires ModelenceQueryClient to be connected to your QueryClient.\n * \n * @example\n * ```tsx\n * import { useQuery } from '@tanstack/react-query';\n * import { modelenceLiveQuery } from '@modelence/react-query';\n * \n * function TodoList() {\n * // Subscribe to live updates - data refreshes automatically when todos change\n * const { data: todos } = useQuery(modelenceLiveQuery('todo.getAll', { userId }));\n * \n * return (\n * <ul>\n * {todos?.map(todo => <li key={todo._id}>{todo.title}</li>)}\n * </ul>\n * );\n * }\n * ```\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 */\nexport function modelenceLiveQuery<T = unknown>(\n methodName: string, \n args: Args = {}\n) {\n const queryKey = ['live', methodName, args] as const;\n const subscriptionKey = hashKey(queryKey);\n\n return {\n queryKey,\n queryFn: () => new Promise<T>((resolve, reject) => {\n if (!queryClientRef) {\n const error = new Error('ModelenceQueryClient must be connected before using modelenceLiveQuery()');\n console.error('[Modelence]', error.message);\n reject(error);\n return;\n }\n\n let sub = subscriptions.get(subscriptionKey);\n\n if (!sub) {\n const unsubscribe = subscribeLiveQuery<T>(\n methodName,\n args,\n (data) => {\n const currentSub = subscriptions.get(subscriptionKey);\n \n if (currentSub?.resolvers.size) {\n currentSub.resolvers.forEach((r) => r.resolve(data));\n currentSub.resolvers.clear();\n }\n\n if (queryClientRef) {\n queryClientRef.setQueryData(queryKey, data);\n }\n },\n (error) => {\n const currentSub = subscriptions.get(subscriptionKey);\n if (currentSub) {\n if (currentSub.resolvers.size) {\n currentSub.resolvers.forEach((r) => r.reject(new Error(error)));\n currentSub.resolvers.clear();\n }\n currentSub.unsubscribe();\n subscriptions.delete(subscriptionKey);\n }\n }\n );\n\n sub = { unsubscribe, resolvers: new Set() };\n subscriptions.set(subscriptionKey, sub);\n }\n\n sub.resolvers.add({\n resolve: resolve as (data: unknown) => void,\n reject,\n });\n }),\n staleTime: Infinity,\n refetchOnWindowFocus: false,\n refetchOnMount: false,\n refetchOnReconnect: false,\n gcTime: 0,\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"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@modelence/react-query",
|
|
4
|
-
"version": "1.2.
|
|
4
|
+
"version": "1.2.2",
|
|
5
5
|
"description": "React Query utilities for Modelence",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@tanstack/react-query": "^5.76.2",
|
|
28
28
|
"@types/react": "^19.0.0",
|
|
29
|
-
"modelence": "^0.10.
|
|
29
|
+
"modelence": "^0.10.2",
|
|
30
30
|
"react": "^19.0.0",
|
|
31
31
|
"tsup": "^8.3.6",
|
|
32
32
|
"typescript": "^5.7.2"
|
package/src/index.ts
CHANGED
|
@@ -61,7 +61,13 @@ export class ModelenceQueryClient {
|
|
|
61
61
|
if (event.type === 'removed') {
|
|
62
62
|
const subscriptionKey = hashKey(event.query.queryKey);
|
|
63
63
|
const sub = subscriptions.get(subscriptionKey);
|
|
64
|
-
if (sub
|
|
64
|
+
if (sub) {
|
|
65
|
+
// Reject any pending resolvers since the query was removed
|
|
66
|
+
if (sub.resolvers.size > 0) {
|
|
67
|
+
const cancelError = new Error('Query was removed from cache');
|
|
68
|
+
sub.resolvers.forEach((r) => r.reject(cancelError));
|
|
69
|
+
sub.resolvers.clear();
|
|
70
|
+
}
|
|
65
71
|
sub.unsubscribe();
|
|
66
72
|
subscriptions.delete(subscriptionKey);
|
|
67
73
|
}
|
|
@@ -174,9 +180,13 @@ export function modelenceLiveQuery<T = unknown>(
|
|
|
174
180
|
},
|
|
175
181
|
(error) => {
|
|
176
182
|
const currentSub = subscriptions.get(subscriptionKey);
|
|
177
|
-
if (currentSub
|
|
178
|
-
currentSub.resolvers.
|
|
179
|
-
|
|
183
|
+
if (currentSub) {
|
|
184
|
+
if (currentSub.resolvers.size) {
|
|
185
|
+
currentSub.resolvers.forEach((r) => r.reject(new Error(error)));
|
|
186
|
+
currentSub.resolvers.clear();
|
|
187
|
+
}
|
|
188
|
+
currentSub.unsubscribe();
|
|
189
|
+
subscriptions.delete(subscriptionKey);
|
|
180
190
|
}
|
|
181
191
|
}
|
|
182
192
|
);
|