appflare 0.2.3 → 0.2.5

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/Documentation.md CHANGED
@@ -604,13 +604,9 @@ import { useMutation } from "appflare/react";
604
604
  import { appflare } from "./appflare-client";
605
605
 
606
606
  export function CreatePostButton() {
607
- const mutation = useMutation(
608
- appflare.mutations.test.newTest,
609
- {},
610
- {
611
- onSuccess: (data) => console.log("created", data),
612
- },
613
- );
607
+ const mutation = useMutation(appflare.mutations.test.newTest, {
608
+ onSuccess: (data) => console.log("created", data),
609
+ });
614
610
 
615
611
  return (
616
612
  <button onClick={() => mutation.mutate()} disabled={mutation.isPending}>
@@ -1,5 +1,6 @@
1
1
  import chokidar from "chokidar";
2
- import { resolve } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, resolve } from "node:path";
3
4
  import { generateArtifacts } from "../generate";
4
5
  import { loadConfig } from "../load-config";
5
6
 
@@ -9,6 +10,23 @@ type MigrateOptions = {
9
10
  preview?: boolean;
10
11
  };
11
12
 
13
+ function findNearestPackageDir(startDir: string): string {
14
+ let currentDir = startDir;
15
+
16
+ while (true) {
17
+ if (existsSync(resolve(currentDir, "package.json"))) {
18
+ return currentDir;
19
+ }
20
+
21
+ const parentDir = dirname(currentDir);
22
+ if (parentDir === currentDir) {
23
+ return startDir;
24
+ }
25
+
26
+ currentDir = parentDir;
27
+ }
28
+ }
29
+
12
30
  export async function runBuild(configPath?: string): Promise<void> {
13
31
  const loadedConfig = await loadConfig(configPath);
14
32
  await generateArtifacts(loadedConfig);
@@ -75,6 +93,8 @@ export async function runMigrate(
75
93
  options: MigrateOptions = {},
76
94
  ): Promise<void> {
77
95
  const loadedConfig = await loadConfig(configPath);
96
+ const npxCommand = process.platform === "win32" ? "npx.cmd" : "npx";
97
+ const packageDir = findNearestPackageDir(process.cwd());
78
98
  const selectedTargetCount = [
79
99
  Boolean(options.local),
80
100
  Boolean(options.remote),
@@ -90,15 +110,16 @@ export async function runMigrate(
90
110
  "drizzle.config.ts",
91
111
  );
92
112
  const drizzleGenerate = Bun.spawn(
93
- ["npx", "drizzle-kit", "generate", "--config", drizzleConfigPath],
113
+ [npxCommand, "drizzle-kit", "generate", "--config", drizzleConfigPath],
94
114
  {
95
- cwd: loadedConfig.configDir,
115
+ cwd: packageDir,
96
116
  stdin: "inherit",
97
117
  stdout: "inherit",
98
118
  stderr: "inherit",
99
119
  },
100
120
  );
101
121
 
122
+ console.log(`npx drizzle-kit generate --config ${drizzleConfigPath}`);
102
123
  const drizzleExitCode = await drizzleGenerate.exited;
103
124
  if (drizzleExitCode !== 0) {
104
125
  throw new Error(
@@ -108,7 +129,7 @@ export async function runMigrate(
108
129
 
109
130
  const databaseName = loadedConfig.config.database[0].databaseName;
110
131
  const wranglerArgs = [
111
- "npx",
132
+ npxCommand,
112
133
  "wrangler",
113
134
  "d1",
114
135
  "migrations",
@@ -142,9 +142,9 @@ function renderRouteFactory(operation: HttpOperation): string {
142
142
  runtime: RequestRuntime,
143
143
  ): AppflareQueryRouteClient<typeof ${operation.schemaConst}, ${outputType}> => {
144
144
  const run: AppflareQueryRouteClient<typeof ${operation.schemaConst}, ${outputType}>["run"] = async (
145
- args: ${inputType},
146
- options?: AppflareResultRouteCallOptions,
145
+ ...params: AppflareRunParams<${inputType}>
147
146
  ) => {
147
+ const { args, options } = resolveRunParams<${inputType}>(params);
148
148
  const mergedOptions = mergeRouteOptions(runtime.options, options);
149
149
  const resultOptions: AppflareRouteCallOptions<"return"> = {
150
150
  ...(mergedOptions ?? {}),
@@ -168,9 +168,7 @@ function renderRouteFactory(operation: HttpOperation): string {
168
168
  signal,
169
169
  }: AppflareQuerySubscribeOptions<${inputType}, ${outputType}>): AppflareRealtimeSubscription => {
170
170
  const mergedOptions = mergeRouteOptions(runtime.options, requestOptions);
171
- const parsedArgs = ${operation.schemaConst}.parse(
172
- (args ?? {}) as ${inputType},
173
- ) as ${inputType};
171
+ const parsedArgs = ${operation.schemaConst}.parse(normalizeRouteInput(args));
174
172
  const requestAuthToken = resolveRealtimeAuthToken(authToken, mergedOptions?.headers);
175
173
 
176
174
  let removed = false;
@@ -307,9 +305,9 @@ function renderRouteFactory(operation: HttpOperation): string {
307
305
  runtime: RequestRuntime,
308
306
  ): AppflareRouteClient<typeof ${operation.schemaConst}, ${outputType}> => {
309
307
  const run: AppflareRouteClient<typeof ${operation.schemaConst}, ${outputType}>["run"] = async (
310
- args: ${inputType},
311
- options?: AppflareResultRouteCallOptions,
308
+ ...params: AppflareRunParams<${inputType}>
312
309
  ) => {
310
+ const { args, options } = resolveRunParams<${inputType}>(params);
313
311
  const mergedOptions = mergeRouteOptions(runtime.options, options);
314
312
  const resultOptions: AppflareRouteCallOptions<"return"> = {
315
313
  ...(mergedOptions ?? {}),
@@ -385,6 +383,7 @@ import type {
385
383
  AppflareResultRouteCallOptions,
386
384
  AppflareRouteCallOptions,
387
385
  AppflareRouteClient,
386
+ AppflareRunParams,
388
387
  } from "./types";
389
388
  ${imports ? `\n${imports}` : ""}
390
389
 
@@ -429,6 +428,25 @@ function mergeRouteOptions(
429
428
  };
430
429
  }
431
430
 
431
+ function normalizeRouteInput<TInput extends Record<string, unknown>>(
432
+ input: TInput | undefined,
433
+ ): TInput {
434
+ return (input ?? {}) as TInput;
435
+ }
436
+
437
+ function resolveRunParams<TInput extends Record<string, unknown>>(
438
+ params: AppflareRunParams<TInput>,
439
+ ): {
440
+ args: TInput;
441
+ options?: AppflareResultRouteCallOptions;
442
+ } {
443
+ const [args, options] = params;
444
+ return {
445
+ args: normalizeRouteInput(args),
446
+ options,
447
+ };
448
+ }
449
+
432
450
  function readHeaderCaseInsensitive(
433
451
  headers: HeadersInit | undefined,
434
452
  headerName: string,
@@ -52,15 +52,30 @@ export type AppflareResultRouteCallOptions = Omit<
52
52
  "errorMode"
53
53
  >;
54
54
 
55
+ export type AppflareRouteInput<
56
+ TSchema extends import("zod").ZodObject<import("zod").ZodRawShape>,
57
+ > = import("zod").input<TSchema>;
58
+
59
+ type AppflareRequiredInputKeys<TInput extends Record<string, unknown>> = {
60
+ [K in keyof TInput]-?: undefined extends TInput[K] ? never : K;
61
+ }[keyof TInput];
62
+
63
+ type AppflareHasRequiredInputKeys<TInput extends Record<string, unknown>> =
64
+ [AppflareRequiredInputKeys<TInput>] extends [never] ? false : true;
65
+
66
+ export type AppflareRunParams<TInput extends Record<string, unknown>> =
67
+ AppflareHasRequiredInputKeys<TInput> extends true
68
+ ? [args: TInput, options?: AppflareResultRouteCallOptions]
69
+ : [args?: TInput, options?: AppflareResultRouteCallOptions];
70
+
55
71
  export type AppflareRouteClient<
56
72
  TSchema extends import("zod").ZodObject<import("zod").ZodRawShape>,
57
73
  TOutput,
58
74
  > = {
59
75
  schema: TSchema;
60
- run(
61
- args: import("zod").input<TSchema>,
62
- options?: AppflareResultRouteCallOptions,
63
- ): Promise<AppflareRequestResult<TOutput>>;
76
+ run(...params: AppflareRunParams<AppflareRouteInput<TSchema>>): Promise<
77
+ AppflareRequestResult<TOutput>
78
+ >;
64
79
  };
65
80
 
66
81
  export type AppflareRealtimeSubscription = {
@@ -100,12 +115,10 @@ export type InferRouteSchema<TRoute> = TRoute extends {
100
115
  ? TSchema
101
116
  : never;
102
117
 
103
- export type InferRouteInput<TRoute> = TRoute extends {
104
- run: (...args: infer TArgs) => Promise<unknown>;
105
- }
106
- ? TArgs extends [infer TInput, ...unknown[]]
107
- ? TInput
108
- : never
118
+ export type InferRouteInput<TRoute> = InferRouteSchema<TRoute> extends import("zod").ZodObject<
119
+ import("zod").ZodRawShape
120
+ >
121
+ ? AppflareRouteInput<InferRouteSchema<TRoute>>
109
122
  : never;
110
123
 
111
124
  export type InferRouteResult<TRoute> = TRoute extends {
@@ -10,7 +10,7 @@ export class AppflareRealtimeDurableObject {
10
10
 
11
11
  if (request.method === "POST" && url.pathname === "/subscribe") {
12
12
  const payload = (await request.json().catch(() => null)) as RealtimeSubscription | null;
13
- if (!payload?.token || !payload.queryName || !payload.authToken) {
13
+ if (!payload?.token || !payload.queryName ) {
14
14
  return new Response(JSON.stringify({ message: "Invalid subscription payload" }), {
15
15
  status: 400,
16
16
  headers: { "content-type": "application/json" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "appflare",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "bin": {
5
5
  "appflare": "./cli/index.ts"
6
6
  },
@@ -1,4 +1,5 @@
1
1
  import {
2
+ hashKey,
2
3
  type InfiniteData,
3
4
  type QueryKey,
4
5
  type UseInfiniteQueryOptions,
@@ -6,7 +7,7 @@ import {
6
7
  useInfiniteQuery as useTanstackInfiniteQuery,
7
8
  useQueryClient,
8
9
  } from "@tanstack/react-query";
9
- import { useEffect, useMemo } from "react";
10
+ import { useEffect, useMemo, useRef } from "react";
10
11
 
11
12
  type AppflareRequestErrorLike = {
12
13
  message: string;
@@ -31,10 +32,24 @@ type AppflareRealtimeQueryUpdateLike<TData> = {
31
32
  };
32
33
  };
33
34
 
34
- type AppflareQueryLike<TArgs, TData> = {
35
+ type AppflareRequiredInputKeys<TInput extends Record<string, unknown>> = {
36
+ [K in keyof TInput]-?: undefined extends TInput[K] ? never : K;
37
+ }[keyof TInput];
38
+
39
+ type AppflareHasRequiredInputKeys<TInput extends Record<string, unknown>> = [
40
+ AppflareRequiredInputKeys<TInput>,
41
+ ] extends [never]
42
+ ? false
43
+ : true;
44
+
45
+ type AppflareRouteRunArgs<TInput extends Record<string, unknown>> =
46
+ AppflareHasRequiredInputKeys<TInput> extends true
47
+ ? [args: TInput, options?: any]
48
+ : [args?: TInput, options?: any];
49
+
50
+ type AppflareQueryLike<TArgs extends Record<string, unknown>, TData> = {
35
51
  run: (
36
- args: TArgs,
37
- options?: any,
52
+ ...params: AppflareRouteRunArgs<TArgs>
38
53
  ) => Promise<AppflareRequestResultLike<TData>>;
39
54
  subscribe?: (options: {
40
55
  args?: TArgs;
@@ -50,7 +65,7 @@ type AppflareQueryLike<TArgs, TData> = {
50
65
  };
51
66
 
52
67
  export type UseAppflareInfiniteQueryOptions<
53
- TArgs,
68
+ TArgs extends Record<string, unknown>,
54
69
  TData,
55
70
  TPageParam,
56
71
  TSelected = InfiniteData<TData, TPageParam>,
@@ -76,6 +91,35 @@ export type UseAppflareInfiniteQueryOptions<
76
91
  };
77
92
  };
78
93
 
94
+ type UseInfiniteQueryCallParams<
95
+ TArgs extends Record<string, unknown>,
96
+ TData,
97
+ TPageParam,
98
+ TSelected,
99
+ TKey extends QueryKey,
100
+ > =
101
+ AppflareHasRequiredInputKeys<TArgs> extends true
102
+ ? [
103
+ args: TArgs,
104
+ options?: UseAppflareInfiniteQueryOptions<
105
+ TArgs,
106
+ TData,
107
+ TPageParam,
108
+ TSelected,
109
+ TKey
110
+ >,
111
+ ]
112
+ : [
113
+ args?: TArgs,
114
+ options?: UseAppflareInfiniteQueryOptions<
115
+ TArgs,
116
+ TData,
117
+ TPageParam,
118
+ TSelected,
119
+ TKey
120
+ >,
121
+ ];
122
+
79
123
  function toError(error: AppflareRequestErrorLike): Error {
80
124
  const next = new Error(error.message);
81
125
  if (error.status !== undefined) {
@@ -85,23 +129,32 @@ function toError(error: AppflareRequestErrorLike): Error {
85
129
  }
86
130
 
87
131
  export function useInfiniteQuery<
88
- TArgs,
132
+ TArgs extends Record<string, unknown>,
89
133
  TData,
90
134
  TPageParam = unknown,
91
135
  TSelected = InfiniteData<TData, TPageParam>,
92
136
  TKey extends QueryKey = QueryKey,
93
137
  >(
94
138
  query: AppflareQueryLike<TArgs, TData>,
95
- args: TArgs,
96
- options?: UseAppflareInfiniteQueryOptions<
139
+ ...params: UseInfiniteQueryCallParams<
97
140
  TArgs,
98
141
  TData,
99
142
  TPageParam,
100
143
  TSelected,
101
144
  TKey
102
- >,
145
+ >
103
146
  ): UseInfiniteQueryResult<TSelected, Error> {
147
+ const args = (params[0] ?? {}) as TArgs;
148
+ const options = params[1];
104
149
  const queryClient = useQueryClient();
150
+ const realtimeOnChangeRef = useRef(options?.realtime?.onChange);
151
+ const realtimeOnErrorRef = useRef(options?.realtime?.onError);
152
+
153
+ useEffect(() => {
154
+ realtimeOnChangeRef.current = options?.realtime?.onChange;
155
+ realtimeOnErrorRef.current = options?.realtime?.onError;
156
+ }, [options?.realtime?.onChange, options?.realtime?.onError]);
157
+
105
158
  const resolvedQueryKey = useMemo(() => {
106
159
  return (
107
160
  options?.queryOptions?.queryKey ??
@@ -109,6 +162,18 @@ export function useInfiniteQuery<
109
162
  );
110
163
  }, [args, options?.queryOptions?.queryKey, query]);
111
164
 
165
+ const resolvedQueryKeyHash = useMemo(
166
+ () => hashKey(resolvedQueryKey as QueryKey),
167
+ [resolvedQueryKey],
168
+ );
169
+
170
+ const realtimeArgsHash = useMemo(() => hashKey([args] as QueryKey), [args]);
171
+
172
+ const realtimeRequestOptionsHash = useMemo(
173
+ () => hashKey([options?.realtime?.requestOptions] as QueryKey),
174
+ [options?.realtime?.requestOptions],
175
+ );
176
+
112
177
  const result = useTanstackInfiniteQuery<
113
178
  TData,
114
179
  Error,
@@ -159,13 +224,13 @@ export function useInfiniteQuery<
159
224
  };
160
225
  },
161
226
  );
162
- options.realtime?.onChange?.(
227
+ realtimeOnChangeRef.current?.(
163
228
  data as TData,
164
229
  update as AppflareRealtimeQueryUpdateLike<TData>,
165
230
  );
166
231
  },
167
232
  onError: (error) => {
168
- options.realtime?.onError?.(error);
233
+ realtimeOnErrorRef.current?.(error);
169
234
  },
170
235
  });
171
236
 
@@ -174,16 +239,13 @@ export function useInfiniteQuery<
174
239
  subscription.remove();
175
240
  };
176
241
  }, [
177
- args,
242
+ realtimeArgsHash,
178
243
  options?.realtime?.authToken,
179
244
  options?.realtime?.enabled,
180
- options?.realtime?.onChange,
181
- options?.realtime?.onError,
182
- options?.realtime?.requestOptions,
183
- query,
245
+ realtimeRequestOptionsHash,
184
246
  query.subscribe,
185
247
  queryClient,
186
- resolvedQueryKey,
248
+ resolvedQueryKeyHash,
187
249
  ]);
188
250
 
189
251
  return result;
@@ -14,10 +14,27 @@ type AppflareRequestResultLike<TData> = {
14
14
  error: AppflareRequestErrorLike | null;
15
15
  };
16
16
 
17
- type AppflareMutationLike<TArgs, TData> = {
17
+ type AppflareRequiredInputKeys<TInput extends Record<string, unknown>> = {
18
+ [K in keyof TInput]-?: undefined extends TInput[K] ? never : K;
19
+ }[keyof TInput];
20
+
21
+ type AppflareHasRequiredInputKeys<TInput extends Record<string, unknown>> = [
22
+ AppflareRequiredInputKeys<TInput>,
23
+ ] extends [never]
24
+ ? false
25
+ : true;
26
+
27
+ type AppflareRouteRunArgs<TInput extends Record<string, unknown>> =
28
+ AppflareHasRequiredInputKeys<TInput> extends true
29
+ ? [args: TInput, options?: any]
30
+ : [args?: TInput, options?: any];
31
+
32
+ type AppflareMutationVariables<TArgs extends Record<string, unknown>> =
33
+ AppflareHasRequiredInputKeys<TArgs> extends true ? TArgs : TArgs | void;
34
+
35
+ type AppflareMutationLike<TArgs extends Record<string, unknown>, TData> = {
18
36
  run: (
19
- args: TArgs,
20
- options?: any,
37
+ ...params: AppflareRouteRunArgs<TArgs>
21
38
  ) => Promise<AppflareRequestResultLike<TData>>;
22
39
  };
23
40
 
@@ -29,21 +46,39 @@ function toError(error: AppflareRequestErrorLike): Error {
29
46
  return next;
30
47
  }
31
48
 
32
- export function useMutation<TArgs, TData, TContext = unknown>(
49
+ export function useMutation<
50
+ TArgs extends Record<string, unknown>,
51
+ TData,
52
+ TContext = unknown,
53
+ >(
33
54
  mutation: AppflareMutationLike<TArgs, TData>,
34
- args: TArgs,
35
55
  mutationOptions?: Omit<
36
- UseMutationOptions<TData, Error, void, TContext>,
56
+ UseMutationOptions<
57
+ TData,
58
+ Error,
59
+ AppflareMutationVariables<TArgs>,
60
+ TContext
61
+ >,
37
62
  "mutationFn"
38
63
  >,
39
- ): UseMutationResult<TData, Error, void, TContext> {
40
- return useTanstackMutation<TData, Error, void, TContext>({
64
+ ): UseMutationResult<TData, Error, AppflareMutationVariables<TArgs>, TContext> {
65
+ return useTanstackMutation<
66
+ TData,
67
+ Error,
68
+ AppflareMutationVariables<TArgs>,
69
+ TContext
70
+ >({
41
71
  ...(mutationOptions as Omit<
42
- UseMutationOptions<TData, Error, void, TContext>,
72
+ UseMutationOptions<
73
+ TData,
74
+ Error,
75
+ AppflareMutationVariables<TArgs>,
76
+ TContext
77
+ >,
43
78
  "mutationFn"
44
79
  >),
45
- mutationFn: async () => {
46
- const response = await mutation.run(args);
80
+ mutationFn: async (args: AppflareMutationVariables<TArgs>) => {
81
+ const response = await mutation.run((args ?? {}) as TArgs);
47
82
  if (response.error) {
48
83
  throw toError(response.error);
49
84
  }
@@ -1,11 +1,12 @@
1
1
  import {
2
+ hashKey,
2
3
  type QueryKey,
3
4
  type UseQueryOptions,
4
5
  type UseQueryResult,
5
6
  useQuery as useTanstackQuery,
6
7
  useQueryClient,
7
8
  } from "@tanstack/react-query";
8
- import { useEffect, useMemo } from "react";
9
+ import { useEffect, useMemo, useRef } from "react";
9
10
 
10
11
  type AppflareRequestErrorLike = {
11
12
  message: string;
@@ -30,10 +31,24 @@ type AppflareRealtimeQueryUpdateLike<TData> = {
30
31
  };
31
32
  };
32
33
 
33
- type AppflareQueryLike<TArgs, TData> = {
34
+ type AppflareRequiredInputKeys<TInput extends Record<string, unknown>> = {
35
+ [K in keyof TInput]-?: undefined extends TInput[K] ? never : K;
36
+ }[keyof TInput];
37
+
38
+ type AppflareHasRequiredInputKeys<TInput extends Record<string, unknown>> = [
39
+ AppflareRequiredInputKeys<TInput>,
40
+ ] extends [never]
41
+ ? false
42
+ : true;
43
+
44
+ type AppflareRouteRunArgs<TInput extends Record<string, unknown>> =
45
+ AppflareHasRequiredInputKeys<TInput> extends true
46
+ ? [args: TInput, options?: any]
47
+ : [args?: TInput, options?: any];
48
+
49
+ type AppflareQueryLike<TArgs extends Record<string, unknown>, TData> = {
34
50
  run: (
35
- args: TArgs,
36
- options?: any,
51
+ ...params: AppflareRouteRunArgs<TArgs>
37
52
  ) => Promise<AppflareRequestResultLike<TData>>;
38
53
  subscribe?: (options: {
39
54
  args?: TArgs;
@@ -49,7 +64,7 @@ type AppflareQueryLike<TArgs, TData> = {
49
64
  };
50
65
 
51
66
  export type UseAppflareQueryOptions<
52
- TArgs,
67
+ TArgs extends Record<string, unknown>,
53
68
  TData,
54
69
  TSelected = TData,
55
70
  TKey extends QueryKey = QueryKey,
@@ -73,6 +88,22 @@ export type UseAppflareQueryOptions<
73
88
  };
74
89
  };
75
90
 
91
+ type UseQueryCallParams<
92
+ TArgs extends Record<string, unknown>,
93
+ TData,
94
+ TSelected,
95
+ TKey extends QueryKey,
96
+ > =
97
+ AppflareHasRequiredInputKeys<TArgs> extends true
98
+ ? [
99
+ args: TArgs,
100
+ options?: UseAppflareQueryOptions<TArgs, TData, TSelected, TKey>,
101
+ ]
102
+ : [
103
+ args?: TArgs,
104
+ options?: UseAppflareQueryOptions<TArgs, TData, TSelected, TKey>,
105
+ ];
106
+
76
107
  function toError(error: AppflareRequestErrorLike): Error {
77
108
  const next = new Error(error.message);
78
109
  if (error.status !== undefined) {
@@ -82,16 +113,25 @@ function toError(error: AppflareRequestErrorLike): Error {
82
113
  }
83
114
 
84
115
  export function useQuery<
85
- TArgs,
116
+ TArgs extends Record<string, unknown>,
86
117
  TData,
87
118
  TSelected = TData,
88
119
  TKey extends QueryKey = QueryKey,
89
120
  >(
90
121
  query: AppflareQueryLike<TArgs, TData>,
91
- args: TArgs,
92
- options?: UseAppflareQueryOptions<TArgs, TData, TSelected, TKey>,
122
+ ...params: UseQueryCallParams<TArgs, TData, TSelected, TKey>
93
123
  ): UseQueryResult<TSelected, Error> {
124
+ const args = (params[0] ?? {}) as TArgs;
125
+ const options = params[1];
94
126
  const queryClient = useQueryClient();
127
+ const realtimeOnChangeRef = useRef(options?.realtime?.onChange);
128
+ const realtimeOnErrorRef = useRef(options?.realtime?.onError);
129
+
130
+ useEffect(() => {
131
+ realtimeOnChangeRef.current = options?.realtime?.onChange;
132
+ realtimeOnErrorRef.current = options?.realtime?.onError;
133
+ }, [options?.realtime?.onChange, options?.realtime?.onError]);
134
+
95
135
  const resolvedQueryKey = useMemo(() => {
96
136
  return (
97
137
  options?.queryOptions?.queryKey ??
@@ -99,6 +139,18 @@ export function useQuery<
99
139
  );
100
140
  }, [args, options?.queryOptions?.queryKey, query]);
101
141
 
142
+ const resolvedQueryKeyHash = useMemo(
143
+ () => hashKey(resolvedQueryKey as QueryKey),
144
+ [resolvedQueryKey],
145
+ );
146
+
147
+ const realtimeArgsHash = useMemo(() => hashKey([args] as QueryKey), [args]);
148
+
149
+ const realtimeRequestOptionsHash = useMemo(
150
+ () => hashKey([options?.realtime?.requestOptions] as QueryKey),
151
+ [options?.realtime?.requestOptions],
152
+ );
153
+
102
154
  const result = useTanstackQuery<TData, Error, TSelected, TKey>({
103
155
  ...(options?.queryOptions as Omit<
104
156
  UseQueryOptions<TData, Error, TSelected, TKey>,
@@ -127,13 +179,13 @@ export function useQuery<
127
179
  signal: controller.signal,
128
180
  onChange: (data, update) => {
129
181
  queryClient.setQueryData<TData>(resolvedQueryKey, data as TData);
130
- options.realtime?.onChange?.(
182
+ realtimeOnChangeRef.current?.(
131
183
  data as TData,
132
184
  update as AppflareRealtimeQueryUpdateLike<TData>,
133
185
  );
134
186
  },
135
187
  onError: (error) => {
136
- options.realtime?.onError?.(error);
188
+ realtimeOnErrorRef.current?.(error);
137
189
  },
138
190
  });
139
191
 
@@ -142,16 +194,13 @@ export function useQuery<
142
194
  subscription.remove();
143
195
  };
144
196
  }, [
145
- args,
197
+ realtimeArgsHash,
146
198
  options?.realtime?.authToken,
147
199
  options?.realtime?.enabled,
148
- options?.realtime?.onChange,
149
- options?.realtime?.onError,
150
- options?.realtime?.requestOptions,
151
- query,
200
+ realtimeRequestOptionsHash,
152
201
  query.subscribe,
153
202
  queryClient,
154
- resolvedQueryKey,
203
+ resolvedQueryKeyHash,
155
204
  ]);
156
205
 
157
206
  return result;