@modelence/react-query 1.0.1 → 1.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.
Files changed (46) hide show
  1. package/.yalc/modelence/README.md +86 -0
  2. package/.yalc/modelence/dist/bin/modelence.js +5 -0
  3. package/.yalc/modelence/dist/bin/modelence.js.map +1 -0
  4. package/.yalc/modelence/dist/chunk-3S2FFBNS.js +2 -0
  5. package/.yalc/modelence/dist/chunk-3S2FFBNS.js.map +1 -0
  6. package/.yalc/modelence/dist/chunk-3YAV3UUU.js +3 -0
  7. package/.yalc/modelence/dist/chunk-3YAV3UUU.js.map +1 -0
  8. package/.yalc/modelence/dist/chunk-55J6XMHW.js +2 -0
  9. package/.yalc/modelence/dist/chunk-55J6XMHW.js.map +1 -0
  10. package/.yalc/modelence/dist/chunk-C3UESBRX.js +2 -0
  11. package/.yalc/modelence/dist/chunk-C3UESBRX.js.map +1 -0
  12. package/.yalc/modelence/dist/chunk-DO5TZLF5.js +2 -0
  13. package/.yalc/modelence/dist/chunk-DO5TZLF5.js.map +1 -0
  14. package/.yalc/modelence/dist/chunk-DVECB2TP.js +3 -0
  15. package/.yalc/modelence/dist/chunk-DVECB2TP.js.map +1 -0
  16. package/.yalc/modelence/dist/client.d.ts +135 -0
  17. package/.yalc/modelence/dist/client.js +2 -0
  18. package/.yalc/modelence/dist/client.js.map +1 -0
  19. package/.yalc/modelence/dist/index-CwdohC5n.d.ts +15 -0
  20. package/.yalc/modelence/dist/index.d.ts +31 -0
  21. package/.yalc/modelence/dist/index.js +2 -0
  22. package/.yalc/modelence/dist/index.js.map +1 -0
  23. package/.yalc/modelence/dist/mongo.d.ts +5 -0
  24. package/.yalc/modelence/dist/mongo.js +2 -0
  25. package/.yalc/modelence/dist/mongo.js.map +1 -0
  26. package/.yalc/modelence/dist/package-3YQBVIVQ.js +2 -0
  27. package/.yalc/modelence/dist/package-3YQBVIVQ.js.map +1 -0
  28. package/.yalc/modelence/dist/server.d.ts +569 -0
  29. package/.yalc/modelence/dist/server.js +19 -0
  30. package/.yalc/modelence/dist/server.js.map +1 -0
  31. package/.yalc/modelence/dist/telemetry.d.ts +11 -0
  32. package/.yalc/modelence/dist/telemetry.js +2 -0
  33. package/.yalc/modelence/dist/telemetry.js.map +1 -0
  34. package/.yalc/modelence/dist/types-Ds1ESQSs.d.ts +106 -0
  35. package/.yalc/modelence/dist/types-WgRbQ-tj.d.ts +645 -0
  36. package/.yalc/modelence/dist/types.d.ts +7 -0
  37. package/.yalc/modelence/dist/types.js +2 -0
  38. package/.yalc/modelence/dist/types.js.map +1 -0
  39. package/.yalc/modelence/package.json +80 -0
  40. package/.yalc/modelence/yalc.sig +1 -0
  41. package/dist/index.d.ts +68 -1
  42. package/dist/index.js +1 -1
  43. package/dist/index.js.map +1 -1
  44. package/package.json +3 -2
  45. package/src/index.ts +158 -1
  46. package/yalc.lock +10 -0
