appflare 0.0.28 → 0.1.1

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 (151) hide show
  1. package/cli/commands/index.ts +140 -0
  2. package/cli/generate.ts +149 -0
  3. package/cli/index.ts +56 -447
  4. package/cli/load-config.ts +182 -0
  5. package/cli/schema-compiler.ts +657 -0
  6. package/cli/templates/auth/README.md +156 -0
  7. package/cli/templates/auth/config.ts +61 -0
  8. package/cli/templates/auth/route-config.ts +18 -0
  9. package/cli/templates/auth/route-handler.ts +18 -0
  10. package/cli/templates/auth/route-request-utils.ts +55 -0
  11. package/cli/templates/auth/route.ts +14 -0
  12. package/cli/templates/core/README.md +266 -0
  13. package/cli/templates/core/app-creation.ts +19 -0
  14. package/cli/templates/core/client/appflare.ts +37 -0
  15. package/cli/templates/core/client/index.ts +6 -0
  16. package/cli/templates/core/client/storage.ts +100 -0
  17. package/cli/templates/core/client/types.ts +54 -0
  18. package/cli/templates/core/client-modules/appflare.ts +112 -0
  19. package/cli/templates/core/client-modules/handlers/index.ts +747 -0
  20. package/cli/templates/core/client-modules/handlers.ts +1 -0
  21. package/cli/templates/core/client-modules/index.ts +7 -0
  22. package/cli/templates/core/client-modules/storage.ts +180 -0
  23. package/cli/templates/core/client-modules/types.ts +145 -0
  24. package/cli/templates/core/client.ts +39 -0
  25. package/cli/templates/core/drizzle.ts +15 -0
  26. package/cli/templates/core/export.ts +14 -0
  27. package/cli/templates/core/handlers-route.ts +23 -0
  28. package/cli/templates/core/handlers.ts +1 -0
  29. package/cli/templates/core/imports.ts +8 -0
  30. package/cli/templates/core/server.ts +38 -0
  31. package/cli/templates/core/types.ts +6 -0
  32. package/cli/templates/core/wrangler.ts +109 -0
  33. package/cli/templates/handlers/README.md +353 -0
  34. package/cli/templates/handlers/auth.ts +37 -0
  35. package/cli/templates/handlers/execution.ts +39 -0
  36. package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
  37. package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
  38. package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
  39. package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
  40. package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
  41. package/cli/templates/handlers/generators/context/types.ts +18 -0
  42. package/cli/templates/handlers/generators/context.ts +43 -0
  43. package/cli/templates/handlers/generators/execution.ts +15 -0
  44. package/cli/templates/handlers/generators/handlers.ts +13 -0
  45. package/cli/templates/handlers/generators/registration/modules/cron.ts +30 -0
  46. package/cli/templates/handlers/generators/registration/modules/realtime.ts +631 -0
  47. package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -0
  48. package/cli/templates/handlers/generators/registration/modules/storage.ts +194 -0
  49. package/cli/templates/handlers/generators/registration/sections.ts +210 -0
  50. package/cli/templates/handlers/generators/types/context.ts +64 -0
  51. package/cli/templates/handlers/generators/types/core.ts +101 -0
  52. package/cli/templates/handlers/generators/types/operations.ts +135 -0
  53. package/cli/templates/handlers/generators/types/query-definitions.ts +1029 -0
  54. package/cli/templates/handlers/generators/types/query-runtime.ts +417 -0
  55. package/cli/templates/handlers/index.ts +43 -0
  56. package/cli/templates/handlers/operations.ts +116 -0
  57. package/cli/templates/handlers/registration.ts +83 -0
  58. package/cli/templates/handlers/types.ts +15 -0
  59. package/cli/templates/handlers/utils.ts +48 -0
  60. package/cli/types.ts +108 -0
  61. package/cli/utils/handler-discovery.ts +366 -0
  62. package/cli/utils/json-utils.ts +24 -0
  63. package/cli/utils/path-utils.ts +19 -0
  64. package/cli/utils/schema-discovery.ts +390 -0
  65. package/index.ts +27 -4
  66. package/package.json +23 -20
  67. package/react/index.ts +5 -3
  68. package/react/use-infinite-query.ts +190 -0
  69. package/react/use-mutation.ts +54 -0
  70. package/react/use-query.ts +158 -0
  71. package/schema.ts +262 -0
  72. package/tsconfig.json +2 -4
  73. package/cli/README.md +0 -108
  74. package/cli/core/build.ts +0 -187
  75. package/cli/core/config.ts +0 -92
  76. package/cli/core/discover-handlers.ts +0 -143
  77. package/cli/core/handlers.ts +0 -7
  78. package/cli/core/index.ts +0 -205
  79. package/cli/generators/generate-api-client/client.ts +0 -163
  80. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  81. package/cli/generators/generate-api-client/index.ts +0 -973
  82. package/cli/generators/generate-api-client/types.ts +0 -164
  83. package/cli/generators/generate-api-client/utils.ts +0 -22
  84. package/cli/generators/generate-api-client.ts +0 -1
  85. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  86. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  87. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  88. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  89. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  90. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  91. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  92. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  93. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  94. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  95. package/cli/generators/generate-db-handlers/index.ts +0 -33
  96. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  97. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  98. package/cli/generators/generate-db-handlers.ts +0 -1
  99. package/cli/generators/generate-hono-server/auth.ts +0 -97
  100. package/cli/generators/generate-hono-server/imports.ts +0 -55
  101. package/cli/generators/generate-hono-server/index.ts +0 -52
  102. package/cli/generators/generate-hono-server/routes.ts +0 -115
  103. package/cli/generators/generate-hono-server/template.ts +0 -371
  104. package/cli/generators/generate-hono-server.ts +0 -1
  105. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  106. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  107. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  108. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  109. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  110. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  111. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  112. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  113. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  114. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  115. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  116. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  117. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  118. package/cli/schema/schema-static-types.ts +0 -702
  119. package/cli/schema/schema.ts +0 -151
  120. package/cli/utils/tsc.ts +0 -54
  121. package/cli/utils/utils.ts +0 -190
  122. package/cli/utils/zod-utils.ts +0 -121
  123. package/lib/README.md +0 -50
  124. package/lib/db.ts +0 -19
  125. package/lib/location.ts +0 -110
  126. package/lib/values.ts +0 -27
  127. package/react/README.md +0 -67
  128. package/react/hooks/useMutation.ts +0 -89
  129. package/react/hooks/usePaginatedQuery.ts +0 -213
  130. package/react/hooks/useQuery.ts +0 -106
  131. package/react/shared/queryShared.ts +0 -174
  132. package/server/README.md +0 -218
  133. package/server/auth.ts +0 -107
  134. package/server/database/builders.ts +0 -83
  135. package/server/database/context.ts +0 -327
  136. package/server/database/populate.ts +0 -234
  137. package/server/database/query-builder.ts +0 -161
  138. package/server/database/query-utils.ts +0 -25
  139. package/server/db.ts +0 -2
  140. package/server/storage/auth.ts +0 -16
  141. package/server/storage/bucket.ts +0 -22
  142. package/server/storage/context.ts +0 -34
  143. package/server/storage/index.ts +0 -38
  144. package/server/storage/operations.ts +0 -149
  145. package/server/storage/route-handler.ts +0 -60
  146. package/server/storage/types.ts +0 -55
  147. package/server/storage/utils.ts +0 -47
  148. package/server/storage.ts +0 -6
  149. package/server/types/schema-refs.ts +0 -66
  150. package/server/types/types.ts +0 -633
  151. package/server/utils/id-utils.ts +0 -230
