appflare 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/cli/README.md +101 -0
  2. package/cli/core/build.ts +136 -0
  3. package/cli/core/config.ts +29 -0
  4. package/cli/core/discover-handlers.ts +61 -0
  5. package/cli/core/handlers.ts +5 -0
  6. package/cli/core/index.ts +157 -0
  7. package/cli/generators/generate-api-client/client.ts +93 -0
  8. package/cli/generators/generate-api-client/index.ts +529 -0
  9. package/cli/generators/generate-api-client/types.ts +59 -0
  10. package/cli/generators/generate-api-client/utils.ts +18 -0
  11. package/cli/generators/generate-api-client.ts +1 -0
  12. package/cli/generators/generate-db-handlers.ts +138 -0
  13. package/cli/generators/generate-hono-server.ts +238 -0
  14. package/cli/generators/generate-websocket-durable-object.ts +537 -0
  15. package/cli/index.ts +157 -0
  16. package/cli/schema/schema-static-types.ts +252 -0
  17. package/cli/schema/schema.ts +105 -0
  18. package/cli/utils/tsc.ts +53 -0
  19. package/cli/utils/utils.ts +126 -0
  20. package/cli/utils/zod-utils.ts +115 -0
  21. package/index.ts +2 -0
  22. package/lib/README.md +43 -0
  23. package/lib/db.ts +9 -0
  24. package/lib/values.ts +23 -0
  25. package/package.json +28 -0
  26. package/react/README.md +67 -0
  27. package/react/hooks/useMutation.ts +89 -0
  28. package/react/hooks/usePaginatedQuery.ts +213 -0
  29. package/react/hooks/useQuery.ts +106 -0
  30. package/react/index.ts +3 -0
  31. package/react/shared/queryShared.ts +169 -0
  32. package/server/README.md +153 -0
  33. package/server/database/builders.ts +83 -0
  34. package/server/database/context.ts +265 -0
  35. package/server/database/populate.ts +160 -0
  36. package/server/database/query-builder.ts +101 -0
  37. package/server/database/query-utils.ts +25 -0
  38. package/server/db.ts +2 -0
  39. package/server/types/schema-refs.ts +66 -0
  40. package/server/types/types.ts +419 -0
  41. package/server/utils/id-utils.ts +123 -0
  42. package/tsconfig.json +7 -0