@@ -0,0 +1,80 @@
1
+ {
2
+ "type": "module",
3
+ "name": "modelence",
4
+ "version": "0.8.0",
5
+ "description": "The Node.js Framework for Real-Time MongoDB Apps",
6
+ "main": "dist/index.js",
7
+ "types": "dist/global.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./client": "./dist/client.js",
11
+ "./server": "./dist/server.js",
12
+ "./telemetry": "./dist/telemetry.js",
13
+ "./mongodb": "./dist/mongo.js",
14
+ "./types": {
15
+ "types": "./dist/types.d.ts",
16
+ "default": "./dist/types.js"
17
+ }
18
+ },
19
+ "files": [
20
+ "dist",
21
+ "dist/bin"
22
+ ],
23
+ "bin": {
24
+ "modelence": "./dist/bin/modelence.js"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
30
+ "format:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,json,css,md}\"",
31
+ "lint": "eslint src --ext .ts,.tsx --fix",
32
+ "lint:check": "eslint src --ext .ts,.tsx",
33
+ "prepublishOnly": "npm run build",
34
+ "test": "NODE_OPTIONS=--experimental-vm-modules jest",
35
+ "test:watch": "NODE_OPTIONS=--experimental-vm-modules jest --watch",
36
+ "test:coverage": "NODE_OPTIONS=--experimental-vm-modules jest --coverage",
37
+ "postversion": "git push && git push --tags"
38
+ },
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/modelence/modelence.git"
42
+ },
43
+ "author": "Modelence",
44
+ "license": "SEE LICENSE IN LICENSE",
45
+ "bugs": {
46
+ "url": "https://github.com/modelence/modelence/issues"
47
+ },
48
+ "homepage": "https://modelence.com",
49
+ "dependencies": {
50
+ "@socket.io/mongo-adapter": "^0.4.0",
51
+ "@vitejs/plugin-react": "^4.3.4",
52
+ "archiver": "^7.0.1",
53
+ "bcrypt": "^5.1.1",
54
+ "commander": "^12.0.0",
55
+ "cookie-parser": "^1.4.7",
56
+ "dotenv": "^16.4.5",
57
+ "elastic-apm-node": "^4.8.0",
58
+ "express": "^4.21.0",
59
+ "fs-extra": "^11.2.0",
60
+ "jiti": "^2.4.2",
61
+ "mongodb": "^6.8.1",
62
+ "open": "^10.1.0",
63
+ "socket.io": "^4.8.1",
64
+ "socket.io-client": "^4.8.1",
65
+ "tsup": "^8.3.6",
66
+ "tsx": "^4.19.3",
67
+ "typescript": "^5.7.2",
68
+ "vite": "^6.0.3",
69
+ "vite-plugin-eslint": "^1.8.1",
70
+ "winston": "^3.15.0",
71
+ "winston-elasticsearch": "^0.19.0",
72
+ "zod": "^3.23.8",
73
+ "zustand": "^5.0.2"
74
+ },
75
+ "peerDependencies": {
76
+ "react": ">=18.0.0",
77
+ "react-dom": ">=18.0.0"
78
+ },
79
+ "yalcSig": "54beb281dea95e11540cf0bb45ff9771"
80
+ }
@@ -0,0 +1 @@
1
+ 54beb281dea95e11540cf0bb45ff9771
package/dist/index.d.ts CHANGED
@@ -1,4 +1,34 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+
1
3
  type Args = Record<string, unknown>;
