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.
- package/cli/commands/index.ts +140 -0
- package/cli/generate.ts +149 -0
- package/cli/index.ts +56 -447
- package/cli/load-config.ts +182 -0
- package/cli/schema-compiler.ts +657 -0
- package/cli/templates/auth/README.md +156 -0
- package/cli/templates/auth/config.ts +61 -0
- package/cli/templates/auth/route-config.ts +18 -0
- package/cli/templates/auth/route-handler.ts +18 -0
- package/cli/templates/auth/route-request-utils.ts +55 -0
- package/cli/templates/auth/route.ts +14 -0
- package/cli/templates/core/README.md +266 -0
- package/cli/templates/core/app-creation.ts +19 -0
- package/cli/templates/core/client/appflare.ts +37 -0
- package/cli/templates/core/client/index.ts +6 -0
- package/cli/templates/core/client/storage.ts +100 -0
- package/cli/templates/core/client/types.ts +54 -0
- package/cli/templates/core/client-modules/appflare.ts +112 -0
- package/cli/templates/core/client-modules/handlers/index.ts +740 -0
- package/cli/templates/core/client-modules/handlers.ts +1 -0
- package/cli/templates/core/client-modules/index.ts +7 -0
- package/cli/templates/core/client-modules/storage.ts +180 -0
- package/cli/templates/core/client-modules/types.ts +145 -0
- package/cli/templates/core/client.ts +39 -0
- package/cli/templates/core/drizzle.ts +15 -0
- package/cli/templates/core/export.ts +14 -0
- package/cli/templates/core/handlers-route.ts +23 -0
- package/cli/templates/core/handlers.ts +1 -0
- package/cli/templates/core/imports.ts +8 -0
- package/cli/templates/core/server.ts +38 -0
- package/cli/templates/core/types.ts +6 -0
- package/cli/templates/core/wrangler.ts +109 -0
- package/cli/templates/handlers/README.md +265 -0
- package/cli/templates/handlers/auth.ts +36 -0
- package/cli/templates/handlers/execution.ts +39 -0
- package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
- package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
- package/cli/templates/handlers/generators/context/types.ts +18 -0
- package/cli/templates/handlers/generators/context.ts +43 -0
- package/cli/templates/handlers/generators/execution.ts +15 -0
- package/cli/templates/handlers/generators/handlers.ts +13 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +1114 -0
- package/cli/templates/handlers/types.ts +960 -0
- package/cli/templates/handlers/utils.ts +48 -0
- package/cli/types.ts +108 -0
- package/cli/utils/handler-discovery.ts +366 -0
- package/cli/utils/json-utils.ts +24 -0
- package/cli/utils/path-utils.ts +19 -0
- package/cli/utils/schema-discovery.ts +390 -0
- package/index.ts +27 -4
- package/package.json +23 -20
- package/react/index.ts +5 -3
- package/react/use-infinite-query.ts +190 -0
- package/react/use-mutation.ts +54 -0
- package/react/use-query.ts +158 -0
- package/schema.ts +262 -0
- package/tsconfig.json +2 -4
- package/cli/README.md +0 -108
- package/cli/core/build.ts +0 -187
- package/cli/core/config.ts +0 -92
- package/cli/core/discover-handlers.ts +0 -143
- package/cli/core/handlers.ts +0 -7
- package/cli/core/index.ts +0 -205
- package/cli/generators/generate-api-client/client.ts +0 -163
- package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
- package/cli/generators/generate-api-client/index.ts +0 -973
- package/cli/generators/generate-api-client/types.ts +0 -164
- package/cli/generators/generate-api-client/utils.ts +0 -22
- package/cli/generators/generate-api-client.ts +0 -1
- package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
- package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
- package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
- package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
- package/cli/generators/generate-cloudflare-worker.ts +0 -4
- package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
- package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
- package/cli/generators/generate-cron-handlers/index.ts +0 -61
- package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
- package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
- package/cli/generators/generate-db-handlers/index.ts +0 -33
- package/cli/generators/generate-db-handlers/prepare.ts +0 -24
- package/cli/generators/generate-db-handlers/templates.ts +0 -189
- package/cli/generators/generate-db-handlers.ts +0 -1
- package/cli/generators/generate-hono-server/auth.ts +0 -97
- package/cli/generators/generate-hono-server/imports.ts +0 -55
- package/cli/generators/generate-hono-server/index.ts +0 -52
- package/cli/generators/generate-hono-server/routes.ts +0 -115
- package/cli/generators/generate-hono-server/template.ts +0 -371
- package/cli/generators/generate-hono-server.ts +0 -1
- package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
- package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
- package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
- package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers.ts +0 -1
- package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
- package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
- package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
- package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
- package/cli/generators/generate-websocket-durable-object.ts +0 -1
- package/cli/schema/schema-static-types.ts +0 -702
- package/cli/schema/schema.ts +0 -151
- package/cli/utils/tsc.ts +0 -54
- package/cli/utils/utils.ts +0 -190
- package/cli/utils/zod-utils.ts +0 -121
- package/lib/README.md +0 -50
- package/lib/db.ts +0 -19
- package/lib/location.ts +0 -110
- package/lib/values.ts +0 -27
- package/react/README.md +0 -67
- package/react/hooks/useMutation.ts +0 -89
- package/react/hooks/usePaginatedQuery.ts +0 -213
- package/react/hooks/useQuery.ts +0 -106
- package/react/shared/queryShared.ts +0 -174
- package/server/README.md +0 -218
- package/server/auth.ts +0 -107
- package/server/database/builders.ts +0 -83
- package/server/database/context.ts +0 -327
- package/server/database/populate.ts +0 -234
- package/server/database/query-builder.ts +0 -161
- package/server/database/query-utils.ts +0 -25
- package/server/db.ts +0 -2
- package/server/storage/auth.ts +0 -16
- package/server/storage/bucket.ts +0 -22
- package/server/storage/context.ts +0 -34
- package/server/storage/index.ts +0 -38
- package/server/storage/operations.ts +0 -149
- package/server/storage/route-handler.ts +0 -60
- package/server/storage/types.ts +0 -55
- package/server/storage/utils.ts +0 -47
- package/server/storage.ts +0 -6
- package/server/types/schema-refs.ts +0 -66
- package/server/types/types.ts +0 -633
- 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
|
-
}
|
package/react/hooks/useQuery.ts
DELETED
|
@@ -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
|
-
}
|