@@ -0,0 +1,529 @@
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
+ } from "./types";
16
+
17
+ const HEADER_TEMPLATE = `/* eslint-disable */
18
+ /**
19
+ * This file is auto-generated by appflare/handler-build.ts.
20
+ * Do not edit directly.
21
+ */
22
+
23
+ import fetch from "better-fetch";
24
+ import { z } from "zod";
25
+
26
+ import type {
27
+ AnyValidator,
28
+ InferQueryArgs,
29
+ MutationDefinition,
30
+ QueryArgsShape,
31
+ QueryDefinition,
32
+ } from "./schema-types";
33
+
34
+ `;
35
+
36
+ const TYPE_DEFINITIONS_TEMPLATE = `
37
+ type AnyArgsShape = Record<string, AnyValidator>;
38
+
39
+ type AnyHandlerDefinition = QueryDefinition<AnyArgsShape, unknown> | MutationDefinition<AnyArgsShape, unknown>;
40
+
41
+ type Simplify<T> = { [K in keyof T]: T[K] };
42
+
43
+ type OptionalKeys<TArgs extends QueryArgsShape> = {
44
+ [K in keyof TArgs]: TArgs[K] extends z.ZodOptional<any> | z.ZodDefault<any> ? K : never;
45
+ }[keyof TArgs];
46
+
47
+ type RequiredKeys<TArgs extends QueryArgsShape> = Exclude<keyof TArgs, OptionalKeys<TArgs>>;
48
+
49
+ type HandlerArgsFromShape<TArgs extends QueryArgsShape> = Simplify<
50
+ Partial<Pick<InferQueryArgs<TArgs>, OptionalKeys<TArgs>>> &
51
+ Pick<InferQueryArgs<TArgs>, RequiredKeys<TArgs>>
52
+ >;
53
+
54
+ type HandlerArgs<THandler extends AnyHandlerDefinition> =
55
+ THandler extends { args: infer TArgs extends QueryArgsShape }
56
+ ? HandlerArgsFromShape<TArgs>
57
+ : never;
58
+
59
+ type HandlerResult<THandler extends AnyHandlerDefinition> = THandler extends {
60
+ handler: (...args: any[]) => Promise<infer TResult>;
61
+ }
62
+ ? TResult
63
+ : never;
64
+
65
+ type HandlerInvoker<TArgs, TResult> = (args: TArgs, init?: RequestInit) => Promise<TResult>;
66
+
67
+ type HandlerArgsShape<THandler extends AnyHandlerDefinition> =
68
+ THandler extends { args: infer TArgs extends QueryArgsShape }
69
+ ? TArgs
70
+ : never;
71
+
72
+ type HandlerSchemaFromShape<TArgs extends QueryArgsShape> = z.ZodObject<{
73
+ [K in keyof TArgs]: z.ZodType<InferQueryArgs<TArgs>[K]>;
74
+ }>;
75
+
76
+ type HandlerSchema<THandler extends AnyHandlerDefinition> = HandlerSchemaFromShape<
77
+ HandlerArgsShape<THandler>
78
+ >;
79
+
80
+ type WebSocketFactory = (url: string, protocols?: string | string[]) => WebSocket;
81
+
82
+ export type RealtimeMessage<TResult> = {
83
+ type?: string;
84
+ data?: TResult[];
85
+ table?: string;
86
+ where?: unknown;
87
+ [key: string]: unknown;
88
+ };
89
+
90
+ export type HandlerWebsocketOptions<TResult> = {
91
+ baseUrl?: string;
92
+ table?: string;
93
+ handler?: { file: string; name: string };
94
+ handlerFile?: string;
95
+ handlerName?: string;
96
+ where?: Record<string, unknown>;
97
+ orderBy?: Record<string, unknown>;
98
+ take?: number;
99
+ skip?: number;
100
+ path?: string;
101
+ protocols?: string | string[];
102
+ signal?: AbortSignal;
103
+ websocketImpl?: WebSocketFactory;
104
+ onOpen?: (event: any) => void;
105
+ onClose?: (event: any) => void;
106
+ onError?: (event: any) => void;
107
+ onMessage?: (message: RealtimeMessage<TResult>, raw: any) => void;
108
+ onData?: (data: TResult[], message: RealtimeMessage<TResult>) => void;
109
+ };
110
+
111
+ type HandlerMetadata<THandler extends AnyHandlerDefinition> = {
112
+ schema: HandlerSchema<THandler>;
113
+ websocket: HandlerWebsocket<
114
+ HandlerArgs<THandler>,
115
+ HandlerResult<THandler>
116
+ >;
117
+ path: string;
118
+ };
119
+
120
+ export type HandlerWebsocket<TArgs, TResult> = (
121
+ args?: TArgs,
122
+ options?: HandlerWebsocketOptions<TResult>
123
+ ) => WebSocket;
124
+
125
+ export type AppflareHandler<THandler extends AnyHandlerDefinition> = HandlerInvoker<
126
+ HandlerArgs<THandler>,
127
+ HandlerResult<THandler>
128
+ > &
129
+ HandlerMetadata<THandler>;
130
+
131
+ type RequestExecutor = (
132
+ input: RequestInfo | URL,
133
+ init?: RequestInit
134
+ ) => Promise<Response>;
135
+
136
+ const defaultFetcher: RequestExecutor = (input, init) => fetch(input, init);
137
+
138
+ const defaultWebSocketFactory: WebSocketFactory = (url, protocols) => {
139
+ if (typeof WebSocket === "undefined") {
140
+ throw new Error(
141
+ "WebSocket is not available in this environment. Provide options.realtime.websocketImpl to create websockets."
142
+ );
143
+ }
144
+ return new WebSocket(url, protocols);
145
+ };
146
+
147
+ type ResolvedRealtimeConfig = {
148
+ baseUrl?: string;
149
+ path: string;
150
+ websocketImpl?: WebSocketFactory;
151
+ };
152
+
153
+ type RealtimeConfig = {
154
+ baseUrl?: string;
155
+ path?: string;
156
+ websocketImpl?: WebSocketFactory;
157
+ };
158
+
159
+ `;
160
+
161
+ const CLIENT_TYPES_TEMPLATE = `
162
+ export type QueriesClient = {{queriesTypeDef}};
163
+
164
+ export type MutationsClient = {{mutationsTypeDef}};
165
+
166
+ export type AppflareApiClient = {
167
+ queries: QueriesClient;
168
+ mutations: MutationsClient;
169
+ };
170
+
171
+ export type AppflareApiOptions = {
172
+ baseUrl?: string;
173
+ fetcher?: RequestExecutor;
174
+ realtime?: RealtimeConfig;
175
+ };
176
+
177
+ export function createAppflareApi(options: AppflareApiOptions = {}): AppflareApiClient {
178
+ const baseUrl = normalizeBaseUrl(options.baseUrl);
179
+ const request = options.fetcher ?? defaultFetcher;
180
+ const realtime = resolveRealtimeConfig(baseUrl, options.realtime);
181
+ const queries: QueriesClient = {{queriesInit}};
182
+ const mutations: MutationsClient = {{mutationsInit}};
183
+ return { queries, mutations };
184
+ }
185
+
186
+ `;
187
+
188
+ const UTILITY_FUNCTIONS_TEMPLATE_PART1 = `
189
+ function withHandlerMetadata<THandler extends AnyHandlerDefinition>(
190
+ invoke: HandlerInvoker<HandlerArgs<THandler>, HandlerResult<THandler>>,
191
+ meta: HandlerMetadata<THandler>
192
+ ): AppflareHandler<THandler> {
193
+ const fn = invoke as AppflareHandler<THandler>;
194
+ fn.schema = meta.schema;
195
+ fn.websocket = meta.websocket;
196
+ fn.path = meta.path;
197
+ return fn;
198
+ }
199
+
200
+ function resolveRealtimeConfig(
201
+ baseUrl: string,
202
+ realtime?: RealtimeConfig
203
+ ): ResolvedRealtimeConfig {
204
+ return {
205
+ baseUrl: normalizeWsBaseUrl(realtime?.baseUrl ?? baseUrl),
206
+ path: realtime?.path ?? "/ws",
207
+ websocketImpl: realtime?.websocketImpl ?? defaultWebSocketFactory,
208
+ };
209
+ }
210
+
211
+ function createHandlerSchema<TArgs extends QueryArgsShape>(
212
+ args: TArgs
213
+ ): HandlerSchemaFromShape<TArgs> {
214
+ return z.object(args as any as Record<string, z.ZodTypeAny>) as HandlerSchemaFromShape<TArgs>;
215
+ }
216
+ `;
217
+
218
+ const UTILITY_FUNCTIONS_TEMPLATE_PART2 = `
219
+ function createHandlerWebsocket<TArgs, TResult>(
220
+ realtime: ResolvedRealtimeConfig,
221
+ defaults: { defaultTable: string; defaultHandler: { file: string; name: string } }
222
+ ): HandlerWebsocket<TArgs, TResult> {
223
+ return (args, options) => {
224
+ const baseUrl = normalizeWsBaseUrl(options?.baseUrl ?? realtime.baseUrl);
225
+ if (!baseUrl) {
226
+ throw new Error(
227
+ "Missing realtime baseUrl. Provide createAppflareApi({ realtime: { baseUrl } }) or handler websocket options.baseUrl."
228
+ );
229
+ }
230
+ const params = new URLSearchParams();
231
+ const tableParam = options?.table ?? defaults.defaultTable;
232
+ const normalizedTable = tableParam.endsWith("s")
233
+ ? tableParam
234
+ : tableParam + "s";
235
+ params.set("table", normalizedTable);
236
+
237
+ const handlerRef =
238
+ options?.handler ??
239
+ (options?.handlerFile && options?.handlerName
240
+ ? { file: options.handlerFile, name: options.handlerName }
241
+ : defaults.defaultHandler);
242
+ if (handlerRef) {
243
+ params.set("handler", JSON.stringify(handlerRef));
244
+ }
245
+ const where = options?.where ?? (isPlainObject(args) ? (args as Record<string, unknown>) : undefined);
246
+ if (where && Object.keys(where).length > 0) {
247
+ params.set("where", JSON.stringify(where));
248
+ }
249
+ if (options?.orderBy) params.set("orderBy", JSON.stringify(options.orderBy));
250
+ if (options?.take !== undefined) params.set("take", String(options.take));
251
+ if (options?.skip !== undefined) params.set("skip", String(options.skip));
252
+
253
+ const path = options?.path ?? realtime.path;
254
+ const url = buildRealtimeUrl(baseUrl, path, params);
255
+ const websocketFactory = options?.websocketImpl ?? realtime.websocketImpl ?? defaultWebSocketFactory;
256
+ const socket = websocketFactory(url, options?.protocols);
257
+
258
+ if (options?.onOpen) socket.addEventListener("open", options.onOpen as any);
259
+ if (options?.onClose) socket.addEventListener("close", options.onClose as any);
260
+ if (options?.onError) socket.addEventListener("error", options.onError as any);
261
+
262
+ const onMessage = (event: any) => {
263
+ const message = parseRealtimeMessage<TResult>(event?.data ?? event);
264
+ if (options?.onMessage) options.onMessage(message, event);
265
+ if (message?.type === "data" && Array.isArray((message as any).data)) {
266
+ options?.onData?.((message as any).data as TResult[], message);
267
+ }
268
+ };
269
+
270
+ socket.addEventListener("message", onMessage as any);
271
+
272
+ if (options?.signal) {
273
+ const abortHandler = () => socket.close(1000, "aborted");
274
+ if (options.signal.aborted) abortHandler();
275
+ else options.signal.addEventListener("abort", abortHandler, { once: true });
276
+ }
277
+
278
+ return socket;
279
+ };
280
+ }
281
+ `;
282
+
283
+ const UTILITY_FUNCTIONS_TEMPLATE_PART3 = `
284
+ function normalizeBaseUrl(baseUrl?: string): string {
285
+ if (!baseUrl) {
286
+ return "";
287
+ }
288
+ return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
289
+ }
290
+
291
+ function normalizeWsBaseUrl(baseUrl?: string): string | undefined {
292
+ if (!baseUrl) return undefined;
293
+ if (baseUrl.startsWith("ws://") || baseUrl.startsWith("wss://")) {
294
+ return baseUrl.replace(/\\/+$/, "");
295
+ }
296
+ if (baseUrl.startsWith("https://")) return baseUrl.replace(/^https/, "wss").replace(/\\/$/, "");
297
+ if (baseUrl.startsWith("http://")) return baseUrl.replace(/^http/, "ws").replace(/\\/$/, "");
298
+ return baseUrl.replace(/\\/$/, "");
299
+ }
300
+
301
+ function buildUrl(baseUrl: string, path: string): string {
302
+ if (!baseUrl) {
303
+ return path;
304
+ }
305
+ const normalizedPath = path.startsWith("/") ? path : "/" + path;
306
+ return baseUrl + normalizedPath;
307
+ }
308
+
309
+ function buildRealtimeUrl(
310
+ baseUrl: string,
311
+ path: string,
312
+ params: URLSearchParams
313
+ ): string {
314
+ const normalizedBase = baseUrl.replace(/\\/+$/, "");
315
+ const normalizedPath = path.startsWith("/") ? path : "/" + path;
316
+ const query = params.toString();
317
+ return query ? normalizedBase + normalizedPath + "?" + query : normalizedBase + normalizedPath;
318
+ }
319
+
320
+ function buildQueryUrl(
321
+ baseUrl: string,
322
+ path: string,
323
+ params: Record<string, unknown> | undefined
324
+ ): string {
325
+ const url = buildUrl(baseUrl, path);
326
+ const query = serializeQueryParams(params);
327
+ return query ? url + "?" + query : url;
328
+ }
329
+
330
+ function serializeQueryParams(
331
+ params: Record<string, unknown> | undefined
332
+ ): string {
333
+ if (!params) {
334
+ return "";
335
+ }
336
+ const searchParams = new URLSearchParams();
337
+ for (const [key, value] of Object.entries(params)) {
338
+ if (value === undefined || value === null) {
339
+ continue;
340
+ }
341
+ if (Array.isArray(value)) {
342
+ for (const entry of value) {
343
+ searchParams.append(key, serializeQueryValue(entry));
344
+ }
345
+ continue;
346
+ }
347
+ searchParams.append(key, serializeQueryValue(value));
348
+ }
349
+ return searchParams.toString();
350
+ }
351
+
352
+ function serializeQueryValue(value: unknown): string {
353
+ if (value instanceof Date) {
354
+ return value.toISOString();
355
+ }
356
+ if (typeof value === "object") {
357
+ return JSON.stringify(value);
358
+ }
359
+ return String(value);
360
+ }
361
+
362
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
363
+ return !!value && typeof value === "object" && !Array.isArray(value);
364
+ }
365
+
366
+ function parseRealtimeMessage<TResult>(value: unknown): RealtimeMessage<TResult> {
367
+ if (typeof value === "string") {
368
+ try {
369
+ return JSON.parse(value) as RealtimeMessage<TResult>;
370
+ } catch {
371
+ return { type: "message", raw: value } as any;
372
+ }
373
+ }
374
+ return value as RealtimeMessage<TResult>;
375
+ }
376
+
377
+ function ensureJsonHeaders(headers?: HeadersInit): HeadersInit {
378
+ if (!headers) {
379
+ return { "content-type": "application/json" };
380
+ }
381
+ if (typeof Headers !== "undefined" && headers instanceof Headers) {
382
+ const next = new Headers(headers);
383
+ if (!next.has("content-type")) {
384
+ next.set("content-type", "application/json");
385
+ }
386
+ return next;
387
+ }
388
+ if (Array.isArray(headers)) {
389
+ const entries = headers.slice();
390
+ const hasContentType = entries.some(
391
+ ([key]) => key.toLowerCase() === "content-type"
392
+ );
393
+ if (!hasContentType) {
394
+ entries.push(["content-type", "application/json"]);
395
+ }
396
+ return entries;
397
+ }
398
+ if (typeof headers === "object") {
399
+ const record = { ...(headers as Record<string, string>) };
400
+ if (!hasHeader(record, "content-type")) {
401
+ record["content-type"] = "application/json";
402
+ }
403
+ return record;
404
+ }
405
+ return { "content-type": "application/json" };
406
+ }
407
+
408
+ function hasHeader(record: Record<string, string>, name: string): boolean {
409
+ const needle = name.toLowerCase();
410
+ return Object.keys(record).some((key) => key.toLowerCase() === needle);
411
+ }
412
+
413
+ async function parseJson<TResult>(response: Response): Promise<TResult> {
414
+ if (!response.ok) {
415
+ throw new Error("Request failed with status " + response.status);
416
+ }
417
+ return (await response.json()) as TResult;
418
+ }
419
+ `;
420
+
421
+ const UTILITY_FUNCTIONS_TEMPLATE =
422
+ UTILITY_FUNCTIONS_TEMPLATE_PART1 +
423
+ UTILITY_FUNCTIONS_TEMPLATE_PART2 +
424
+ UTILITY_FUNCTIONS_TEMPLATE_PART3;
425
+
426
+ function generateImports(params: {
427
+ handlers: DiscoveredHandler[];
428
+ outDirAbs: string;
429
+ }): { importLines: string[]; importAliasBySource: Map<string, string> } {
430
+ const handlerImportsGrouped = groupBy(
431
+ params.handlers,
432
+ (h) => h.sourceFileAbs
433
+ );
434
+
435
+ const importLines: string[] = [];
436
+ const importAliasBySource = new Map<string, string>();
437
+ for (const [fileAbs, list] of Array.from(handlerImportsGrouped.entries())) {
438
+ const alias = `__appflare_${pascalCase(list[0].fileName)}`;
439
+ importAliasBySource.set(fileAbs, alias);
440
+ const importPath = toImportPathFromGeneratedSrc(params.outDirAbs, fileAbs);
441
+ importLines.push(
442
+ `import * as ${alias} from ${JSON.stringify(importPath)};`
443
+ );
444
+ }
445
+ return { importLines, importAliasBySource };
446
+ }
447
+
448
+ function generateGroupedHandlers(handlers: DiscoveredHandler[]): {
449
+ queriesByFile: Map<string, DiscoveredHandler[]>;
450
+ mutationsByFile: Map<string, DiscoveredHandler[]>;
451
+ } {
452
+ const queries = handlers.filter((h) => h.kind === "query");
453
+ const mutations = handlers.filter((h) => h.kind === "mutation");
454
+
455
+ const queriesByFile = groupBy(queries, (h) => h.fileName);
456
+ const mutationsByFile = groupBy(mutations, (h) => h.fileName);
457
+
458
+ return { queriesByFile, mutationsByFile };
459
+ }
460
+
461
+ function generateTypeDefs(
462
+ queriesByFile: Map<string, DiscoveredHandler[]>,
463
+ mutationsByFile: Map<string, DiscoveredHandler[]>
464
+ ): { queriesTypeDef: string; mutationsTypeDef: string } {
465
+ const queriesTypeLines = generateQueriesTypeLines(queriesByFile);
466
+ const mutationsTypeLines = generateMutationsTypeLines(mutationsByFile);
467
+
468
+ const queriesTypeDef =
469
+ queriesByFile.size === 0 ? "{}" : `{\n${queriesTypeLines}\n}`;
470
+ const mutationsTypeDef =
471
+ mutationsByFile.size === 0 ? "{}" : `{\n${mutationsTypeLines}\n}`;
472
+
473
+ return { queriesTypeDef, mutationsTypeDef };
474
+ }
475
+
476
+ function generateClientInits(
477
+ queriesByFile: Map<string, DiscoveredHandler[]>,
478
+ mutationsByFile: Map<string, DiscoveredHandler[]>,
479
+ importAliasBySource: Map<string, string>
480
+ ): { queriesInit: string; mutationsInit: string } {
481
+ const queriesClientLines = generateQueriesClientLines(
482
+ queriesByFile,
483
+ importAliasBySource
484
+ );
485
+ const mutationsClientLines = generateMutationsClientLines(
486
+ mutationsByFile,
487
+ importAliasBySource
488
+ );
489
+
490
+ const queriesInit =
491
+ queriesByFile.size === 0 ? "{}" : `{\n${queriesClientLines}\n\t}`;
492
+ const mutationsInit =
493
+ mutationsByFile.size === 0 ? "{}" : `{\n${mutationsClientLines}\n\t}`;
494
+
495
+ return { queriesInit, mutationsInit };
496
+ }
497
+
498
+ export function generateApiClient(params: {
499
+ handlers: DiscoveredHandler[];
500
+ outDirAbs: string;
501
+ }): string {
502
+ const { importLines, importAliasBySource } = generateImports(params);
503
+ const { queriesByFile, mutationsByFile } = generateGroupedHandlers(
504
+ params.handlers
505
+ );
506
+ const { queriesTypeDef, mutationsTypeDef } = generateTypeDefs(
507
+ queriesByFile,
508
+ mutationsByFile
509
+ );
510
+ const { queriesInit, mutationsInit } = generateClientInits(
511
+ queriesByFile,
512
+ mutationsByFile,
513
+ importAliasBySource
514
+ );
515
+
516
+ const typeBlocks = generateTypeBlocks(params.handlers, importAliasBySource);
517
+
518
+ return (
519
+ HEADER_TEMPLATE +
520
+ importLines.join("\n") +
521
+ TYPE_DEFINITIONS_TEMPLATE +
522
+ typeBlocks.join("\n\n") +
523
+ CLIENT_TYPES_TEMPLATE.replace("{{queriesTypeDef}}", queriesTypeDef)
524
+ .replace("{{mutationsTypeDef}}", mutationsTypeDef)
525
+ .replace("{{queriesInit}}", queriesInit)
526
+ .replace("{{mutationsInit}}", mutationsInit) +
527
+ UTILITY_FUNCTIONS_TEMPLATE
528
+ );
529
+ }
@@ -0,0 +1,59 @@
1
+ import { DiscoveredHandler } from "../../utils/utils";
2
+ import { handlerTypePrefix, renderObjectKey, sortedEntries } from "./utils";
3
+
4
+ export function generateTypeBlocks(
5
+ handlers: DiscoveredHandler[],
6
+ importAliasBySource: Map<string, string>
7
+ ): string[] {
8
+ const typeBlocks: string[] = [];
9
+ for (const h of handlers) {
10
+ const importAlias = importAliasBySource.get(h.sourceFileAbs)!;
11
+ const handlerAccessor = `${importAlias}[${JSON.stringify(h.name)}]`;
12
+ const pascal = handlerTypePrefix(h);
13
+ typeBlocks.push(
14
+ `type ${pascal}Definition = typeof ${handlerAccessor};\n` +
15
+ `type ${pascal}Args = HandlerArgs<${pascal}Definition>;\n` +
16
+ `type ${pascal}Result = HandlerResult<${pascal}Definition>;\n` +
17
+ `type ${pascal}Client = AppflareHandler<${pascal}Definition>;`
18
+ );
19
+ }
20
+ return typeBlocks;
21
+ }
22
+
23
+ export function generateQueriesTypeLines(
24
+ queriesByFile: Map<string, DiscoveredHandler[]>
25
+ ): string {
26
+ return sortedEntries(queriesByFile)
27
+ .map(([fileName, list]) => {
28
+ const fileKey = renderObjectKey(fileName);
29
+ const inner = list
30
+ .slice()
31
+ .sort((a, b) => a.name.localeCompare(b.name))
32
+ .map((h) => {
33
+ const pascal = handlerTypePrefix(h);
34
+ return `\t\t${h.name}: ${pascal}Client;`;
35
+ })
36
+ .join("\n");
37
+ return `\t${fileKey}: {\n${inner || "\t\t// (none)"}\n\t};`;
38
+ })
39
+ .join("\n");
40
+ }
41
+
42
+ export function generateMutationsTypeLines(
43
+ mutationsByFile: Map<string, DiscoveredHandler[]>
44
+ ): string {
45
+ return sortedEntries(mutationsByFile)
46
+ .map(([fileName, list]) => {
47
+ const fileKey = renderObjectKey(fileName);
48
+ const inner = list
49
+ .slice()
50
+ .sort((a, b) => a.name.localeCompare(b.name))
51
+ .map((h) => {
52
+ const pascal = handlerTypePrefix(h);
53
+ return `\t\t${h.name}: ${pascal}Client;`;
54
+ })
55
+ .join("\n");
56
+ return `\t${fileKey}: {\n${inner || "\t\t// (none)"}\n\t};`;
57
+ })
58
+ .join("\n");
59
+ }
@@ -0,0 +1,18 @@
1
+ import { isValidIdentifier } from "../../utils/utils";
2
+
3
+ export const sortedEntries = <T>(map: Map<string, T[]>): Array<[string, T[]]> =>
4
+ Array.from(map.entries()).sort(([a], [b]) => a.localeCompare(b));
5
+
6
+ export const renderObjectKey = (key: string): string =>
7
+ isValidIdentifier(key) ? key : JSON.stringify(key);
8
+
9
+ export const handlerTypePrefix = (h: any): string =>
10
+ h.fileName
11
+ .replace(/[^a-zA-Z0-9]/g, "")
12
+ .replace(/^./, (c: string) => c.toUpperCase()) +
13
+ h.name
14
+ .replace(/[^a-zA-Z0-9]/g, "")
15
+ .replace(/^./, (c: string) => c.toUpperCase());
16
+
17
+ export const normalizeTableName = (fileName: string): string =>
18
+ fileName.endsWith("s") ? fileName : `${fileName}s`;
@@ -0,0 +1 @@
1
+ export { generateApiClient } from "./generate-api-client/index";