4
+ /**
5
+ * Client for managing Modelence live queries with TanStack Query.
6
+ * Create one instance and connect it to your QueryClient.
7
+ *
8
+ * @example
9
+ * ```tsx
10
+ * import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
11
+ * import { ModelenceQueryClient } from '@modelence/react-query';
12
+ *
13
+ * const queryClient = new QueryClient();
14
+ * new ModelenceQueryClient().connect(queryClient);
15
+ *
16
+ * function App() {
17
+ * return (
18
+ * <QueryClientProvider client={queryClient}>
19
+ * <YourApp />
20
+ * </QueryClientProvider>
21
+ * );
22
+ * }
23
+ * ```
24
+ */
25
+ declare class ModelenceQueryClient {
26
+ /**
27
+ * Connects to a TanStack Query QueryClient.
28
+ * This enables live query subscriptions and cache updates.
29
+ */
30
+ connect(queryClient: QueryClient): void;
31
+ }
2
32
  /**
3
33
  * Creates query options for use with TanStack Query's useQuery hook.
4
34
  *
@@ -32,6 +62,43 @@ declare function modelenceQuery<T = unknown>(methodName: string, args?: Args): {
32
62
  queryKey: (string | Args)[];
33
63
  queryFn: () => Promise<T>;
34
64
  };
65
+ /**
66
+ * Creates query options for live queries with TanStack Query's useQuery hook.
67
+ * Data will be updated in real-time when underlying data changes.
68
+ *
69
+ * Requires ModelenceQueryClient to be connected to your QueryClient.
70
+ *
71
+ * @example
72
+ * ```tsx
73
+ * import { useQuery } from '@tanstack/react-query';
74
+ * import { modelenceLiveQuery } from '@modelence/react-query';
75
+ *
76
+ * function TodoList() {
77
+ * // Subscribe to live updates - data refreshes automatically when todos change
78
+ * const { data: todos } = useQuery(modelenceLiveQuery('todo.getAll', { userId }));
79
+ *
80
+ * return (
81
+ * <ul>
82
+ * {todos?.map(todo => <li key={todo._id}>{todo.title}</li>)}
83
+ * </ul>
84
+ * );
85
+ * }
86
+ * ```
87
+ *
88
+ * @typeParam T - The expected return type of the query
89
+ * @param methodName - The name of the method to query
90
+ * @param args - Optional arguments to pass to the method
91
+ * @returns Query options object for TanStack Query's useQuery
92
+ */
93
+ declare function modelenceLiveQuery<T = unknown>(methodName: string, args?: Args): {
94
+ queryKey: readonly ["live", string, Args];
95
+ queryFn: () => Promise<T>;
96
+ staleTime: number;
97
+ refetchOnWindowFocus: boolean;
98
+ refetchOnMount: boolean;
99
+ refetchOnReconnect: boolean;
100
+ gcTime: number;
101
+ };
35
102
  /**
36
103
  * Creates mutation options for use with TanStack Query's useMutation hook.
37
104
  *
@@ -95,4 +162,4 @@ type ModelenceQueryKey<T extends string, U extends Args = Args> = readonly [T, U
95
162
  */
96
163
  declare function createQueryKey<T extends string, U extends Args = Args>(methodName: T, args?: U): ModelenceQueryKey<T, U>;
97
164
 
