appflare 0.0.28 → 0.1.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 (141) 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 +740 -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 +265 -0
  34. package/cli/templates/handlers/auth.ts +36 -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/index.ts +43 -0
  46. package/cli/templates/handlers/operations.ts +116 -0
  47. package/cli/templates/handlers/registration.ts +1114 -0
  48. package/cli/templates/handlers/types.ts +960 -0
  49. package/cli/templates/handlers/utils.ts +48 -0
  50. package/cli/types.ts +108 -0
  51. package/cli/utils/handler-discovery.ts +366 -0
  52. package/cli/utils/json-utils.ts +24 -0
  53. package/cli/utils/path-utils.ts +19 -0
  54. package/cli/utils/schema-discovery.ts +390 -0
  55. package/index.ts +27 -4
  56. package/package.json +23 -20
  57. package/react/index.ts +5 -3
  58. package/react/use-infinite-query.ts +190 -0
  59. package/react/use-mutation.ts +54 -0
  60. package/react/use-query.ts +158 -0
  61. package/schema.ts +262 -0
  62. package/tsconfig.json +2 -4
  63. package/cli/README.md +0 -108
  64. package/cli/core/build.ts +0 -187
  65. package/cli/core/config.ts +0 -92
  66. package/cli/core/discover-handlers.ts +0 -143
  67. package/cli/core/handlers.ts +0 -7
  68. package/cli/core/index.ts +0 -205
  69. package/cli/generators/generate-api-client/client.ts +0 -163
  70. package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
  71. package/cli/generators/generate-api-client/index.ts +0 -973
  72. package/cli/generators/generate-api-client/types.ts +0 -164
  73. package/cli/generators/generate-api-client/utils.ts +0 -22
  74. package/cli/generators/generate-api-client.ts +0 -1
  75. package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
  76. package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
  77. package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
  78. package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
  79. package/cli/generators/generate-cloudflare-worker.ts +0 -4
  80. package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
  81. package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
  82. package/cli/generators/generate-cron-handlers/index.ts +0 -61
  83. package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
  84. package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
  85. package/cli/generators/generate-db-handlers/index.ts +0 -33
  86. package/cli/generators/generate-db-handlers/prepare.ts +0 -24
  87. package/cli/generators/generate-db-handlers/templates.ts +0 -189
  88. package/cli/generators/generate-db-handlers.ts +0 -1
  89. package/cli/generators/generate-hono-server/auth.ts +0 -97
  90. package/cli/generators/generate-hono-server/imports.ts +0 -55
  91. package/cli/generators/generate-hono-server/index.ts +0 -52
  92. package/cli/generators/generate-hono-server/routes.ts +0 -115
  93. package/cli/generators/generate-hono-server/template.ts +0 -371
  94. package/cli/generators/generate-hono-server.ts +0 -1
  95. package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
  96. package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
  97. package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
  98. package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
  99. package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
  100. package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
  101. package/cli/generators/generate-scheduler-handlers.ts +0 -1
  102. package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
  103. package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
  104. package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
  105. package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
  106. package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
  107. package/cli/generators/generate-websocket-durable-object.ts +0 -1
  108. package/cli/schema/schema-static-types.ts +0 -702
  109. package/cli/schema/schema.ts +0 -151
  110. package/cli/utils/tsc.ts +0 -54
  111. package/cli/utils/utils.ts +0 -190
  112. package/cli/utils/zod-utils.ts +0 -121
  113. package/lib/README.md +0 -50
  114. package/lib/db.ts +0 -19
  115. package/lib/location.ts +0 -110
  116. package/lib/values.ts +0 -27
  117. package/react/README.md +0 -67
  118. package/react/hooks/useMutation.ts +0 -89
  119. package/react/hooks/usePaginatedQuery.ts +0 -213
  120. package/react/hooks/useQuery.ts +0 -106
  121. package/react/shared/queryShared.ts +0 -174
  122. package/server/README.md +0 -218
  123. package/server/auth.ts +0 -107
  124. package/server/database/builders.ts +0 -83
  125. package/server/database/context.ts +0 -327
  126. package/server/database/populate.ts +0 -234
  127. package/server/database/query-builder.ts +0 -161
  128. package/server/database/query-utils.ts +0 -25
  129. package/server/db.ts +0 -2
  130. package/server/storage/auth.ts +0 -16
  131. package/server/storage/bucket.ts +0 -22
  132. package/server/storage/context.ts +0 -34
  133. package/server/storage/index.ts +0 -38
  134. package/server/storage/operations.ts +0 -149
  135. package/server/storage/route-handler.ts +0 -60
  136. package/server/storage/types.ts +0 -55
  137. package/server/storage/utils.ts +0 -47
  138. package/server/storage.ts +0 -6
  139. package/server/types/schema-refs.ts +0 -66
  140. package/server/types/types.ts +0 -633
  141. 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
- }