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
@@ -1,973 +0,0 @@
1
- import {
2
- DiscoveredHandler,
3
- groupBy,
4
- pascalCase,
5
- toImportPathFromGeneratedSrc,
6
- } from "../../utils/utils";
7
- import {
8
- generateMutationsClientLines,
9
- generateQueriesClientLines,
10
- } from "./client";
11
- import {
12
- generateMutationsTypeLines,
13
- generateQueriesTypeLines,
14
- generateTypeBlocks,
15
- generateInternalTypeLines,
16
- } from "./types";
17
- import { renderObjectKey } from "./utils";
18
- import { extractClientConfig } from "./extract-configuration";
19
-
20
- const HEADER_TEMPLATE = `/* eslint-disable */
21
- /**
22
- * This file is auto-generated by appflare/handler-build.ts.
23
- * Do not edit directly.
24
- */
25
-
26
- import fetch from "better-fetch";
27
- import { z } from "zod";
28
- import type { BetterAuthClientOptions } from "better-auth/client";
29
- import { createAuthClient } from "better-auth/client";
30
-
31
- import type {
32
- AnyValidator,
33
- InferQueryArgs,
34
- MutationDefinition,
35
- QueryArgsShape,
36
- QueryDefinition,
37
- InternalMutationContext,
38
- InternalMutationDefinition,
39
- InternalQueryContext,
40
- InternalQueryDefinition,
41
- } from "./schema-types";
42
- {{configImport}}
43
- `;
44
-
45
- const TYPE_DEFINITIONS_TEMPLATE = `
46
- type AnyArgsShape = Record<string, AnyValidator>;
47
-
48
- type AnyHandlerDefinition = QueryDefinition<AnyArgsShape, unknown> | MutationDefinition<AnyArgsShape, unknown>;
49
-
50
- type Simplify<T> = { [K in keyof T]: T[K] };
51
-
52
- type OptionalKeys<TArgs extends QueryArgsShape> = {
53
- [K in keyof TArgs]: TArgs[K] extends z.ZodOptional<any> | z.ZodDefault<any> ? K : never;
54
- }[keyof TArgs];
55
-
56
- type RequiredKeys<TArgs extends QueryArgsShape> = Exclude<keyof TArgs, OptionalKeys<TArgs>>;
57
-
58
- type HandlerArgsFromShape<TArgs extends QueryArgsShape> = Simplify<
59
- Partial<Pick<InferQueryArgs<TArgs>, OptionalKeys<TArgs>>> &
60
- Pick<InferQueryArgs<TArgs>, RequiredKeys<TArgs>>
61
- >;
62
-
63
- type HandlerArgs<THandler extends AnyHandlerDefinition> =
64
- THandler extends { args: infer TArgs extends QueryArgsShape }
65
- ? HandlerArgsFromShape<TArgs>
66
- : never;
67
-
68
- type HandlerResult<THandler extends AnyHandlerDefinition> = THandler extends {
69
- handler: (...args: any[]) => Promise<infer TResult>;
70
- }
71
- ? TResult
72
- : never;
73
-
74
- type HandlerInvoker<TArgs, TResult> = (args: TArgs, init?: RequestInit) => Promise<TResult>;
75
-
76
- type HandlerArgsShape<THandler extends AnyHandlerDefinition> =
77
- THandler extends { args: infer TArgs extends QueryArgsShape }
78
- ? TArgs
79
- : never;
80
-
81
- type HandlerSchemaFromShape<TArgs extends QueryArgsShape> = z.ZodObject<{
82
- [K in keyof TArgs]: z.ZodType<InferQueryArgs<TArgs>[K]>;
83
- }>;
84
-
85
- type HandlerSchema<THandler extends AnyHandlerDefinition> = HandlerSchemaFromShape<
86
- HandlerArgsShape<THandler>
87
- >;
88
-
89
- type WebSocketHeaders = Record<string, string>;
90
-
91
- type WebSocketFactoryOptions = { headers?: WebSocketHeaders };
92
-
93
- type WebSocketFactory = (
94
- url: string,
95
- protocols?: string | string[],
96
- options?: WebSocketFactoryOptions
97
- ) => WebSocket;
98
-
99
- export type RealtimeMessage<TResult> = {
100
- type?: string;
101
- data?: TResult[];
102
- table?: string;
103
- where?: unknown;
104
- [key: string]: unknown;
105
- };
106
-
107
- export type HandlerWebsocketOptions<TResult> = {
108
- baseUrl?: string;
109
- table?: string;
110
- handler?: { file: string; name: string };
111
- handlerFile?: string;
112
- handlerName?: string;
113
- where?: Record<string, unknown>;
114
- orderBy?: Record<string, unknown>;
115
- take?: number;
116
- skip?: number;
117
- path?: string;
118
- protocols?: string | string[];
119
- headers?: WebSocketHeaders;
120
- signal?: AbortSignal;
121
- websocketImpl?: WebSocketFactory;
122
- onOpen?: (event: any) => void;
123
- onClose?: (event: any) => void;
124
- onError?: (event: any) => void;
125
- onMessage?: (message: RealtimeMessage<TResult>, raw: any) => void;
126
- onData?: (data: TResult[], message: RealtimeMessage<TResult>) => void;
127
- };
128
-
129
- type HandlerMetadata<THandler extends AnyHandlerDefinition> = {
130
- schema: HandlerSchema<THandler>;
131
- websocket: HandlerWebsocket<
132
- HandlerArgs<THandler>,
133
- HandlerResult<THandler>
134
- >;
135
- path: string;
136
- };
137
-
138
- export type HandlerWebsocket<TArgs, TResult> = (
139
- args?: TArgs,
140
- options?: HandlerWebsocketOptions<TResult>
141
- ) => WebSocket;
142
-
143
- export type AppflareHandler<THandler extends AnyHandlerDefinition> = HandlerInvoker<
144
- HandlerArgs<THandler>,
145
- HandlerResult<THandler>
146
- > &
147
- HandlerMetadata<THandler>;
148
-
149
- export type StoragePutResult = {
150
- key: string;
151
- size: number;
152
- contentType: string;
153
- cacheControl: string;
154
- };
155
-
156
- export type StorageDeleteResult = {
157
- key: string;
158
- deleted: boolean;
159
- };
160
-
161
- export type StorageManagerClient = {
162
- url: (path: string) => string;
163
- get: (path: string, init?: RequestInit) => Promise<Response>;
164
- head: (path: string, init?: RequestInit) => Promise<Response>;
165
- put: (
166
- path: string,
167
- body: BodyInit,
168
- init?: RequestInit
169
- ) => Promise<StoragePutResult>;
170
- post: (
171
- path: string,
172
- body: BodyInit,
173
- init?: RequestInit
174
- ) => Promise<StoragePutResult>;
175
- delete: (path: string, init?: RequestInit) => Promise<StorageDeleteResult>;
176
- };
177
-
178
- export type StorageManagerOptions = {
179
- basePath?: string;
180
- };
181
-
182
- type RequestExecutor = (
183
- input: RequestInfo | URL,
184
- init?: RequestInit
185
- ) => Promise<Response>;
186
-
187
- const defaultFetcher: RequestExecutor = (input, init) => fetch(input, init);
188
-
189
- const defaultWebSocketFactory: WebSocketFactory = (
190
- url,
191
- protocols,
192
- _options
193
- ) => {
194
- if (typeof WebSocket === "undefined") {
195
- throw new Error(
196
- "WebSocket is not available in this environment. Provide options.realtime.websocketImpl to create websockets."
197
- );
198
- }
199
- return new WebSocket(url, protocols);
200
- };
201
-
202
- type ResolvedRealtimeConfig = {
203
- baseUrl?: string;
204
- path: string;
205
- headers?: WebSocketHeaders;
206
- websocketImpl?: WebSocketFactory;
207
- };
208
-
209
- type RealtimeConfig = {
210
- baseUrl?: string;
211
- path?: string;
212
- headers?: WebSocketHeaders;
213
- websocketImpl?: WebSocketFactory;
214
- };
215
-
216
- `;
217
-
218
- const INTERNAL_TEMPLATE = `
219
- export type InternalQueries = {{internalQueriesTypeDef}};
220
-
221
- export type InternalMutations = {{internalMutationsTypeDef}};
222
-
223
- export type InternalHandlers = InternalQueries & InternalMutations;
224
-
225
- export const internal: InternalHandlers = {{internalInit}};
226
-
227
- const __internalQueries = [
228
- {{internalQueriesMeta}}
229
- ];
230
-
231
- const __internalMutations = [
232
- {{internalMutationsMeta}}
233
- ];
234
-
235
- type BoundInternalHandlers = {
236
- [F in keyof InternalHandlers]: {
237
- [N in keyof InternalHandlers[F]]: InternalHandlers[F][N] extends InternalQueryDefinition<
238
- infer TArgs,
239
- infer TResult
240
- >
241
- ? (args: HandlerArgsFromShape<TArgs>) => Promise<TResult>
242
- : InternalHandlers[F][N] extends InternalMutationDefinition<
243
- infer TArgs,
244
- infer TResult
245
- >
246
- ? (args: HandlerArgsFromShape<TArgs>) => Promise<TResult>
247
- : never;
248
- };
249
- };
250
-
251
- export type InternalCaller = {
252
- internal: BoundInternalHandlers;
253
- runQuery: <TArgs extends QueryArgsShape, TResult>(
254
- handler: InternalQueryDefinition<TArgs, TResult>,
255
- args: HandlerArgsFromShape<TArgs>
256
- ) => Promise<TResult>;
257
- runMutation: <TArgs extends QueryArgsShape, TResult>(
258
- handler: InternalMutationDefinition<TArgs, TResult>,
259
- args: HandlerArgsFromShape<TArgs>
260
- ) => Promise<TResult>;
261
- };
262
-
263
- export function createInternalCaller(
264
- ctx: InternalQueryContext | InternalMutationContext
265
- ): InternalCaller {
266
- const bound: Record<string, Record<string, any>> = {};
267
- for (const entry of __internalQueries) {
268
- (bound[entry.file] ||= {})[entry.name] = (args: any) =>
269
- runInternalQuery(ctx as any, entry.handler as any, args as any);
270
- }
271
- for (const entry of __internalMutations) {
272
- (bound[entry.file] ||= {})[entry.name] = (args: any) =>
273
- runInternalMutation(ctx as any, entry.handler as any, args as any);
274
- }
275
- return {
276
- internal: bound as BoundInternalHandlers,
277
- runQuery: (handler, args) =>
278
- runInternalQuery(ctx as any, handler as any, args as any),
279
- runMutation: (handler, args) =>
280
- runInternalMutation(ctx as any, handler as any, args as any),
281
- };
282
- }
283
- `;
284
-
285
- const CLIENT_TYPES_TEMPLATE = `
286
- export type QueriesClient = {{queriesTypeDef}};
287
-
288
- export type MutationsClient = {{mutationsTypeDef}};
289
-
290
- {{authClientTypeDefinitions}}
291
-
292
- export type AppflareApiClient = {
293
- queries: QueriesClient;
294
- mutations: MutationsClient;
295
- storage: StorageManagerClient;
296
- auth?: AppflareAuthClient;
297
- };
298
-
299
- export type AppflareApiOptions = {
300
- baseUrl?: string;
301
- fetcher?: RequestExecutor;
302
- realtime?: RealtimeConfig;
303
- storage?: StorageManagerOptions;
304
- auth?: (BetterAuthClientOptions & { baseURL?: string });
305
- };
306
-
307
- export function createAppflareApi(options: AppflareApiOptions = {}) {
308
- const baseUrl = normalizeBaseUrl(options.baseUrl);
309
- const request = options.fetcher ?? defaultFetcher;
310
- const realtime = resolveRealtimeConfig(baseUrl, options.realtime);
311
- const queries: QueriesClient = {{queriesInit}};
312
- const mutations: MutationsClient = {{mutationsInit}};
313
- const storage = createStorageManagerClient(baseUrl, request, options.storage);
314
- const authBasePath = normalizeAuthBasePath({{authBasePath}}) ?? "/auth";
315
- {{authClientInit}}
316
- return { queries, mutations, storage, auth };
317
- }
318
-
319
- `;
320
-
321
- const UTILITY_FUNCTIONS_TEMPLATE_PART1 = `
322
- function withHandlerMetadata<THandler extends AnyHandlerDefinition>(
323
- invoke: HandlerInvoker<HandlerArgs<THandler>, HandlerResult<THandler>>,
324
- meta: HandlerMetadata<THandler>
325
- ): AppflareHandler<THandler> {
326
- const fn = invoke as AppflareHandler<THandler>;
327
- fn.schema = meta.schema;
328
- fn.websocket = meta.websocket;
329
- fn.path = meta.path;
330
- return fn;
331
- }
332
-
333
- function resolveRealtimeConfig(
334
- baseUrl: string,
335
- realtime?: RealtimeConfig
336
- ): ResolvedRealtimeConfig {
337
- return {
338
- baseUrl: normalizeWsBaseUrl(realtime?.baseUrl ?? baseUrl),
339
- path: realtime?.path ?? "/ws",
340
- headers: realtime?.headers,
341
- websocketImpl: realtime?.websocketImpl ?? defaultWebSocketFactory,
342
- };
343
- }
344
-
345
- function createHandlerSchema<TArgs extends QueryArgsShape>(
346
- args: TArgs
347
- ): HandlerSchemaFromShape<TArgs> {
348
- return z.object(args as any as Record<string, z.ZodTypeAny>) as HandlerSchemaFromShape<TArgs>;
349
- }
350
-
351
- function parseHandlerArgs<THandler extends AnyHandlerDefinition>(
352
- handler: THandler,
353
- args: HandlerArgs<THandler>
354
- ): HandlerArgs<THandler> {
355
- const schema = createHandlerSchema(handler.args as any);
356
- return schema.parse(args ?? ({} as HandlerArgs<THandler>)) as HandlerArgs<THandler>;
357
- }
358
-
359
- export async function runInternalQuery<
360
- TArgs extends QueryArgsShape,
361
- TResult,
362
- >(
363
- ctx: InternalQueryContext,
364
- handler: InternalQueryDefinition<TArgs, TResult>,
365
- args: HandlerArgsFromShape<TArgs>
366
- ): Promise<TResult> {
367
- const parsed = parseHandlerArgs(handler as any, args as any);
368
- if (handler.middleware) {
369
- const middlewareResult = await handler.middleware(
370
- ctx as any,
371
- parsed as any
372
- );
373
- if (typeof middlewareResult !== "undefined") {
374
- return middlewareResult as TResult;
375
- }
376
- }
377
- return handler.handler(ctx as any, parsed as any);
378
- }
379
-
380
- export async function runInternalMutation<
381
- TArgs extends QueryArgsShape,
382
- TResult,
383
- >(
384
- ctx: InternalMutationContext,
385
- handler: InternalMutationDefinition<TArgs, TResult>,
386
- args: HandlerArgsFromShape<TArgs>
387
- ): Promise<TResult> {
388
- const parsed = parseHandlerArgs(handler as any, args as any);
389
- if (handler.middleware) {
390
- const middlewareResult = await handler.middleware(
391
- ctx as any,
392
- parsed as any
393
- );
394
- if (typeof middlewareResult !== "undefined") {
395
- return middlewareResult as TResult;
396
- }
397
- }
398
- return handler.handler(ctx as any, parsed as any);
399
- }
400
-
401
- `;
402
-
403
- const UTILITY_FUNCTIONS_TEMPLATE_PART2 = `
404
- function createHandlerWebsocket<TArgs, TResult>(
405
- realtime: ResolvedRealtimeConfig,
406
- defaults: { defaultTable: string; defaultHandler: { file: string; name: string } }
407
- ): HandlerWebsocket<TArgs, TResult> {
408
- return (args, options) => {
409
- const baseUrl = normalizeWsBaseUrl(options?.baseUrl ?? realtime.baseUrl);
410
- if (!baseUrl) {
411
- throw new Error(
412
- "Missing realtime baseUrl. Provide createAppflareApi({ realtime: { baseUrl } }) or handler websocket options.baseUrl."
413
- );
414
- }
415
- const params = new URLSearchParams();
416
- const tableParam = options?.table ?? defaults.defaultTable;
417
- const normalizedTable = tableParam.endsWith("s")
418
- ? tableParam
419
- : tableParam + "s";
420
- params.set("table", normalizedTable);
421
-
422
- const handlerRef =
423
- options?.handler ??
424
- (options?.handlerFile && options?.handlerName
425
- ? { file: options.handlerFile, name: options.handlerName }
426
- : defaults.defaultHandler);
427
- if (handlerRef) {
428
- params.set("handler", JSON.stringify(handlerRef));
429
- }
430
- const where = options?.where ?? (isPlainObject(args) ? (args as Record<string, unknown>) : undefined);
431
- if (where && Object.keys(where).length > 0) {
432
- params.set("where", JSON.stringify(where));
433
- }
434
- if (options?.orderBy) params.set("orderBy", JSON.stringify(options.orderBy));
435
- if (options?.take !== undefined) params.set("take", String(options.take));
436
- if (options?.skip !== undefined) params.set("skip", String(options.skip));
437
-
438
- const path = options?.path ?? realtime.path;
439
- const url = buildRealtimeUrl(baseUrl, path, params);
440
- const websocketFactory = options?.websocketImpl ?? realtime.websocketImpl ?? defaultWebSocketFactory;
441
- const socket = websocketFactory(url, options?.protocols, {
442
- headers: options?.headers ?? realtime.headers,
443
- });
444
-
445
- if (options?.onOpen) socket.addEventListener("open", options.onOpen as any);
446
- if (options?.onClose) socket.addEventListener("close", options.onClose as any);
447
- if (options?.onError) socket.addEventListener("error", options.onError as any);
448
-
449
- const onMessage = (event: any) => {
450
- const message = parseRealtimeMessage<TResult>(event?.data ?? event);
451
- if (options?.onMessage) options.onMessage(message, event);
452
- if (message?.type === "data" && Array.isArray((message as any).data)) {
453
- options?.onData?.((message as any).data as TResult[], message);
454
- }
455
- };
456
-
457
- socket.addEventListener("message", onMessage as any);
458
-
459
- if (options?.signal) {
460
- const abortHandler = () => socket.close(1000, "aborted");
461
- if (options.signal.aborted) abortHandler();
462
- else options.signal.addEventListener("abort", abortHandler, { once: true });
463
- }
464
-
465
- return socket;
466
- };
467
- }
468
- `;
469
-
470
- const UTILITY_FUNCTIONS_TEMPLATE_PART3 = `
471
- function normalizeAuthBasePath(basePath?: string | null): string | undefined {
472
- if (!basePath) return undefined;
473
- const prefixed = basePath.startsWith("/") ? basePath : \`/\${basePath}\`;
474
- return prefixed.replace(/\\/+$/, "") || "/auth";
475
- }
476
-
477
- function normalizeBaseUrl(baseUrl?: string): string {
478
- if (!baseUrl) {
479
- return "";
480
- }
481
- return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
482
- }
483
-
484
- function normalizeWsBaseUrl(baseUrl?: string): string | undefined {
485
- if (!baseUrl) return undefined;
486
- if (baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://")) {
487
- return baseUrl.replace(/\\/+$/, "");
488
- }
489
- if (baseUrl.startsWith("https://")) return baseUrl.replace(/^https/, "wss").replace(/\\/$/, "");
490
- if (baseUrl.startsWith("http://")) return baseUrl.replace(/^http/, "ws").replace(/\\/$/, "");
491
- return baseUrl.replace(/\\/$/, "");
492
- }
493
-
494
- function buildUrl(baseUrl: string, path: string): string {
495
- if (!baseUrl) {
496
- return path;
497
- }
498
- const normalizedPath = path.startsWith("/") ? path : "/" + path;
499
- return baseUrl + normalizedPath;
500
- }
501
-
502
- function buildRealtimeUrl(
503
- baseUrl: string,
504
- path: string,
505
- params: URLSearchParams
506
- ): string {
507
- const normalizedBase = baseUrl.replace(/\\/+$/, "");
508
- const normalizedPath = path.startsWith("/") ? path : "/" + path;
509
- const query = params.toString();
510
- return query ? normalizedBase + normalizedPath + "?" + query : normalizedBase + normalizedPath;
511
- }
512
-
513
- function buildQueryUrl(
514
- baseUrl: string,
515
- path: string,
516
- params: Record<string, unknown> | undefined
517
- ): string {
518
- const url = buildUrl(baseUrl, path);
519
- const query = serializeQueryParams(params);
520
- return query ? url + "?" + query : url;
521
- }
522
-
523
- function serializeQueryParams(
524
- params: Record<string, unknown> | undefined
525
- ): string {
526
- if (!params) {
527
- return "";
528
- }
529
- const searchParams = new URLSearchParams();
530
- for (const [key, value] of Object.entries(params)) {
531
- if (value === undefined || value === null) {
532
- continue;
533
- }
534
- if (Array.isArray(value)) {
535
- for (const entry of value) {
536
- searchParams.append(key, serializeQueryValue(entry));
537
- }
538
- continue;
539
- }
540
- searchParams.append(key, serializeQueryValue(value));
541
- }
542
- return searchParams.toString();
543
- }
544
-
545
- function serializeQueryValue(value: unknown): string {
546
- if (value instanceof Date) {
547
- return value.toISOString();
548
- }
549
- if (typeof value === "object") {
550
- return JSON.stringify(value);
551
- }
552
- return String(value);
553
- }
554
-
555
- function normalizeStorageBasePath(basePath?: string): string {
556
- if (!basePath) return "/storage";
557
- const prefixed = basePath.startsWith("/") ? basePath : \`/\${basePath}\`;
558
- const trimmed = prefixed.replace(/\\/+$/, "");
559
- return trimmed || "/storage";
560
- }
561
-
562
- function buildStoragePath(basePath: string, path: string): string {
563
- const trimmedBase = basePath.endsWith("/") ? basePath.slice(0, -1) : basePath;
564
- const normalizedPath = path.startsWith("/") ? path : \`/\${path}\`;
565
- if (
566
- normalizedPath === trimmedBase ||
567
- normalizedPath.startsWith(\`\${trimmedBase}/\`)
568
- ) {
569
- return normalizedPath;
570
- }
571
- return \`\${trimmedBase}\${normalizedPath}\`;
572
- }
573
-
574
- function createStorageManagerClient(
575
- baseUrl: string,
576
- request: RequestExecutor,
577
- options?: StorageManagerOptions
578
- ): StorageManagerClient {
579
- const basePath = normalizeStorageBasePath(options?.basePath);
580
- const toUrl = (path: string) => buildUrl(baseUrl, buildStoragePath(basePath, path));
581
-
582
- const sendJson = async <T>(
583
- method: string,
584
- path: string,
585
- init?: RequestInit
586
- ): Promise<T> => {
587
- const response = await request(toUrl(path), { ...(init ?? {}), method });
588
- return parseJson<T>(response);
589
- };
590
-
591
- return {
592
- url: toUrl,
593
- get: (path, init) => request(toUrl(path), { ...(init ?? {}), method: "GET" }),
594
- head: (path, init) => request(toUrl(path), { ...(init ?? {}), method: "HEAD" }),
595
- put: (path, body, init) =>
596
- sendJson<StoragePutResult>("PUT", path, { ...(init ?? {}), body }),
597
- post: (path, body, init) =>
598
- sendJson<StoragePutResult>("POST", path, { ...(init ?? {}), body }),
599
- delete: (path, init) =>
600
- sendJson<StorageDeleteResult>("DELETE", path, { ...(init ?? {}) }),
601
- };
602
- }
603
-
604
- function isPlainObject(value: unknown): value is Record<string, unknown> {
605
- return !!value && typeof value === "object" && !Array.isArray(value);
606
- }
607
-
608
- function parseRealtimeMessage<TResult>(value: unknown): RealtimeMessage<TResult> {
609
- if (typeof value === "string") {
610
- try {
611
- return JSON.parse(value) as RealtimeMessage<TResult>;
612
- } catch {
613
- return { type: "message", raw: value } as any;
614
- }
615
- }
616
- return value as RealtimeMessage<TResult>;
617
- }
618
-
619
- function ensureJsonHeaders(headers?: HeadersInit): HeadersInit {
620
- if (!headers) {
621
- return { "content-type": "application/json" };
622
- }
623
- if (typeof Headers !== "undefined" && headers instanceof Headers) {
624
- const next = new Headers(headers);
625
- if (!next.has("content-type")) {
626
- next.set("content-type", "application/json");
627
- }
628
- return next;
629
- }
630
- if (Array.isArray(headers)) {
631
- const entries = headers.slice();
632
- const hasContentType = entries.some(
633
- ([key]) => key.toLowerCase() === "content-type"
634
- );
635
- if (!hasContentType) {
636
- entries.push(["content-type", "application/json"]);
637
- }
638
- return entries;
639
- }
640
- if (typeof headers === "object") {
641
- const record = { ...(headers as Record<string, string>) };
642
- if (!hasHeader(record, "content-type")) {
643
- record["content-type"] = "application/json";
644
- }
645
- return record;
646
- }
647
- return { "content-type": "application/json" };
648
- }
649
-
650
- function hasHeader(record: Record<string, string>, name: string): boolean {
651
- const needle = name.toLowerCase();
652
- return Object.keys(record).some((key) => key.toLowerCase() === needle);
653
- }
654
-
655
- async function parseJson<TResult>(response: Response): Promise<TResult> {
656
- if (!response.ok) {
657
- throw new Error("Request failed with status " + response.status);
658
- }
659
- return (await response.json()) as TResult;
660
- }
661
- `;
662
-
663
- const UTILITY_FUNCTIONS_TEMPLATE =
664
- UTILITY_FUNCTIONS_TEMPLATE_PART1 +
665
- UTILITY_FUNCTIONS_TEMPLATE_PART2 +
666
- UTILITY_FUNCTIONS_TEMPLATE_PART3;
667
-
668
- function generateImports(params: {
669
- handlers: DiscoveredHandler[];
670
- outDirAbs: string;
671
- }): { importLines: string[]; importAliasBySource: Map<string, string> } {
672
- const handlerImportsGrouped = groupBy(
673
- params.handlers,
674
- (h) => h.sourceFileAbs,
675
- );
676
-
677
- const importLines: string[] = [];
678
- const importAliasBySource = new Map<string, string>();
679
- for (const [fileAbs, list] of Array.from(handlerImportsGrouped.entries())) {
680
- const alias = `__appflare_${pascalCase(list[0].routePath)}`;
681
- importAliasBySource.set(fileAbs, alias);
682
- const importPath = toImportPathFromGeneratedSrc(params.outDirAbs, fileAbs);
683
- importLines.push(
684
- `import * as ${alias} from ${JSON.stringify(importPath)};`,
685
- );
686
- }
687
- return { importLines, importAliasBySource };
688
- }
689
-
690
- function generateGroupedHandlers(handlers: DiscoveredHandler[]): {
691
- queriesByFile: Map<string, DiscoveredHandler[]>;
692
- mutationsByFile: Map<string, DiscoveredHandler[]>;
693
- internalQueriesByFile: Map<string, DiscoveredHandler[]>;
694
- internalMutationsByFile: Map<string, DiscoveredHandler[]>;
695
- } {
696
- const queries = handlers.filter((h) => h.kind === "query");
697
- const mutations = handlers.filter((h) => h.kind === "mutation");
698
- const internalQueries = handlers.filter((h) => h.kind === "internalQuery");
699
- const internalMutations = handlers.filter(
700
- (h) => h.kind === "internalMutation",
701
- );
702
-
703
- const queriesByFile = groupBy(queries, (h) => h.routePath);
704
- const mutationsByFile = groupBy(mutations, (h) => h.routePath);
705
- const internalQueriesByFile = groupBy(internalQueries, (h) => h.routePath);
706
- const internalMutationsByFile = groupBy(
707
- internalMutations,
708
- (h) => h.routePath,
709
- );
710
-
711
- return {
712
- queriesByFile,
713
- mutationsByFile,
714
- internalQueriesByFile,
715
- internalMutationsByFile,
716
- };
717
- }
718
-
719
- function generateTypeDefs(
720
- queriesByFile: Map<string, DiscoveredHandler[]>,
721
- mutationsByFile: Map<string, DiscoveredHandler[]>,
722
- internalQueriesByFile: Map<string, DiscoveredHandler[]>,
723
- internalMutationsByFile: Map<string, DiscoveredHandler[]>,
724
- importAliasBySource: Map<string, string>,
725
- ): {
726
- queriesTypeDef: string;
727
- mutationsTypeDef: string;
728
- internalQueriesTypeDef: string;
729
- internalMutationsTypeDef: string;
730
- } {
731
- const queriesTypeLines = generateQueriesTypeLines(queriesByFile);
732
- const mutationsTypeLines = generateMutationsTypeLines(mutationsByFile);
733
- const internalQueriesTypeLines = generateInternalTypeLines(
734
- internalQueriesByFile,
735
- importAliasBySource,
736
- );
737
- const internalMutationsTypeLines = generateInternalTypeLines(
738
- internalMutationsByFile,
739
- importAliasBySource,
740
- );
741
-
742
- const queriesTypeDef =
743
- queriesByFile.size === 0 ? "{}" : `{\n${queriesTypeLines}\n}`;
744
- const mutationsTypeDef =
745
- mutationsByFile.size === 0 ? "{}" : `{\n${mutationsTypeLines}\n}`;
746
- const internalQueriesTypeDef =
747
- internalQueriesByFile.size === 0
748
- ? "{}"
749
- : `{\n${internalQueriesTypeLines}\n}`;
750
- const internalMutationsTypeDef =
751
- internalMutationsByFile.size === 0
752
- ? "{}"
753
- : `{\n${internalMutationsTypeLines}\n}`;
754
-
755
- return {
756
- queriesTypeDef,
757
- mutationsTypeDef,
758
- internalQueriesTypeDef,
759
- internalMutationsTypeDef,
760
- };
761
- }
762
-
763
- function generateClientInits(
764
- queriesByFile: Map<string, DiscoveredHandler[]>,
765
- mutationsByFile: Map<string, DiscoveredHandler[]>,
766
- importAliasBySource: Map<string, string>,
767
- ): { queriesInit: string; mutationsInit: string } {
768
- const queriesClientLines = generateQueriesClientLines(
769
- queriesByFile,
770
- importAliasBySource,
771
- );
772
- const mutationsClientLines = generateMutationsClientLines(
773
- mutationsByFile,
774
- importAliasBySource,
775
- );
776
-
777
- const queriesInit =
778
- queriesByFile.size === 0 ? "{}" : `{\n${queriesClientLines}\n\t}`;
779
- const mutationsInit =
780
- mutationsByFile.size === 0 ? "{}" : `{\n${mutationsClientLines}\n\t}`;
781
-
782
- return { queriesInit, mutationsInit };
783
- }
784
-
785
- function generateInternalInit(
786
- internalByFile: Map<string, DiscoveredHandler[]>,
787
- importAliasBySource: Map<string, string>,
788
- ): string {
789
- if (internalByFile.size === 0) return "{}";
790
- const lines: string[] = [];
791
- for (const [fileName, list] of Array.from(internalByFile.entries()).sort(
792
- (a, b) => a[0].localeCompare(b[0]),
793
- )) {
794
- const fileKey = renderObjectKey(fileName);
795
- const inner = list
796
- .slice()
797
- .sort((a, b) => a.name.localeCompare(b.name))
798
- .map((h) => {
799
- const alias = importAliasBySource.get(h.sourceFileAbs)!;
800
- return `\t\t${h.name}: ${alias}.${h.name},`;
801
- })
802
- .join("\n");
803
- lines.push(`\t${fileKey}: {\n${inner}\n\t}`);
804
- }
805
- return `{
806
- ${lines.join("\n")}
807
- }`;
808
- }
809
-
810
- function generateInternalMeta(
811
- internalByFile: Map<string, DiscoveredHandler[]>,
812
- importAliasBySource: Map<string, string>,
813
- ): string {
814
- if (internalByFile.size === 0) return "";
815
- const lines: string[] = [];
816
- for (const [fileName, list] of Array.from(internalByFile.entries()).sort(
817
- (a, b) => a[0].localeCompare(b[0]),
818
- )) {
819
- for (const h of list.slice().sort((a, b) => a.name.localeCompare(b.name))) {
820
- const alias = importAliasBySource.get(h.sourceFileAbs)!;
821
- lines.push(
822
- `{ file: ${JSON.stringify(fileName)}, name: ${JSON.stringify(
823
- h.name,
824
- )}, handler: ${alias}.${h.name} },`,
825
- );
826
- }
827
- }
828
- return lines.join("\n");
829
- }
830
-
831
- export function generateApiClient(params: {
832
- handlers: DiscoveredHandler[];
833
- outDirAbs: string;
834
- authBasePath?: string;
835
- authEnabled?: boolean;
836
- configPathAbs?: string;
837
- }): { apiTs: string; clientConfigTs: string | null } {
838
- const { importLines, importAliasBySource } = generateImports(params);
839
- const {
840
- queriesByFile,
841
- mutationsByFile,
842
- internalQueriesByFile,
843
- internalMutationsByFile,
844
- } = generateGroupedHandlers(params.handlers);
845
- const {
846
- queriesTypeDef,
847
- mutationsTypeDef,
848
- internalQueriesTypeDef,
849
- internalMutationsTypeDef,
850
- } = generateTypeDefs(
851
- queriesByFile,
852
- mutationsByFile,
853
- internalQueriesByFile,
854
- internalMutationsByFile,
855
- importAliasBySource,
856
- );
857
- const { queriesInit, mutationsInit } = generateClientInits(
858
- queriesByFile,
859
- mutationsByFile,
860
- importAliasBySource,
861
- );
862
- const internalHandlersCombined = new Map<string, DiscoveredHandler[]>();
863
- for (const [file, list] of Array.from(internalQueriesByFile.entries())) {
864
- internalHandlersCombined.set(file, list.slice());
865
- }
866
- for (const [file, list] of Array.from(internalMutationsByFile.entries())) {
867
- const existing = internalHandlersCombined.get(file) ?? [];
868
- internalHandlersCombined.set(file, existing.concat(list));
869
- }
870
- const internalInit = generateInternalInit(
871
- internalHandlersCombined,
872
- importAliasBySource,
873
- );
874
- const internalQueriesMeta = generateInternalMeta(
875
- internalQueriesByFile,
876
- importAliasBySource,
877
- );
878
- const internalMutationsMeta = generateInternalMeta(
879
- internalMutationsByFile,
880
- importAliasBySource,
881
- );
882
-
883
- const authBasePathLiteral = JSON.stringify(params.authBasePath ?? "/auth");
884
-
885
- // Generate config import and auth client options handling
886
- let configImport = "";
887
- let authClientTypeDefinitions =
888
- "type AppflareAuthClient = ReturnType<typeof createAuthClient>;";
889
- let authClientInit = ` const auth = createAuthClient({
890
- ...(options.auth ?? {}),
891
- baseURL:
892
- (options.auth as any)?.baseURL ??
893
- buildUrl(baseUrl, authBasePath),
894
- });`;
895
-
896
- const clientConfigTs =
897
- params.authEnabled && params.configPathAbs
898
- ? extractClientConfig(params.configPathAbs)
899
- : null;
900
-
901
- if (params.authEnabled && params.configPathAbs) {
902
- if (clientConfigTs) {
903
- configImport = `\nimport { clientOptions } from "./client.config";`;
904
-
905
- // Use a factory function pattern to properly infer the client type from clientOptions
906
- authClientTypeDefinitions = `const __getAppflareAuthClientOptions = () => (clientOptions ?? {}) as const;
907
- type AppflareAuthClientOptions = ReturnType<typeof __getAppflareAuthClientOptions>;
908
- const __createTypedAuthClient = (baseURL: string) => createAuthClient({
909
- ...__getAppflareAuthClientOptions(),
910
- baseURL,
911
- });
912
- type AppflareAuthClient = ReturnType<typeof __createTypedAuthClient>;`;
913
-
914
- authClientInit = ` const auth = createAuthClient({
915
- ...__getAppflareAuthClientOptions(),
916
- ...(options.auth ?? {}),
917
- baseURL:
918
- (options.auth as any)?.baseURL ??
919
- buildUrl(baseUrl, authBasePath),
920
- });`;
921
- } else {
922
- const configImportPath = toImportPathFromGeneratedSrc(
923
- params.outDirAbs,
924
- params.configPathAbs,
925
- );
926
- configImport = `\nimport __appflareConfig from ${JSON.stringify(configImportPath)};`;
927
-
928
- // Use a factory function pattern to properly infer the client type from clientOptions
929
- authClientTypeDefinitions = `const __getAppflareAuthClientOptions = () => (__appflareConfig.auth?.clientOptions ?? {}) as const;
930
- type AppflareAuthClientOptions = ReturnType<typeof __getAppflareAuthClientOptions>;
931
- const __createTypedAuthClient = (baseURL: string) => createAuthClient({
932
- ...__getAppflareAuthClientOptions(),
933
- baseURL,
934
- });
935
- type AppflareAuthClient = ReturnType<typeof __createTypedAuthClient>;`;
936
-
937
- authClientInit = ` const auth = createAuthClient({
938
- ...__getAppflareAuthClientOptions(),
939
- ...(options.auth ?? {}),
940
- baseURL:
941
- (options.auth as any)?.baseURL ??
942
- buildUrl(baseUrl, authBasePath),
943
- });`;
944
- }
945
- }
946
-
947
- const typeBlocks = generateTypeBlocks(params.handlers, importAliasBySource);
948
-
949
- return {
950
- apiTs:
951
- HEADER_TEMPLATE.replace("{{configImport}}", configImport) +
952
- importLines.join("\n") +
953
- TYPE_DEFINITIONS_TEMPLATE +
954
- typeBlocks.join("\n\n") +
955
- INTERNAL_TEMPLATE.replace(
956
- "{{internalQueriesTypeDef}}",
957
- internalQueriesTypeDef,
958
- )
959
- .replace("{{internalMutationsTypeDef}}", internalMutationsTypeDef)
960
- .replace("{{internalInit}}", internalInit)
961
- .replace("{{internalQueriesMeta}}", internalQueriesMeta)
962
- .replace("{{internalMutationsMeta}}", internalMutationsMeta) +
963
- CLIENT_TYPES_TEMPLATE.replace("{{queriesTypeDef}}", queriesTypeDef)
964
- .replace("{{mutationsTypeDef}}", mutationsTypeDef)
965
- .replace("{{queriesInit}}", queriesInit)
966
- .replace("{{mutationsInit}}", mutationsInit)
967
- .replace("{{authBasePath}}", authBasePathLiteral)
968
- .replace("{{authClientTypeDefinitions}}", authClientTypeDefinitions)
969
- .replace("{{authClientInit}}", authClientInit) +
970
- UTILITY_FUNCTIONS_TEMPLATE,
971
- clientConfigTs,
972
- };
973
- }