package/lib/values.ts DELETED
@@ -1,27 +0,0 @@
1
- import { z } from "zod";
2
- import { geoPointSchema } from "./location";
3
-
4
- export const v = {
5
- string: () => z.string(),
6
- number: () => z.number(),
7
- boolean: () => z.boolean(),
8
- date: () => z.date(),
9
- point: () => geoPointSchema,
10
- location: () => geoPointSchema,
11
- id: (table: string) =>
12
- z
13
- .string()
14
- .regex(/^[a-f\d]{24}$/i, "Invalid ObjectId")
15
- .describe(`ref:${table}`) as z.ZodString,
16
- array: <T extends z.ZodTypeAny>(item: T) => z.array(item),
17
- object: (shape: Record<string, z.ZodTypeAny>) => z.object(shape),
18
- optional: <T extends z.ZodTypeAny>(schema: T) => schema.optional(),
19
- nullable: <T extends z.ZodTypeAny>(schema: T) => schema.nullable(),
20
- union: <T extends [z.ZodTypeAny, ...z.ZodTypeAny[]]>(...schemas: T) =>
21
- z.union(schemas),
22
- literal: (value: any) => z.literal(value),
23
- buffer: () => z.string(), // or z.instanceof(Buffer) if using Buffer
24
- any: () => z.any(),
25
- unknown: () => z.unknown(),
26
- // Add more as needed
27
- };
package/react/README.md DELETED
@@ -1,67 +0,0 @@
1
- ## React hooks (`react/`)
2
-
3
- Entry point re-exports hooks ([packages/appflare/react/index.ts](packages/appflare/react/index.ts)). Hooks expect handlers generated by the Appflare client (each handler has `.path`, `.schema`, and `.websocket` metadata).
4
-
5
- ### Shared realtime utilities
6
-
7
- - Located in [packages/appflare/react/shared/queryShared.ts](packages/appflare/react/shared/queryShared.ts).
8
- - `useRealtimeSubscription` wires a handler’s `websocket` helper into React Query caches; accepts `realtime` options (`enabled`, `baseUrl`, `where`, `orderBy`, `onMessage`, `onData`, etc.).
9
- - Helpers: `buildQueryKey(queryKey, handler, argsKey)` and `stableSerialize(value)` for deterministic keys.
10
-
11
- ### `useQuery`
12
-
13
- - File: [packages/appflare/react/hooks/useQuery.ts](packages/appflare/react/hooks/useQuery.ts).
14
- - Wraps `@tanstack/react-query` `useQuery` and injects realtime updates when the handler exposes `websocket`.
15
- - Signature: `useQuery({ handler, args?, queryKey?, queryOptions?, realtime? })` or `useQuery(handler, { ... })`.
16
- - Returns the React Query result plus `{ websocket }` (the live socket or `null`).
17
- - Realtime: if enabled, incoming `data` arrays replace the cached result via `setQueryData`.
18
-
19
- ### `usePaginatedQuery`
20
-
21
- - File: [packages/appflare/react/hooks/usePaginatedQuery.ts](packages/appflare/react/hooks/usePaginatedQuery.ts).
22
- - Wraps `useInfiniteQuery` for cursor pagination.
23
- - Options: `pageParamKey` (default `"cursor"`), `initialPageParam`, `getNextPageParam`, `getPreviousPageParam`, plus `realtime` controls.
24
- - On realtime `data`, replaces the first page in the cache to keep pagination metadata intact.
25
-
26
- ### `useMutation`
27
-
28
- - File: [packages/appflare/react/hooks/useMutation.ts](packages/appflare/react/hooks/useMutation.ts).
29
- - Thin wrapper over `useMutation`; builds a `mutationKey` from `handler.path` when provided.
30
- - Signature: `useMutation({ handler, mutationKey?, mutationOptions? })` or `useMutation(handler, { ... })`.
31
-
32
- ### React usage example
33
-
34
- ```tsx
35
- import { createAppflareApi } from "../_generated/src/api";
36
- import { useQuery, useMutation } from "appflare/react";
37
-
38
- const api = createAppflareApi({ baseUrl: "/api" });
39
-
40
- export function Users() {
41
- const users = useQuery(api.queries.users.list, {
42
- realtime: { baseUrl: "wss://my-app/ws" },
43
- });
44
-
45
- const createUser = useMutation(api.mutations.users.create, {
46
- mutationOptions: { onSuccess: () => users.refetch() },
47
- });
48
-
49
- return (
50
- <div>
51
- <button onClick={() => createUser.mutate({ name: "Ada" })}>
52
- Add user
53
- </button>
54
- {users.data?.map((u) => (
55
- <div key={u._id}>{u.name}</div>
56
- ))}
57
- </div>
58
- );
59
- }
60
- ```
61
-
62
- ## Integration tips
63
-
64
- - Handlers come from the generated client; ensure you pass the same `baseUrl`/`realtime.baseUrl` you configured in the server/Durable Object.
65
- - Realtime is opt-in per hook via `realtime: true` or options; if the handler lacks `websocket`, no socket is opened.
66
- - Use `queryKey`/`mutationKey` overrides when multiple instances of the same handler need isolated caches.
67
- - `v.id("table")` pairs with `describe("ref:<table>")`; keep table names consistent with your schema for proper typing in generated code.
@@ -1,89 +0,0 @@
1
- import {
2
- MutationKey,
3
- UseMutationOptions,
4
- UseMutationResult,
5
- useMutation as useNativeMutation,
6
- } from "@tanstack/react-query";
7
-
8
- export type Handler<TArgs, TResult> = {
9
- (args: TArgs, init?: RequestInit): Promise<TResult>;
10
- path?: string;
11
- schema?: unknown;
12
- };
13
-
14
- export type UseAppflareMutationOptions<
15
- TArgs,
16
- TResult,
17
- TError = unknown,
18
- TContext = unknown,
19
- > = {
20
- handler: Handler<TArgs, TResult>;
21
- mutationKey?: MutationKey;
22
- mutationOptions?: Omit<
23
- UseMutationOptions<TResult, TError, TArgs, TContext>,
24
- "mutationFn" | "mutationKey"
25
- >;
26
- };
27
-
28
- export type UseAppflareMutationResult<
29
- TResult,
30
- TError = unknown,
31
- TArgs = unknown,
32
- TContext = unknown,
33
- > = UseMutationResult<TResult, TError, TArgs, TContext>;
34
-
35
- export function useMutation<
36
- TArgs,
37
- TResult,
38
- TError = unknown,
39
- TContext = unknown,
40
- >(
41
- options: UseAppflareMutationOptions<TArgs, TResult, TError, TContext>
42
- ): UseAppflareMutationResult<TResult, TError, TArgs, TContext>;
43
-
44
- export function useMutation<
45
- TArgs,
46
- TResult,
47
- TError = unknown,
48
- TContext = unknown,
49
- >(
50
- handler: Handler<TArgs, TResult>,
51
- options?: Omit<
52
- UseAppflareMutationOptions<TArgs, TResult, TError, TContext>,
53
- "handler"
54
- >
55
- ): UseAppflareMutationResult<TResult, TError, TArgs, TContext>;
56
-
57
- export function useMutation<
58
- TArgs,
59
- TResult,
60
- TError = unknown,
61
- TContext = unknown,
62
- >(
63
- optionsOrHandler:
64
- | UseAppflareMutationOptions<TArgs, TResult, TError, TContext>
65
- | Handler<TArgs, TResult>,
66
- options?: Omit<
67
- UseAppflareMutationOptions<TArgs, TResult, TError, TContext>,
68
- "handler"
69
- >
70
- ): UseAppflareMutationResult<TResult, TError, TArgs, TContext> {
71
- const normalizedOptions =
72
- typeof optionsOrHandler === "function"
73
- ? ({
74
- handler: optionsOrHandler,
75
- ...(options ?? {}),
76
- } as UseAppflareMutationOptions<TArgs, TResult, TError, TContext>)
77
- : optionsOrHandler;
78
-
79
- const { handler, mutationKey, mutationOptions } = normalizedOptions;
80
- const finalMutationKey: MutationKey = mutationKey ?? [
81
- handler?.path ?? "appflare-handler",
82
- ];
83
-
84
- return useNativeMutation<TResult, TError, TArgs, TContext>({
85
- mutationKey: finalMutationKey,
86
- mutationFn: (variables: TArgs) => handler(variables),
87
- ...(mutationOptions ?? {}),
88
- });
89
- }
@@ -1,213 +0,0 @@
1
- import { useCallback, useMemo } from "react";
2
- import {
3
- InfiniteData,
4
- QueryKey,
5
- UseInfiniteQueryOptions,
6
- UseInfiniteQueryResult,
7
- useInfiniteQuery as useNativeInfiniteQuery,
8
- useQueryClient,
9
- } from "@tanstack/react-query";
10
- import {
11
- HandlerWithRealtime,
12
- RealtimeHookOptions,
13
- RealtimeMessage,
14
- buildQueryKey,
15
- stableSerialize,
16
- useRealtimeSubscription,
17
- } from "../shared/queryShared";
18
-
19
- export type PaginatedResult<TResult, TCursor = unknown> = {
20
- items: TResult[];
21
- nextCursor?: TCursor | null;
22
- prevCursor?: TCursor | null;
23
- };
24
-
25
- export type UseAppflarePaginatedQueryOptions<
26
- TArgs,
27
- TResult,
28
- TCursor = unknown,
29
- TError = unknown,
30
- > = {
31
- handler: HandlerWithRealtime<TArgs, PaginatedResult<TResult, TCursor>>;
32
- args?: TArgs;
33
- queryKey?: QueryKey;
34
- pageParamKey?: string;
35
- initialPageParam?: TCursor;
36
- getNextPageParam?: (
37
- lastPage: PaginatedResult<TResult, TCursor>,
38
- pages: PaginatedResult<TResult, TCursor>[]
39
- ) => TCursor | undefined;
40
- getPreviousPageParam?: (
41
- firstPage: PaginatedResult<TResult, TCursor>,
42
- pages: PaginatedResult<TResult, TCursor>[]
43
- ) => TCursor | undefined;
44
- queryOptions?: Omit<
45
- UseInfiniteQueryOptions<
46
- PaginatedResult<TResult, TCursor>,
47
- TError,
48
- InfiniteData<PaginatedResult<TResult, TCursor>, TCursor>,
49
- QueryKey,
50
- TCursor
51
- >,
52
- | "queryKey"
53
- | "queryFn"
54
- | "initialPageParam"
55
- | "getNextPageParam"
56
- | "getPreviousPageParam"
57
- >;
58
- realtime?: boolean | RealtimeHookOptions<PaginatedResult<TResult, TCursor>>;
59
- };
60
-
61
- export type UseAppflarePaginatedQueryResult<
62
- TResult,
63
- TCursor = unknown,
64
- TError = unknown,
65
- > = UseInfiniteQueryResult<
66
- InfiniteData<PaginatedResult<TResult, TCursor>, TCursor>,
67
- TError
68
- > & {
69
- websocket: WebSocket | null;
70
- };
71
-
72
- export function usePaginatedQuery<
73
- TArgs,
74
- TResult,
75
- TCursor = unknown,
76
- TError = unknown,
77
- >(
78
- options: UseAppflarePaginatedQueryOptions<TArgs, TResult, TCursor, TError>
79
- ): UseAppflarePaginatedQueryResult<TResult, TCursor, TError>;
80
-
81
- export function usePaginatedQuery<
82
- TArgs,
83
- TResult,
84
- TCursor = unknown,
85
- TError = unknown,
86
- >(
87
- handler: HandlerWithRealtime<TArgs, PaginatedResult<TResult, TCursor>>,
88
- options?: Omit<
89
- UseAppflarePaginatedQueryOptions<TArgs, TResult, TCursor, TError>,
90
- "handler"
91
- >
92
- ): UseAppflarePaginatedQueryResult<TResult, TCursor, TError>;
93
-
94
- export function usePaginatedQuery<
95
- TArgs,
96
- TResult,
97
- TCursor = unknown,
98
- TError = unknown,
99
- >(
100
- optionsOrHandler:
101
- | UseAppflarePaginatedQueryOptions<TArgs, TResult, TCursor, TError>
102
- | HandlerWithRealtime<TArgs, PaginatedResult<TResult, TCursor>>,
103
- options?: Omit<
104
- UseAppflarePaginatedQueryOptions<TArgs, TResult, TCursor, TError>,
105
- "handler"
106
- >
107
- ): UseAppflarePaginatedQueryResult<TResult, TCursor, TError> {
108
- const normalizedOptions = useMemo(
109
- () =>
110
- typeof optionsOrHandler === "function"
111
- ? ({
112
- handler: optionsOrHandler,
113
- ...(options ?? {}),
114
- } as UseAppflarePaginatedQueryOptions<
115
- TArgs,
116
- TResult,
117
- TCursor,
118
- TError
119
- >)
120
- : optionsOrHandler,
121
- [optionsOrHandler, options]
122
- );
123
-
124
- const realtime = normalizedOptions.realtime;
125
- const handler = normalizedOptions.handler;
126
- const args = normalizedOptions.args;
127
- const queryKey = normalizedOptions.queryKey;
128
- const queryOptions = normalizedOptions.queryOptions;
129
-
130
- const pageParamKey = normalizedOptions.pageParamKey ?? "cursor";
131
- const initialPageParam = normalizedOptions.initialPageParam;
132
- const getNextPageParam = normalizedOptions.getNextPageParam;
133
- const getPreviousPageParam = normalizedOptions.getPreviousPageParam;
134
-
135
- const queryClient = useQueryClient();
136
- const argsKey = useMemo(() => stableSerialize(args), [args]);
137
- const finalQueryKey = useMemo<QueryKey>(
138
- () => buildQueryKey(queryKey, handler, argsKey),
139
- [queryKey, handler, argsKey]
140
- );
141
-
142
- const infiniteQuery = useNativeInfiniteQuery<
143
- PaginatedResult<TResult, TCursor>,
144
- TError,
145
- InfiniteData<PaginatedResult<TResult, TCursor>, TCursor>,
146
- QueryKey,
147
- TCursor
148
- >({
149
- queryKey: finalQueryKey,
150
- initialPageParam: initialPageParam as TCursor,
151
- getNextPageParam:
152
- getNextPageParam ?? ((lastPage) => lastPage?.nextCursor ?? undefined),
153
- getPreviousPageParam:
154
- getPreviousPageParam ??
155
- ((firstPage) => firstPage?.prevCursor ?? undefined),
156
- queryFn: ({ pageParam }) =>
157
- handler(mergeArgsWithPageParam(args, pageParam as TCursor, pageParamKey)),
158
- ...(queryOptions ?? {}),
159
- });
160
-
161
- const handleIncomingPage = useCallback(
162
- (
163
- data?:
164
- | PaginatedResult<TResult, TCursor>[]
165
- | PaginatedResult<TResult, TCursor>
166
- | null,
167
- _message?: RealtimeMessage<PaginatedResult<TResult, TCursor>>
168
- ) => {
169
- const nextFirstPage = Array.isArray(data) ? data[0] : undefined;
170
- if (!nextFirstPage) return;
171
- queryClient.setQueryData<
172
- InfiniteData<PaginatedResult<TResult, TCursor>, TCursor>
173
- >(finalQueryKey, (prev) =>
174
- prev
175
- ? {
176
- ...prev,
177
- pages: prev.pages.map((page, index) =>
178
- index === 0 ? nextFirstPage : page
179
- ),
180
- }
181
- : prev
182
- );
183
- },
184
- [finalQueryKey, queryClient]
185
- );
186
- const deps = useMemo(() => [argsKey], [argsKey]);
187
-
188
- const websocket = useRealtimeSubscription({
189
- handler,
190
- args,
191
- realtime,
192
- finalQueryKey,
193
- deps,
194
- applyIncoming: handleIncomingPage,
195
- });
196
-
197
- return { ...infiniteQuery, websocket };
198
- }
199
-
200
- function mergeArgsWithPageParam<TArgs, TPageParam>(
201
- args: TArgs | undefined,
202
- pageParam: TPageParam,
203
- pageParamKey?: string
204
- ): TArgs {
205
- if (!pageParamKey) return (args ?? (pageParam as unknown as TArgs)) as TArgs;
206
- if (typeof args === "object" && args !== null) {
207
- return { ...(args as any), [pageParamKey]: pageParam } as TArgs;
208
- }
209
- if (args === undefined) {
210
- return { [pageParamKey]: pageParam } as unknown as TArgs;
211
- }
212
- return args as TArgs;
213
- }
@@ -1,106 +0,0 @@
1
- import { useCallback, useMemo } from "react";
2
- import {
3
- QueryKey,
4
- UseQueryOptions,
5
- UseQueryResult,
6
- useQuery as useNativeQuery,
7
- useQueryClient,
8
- } from "@tanstack/react-query";
9
- import {
10
- RealtimeHookOptions,
11
- RealtimeMessage,
12
- HandlerWithRealtime,
13
- HandlerWebsocketOptions,
14
- buildQueryKey,
15
- stableSerialize,
16
- useRealtimeSubscription,
17
- } from "../shared/queryShared";
18
-
19
- export type UseAppflareQueryOptions<TArgs, TResult, TError = unknown> = {
20
- handler: HandlerWithRealtime<TArgs, TResult>;
21
- args?: TArgs;
22
- queryKey?: QueryKey;
23
- queryOptions?: Omit<UseQueryOptions<TResult, TError>, "queryFn" | "queryKey">;
24
- realtime?: boolean | RealtimeHookOptions<TResult>;
25
- };
26
-
27
- export type UseAppflareQueryResult<TResult, TError = unknown> = UseQueryResult<
28
- TResult,
29
- TError
30
- > & { websocket: WebSocket | null };
31
-
32
- export function useQuery<TArgs, TResult, TError = unknown>(
33
- options: UseAppflareQueryOptions<TArgs, TResult, TError>
34
- ): UseAppflareQueryResult<TResult, TError>;
35
-
36
- export function useQuery<TArgs, TResult, TError = unknown>(
37
- handler: HandlerWithRealtime<TArgs, TResult>,
38
- options?: Omit<UseAppflareQueryOptions<TArgs, TResult, TError>, "handler">
39
- ): UseAppflareQueryResult<TResult, TError>;
40
-
41
- export function useQuery<TArgs, TResult, TError = unknown>(
42
- optionsOrHandler:
43
- | UseAppflareQueryOptions<TArgs, TResult, TError>
44
- | HandlerWithRealtime<TArgs, TResult>,
45
- options?: Omit<UseAppflareQueryOptions<TArgs, TResult, TError>, "handler">
46
- ): UseAppflareQueryResult<TResult, TError> {
47
- const normalizedOptions = useMemo(
48
- () =>
49
- typeof optionsOrHandler === "function"
50
- ? ({
51
- handler: optionsOrHandler,
52
- ...(options ?? {}),
53
- } as UseAppflareQueryOptions<TArgs, TResult, TError>)
54
- : optionsOrHandler,
55
- [optionsOrHandler, options]
56
- );
57
-
58
- const realtime = normalizedOptions.realtime;
59
- const handler = normalizedOptions.handler;
60
- const args = normalizedOptions.args;
61
- const queryKey = normalizedOptions.queryKey;
62
- const queryOptions = normalizedOptions.queryOptions;
63
-
64
- const queryClient = useQueryClient();
65
- const argsKey = useMemo(() => stableSerialize(args), [args]);
66
- const finalQueryKey = useMemo<QueryKey>(
67
- () => buildQueryKey(queryKey, handler, argsKey),
68
- [queryKey, handler, argsKey]
69
- );
70
-
71
- const query = useNativeQuery<TResult, TError>({
72
- queryKey: finalQueryKey,
73
- queryFn: () => handler(args),
74
- ...(queryOptions ?? {}),
75
- });
76
-
77
- const handleIncomingData = useCallback(
78
- (
79
- data?: TResult[] | TResult | null,
80
- _message?: RealtimeMessage<TResult>
81
- ) => {
82
- if (!Array.isArray(data)) return;
83
- queryClient.setQueryData(finalQueryKey, () => data as unknown as TResult);
84
- },
85
- [finalQueryKey, queryClient]
86
- );
87
- const deps = useMemo(() => [argsKey], [argsKey]);
88
-
89
- const websocket = useRealtimeSubscription({
90
- handler,
91
- args,
92
- realtime,
93
- finalQueryKey,
94
- deps,
95
- applyIncoming: handleIncomingData,
96
- });
97
-
98
- return { ...query, websocket };
99
- }
100
-
101
- export type {
102
- RealtimeMessage,
103
- HandlerWebsocketOptions,
104
- HandlerWithRealtime,
105
- RealtimeHookOptions,
106
- } from "../shared/queryShared";
@@ -1,174 +0,0 @@
1
- import { useEffect, useRef } from "react";
2
- import { QueryKey } from "@tanstack/react-query";
3
-
4
- export type RealtimeMessage<TResult> = {
5
- type?: string;
6
- data?: TResult[];
7
- [key: string]: unknown;
8
- };
9
-
10
- export type HandlerWebsocketOptions<TResult> = {
11
- baseUrl?: string;
12
- table?: string;
13
- handler?: { file: string; name: string };
14
- handlerFile?: string;
15
- handlerName?: string;
16
- where?: Record<string, unknown>;
17
- orderBy?: Record<string, unknown>;
18
- take?: number;
19
- skip?: number;
20
- path?: string;
21
- protocols?: string | string[];
22
- headers?: Record<string, string>;
23
- signal?: AbortSignal;
24
- websocketImpl?: (
25
- url: string,
26
- protocols?: string | string[],
27
- options?: { headers?: Record<string, string> }
28
- ) => WebSocket;
29
- onOpen?: (event: any) => void;
30
- onClose?: (event: any) => void;
31
- onError?: (event: any) => void;
32
- onMessage?: (message: RealtimeMessage<TResult>, raw: any) => void;
33
- onData?: (data: TResult[], message: RealtimeMessage<TResult>) => void;
34
- };
35
-
36
- export type HandlerWithRealtime<TArgs, TResult> = {
37
- (args: TArgs, init?: RequestInit): Promise<TResult>;
38
- websocket?: (
39
- args?: TArgs,
40
- options?: HandlerWebsocketOptions<TResult>
41
- ) => WebSocket;
42
- schema?: unknown;
43
- path?: string;
44
- };
45
-
46
- export type RealtimeHookOptions<TResult> = HandlerWebsocketOptions<TResult> & {
47
- enabled?: boolean;
48
- replaceData?: boolean;
49
- };
50
-
51
- type RealtimeOptions<TResult> = {
52
- enabled: boolean;
53
- options: RealtimeHookOptions<TResult>;
54
- };
55
-
56
- type RealtimeSubscriptionParams<TArgs, TResult> = {
57
- handler: HandlerWithRealtime<TArgs, TResult>;
58
- args?: TArgs;
59
- realtime?: boolean | RealtimeHookOptions<TResult>;
60
- finalQueryKey: QueryKey;
61
- deps?: unknown[];
62
- applyIncoming: (
63
- data: TResult[] | undefined,
64
- message: RealtimeMessage<TResult> | undefined
65
- ) => void;
66
- };
67
-
68
- export function stableSerialize(value: unknown): string {
69
- try {
70
- return JSON.stringify(value) ?? "";
71
- } catch {
72
- return String(value);
73
- }
74
- }
75
-
76
- export function buildQueryKey(
77
- queryKey: QueryKey | undefined,
78
- handler: { path?: string },
79
- argsKey: string
80
- ): QueryKey {
81
- return queryKey ?? [handler?.path ?? "appflare-handler", argsKey];
82
- }
83
-
84
- export function useRealtimeSubscription<TArgs, TResult>(
85
- params: RealtimeSubscriptionParams<TArgs, TResult>
86
- ): WebSocket | null {
87
- const { handler, args, realtime, finalQueryKey, deps, applyIncoming } =
88
- params;
89
- const depsArray = deps;
90
- const websocketRef = useRef<WebSocket | null>(null);
91
-
92
- // Track latest realtime options so callbacks stay fresh without forcing reconnects.
93
- const latestRealtimeOptionsRef = useRef<
94
- RealtimeHookOptions<TResult> | undefined
95
- >();
96
- const realtimeKey = buildRealtimeKey(realtime);
97
- const parsedRealtime = parseRealtimeOptions<TResult>(realtime);
98
- latestRealtimeOptionsRef.current = parsedRealtime.options;
99
-
100
- useEffect(() => {
101
- const hasWebsocket = typeof handler.websocket === "function";
102
- const { enabled, options } = parsedRealtime;
103
-
104
- if (!enabled || !hasWebsocket) {
105
- return undefined;
106
- }
107
-
108
- const socket = handler.websocket!(args, {
109
- ...options,
110
- onData: (data, message) => {
111
- latestRealtimeOptionsRef.current?.onData?.(data, message);
112
- if (options.replaceData === false) return;
113
- applyIncoming(data, message);
114
- },
115
- onMessage: (message, raw) => {
116
- latestRealtimeOptionsRef.current?.onMessage?.(message, raw);
117
- if (
118
- options.replaceData === false ||
119
- !message ||
120
- message.type !== "data" ||
121
- !Array.isArray((message as any).data)
122
- ) {
123
- return;
124
- }
125
- applyIncoming((message as any).data, message as any);
126
- },
127
- });
128
-
129
- websocketRef.current = socket;
130
-
131
- return () => {
132
- try {
133
- socket.close(1000, "cleanup");
134
- } catch {
135
- // ignore
136
- }
137
- };
138
- }, [handler, finalQueryKey, realtimeKey, applyIncoming, ...depsArray]);
139
-
140
- return websocketRef.current;
141
- }
142
-
143
- function parseRealtimeOptions<TResult>(
144
- realtime?: boolean | RealtimeHookOptions<TResult>
145
- ): RealtimeOptions<TResult> {
146
- const options =
147
- typeof realtime === "object"
148
- ? (realtime as RealtimeHookOptions<TResult>)
149
- : ({} as RealtimeHookOptions<TResult>);
150
-
151
- const enabled =
152
- realtime === true ||
153
- (typeof realtime === "object" && options.enabled !== false);
154
-
155
- return { enabled, options };
156
- }
157
-
158
- function buildRealtimeKey<TResult>(
159
- realtime?: boolean | RealtimeHookOptions<TResult>
160
- ): string {
161
- if (realtime === true) return "true";
162
- if (!realtime) return "false";
163
- const {
164
- onOpen,
165
- onClose,
166
- onError,
167
- onMessage,
168
- onData,
169
- websocketImpl,
170
- signal,
171
- ...rest
172
- } = realtime;
173
- return stableSerialize(rest);
174
- }