98
- export { type ModelenceQueryKey, createQueryKey, modelenceMutation, modelenceQuery };
165
+ export { ModelenceQueryClient, type ModelenceQueryKey, createQueryKey, modelenceLiveQuery, modelenceMutation, modelenceQuery };
package/dist/index.js CHANGED
@@ -1,2 +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
1
+ import {hashKey}from'@tanstack/react-query';import {startWebsockets,callMethod,subscribeLiveQuery}from'modelence/client';var c=null,b=null,s=new Map,d=class{connect(e){if(c)throw new Error("ModelenceQueryClient can only be connected to one QueryClient");b&&(b(),s.forEach(n=>n.unsubscribe()),s.clear()),startWebsockets(),c=e,b=e.getQueryCache().subscribe(n=>{if(n.type==="removed"){let o=hashKey(n.query.queryKey),u=s.get(o);u?.resolvers.size===0&&(u.unsubscribe(),s.delete(o));}});}};function h(r,e={}){return {queryKey:[r,e],queryFn:()=>callMethod(r,e)}}function x(r,e={}){let n=["live",r,e],o=hashKey(n);return {queryKey:n,queryFn:()=>new Promise((u,f)=>{if(!c){let y=new Error("ModelenceQueryClient must be connected before using modelenceLiveQuery()");console.error("[Modelence]",y.message),f(y);return}let i=s.get(o);i||(i={unsubscribe:subscribeLiveQuery(r,e,l=>{let t=s.get(o);t?.resolvers.size&&(t.resolvers.forEach(a=>a.resolve(l)),t.resolvers.clear()),c&&c.setQueryData(n,l);},l=>{let t=s.get(o);t?.resolvers.size&&(t.resolvers.forEach(a=>a.reject(new Error(l))),t.resolvers.clear());}),resolvers:new Set},s.set(o,i)),i.resolvers.add({resolve:u,reject:f});}),staleTime:1/0,refetchOnWindowFocus:false,refetchOnMount:false,refetchOnReconnect:false,gcTime:0}}function A(r,e={}){return {mutationFn:(n={})=>callMethod(r,{...e,...n})}}function K(r,e={}){return [r,e]}export{d as ModelenceQueryClient,K as createQueryKey,x as modelenceLiveQuery,A 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":["modelenceQuery","methodName","args","callMethod","modelenceMutation","defaultArgs","variables","createQueryKey"],"mappings":"0CAiCO,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 * @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 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","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"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@modelence/react-query",
4
- "version": "1.0.1",
4
+ "version": "1.2.0",
5
5
  "description": "React Query utilities for Modelence",
6
6
  "exports": {
7
7
  ".": {
@@ -20,12 +20,13 @@
20
20
  "license": "SEE LICENSE IN LICENSE",
21
21
  "peerDependencies": {
22
22
  "@tanstack/react-query": ">=5.0.0",
23
+ "modelence": "*",
23
24
  "react": ">=18.0.0"
24
25
  },
25
26
  "devDependencies": {
26
- "modelence": "^0.5.0",
27
27
  "@tanstack/react-query": "^5.76.2",
28
28
  "@types/react": "^19.0.0",
29
+ "modelence": "^0.10.0",
29
30
  "react": "^19.0.0",
30
31
  "tsup": "^8.3.6",
31
32
  "typescript": "^5.7.2"
package/src/index.ts CHANGED
@@ -1,7 +1,75 @@
1
- import { callMethod } from 'modelence/client';
1
+ import { QueryClient, hashKey } from '@tanstack/react-query';
2
+ import { callMethod, subscribeLiveQuery, startWebsockets } from 'modelence/client';
2
3
 
3
4
  type Args = Record<string, unknown>;
4
5
 
6
+ interface Subscription {
7
+ unsubscribe: () => void;
8
+ resolvers: Set<{
9
+ resolve: (data: unknown) => void;
10
+ reject: (error: Error) => void;
11
+ }>;
12
+ }
13
+
14
+ let queryClientRef: QueryClient | null = null;
15
+ let cacheUnsubscribe: (() => void) | null = null;
16
+ const subscriptions = new Map<string, Subscription>();
17
+
18
+ /**
19
+ * Client for managing Modelence live queries with TanStack Query.
20
+ * Create one instance and connect it to your QueryClient.
21
+ *
22
+ * @example
23
+ * ```tsx
24
+ * import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
25
+ * import { ModelenceQueryClient } from '@modelence/react-query';
26
+ *
27
+ * const queryClient = new QueryClient();
28
+ * new ModelenceQueryClient().connect(queryClient);
29
+ *
30
+ * function App() {
31
+ * return (
32
+ * <QueryClientProvider client={queryClient}>
33
+ * <YourApp />
34
+ * </QueryClientProvider>
35
+ * );
36
+ * }
37
+ * ```
38
+ */
39
+ export class ModelenceQueryClient {
40
+ /**
41
+ * Connects to a TanStack Query QueryClient.
42
+ * This enables live query subscriptions and cache updates.
43
+ */
44
+ connect(queryClient: QueryClient) {
45
+ // Only support one query client at a time
46
+ if (queryClientRef) {
47
+ throw new Error('ModelenceQueryClient can only be connected to one QueryClient');
48
+ }
49
+
50
+ if (cacheUnsubscribe) {
51
+ cacheUnsubscribe();
52
+ subscriptions.forEach((sub) => sub.unsubscribe());
53
+ subscriptions.clear();
54
+ }
55
+
56
+ startWebsockets();
57
+
58
+ queryClientRef = queryClient;
59
+
60
+ cacheUnsubscribe = queryClient.getQueryCache().subscribe((event) => {
61
+ if (event.type === 'removed') {
62
+ const subscriptionKey = hashKey(event.query.queryKey);
63
+ const sub = subscriptions.get(subscriptionKey);
64
+ if (sub?.resolvers.size === 0) {
65
+ sub.unsubscribe();
66
+ subscriptions.delete(subscriptionKey);
67
+ }
68
+ }
69
+ });
70
+ }
71
+ }
72
+
5
73
  /**
6
74
  * Creates query options for use with TanStack Query's useQuery hook.
7
75
  *
@@ -41,6 +109,95 @@ export function modelenceQuery<T = unknown>(
41
109
  };
42
110
  }
43
111
 
112
+ /**
113
+ * Creates query options for live queries with TanStack Query's useQuery hook.
114
+ * Data will be updated in real-time when underlying data changes.
115
+ *
116
+ * Requires ModelenceQueryClient to be connected to your QueryClient.
117
+ *
118
+ * @example
119
+ * ```tsx
120
+ * import { useQuery } from '@tanstack/react-query';
121
+ * import { modelenceLiveQuery } from '@modelence/react-query';
122
+ *
123
+ * function TodoList() {
124
+ * // Subscribe to live updates - data refreshes automatically when todos change
125
+ * const { data: todos } = useQuery(modelenceLiveQuery('todo.getAll', { userId }));
126
+ *
127
+ * return (
128
+ * <ul>
129
+ * {todos?.map(todo => <li key={todo._id}>{todo.title}</li>)}
130
+ * </ul>
131
+ * );
132
+ * }
133
+ * ```
134
+ *
135
+ * @typeParam T - The expected return type of the query
136
+ * @param methodName - The name of the method to query
137
+ * @param args - Optional arguments to pass to the method
138
+ * @returns Query options object for TanStack Query's useQuery
139
+ */
140
+ export function modelenceLiveQuery<T = unknown>(
141
+ methodName: string,
142
+ args: Args = {}
143
+ ) {
144
+ const queryKey = ['live', methodName, args] as const;
145
+ const subscriptionKey = hashKey(queryKey);
146
+
147
+ return {
148
+ queryKey,
149
+ queryFn: () => new Promise<T>((resolve, reject) => {
150
+ if (!queryClientRef) {
151
+ const error = new Error('ModelenceQueryClient must be connected before using modelenceLiveQuery()');
152
+ console.error('[Modelence]', error.message);
153
+ reject(error);
154
+ return;
155
+ }
156
+
157
+ let sub = subscriptions.get(subscriptionKey);
158
+
159
+ if (!sub) {
160
+ const unsubscribe = subscribeLiveQuery<T>(
161
+ methodName,
162
+ args,
163
+ (data) => {
164
+ const currentSub = subscriptions.get(subscriptionKey);
165
+
166
+ if (currentSub?.resolvers.size) {
167
+ currentSub.resolvers.forEach((r) => r.resolve(data));
168
+ currentSub.resolvers.clear();
169
+ }
170
+
171
+ if (queryClientRef) {
172
+ queryClientRef.setQueryData(queryKey, data);
173
+ }
174
+ },
175
+ (error) => {
176
+ const currentSub = subscriptions.get(subscriptionKey);
177
+ if (currentSub?.resolvers.size) {
178
+ currentSub.resolvers.forEach((r) => r.reject(new Error(error)));
179
+ currentSub.resolvers.clear();
180
+ }
181
+ }
182
+ );
183
+
184
+ sub = { unsubscribe, resolvers: new Set() };
185
+ subscriptions.set(subscriptionKey, sub);
186
+ }
187
+
188
+ sub.resolvers.add({
189
+ resolve: resolve as (data: unknown) => void,
190
+ reject,
191
+ });
192
+ }),
193
+ staleTime: Infinity,
194
+ refetchOnWindowFocus: false,
195
+ refetchOnMount: false,
196
+ refetchOnReconnect: false,
197
+ gcTime: 0,
198
+ };
199
+ }
200
+
44
201
  /**
45
202
  * Creates mutation options for use with TanStack Query's useMutation hook.
46
203
  *
package/yalc.lock ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": "v1",
3
+ "packages": {
4
+ "modelence": {
5
+ "signature": "54beb281dea95e11540cf0bb45ff9771",
6
+ "file": true,
7
+ "replaced": "^0.6.2"
8
+ }
9
+ }
10
+ }