appflare 0.0.28 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +747 -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 +353 -0
- package/cli/templates/handlers/auth.ts +37 -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/generators/registration/modules/cron.ts +30 -0
- package/cli/templates/handlers/generators/registration/modules/realtime.ts +631 -0
- package/cli/templates/handlers/generators/registration/modules/scheduler.ts +56 -0
- package/cli/templates/handlers/generators/registration/modules/storage.ts +194 -0
- package/cli/templates/handlers/generators/registration/sections.ts +210 -0
- package/cli/templates/handlers/generators/types/context.ts +64 -0
- package/cli/templates/handlers/generators/types/core.ts +101 -0
- package/cli/templates/handlers/generators/types/operations.ts +135 -0
- package/cli/templates/handlers/generators/types/query-definitions.ts +1029 -0
- package/cli/templates/handlers/generators/types/query-runtime.ts +417 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +83 -0
- package/cli/templates/handlers/types.ts +15 -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
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { DiscoveredColumn } from "../../utils/schema-discovery";
|
|
2
|
+
|
|
3
|
+
export function toPascalCase(value: string): string {
|
|
4
|
+
return value
|
|
5
|
+
.replace(/[_-]+/g, " ")
|
|
6
|
+
.replace(/\s+(.)/g, (_match, char: string) => char.toUpperCase())
|
|
7
|
+
.replace(/\s/g, "")
|
|
8
|
+
.replace(/^(.)/, (_match, char: string) => char.toUpperCase());
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function singularize(value: string): string {
|
|
12
|
+
if (value.endsWith("ies")) {
|
|
13
|
+
return `${value.slice(0, -3)}y`;
|
|
14
|
+
}
|
|
15
|
+
if (value.endsWith("ses")) {
|
|
16
|
+
return value.slice(0, -2);
|
|
17
|
+
}
|
|
18
|
+
if (value.endsWith("s") && value.length > 1) {
|
|
19
|
+
return value.slice(0, -1);
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function zodForColumn(column: DiscoveredColumn): string {
|
|
25
|
+
if (column.type === "number") {
|
|
26
|
+
return `z.number()${column.optional ? ".optional()" : ""}`;
|
|
27
|
+
}
|
|
28
|
+
if (column.type === "boolean") {
|
|
29
|
+
return `z.boolean()${column.optional ? ".optional()" : ""}`;
|
|
30
|
+
}
|
|
31
|
+
if (column.type === "string") {
|
|
32
|
+
return `z.string()${column.optional ? ".optional()" : ""}`;
|
|
33
|
+
}
|
|
34
|
+
return `z.unknown()${column.optional ? ".optional()" : ""}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function zodForQueryColumn(column: DiscoveredColumn): string {
|
|
38
|
+
if (column.type === "number") {
|
|
39
|
+
return "z.coerce.number().optional()";
|
|
40
|
+
}
|
|
41
|
+
if (column.type === "boolean") {
|
|
42
|
+
return "z.coerce.boolean().optional()";
|
|
43
|
+
}
|
|
44
|
+
if (column.type === "string") {
|
|
45
|
+
return "z.string().optional()";
|
|
46
|
+
}
|
|
47
|
+
return "z.unknown().optional()";
|
|
48
|
+
}
|
package/cli/types.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import type { BetterAuthOptions } from "better-auth";
|
|
2
|
+
import type { BetterAuthClientOptions } from "better-auth/client";
|
|
3
|
+
|
|
4
|
+
export type JsonPrimitive = string | number | boolean | null;
|
|
5
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
|
|
6
|
+
export type JsonObject = {
|
|
7
|
+
[key: string]: JsonValue;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type AppflareDatabaseBinding = {
|
|
11
|
+
binding: string;
|
|
12
|
+
databaseName: string;
|
|
13
|
+
databaseId: string;
|
|
14
|
+
previewDatabaseId?: string;
|
|
15
|
+
migrationsDir?: string;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type AppflareKVNamespace = {
|
|
19
|
+
binding: string;
|
|
20
|
+
id: string;
|
|
21
|
+
previewId?: string;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type AppflareR2Bucket = {
|
|
25
|
+
binding: string;
|
|
26
|
+
bucketName: string;
|
|
27
|
+
previewBucketName?: string;
|
|
28
|
+
jurisdiction?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export type AppflareSchemaDslConfig = {
|
|
32
|
+
entry: string;
|
|
33
|
+
exportName?: string;
|
|
34
|
+
outFile?: string;
|
|
35
|
+
typesOutFile?: string;
|
|
36
|
+
zodOutFile?: string;
|
|
37
|
+
namingStrategy?: "camelToSnake";
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
export type AppflareSchedulerConfig = {
|
|
41
|
+
enabled?: boolean;
|
|
42
|
+
binding?: string;
|
|
43
|
+
queue?: string;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export type AppflareRealtimeConfig = {
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
binding?: string;
|
|
49
|
+
className?: string;
|
|
50
|
+
objectName?: string;
|
|
51
|
+
subscribePath?: string;
|
|
52
|
+
websocketPath?: string;
|
|
53
|
+
protocol?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type AppflareConfig = {
|
|
57
|
+
scanDir: string;
|
|
58
|
+
outDir: string;
|
|
59
|
+
wranglerOutDir?: string;
|
|
60
|
+
wranglerOutPath?: string;
|
|
61
|
+
schema: string[];
|
|
62
|
+
schemaDsl?: AppflareSchemaDslConfig;
|
|
63
|
+
database: AppflareDatabaseBinding | AppflareDatabaseBinding[];
|
|
64
|
+
kv?: AppflareKVNamespace | AppflareKVNamespace[];
|
|
65
|
+
r2?: AppflareR2Bucket | AppflareR2Bucket[];
|
|
66
|
+
auth: {
|
|
67
|
+
enabled: boolean;
|
|
68
|
+
basePath: string;
|
|
69
|
+
options: Partial<BetterAuthOptions>;
|
|
70
|
+
clientOptions: BetterAuthClientOptions;
|
|
71
|
+
};
|
|
72
|
+
scheduler?: AppflareSchedulerConfig;
|
|
73
|
+
realtime?: AppflareRealtimeConfig;
|
|
74
|
+
wranglerOverrides?: JsonObject;
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export type NormalizedAppflareConfig = Omit<
|
|
78
|
+
AppflareConfig,
|
|
79
|
+
"database" | "kv" | "r2" | "wranglerOutPath"
|
|
80
|
+
> & {
|
|
81
|
+
database: AppflareDatabaseBinding[];
|
|
82
|
+
kv: AppflareKVNamespace[];
|
|
83
|
+
r2: AppflareR2Bucket[];
|
|
84
|
+
wranglerOutDir: string;
|
|
85
|
+
scheduler: Required<Pick<AppflareSchedulerConfig, "enabled" | "binding">> &
|
|
86
|
+
Pick<AppflareSchedulerConfig, "queue">;
|
|
87
|
+
realtime: Required<
|
|
88
|
+
Pick<
|
|
89
|
+
AppflareRealtimeConfig,
|
|
90
|
+
| "enabled"
|
|
91
|
+
| "binding"
|
|
92
|
+
| "className"
|
|
93
|
+
| "objectName"
|
|
94
|
+
| "subscribePath"
|
|
95
|
+
| "websocketPath"
|
|
96
|
+
| "protocol"
|
|
97
|
+
>
|
|
98
|
+
>;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
export type LoadedAppflareConfig = {
|
|
102
|
+
configPath: string;
|
|
103
|
+
configDir: string;
|
|
104
|
+
scanDirAbs: string;
|
|
105
|
+
outDirAbs: string;
|
|
106
|
+
wranglerOutDirAbs: string;
|
|
107
|
+
config: NormalizedAppflareConfig;
|
|
108
|
+
};
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import { readdir } from "node:fs/promises";
|
|
2
|
+
import { extname, relative, resolve } from "node:path";
|
|
3
|
+
import * as ts from "typescript";
|
|
4
|
+
import type { LoadedAppflareConfig } from "../types";
|
|
5
|
+
import { ensureRelativeImportPath } from "./path-utils";
|
|
6
|
+
|
|
7
|
+
export type HandlerKind =
|
|
8
|
+
| "query"
|
|
9
|
+
| "mutation"
|
|
10
|
+
| "scheduler"
|
|
11
|
+
| "cron"
|
|
12
|
+
| "storage";
|
|
13
|
+
|
|
14
|
+
export type DiscoveredHandlerOperation = {
|
|
15
|
+
kind: HandlerKind;
|
|
16
|
+
exportName: string;
|
|
17
|
+
filePath: string;
|
|
18
|
+
importPath: string;
|
|
19
|
+
clientImportPath: string;
|
|
20
|
+
routePath: string;
|
|
21
|
+
handlerName?: string;
|
|
22
|
+
clientSegments?: string[];
|
|
23
|
+
taskName?: string;
|
|
24
|
+
cronTriggers?: string[];
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const supportedExtensions = new Set([".ts", ".tsx", ".mts", ".cts"]);
|
|
28
|
+
|
|
29
|
+
async function discoverFiles(dirPath: string): Promise<string[]> {
|
|
30
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
31
|
+
const files: string[] = [];
|
|
32
|
+
|
|
33
|
+
for (const entry of entries) {
|
|
34
|
+
if (entry.name.startsWith(".")) {
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (entry.name === "node_modules" || entry.name === "_generated") {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const entryPath = resolve(dirPath, entry.name);
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
files.push(...(await discoverFiles(entryPath)));
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (entry.isFile() && supportedExtensions.has(extname(entry.name))) {
|
|
48
|
+
files.push(entryPath);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return files;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function withoutExtension(filePath: string): string {
|
|
56
|
+
return filePath.replace(/\.[cm]?tsx?$/, "");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
type DiscoveredExport = {
|
|
60
|
+
exportName: string;
|
|
61
|
+
kind: HandlerKind;
|
|
62
|
+
cronTriggers: string[];
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
function isExportedConst(
|
|
66
|
+
statement: ts.Statement,
|
|
67
|
+
): statement is ts.VariableStatement {
|
|
68
|
+
if (!ts.isVariableStatement(statement)) {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
statement.modifiers?.some(
|
|
74
|
+
(modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword,
|
|
75
|
+
) ?? false
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readIdentifierText(expression: ts.Expression): string | null {
|
|
80
|
+
if (ts.isIdentifier(expression)) {
|
|
81
|
+
return expression.text;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (ts.isParenthesizedExpression(expression)) {
|
|
85
|
+
return readIdentifierText(expression.expression);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function readCronTriggers(argument: ts.Expression | undefined): string[] {
|
|
92
|
+
if (!argument || !ts.isObjectLiteralExpression(argument)) {
|
|
93
|
+
return [];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const cronProperty = argument.properties.find((property) => {
|
|
97
|
+
if (!ts.isPropertyAssignment(property)) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!ts.isIdentifier(property.name)) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return property.name.text === "cronTrigger";
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!cronProperty || !ts.isPropertyAssignment(cronProperty)) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const initializer = cronProperty.initializer;
|
|
113
|
+
if (
|
|
114
|
+
ts.isStringLiteral(initializer) ||
|
|
115
|
+
ts.isNoSubstitutionTemplateLiteral(initializer)
|
|
116
|
+
) {
|
|
117
|
+
return [initializer.text.trim()].filter((value) => value.length > 0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (ts.isArrayLiteralExpression(initializer)) {
|
|
121
|
+
return initializer.elements
|
|
122
|
+
.map((element) => {
|
|
123
|
+
if (
|
|
124
|
+
ts.isStringLiteral(element) ||
|
|
125
|
+
ts.isNoSubstitutionTemplateLiteral(element)
|
|
126
|
+
) {
|
|
127
|
+
return element.text.trim();
|
|
128
|
+
}
|
|
129
|
+
return "";
|
|
130
|
+
})
|
|
131
|
+
.filter((value) => value.length > 0);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return [];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function discoverExports(source: string, filePath: string): DiscoveredExport[] {
|
|
138
|
+
const sourceFile = ts.createSourceFile(
|
|
139
|
+
filePath,
|
|
140
|
+
source,
|
|
141
|
+
ts.ScriptTarget.Latest,
|
|
142
|
+
true,
|
|
143
|
+
ts.ScriptKind.TS,
|
|
144
|
+
);
|
|
145
|
+
const exports: DiscoveredExport[] = [];
|
|
146
|
+
|
|
147
|
+
for (const statement of sourceFile.statements) {
|
|
148
|
+
if (!isExportedConst(statement)) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
153
|
+
if (!ts.isIdentifier(declaration.name) || !declaration.initializer) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!ts.isCallExpression(declaration.initializer)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const callee = readIdentifierText(declaration.initializer.expression);
|
|
162
|
+
if (
|
|
163
|
+
callee !== "query" &&
|
|
164
|
+
callee !== "mutation" &&
|
|
165
|
+
callee !== "scheduler" &&
|
|
166
|
+
callee !== "cron" &&
|
|
167
|
+
callee !== "storageManager"
|
|
168
|
+
) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
exports.push({
|
|
173
|
+
exportName: declaration.name.text,
|
|
174
|
+
kind: callee === "storageManager" ? "storage" : callee,
|
|
175
|
+
cronTriggers:
|
|
176
|
+
callee === "cron"
|
|
177
|
+
? readCronTriggers(declaration.initializer.arguments[0])
|
|
178
|
+
: [],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return exports;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function buildRoutePath(
|
|
187
|
+
kindDirectory: "queries" | "mutations",
|
|
188
|
+
relativeFilePath: string,
|
|
189
|
+
exportName: string,
|
|
190
|
+
): string {
|
|
191
|
+
const normalizedRelativePath = relativeFilePath.replace(/\\/g, "/");
|
|
192
|
+
const relativeWithoutExtension = withoutExtension(normalizedRelativePath);
|
|
193
|
+
const segments = relativeWithoutExtension.split("/").filter(Boolean);
|
|
194
|
+
const routeSegments = [kindDirectory, ...segments, exportName]
|
|
195
|
+
.filter(Boolean)
|
|
196
|
+
.map((segment) => segment.trim())
|
|
197
|
+
.filter((segment) => segment.length > 0);
|
|
198
|
+
|
|
199
|
+
return `/${routeSegments.join("/")}`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function relativePathForKind(
|
|
203
|
+
relativeToScanDir: string,
|
|
204
|
+
kindDirectory: "queries" | "mutations" | "schedulers" | "crons",
|
|
205
|
+
): string {
|
|
206
|
+
const normalized = relativeToScanDir.replace(/\\/g, "/");
|
|
207
|
+
const marker = `${kindDirectory}/`;
|
|
208
|
+
const markerIndex = normalized.indexOf(marker);
|
|
209
|
+
|
|
210
|
+
if (markerIndex >= 0) {
|
|
211
|
+
return normalized.slice(markerIndex + marker.length);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (normalized === kindDirectory) {
|
|
215
|
+
return "index.ts";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return normalized;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function buildTaskName(relativeFilePath: string, exportName: string): string {
|
|
222
|
+
const normalizedRelativePath = relativeFilePath.replace(/\\/g, "/");
|
|
223
|
+
const relativeWithoutExtension = withoutExtension(normalizedRelativePath);
|
|
224
|
+
const segments = relativeWithoutExtension.split("/").filter(Boolean);
|
|
225
|
+
const fileName = segments[segments.length - 1] ?? "index";
|
|
226
|
+
const dirName = segments.length > 1 ? segments[segments.length - 2] : "root";
|
|
227
|
+
|
|
228
|
+
return [dirName, fileName, exportName]
|
|
229
|
+
.map((segment) => segment.trim())
|
|
230
|
+
.filter((segment) => segment.length > 0)
|
|
231
|
+
.join("/");
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
export async function discoverHandlerOperations(
|
|
235
|
+
loadedConfig: LoadedAppflareConfig,
|
|
236
|
+
): Promise<DiscoveredHandlerOperation[]> {
|
|
237
|
+
const operations: DiscoveredHandlerOperation[] = [];
|
|
238
|
+
|
|
239
|
+
const files = await discoverFiles(loadedConfig.scanDirAbs).catch(() => []);
|
|
240
|
+
for (const filePath of files) {
|
|
241
|
+
const sourceFile = Bun.file(filePath);
|
|
242
|
+
if (!(await sourceFile.exists())) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const source = await sourceFile.text();
|
|
247
|
+
const relativeToScanDir = relative(loadedConfig.scanDirAbs, filePath);
|
|
248
|
+
const discoveredExports = discoverExports(source, filePath);
|
|
249
|
+
|
|
250
|
+
const discoveredKinds: Array<{
|
|
251
|
+
kind: HandlerKind;
|
|
252
|
+
kindDirectory: "queries" | "mutations" | "schedulers" | "crons";
|
|
253
|
+
exports: DiscoveredExport[];
|
|
254
|
+
}> = [
|
|
255
|
+
{
|
|
256
|
+
kind: "query",
|
|
257
|
+
kindDirectory: "queries",
|
|
258
|
+
exports: discoveredExports.filter((entry) => entry.kind === "query"),
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
kind: "mutation",
|
|
262
|
+
kindDirectory: "mutations",
|
|
263
|
+
exports: discoveredExports.filter((entry) => entry.kind === "mutation"),
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
kind: "scheduler",
|
|
267
|
+
kindDirectory: "schedulers",
|
|
268
|
+
exports: discoveredExports.filter(
|
|
269
|
+
(entry) => entry.kind === "scheduler",
|
|
270
|
+
),
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
kind: "cron",
|
|
274
|
+
kindDirectory: "crons",
|
|
275
|
+
exports: discoveredExports.filter((entry) => entry.kind === "cron"),
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
kind: "storage",
|
|
279
|
+
kindDirectory: "queries",
|
|
280
|
+
exports: discoveredExports.filter((entry) => entry.kind === "storage"),
|
|
281
|
+
},
|
|
282
|
+
];
|
|
283
|
+
|
|
284
|
+
for (const discovered of discoveredKinds) {
|
|
285
|
+
if (discovered.exports.length === 0) {
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const relativeForKind = relativePathForKind(
|
|
290
|
+
relativeToScanDir,
|
|
291
|
+
discovered.kindDirectory,
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
for (const discoveredExport of discovered.exports) {
|
|
295
|
+
const handlerName =
|
|
296
|
+
discovered.kind === "query" || discovered.kind === "mutation"
|
|
297
|
+
? buildTaskName(relativeForKind, discoveredExport.exportName)
|
|
298
|
+
: undefined;
|
|
299
|
+
const taskName =
|
|
300
|
+
discovered.kind === "scheduler" || discovered.kind === "cron"
|
|
301
|
+
? buildTaskName(relativeForKind, discoveredExport.exportName)
|
|
302
|
+
: undefined;
|
|
303
|
+
const clientSegments =
|
|
304
|
+
discovered.kind === "query" || discovered.kind === "mutation"
|
|
305
|
+
? [
|
|
306
|
+
...withoutExtension(relativeForKind).split("/").filter(Boolean),
|
|
307
|
+
discoveredExport.exportName,
|
|
308
|
+
]
|
|
309
|
+
: undefined;
|
|
310
|
+
const routePath =
|
|
311
|
+
discovered.kind === "query"
|
|
312
|
+
? buildRoutePath(
|
|
313
|
+
"queries",
|
|
314
|
+
relativeForKind,
|
|
315
|
+
discoveredExport.exportName,
|
|
316
|
+
)
|
|
317
|
+
: discovered.kind === "mutation"
|
|
318
|
+
? buildRoutePath(
|
|
319
|
+
"mutations",
|
|
320
|
+
relativeForKind,
|
|
321
|
+
discoveredExport.exportName,
|
|
322
|
+
)
|
|
323
|
+
: discovered.kind === "storage"
|
|
324
|
+
? `/storage/managers/${discoveredExport.exportName}`
|
|
325
|
+
: `/${discovered.kindDirectory}/${taskName}`;
|
|
326
|
+
|
|
327
|
+
operations.push({
|
|
328
|
+
kind: discovered.kind,
|
|
329
|
+
exportName: discoveredExport.exportName,
|
|
330
|
+
filePath,
|
|
331
|
+
importPath: ensureRelativeImportPath(
|
|
332
|
+
loadedConfig.outDirAbs,
|
|
333
|
+
filePath,
|
|
334
|
+
),
|
|
335
|
+
clientImportPath: ensureRelativeImportPath(
|
|
336
|
+
resolve(loadedConfig.outDirAbs, "client"),
|
|
337
|
+
filePath,
|
|
338
|
+
),
|
|
339
|
+
routePath,
|
|
340
|
+
handlerName,
|
|
341
|
+
clientSegments,
|
|
342
|
+
taskName,
|
|
343
|
+
cronTriggers: discoveredExport.cronTriggers,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
operations.sort((a, b) => a.routePath.localeCompare(b.routePath));
|
|
350
|
+
|
|
351
|
+
const seen = new Map<string, string>();
|
|
352
|
+
for (const operation of operations) {
|
|
353
|
+
const uniqueKey = operation.taskName
|
|
354
|
+
? `task:${operation.taskName}`
|
|
355
|
+
: `route:${operation.routePath}`;
|
|
356
|
+
const previous = seen.get(uniqueKey);
|
|
357
|
+
if (previous) {
|
|
358
|
+
throw new Error(
|
|
359
|
+
`Duplicate handler operation discovered: ${operation.taskName ?? operation.routePath} (${previous} and ${operation.filePath}#${operation.exportName}).`,
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
seen.set(uniqueKey, `${operation.filePath}#${operation.exportName}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return operations;
|
|
366
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { JsonObject, JsonValue } from "../types";
|
|
2
|
+
|
|
3
|
+
function isObject(value: JsonValue | undefined): value is JsonObject {
|
|
4
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function deepMergeJson(
|
|
8
|
+
base: JsonObject,
|
|
9
|
+
override: JsonObject,
|
|
10
|
+
): JsonObject {
|
|
11
|
+
const merged: JsonObject = { ...base };
|
|
12
|
+
|
|
13
|
+
for (const [key, overrideValue] of Object.entries(override)) {
|
|
14
|
+
const currentValue = merged[key];
|
|
15
|
+
if (isObject(currentValue) && isObject(overrideValue)) {
|
|
16
|
+
merged[key] = deepMergeJson(currentValue, overrideValue);
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
merged[key] = overrideValue;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return merged;
|
|
24
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { relative } from "node:path";
|
|
2
|
+
|
|
3
|
+
function toPosixPath(value: string): string {
|
|
4
|
+
return value.replaceAll("\\", "/");
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function ensureRelativeImportPath(
|
|
8
|
+
fromDir: string,
|
|
9
|
+
toFile: string,
|
|
10
|
+
): string {
|
|
11
|
+
const relativePath = toPosixPath(relative(fromDir, toFile)).replace(
|
|
12
|
+
/\.tsx?$/,
|
|
13
|
+
"",
|
|
14
|
+
);
|
|
15
|
+
if (relativePath.startsWith(".")) {
|
|
16
|
+
return relativePath;
|
|
17
|
+
}
|
|
18
|
+
return `./${relativePath}`;
|
|
19
|
+
}
|