appflare 0.0.28 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/commands/index.ts +140 -0
- package/cli/generate.ts +149 -0
- package/cli/index.ts +56 -447
- package/cli/load-config.ts +182 -0
- package/cli/schema-compiler.ts +657 -0
- package/cli/templates/auth/README.md +156 -0
- package/cli/templates/auth/config.ts +61 -0
- package/cli/templates/auth/route-config.ts +18 -0
- package/cli/templates/auth/route-handler.ts +18 -0
- package/cli/templates/auth/route-request-utils.ts +55 -0
- package/cli/templates/auth/route.ts +14 -0
- package/cli/templates/core/README.md +266 -0
- package/cli/templates/core/app-creation.ts +19 -0
- package/cli/templates/core/client/appflare.ts +37 -0
- package/cli/templates/core/client/index.ts +6 -0
- package/cli/templates/core/client/storage.ts +100 -0
- package/cli/templates/core/client/types.ts +54 -0
- package/cli/templates/core/client-modules/appflare.ts +112 -0
- package/cli/templates/core/client-modules/handlers/index.ts +740 -0
- package/cli/templates/core/client-modules/handlers.ts +1 -0
- package/cli/templates/core/client-modules/index.ts +7 -0
- package/cli/templates/core/client-modules/storage.ts +180 -0
- package/cli/templates/core/client-modules/types.ts +145 -0
- package/cli/templates/core/client.ts +39 -0
- package/cli/templates/core/drizzle.ts +15 -0
- package/cli/templates/core/export.ts +14 -0
- package/cli/templates/core/handlers-route.ts +23 -0
- package/cli/templates/core/handlers.ts +1 -0
- package/cli/templates/core/imports.ts +8 -0
- package/cli/templates/core/server.ts +38 -0
- package/cli/templates/core/types.ts +6 -0
- package/cli/templates/core/wrangler.ts +109 -0
- package/cli/templates/handlers/README.md +265 -0
- package/cli/templates/handlers/auth.ts +36 -0
- package/cli/templates/handlers/execution.ts +39 -0
- package/cli/templates/handlers/generators/context/context-creation.ts +80 -0
- package/cli/templates/handlers/generators/context/error-helpers.ts +11 -0
- package/cli/templates/handlers/generators/context/scheduler.ts +24 -0
- package/cli/templates/handlers/generators/context/storage-api.ts +112 -0
- package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -0
- package/cli/templates/handlers/generators/context/types.ts +18 -0
- package/cli/templates/handlers/generators/context.ts +43 -0
- package/cli/templates/handlers/generators/execution.ts +15 -0
- package/cli/templates/handlers/generators/handlers.ts +13 -0
- package/cli/templates/handlers/index.ts +43 -0
- package/cli/templates/handlers/operations.ts +116 -0
- package/cli/templates/handlers/registration.ts +1114 -0
- package/cli/templates/handlers/types.ts +960 -0
- package/cli/templates/handlers/utils.ts +48 -0
- package/cli/types.ts +108 -0
- package/cli/utils/handler-discovery.ts +366 -0
- package/cli/utils/json-utils.ts +24 -0
- package/cli/utils/path-utils.ts +19 -0
- package/cli/utils/schema-discovery.ts +390 -0
- package/index.ts +27 -4
- package/package.json +23 -20
- package/react/index.ts +5 -3
- package/react/use-infinite-query.ts +190 -0
- package/react/use-mutation.ts +54 -0
- package/react/use-query.ts +158 -0
- package/schema.ts +262 -0
- package/tsconfig.json +2 -4
- package/cli/README.md +0 -108
- package/cli/core/build.ts +0 -187
- package/cli/core/config.ts +0 -92
- package/cli/core/discover-handlers.ts +0 -143
- package/cli/core/handlers.ts +0 -7
- package/cli/core/index.ts +0 -205
- package/cli/generators/generate-api-client/client.ts +0 -163
- package/cli/generators/generate-api-client/extract-configuration.ts +0 -121
- package/cli/generators/generate-api-client/index.ts +0 -973
- package/cli/generators/generate-api-client/types.ts +0 -164
- package/cli/generators/generate-api-client/utils.ts +0 -22
- package/cli/generators/generate-api-client.ts +0 -1
- package/cli/generators/generate-cloudflare-worker/helpers.ts +0 -24
- package/cli/generators/generate-cloudflare-worker/index.ts +0 -2
- package/cli/generators/generate-cloudflare-worker/worker.ts +0 -148
- package/cli/generators/generate-cloudflare-worker/wrangler.ts +0 -108
- package/cli/generators/generate-cloudflare-worker.ts +0 -4
- package/cli/generators/generate-cron-handlers/cron-handlers-block.ts +0 -2
- package/cli/generators/generate-cron-handlers/handler-entries.ts +0 -29
- package/cli/generators/generate-cron-handlers/index.ts +0 -61
- package/cli/generators/generate-cron-handlers/runtime-block.ts +0 -49
- package/cli/generators/generate-cron-handlers/type-helpers-block.ts +0 -60
- package/cli/generators/generate-db-handlers/index.ts +0 -33
- package/cli/generators/generate-db-handlers/prepare.ts +0 -24
- package/cli/generators/generate-db-handlers/templates.ts +0 -189
- package/cli/generators/generate-db-handlers.ts +0 -1
- package/cli/generators/generate-hono-server/auth.ts +0 -97
- package/cli/generators/generate-hono-server/imports.ts +0 -55
- package/cli/generators/generate-hono-server/index.ts +0 -52
- package/cli/generators/generate-hono-server/routes.ts +0 -115
- package/cli/generators/generate-hono-server/template.ts +0 -371
- package/cli/generators/generate-hono-server.ts +0 -1
- package/cli/generators/generate-scheduler-handlers/constants.ts +0 -8
- package/cli/generators/generate-scheduler-handlers/handler-entries.ts +0 -22
- package/cli/generators/generate-scheduler-handlers/index.ts +0 -51
- package/cli/generators/generate-scheduler-handlers/runtime-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers/scheduler-handlers-block.ts +0 -2
- package/cli/generators/generate-scheduler-handlers/type-helpers-block.ts +0 -68
- package/cli/generators/generate-scheduler-handlers.ts +0 -1
- package/cli/generators/generate-websocket-durable-object/auth.ts +0 -30
- package/cli/generators/generate-websocket-durable-object/imports.ts +0 -55
- package/cli/generators/generate-websocket-durable-object/index.ts +0 -41
- package/cli/generators/generate-websocket-durable-object/query-handlers.ts +0 -18
- package/cli/generators/generate-websocket-durable-object/template.ts +0 -714
- package/cli/generators/generate-websocket-durable-object.ts +0 -1
- package/cli/schema/schema-static-types.ts +0 -702
- package/cli/schema/schema.ts +0 -151
- package/cli/utils/tsc.ts +0 -54
- package/cli/utils/utils.ts +0 -190
- package/cli/utils/zod-utils.ts +0 -121
- package/lib/README.md +0 -50
- package/lib/db.ts +0 -19
- package/lib/location.ts +0 -110
- package/lib/values.ts +0 -27
- package/react/README.md +0 -67
- package/react/hooks/useMutation.ts +0 -89
- package/react/hooks/usePaginatedQuery.ts +0 -213
- package/react/hooks/useQuery.ts +0 -106
- package/react/shared/queryShared.ts +0 -174
- package/server/README.md +0 -218
- package/server/auth.ts +0 -107
- package/server/database/builders.ts +0 -83
- package/server/database/context.ts +0 -327
- package/server/database/populate.ts +0 -234
- package/server/database/query-builder.ts +0 -161
- package/server/database/query-utils.ts +0 -25
- package/server/db.ts +0 -2
- package/server/storage/auth.ts +0 -16
- package/server/storage/bucket.ts +0 -22
- package/server/storage/context.ts +0 -34
- package/server/storage/index.ts +0 -38
- package/server/storage/operations.ts +0 -149
- package/server/storage/route-handler.ts +0 -60
- package/server/storage/types.ts +0 -55
- package/server/storage/utils.ts +0 -47
- package/server/storage.ts +0 -6
- package/server/types/schema-refs.ts +0 -66
- package/server/types/types.ts +0 -633
- package/server/utils/id-utils.ts +0 -230
package/cli/index.ts
CHANGED
|
@@ -1,468 +1,77 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
import chokidar, { FSWatcher } from "chokidar";
|
|
4
2
|
import { Command } from "commander";
|
|
5
|
-
import {
|
|
6
|
-
import os from "node:os";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { pathToFileURL } from "node:url";
|
|
9
|
-
import {
|
|
10
|
-
discoverHandlers,
|
|
11
|
-
generateApiClient,
|
|
12
|
-
generateDbHandlers,
|
|
13
|
-
generateHonoServer,
|
|
14
|
-
generateWebsocketDurableObject,
|
|
15
|
-
generateSchedulerHandlers,
|
|
16
|
-
generateCronHandlers,
|
|
17
|
-
} from "./core/handlers";
|
|
18
|
-
import {
|
|
19
|
-
generateCloudflareWorkerIndex,
|
|
20
|
-
generateWranglerJson,
|
|
21
|
-
} from "./generators/generate-cloudflare-worker";
|
|
22
|
-
import { generateSchemaTypes, getSchemaTableNames } from "./schema/schema";
|
|
23
|
-
import { runTscEmit, writeEmitTsconfig } from "./utils/tsc";
|
|
24
|
-
import {
|
|
25
|
-
AppflareConfig,
|
|
26
|
-
assertDirExists,
|
|
27
|
-
assertFileExists,
|
|
28
|
-
toImportPathFromGeneratedSrc,
|
|
29
|
-
} from "./utils/utils";
|
|
30
|
-
|
|
31
|
-
type WatchConfig = {
|
|
32
|
-
targets: string[];
|
|
33
|
-
ignored: string[];
|
|
34
|
-
};
|
|
3
|
+
import { runBuild, runDev, runMigrate } from "./commands/index";
|
|
35
4
|
|
|
36
5
|
const program = new Command();
|
|
37
6
|
|
|
38
|
-
program
|
|
7
|
+
program
|
|
8
|
+
.name("appflare")
|
|
9
|
+
.description(
|
|
10
|
+
"Appflare compiler/bundler for Cloudflare-native backends and SDK generation",
|
|
11
|
+
)
|
|
12
|
+
.version("0.0.28");
|
|
39
13
|
|
|
40
14
|
program
|
|
41
15
|
.command("build")
|
|
42
16
|
.description(
|
|
43
|
-
"Generate
|
|
17
|
+
"Generate server.ts, client.ts, auth.config.ts, drizzle.config.ts, and wrangler.json artifacts",
|
|
44
18
|
)
|
|
45
19
|
.option(
|
|
46
20
|
"-c, --config <path>",
|
|
47
21
|
"Path to appflare.config.ts",
|
|
48
22
|
"appflare.config.ts",
|
|
49
23
|
)
|
|
50
|
-
.
|
|
51
|
-
|
|
52
|
-
.action(
|
|
53
|
-
async (options: { config: string; emit?: boolean; watch?: boolean }) => {
|
|
54
|
-
try {
|
|
55
|
-
const configPath = path.resolve(process.cwd(), options.config);
|
|
56
|
-
|
|
57
|
-
if (options.watch) {
|
|
58
|
-
await watchAndBuild({
|
|
59
|
-
configPathAbs: configPath,
|
|
60
|
-
emit: Boolean(options.emit),
|
|
61
|
-
});
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const { config, configDirAbs } = await loadConfig(configPath);
|
|
66
|
-
await buildFromConfig({
|
|
67
|
-
config,
|
|
68
|
-
configDirAbs,
|
|
69
|
-
configPathAbs: configPath,
|
|
70
|
-
emit: Boolean(options.emit),
|
|
71
|
-
});
|
|
72
|
-
} catch (err) {
|
|
73
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
74
|
-
console.error(message);
|
|
75
|
-
process.exitCode = 1;
|
|
76
|
-
}
|
|
77
|
-
},
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
void main();
|
|
81
|
-
|
|
82
|
-
async function main(): Promise<void> {
|
|
83
|
-
await program.parseAsync(process.argv);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Regex that matches ES import lines pulling in React Native / Expo native
|
|
88
|
-
* modules (e.g. `import * as SecureStore from "expo-secure-store"`).
|
|
89
|
-
* These cannot be transpiled by Bun and are only needed at client runtime.
|
|
90
|
-
*/
|
|
91
|
-
const NATIVE_IMPORT_RE =
|
|
92
|
-
/^import\s+(?:(?:\*\s+as\s+(\w+))|(?:\{[^}]*\})|(?:(\w+)(?:\s*,\s*\{[^}]*\})?))?\s*from\s*["'](expo-[^"']+)["'];?\s*$/gm;
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Strip native-module imports from a config source and replace them with
|
|
96
|
-
* harmless stub declarations so the CLI can evaluate the config object
|
|
97
|
-
* without triggering Bun transpilation errors on native code.
|
|
98
|
-
*/
|
|
99
|
-
function sanitizeConfigSource(source: string): string {
|
|
100
|
-
const stubs: string[] = [];
|
|
101
|
-
const sanitized = source.replace(
|
|
102
|
-
NATIVE_IMPORT_RE,
|
|
103
|
-
(_match, starAs, defaultImport, _mod) => {
|
|
104
|
-
const name = starAs || defaultImport;
|
|
105
|
-
if (name) {
|
|
106
|
-
stubs.push(`const ${name} = {} as any;`);
|
|
107
|
-
}
|
|
108
|
-
return ""; // remove the original import line
|
|
109
|
-
},
|
|
110
|
-
);
|
|
111
|
-
return stubs.length > 0 ? stubs.join("\n") + "\n" + sanitized : sanitized;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
async function loadConfig(
|
|
115
|
-
configPathAbs: string,
|
|
116
|
-
): Promise<{ config: AppflareConfig; configDirAbs: string }> {
|
|
117
|
-
await assertFileExists(configPathAbs, `Config not found: ${configPathAbs}`);
|
|
118
|
-
const configDirAbs = path.dirname(configPathAbs);
|
|
119
|
-
|
|
120
|
-
// Read the config source and strip native-module imports (e.g. expo-secure-store)
|
|
121
|
-
// that Bun cannot transpile. Write the sanitized source to a temp file and import that.
|
|
122
|
-
const raw = await fs.readFile(configPathAbs, "utf-8");
|
|
123
|
-
const sanitized = sanitizeConfigSource(raw);
|
|
124
|
-
|
|
125
|
-
let mod: Record<string, unknown>;
|
|
126
|
-
if (sanitized !== raw) {
|
|
127
|
-
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "appflare-cfg-"));
|
|
128
|
-
const tmpFile = path.join(tmpDir, path.basename(configPathAbs));
|
|
129
|
-
await fs.writeFile(tmpFile, sanitized);
|
|
130
|
-
try {
|
|
131
|
-
mod = await import(pathToFileURL(tmpFile).href);
|
|
132
|
-
} finally {
|
|
133
|
-
await fs.rm(tmpDir, { recursive: true, force: true }).catch(() => {});
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
mod = await import(pathToFileURL(configPathAbs).href);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const config = (mod?.default ?? mod) as Partial<AppflareConfig>;
|
|
140
|
-
if (!config || typeof config !== "object") {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`Invalid config export in ${configPathAbs} (expected default export object)`,
|
|
143
|
-
);
|
|
144
|
-
}
|
|
145
|
-
if (typeof config.dir !== "string" || !config.dir) {
|
|
146
|
-
throw new Error(`Invalid config.dir in ${configPathAbs}`);
|
|
147
|
-
}
|
|
148
|
-
if (typeof config.schema !== "string" || !config.schema) {
|
|
149
|
-
throw new Error(`Invalid config.schema in ${configPathAbs}`);
|
|
150
|
-
}
|
|
151
|
-
if (typeof config.outDir !== "string" || !config.outDir) {
|
|
152
|
-
throw new Error(`Invalid config.outDir in ${configPathAbs}`);
|
|
153
|
-
}
|
|
154
|
-
if (
|
|
155
|
-
config.wranglerOutPath !== undefined &&
|
|
156
|
-
(typeof config.wranglerOutPath !== "string" || !config.wranglerOutPath)
|
|
157
|
-
) {
|
|
158
|
-
throw new Error(`Invalid config.wranglerOutPath in ${configPathAbs}`);
|
|
159
|
-
}
|
|
160
|
-
if (
|
|
161
|
-
config.wranglerMain !== undefined &&
|
|
162
|
-
(typeof config.wranglerMain !== "string" || !config.wranglerMain)
|
|
163
|
-
) {
|
|
164
|
-
throw new Error(`Invalid config.wranglerMain in ${configPathAbs}`);
|
|
165
|
-
}
|
|
166
|
-
return { config: config as AppflareConfig, configDirAbs };
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
async function buildFromConfig(params: {
|
|
170
|
-
config: AppflareConfig;
|
|
171
|
-
configDirAbs: string;
|
|
172
|
-
configPathAbs: string;
|
|
173
|
-
emit: boolean;
|
|
174
|
-
}): Promise<void> {
|
|
175
|
-
const { config, configDirAbs, emit, configPathAbs } = params;
|
|
176
|
-
|
|
177
|
-
const projectDirAbs = path.resolve(configDirAbs, config.dir);
|
|
178
|
-
const schemaPathAbs = path.resolve(configDirAbs, config.schema);
|
|
179
|
-
const outDirAbs = path.resolve(configDirAbs, config.outDir);
|
|
180
|
-
|
|
181
|
-
await assertDirExists(
|
|
182
|
-
projectDirAbs,
|
|
183
|
-
`Project dir not found: ${projectDirAbs}`,
|
|
184
|
-
);
|
|
185
|
-
await assertFileExists(schemaPathAbs, `Schema not found: ${schemaPathAbs}`);
|
|
186
|
-
|
|
187
|
-
await fs.mkdir(path.join(outDirAbs, "src"), { recursive: true });
|
|
188
|
-
await fs.mkdir(path.join(outDirAbs, "server"), { recursive: true });
|
|
189
|
-
|
|
190
|
-
// Re-export the user schema inside the generated output so downstream code can import it from the build directory.
|
|
191
|
-
const schemaImportPathForGeneratedSrc = toImportPathFromGeneratedSrc(
|
|
192
|
-
outDirAbs,
|
|
193
|
-
schemaPathAbs,
|
|
194
|
-
);
|
|
195
|
-
const schemaReexport = `import schema from ${JSON.stringify(schemaImportPathForGeneratedSrc)};
|
|
196
|
-
export type AppflareGeneratedSchema = typeof schema;
|
|
197
|
-
export default schema;
|
|
198
|
-
`;
|
|
199
|
-
await fs.writeFile(path.join(outDirAbs, "src", "schema.ts"), schemaReexport);
|
|
200
|
-
|
|
201
|
-
const schemaTypesTs = await generateSchemaTypes({
|
|
202
|
-
schemaPathAbs,
|
|
203
|
-
configPathAbs,
|
|
204
|
-
outDirAbs,
|
|
205
|
-
});
|
|
206
|
-
await fs.writeFile(
|
|
207
|
-
path.join(outDirAbs, "src", "schema-types.ts"),
|
|
208
|
-
schemaTypesTs,
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
// (Re)generate built-in DB handlers based on the schema tables.
|
|
212
|
-
const schemaTableNames = await getSchemaTableNames(schemaPathAbs);
|
|
213
|
-
await generateDbHandlers({ outDirAbs, tableNames: schemaTableNames });
|
|
214
|
-
|
|
215
|
-
const handlers = await discoverHandlers({
|
|
216
|
-
projectDirAbs,
|
|
217
|
-
schemaPathAbs,
|
|
218
|
-
outDirAbs,
|
|
219
|
-
configPathAbs,
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
const { apiTs, clientConfigTs } = generateApiClient({
|
|
223
|
-
handlers,
|
|
224
|
-
outDirAbs,
|
|
225
|
-
authBasePath:
|
|
226
|
-
config.auth && config.auth.enabled === false
|
|
227
|
-
? undefined
|
|
228
|
-
: (config.auth?.basePath ?? "/auth"),
|
|
229
|
-
authEnabled: config.auth?.enabled !== false,
|
|
230
|
-
configPathAbs,
|
|
231
|
-
});
|
|
232
|
-
await fs.writeFile(path.join(outDirAbs, "src", "api.ts"), apiTs);
|
|
233
|
-
if (clientConfigTs) {
|
|
234
|
-
await fs.writeFile(
|
|
235
|
-
path.join(outDirAbs, "src", "client.config.ts"),
|
|
236
|
-
clientConfigTs,
|
|
237
|
-
);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
const serverTs = generateHonoServer({
|
|
241
|
-
handlers,
|
|
242
|
-
outDirAbs,
|
|
243
|
-
schemaPathAbs,
|
|
244
|
-
configPathAbs,
|
|
245
|
-
config,
|
|
246
|
-
});
|
|
247
|
-
await fs.writeFile(path.join(outDirAbs, "server", "server.ts"), serverTs);
|
|
248
|
-
|
|
249
|
-
const websocketDoTs = generateWebsocketDurableObject({
|
|
250
|
-
handlers,
|
|
251
|
-
outDirAbs,
|
|
252
|
-
schemaPathAbs,
|
|
253
|
-
configPathAbs,
|
|
254
|
-
config,
|
|
255
|
-
});
|
|
256
|
-
await fs.writeFile(
|
|
257
|
-
path.join(outDirAbs, "server", "websocket-hibernation-server.ts"),
|
|
258
|
-
websocketDoTs,
|
|
259
|
-
);
|
|
260
|
-
|
|
261
|
-
const schedulerTs = generateSchedulerHandlers({
|
|
262
|
-
handlers,
|
|
263
|
-
outDirAbs,
|
|
264
|
-
schemaPathAbs,
|
|
265
|
-
configPathAbs,
|
|
266
|
-
});
|
|
267
|
-
await fs.writeFile(
|
|
268
|
-
path.join(outDirAbs, "server", "scheduler.ts"),
|
|
269
|
-
schedulerTs,
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
const cronHandlersPresent = handlers.some(
|
|
273
|
-
(handler) => handler.kind === "cron",
|
|
274
|
-
);
|
|
275
|
-
const { code: cronTs, cronTriggers } = generateCronHandlers({
|
|
276
|
-
handlers,
|
|
277
|
-
outDirAbs,
|
|
278
|
-
schemaPathAbs,
|
|
279
|
-
configPathAbs,
|
|
280
|
-
});
|
|
281
|
-
await fs.writeFile(path.join(outDirAbs, "server", "cron.ts"), cronTs);
|
|
282
|
-
|
|
283
|
-
const allowedOrigins = normalizeAllowedOrigins(
|
|
284
|
-
process.env.APPFLARE_ALLOWED_ORIGINS ??
|
|
285
|
-
config.corsOrigin ??
|
|
286
|
-
"http://localhost:3000",
|
|
287
|
-
);
|
|
288
|
-
const workerIndexTs = generateCloudflareWorkerIndex({
|
|
289
|
-
allowedOrigins,
|
|
290
|
-
hasCronHandlers: cronHandlersPresent,
|
|
24
|
+
.action(async (options: { config: string }) => {
|
|
25
|
+
await runBuild(options.config);
|
|
291
26
|
});
|
|
292
|
-
await fs.writeFile(path.join(outDirAbs, "server", "index.ts"), workerIndexTs);
|
|
293
27
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
28
|
+
program
|
|
29
|
+
.command("dev")
|
|
30
|
+
.description("Run generator in development mode")
|
|
31
|
+
.option(
|
|
32
|
+
"-c, --config <path>",
|
|
33
|
+
"Path to appflare.config.ts",
|
|
34
|
+
"appflare.config.ts",
|
|
35
|
+
)
|
|
36
|
+
.option("-w, --watch", "Watch scanDir and regenerate on changes", false)
|
|
37
|
+
.action(async (options: { config: string; watch: boolean }) => {
|
|
38
|
+
await runDev(options.config, options.watch);
|
|
299
39
|
});
|
|
300
|
-
const wranglerOutPath =
|
|
301
|
-
config.wranglerOutPath ?? path.join(config.outDir, "wrangler.json");
|
|
302
|
-
const wranglerOutAbs = path.resolve(configDirAbs, wranglerOutPath);
|
|
303
|
-
await fs.mkdir(path.dirname(wranglerOutAbs), { recursive: true });
|
|
304
|
-
await fs.writeFile(wranglerOutAbs, wranglerJson);
|
|
305
|
-
|
|
306
|
-
if (emit) {
|
|
307
|
-
// Remove previous emit output to avoid stale files lingering.
|
|
308
|
-
await fs.rm(path.join(outDirAbs, "dist"), { recursive: true, force: true });
|
|
309
40
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
ignored: normalized.ignored,
|
|
343
|
-
ignoreInitial: true,
|
|
344
|
-
persistent: true,
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
watcher.on("all", (_event, filePath) => {
|
|
348
|
-
const rel = path.relative(process.cwd(), filePath) || filePath;
|
|
349
|
-
console.log(`[appflare] change detected: ${rel}`);
|
|
350
|
-
scheduleBuild();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
lastWatchConfig = normalized;
|
|
354
|
-
console.log(
|
|
355
|
-
`[appflare] watching ${normalized.targets.length} path(s) (ignoring ${normalized.ignored.length})`,
|
|
356
|
-
);
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
const scheduleBuild = () => {
|
|
360
|
-
if (isBuilding) {
|
|
361
|
-
pendingBuild = true;
|
|
362
|
-
return;
|
|
363
|
-
}
|
|
364
|
-
void runBuild();
|
|
365
|
-
};
|
|
366
|
-
|
|
367
|
-
const runBuild = async () => {
|
|
368
|
-
isBuilding = true;
|
|
369
|
-
const startedAt = Date.now();
|
|
370
|
-
try {
|
|
371
|
-
const { config, configDirAbs } = await loadConfig(params.configPathAbs);
|
|
372
|
-
await applyWatchConfig(
|
|
373
|
-
computeWatchConfig({
|
|
374
|
-
config,
|
|
375
|
-
configDirAbs,
|
|
376
|
-
configPathAbs: params.configPathAbs,
|
|
377
|
-
}),
|
|
378
|
-
);
|
|
379
|
-
console.log("[appflare] build started");
|
|
380
|
-
await buildFromConfig({
|
|
381
|
-
config,
|
|
382
|
-
configDirAbs,
|
|
383
|
-
configPathAbs: params.configPathAbs,
|
|
384
|
-
emit: params.emit,
|
|
41
|
+
program
|
|
42
|
+
.command("migrate")
|
|
43
|
+
.description(
|
|
44
|
+
"Generate drizzle migration files from outDir/auth.schema.ts and apply them to the configured D1 database",
|
|
45
|
+
)
|
|
46
|
+
.option(
|
|
47
|
+
"-c, --config <path>",
|
|
48
|
+
"Path to appflare.config.ts",
|
|
49
|
+
"appflare.config.ts",
|
|
50
|
+
)
|
|
51
|
+
.option(
|
|
52
|
+
"--local",
|
|
53
|
+
"Execute commands/files against a local DB for use with wrangler dev",
|
|
54
|
+
false,
|
|
55
|
+
)
|
|
56
|
+
.option(
|
|
57
|
+
"--remote",
|
|
58
|
+
"Execute commands/files against a remote DB for use with wrangler dev --remote",
|
|
59
|
+
false,
|
|
60
|
+
)
|
|
61
|
+
.option("--preview", "Execute commands/files against a preview D1 DB", false)
|
|
62
|
+
.action(
|
|
63
|
+
async (options: {
|
|
64
|
+
config: string;
|
|
65
|
+
local: boolean;
|
|
66
|
+
remote: boolean;
|
|
67
|
+
preview: boolean;
|
|
68
|
+
}) => {
|
|
69
|
+
await runMigrate(options.config, {
|
|
70
|
+
local: options.local,
|
|
71
|
+
remote: options.remote,
|
|
72
|
+
preview: options.preview,
|
|
385
73
|
});
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
} catch (err) {
|
|
389
|
-
const message =
|
|
390
|
-
err instanceof Error ? (err.stack ?? err.message) : String(err);
|
|
391
|
-
console.error(`[appflare] build failed: ${message}`);
|
|
392
|
-
} finally {
|
|
393
|
-
isBuilding = false;
|
|
394
|
-
if (pendingBuild && !closed) {
|
|
395
|
-
pendingBuild = false;
|
|
396
|
-
scheduleBuild();
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
};
|
|
400
|
-
|
|
401
|
-
const handleExit = async () => {
|
|
402
|
-
closed = true;
|
|
403
|
-
await closeWatcher();
|
|
404
|
-
};
|
|
405
|
-
|
|
406
|
-
process.once("SIGINT", handleExit);
|
|
407
|
-
process.once("SIGTERM", handleExit);
|
|
408
|
-
|
|
409
|
-
await runBuild();
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
function computeWatchConfig(params: {
|
|
413
|
-
config: AppflareConfig;
|
|
414
|
-
configDirAbs: string;
|
|
415
|
-
configPathAbs: string;
|
|
416
|
-
}): WatchConfig {
|
|
417
|
-
const { config, configDirAbs, configPathAbs } = params;
|
|
418
|
-
const projectDirAbs = path.resolve(configDirAbs, config.dir);
|
|
419
|
-
const schemaPathAbs = path.resolve(configDirAbs, config.schema);
|
|
420
|
-
const outDirAbs = path.resolve(configDirAbs, config.outDir);
|
|
421
|
-
|
|
422
|
-
return {
|
|
423
|
-
targets: [projectDirAbs, schemaPathAbs, configPathAbs],
|
|
424
|
-
ignored: [
|
|
425
|
-
outDirAbs,
|
|
426
|
-
path.join(outDirAbs, "**"),
|
|
427
|
-
path.join(projectDirAbs, "node_modules/**"),
|
|
428
|
-
path.join(projectDirAbs, "dist/**"),
|
|
429
|
-
path.join(projectDirAbs, "build/**"),
|
|
430
|
-
"**/node_modules/**",
|
|
431
|
-
"**/.git/**",
|
|
432
|
-
"**/dist/**",
|
|
433
|
-
"**/build/**",
|
|
434
|
-
],
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
function normalizeWatchConfig(config: WatchConfig): WatchConfig {
|
|
439
|
-
const normalizeList = (list: string[]): string[] =>
|
|
440
|
-
[
|
|
441
|
-
...new Set(
|
|
442
|
-
list.map((item) => (hasGlob(item) ? item : path.resolve(item))),
|
|
443
|
-
),
|
|
444
|
-
].sort();
|
|
445
|
-
|
|
446
|
-
return {
|
|
447
|
-
targets: normalizeList(config.targets),
|
|
448
|
-
ignored: normalizeList(config.ignored),
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function watchConfigsEqual(a?: WatchConfig, b?: WatchConfig): boolean {
|
|
453
|
-
if (!a || !b) return false;
|
|
454
|
-
return arraysEqual(a.targets, b.targets) && arraysEqual(a.ignored, b.ignored);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
function arraysEqual(a: string[], b: string[]): boolean {
|
|
458
|
-
return a.length === b.length && a.every((value, index) => value === b[index]);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
function hasGlob(value: string): boolean {
|
|
462
|
-
return value.includes("*") || value.includes("?") || value.includes("[");
|
|
463
|
-
}
|
|
74
|
+
},
|
|
75
|
+
);
|
|
464
76
|
|
|
465
|
-
|
|
466
|
-
const list = Array.isArray(origins) ? origins : origins.split(",");
|
|
467
|
-
return list.map((origin) => origin.trim()).filter(Boolean);
|
|
468
|
-
}
|
|
77
|
+
await program.parseAsync(process.argv);
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { dirname, isAbsolute, resolve } from "node:path";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import type {
|
|
5
|
+
AppflareConfig,
|
|
6
|
+
LoadedAppflareConfig,
|
|
7
|
+
NormalizedAppflareConfig,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
const databaseSchema = z
|
|
11
|
+
.object({
|
|
12
|
+
binding: z.string().min(1),
|
|
13
|
+
databaseName: z.string().min(1),
|
|
14
|
+
databaseId: z.string().min(1),
|
|
15
|
+
previewDatabaseId: z.string().min(1).optional(),
|
|
16
|
+
migrationsDir: z.string().min(1).optional(),
|
|
17
|
+
})
|
|
18
|
+
.strict();
|
|
19
|
+
|
|
20
|
+
const kvSchema = z
|
|
21
|
+
.object({
|
|
22
|
+
binding: z.string().min(1),
|
|
23
|
+
id: z.string().min(1),
|
|
24
|
+
previewId: z.string().min(1).optional(),
|
|
25
|
+
})
|
|
26
|
+
.strict();
|
|
27
|
+
|
|
28
|
+
const r2Schema = z
|
|
29
|
+
.object({
|
|
30
|
+
binding: z.string().min(1),
|
|
31
|
+
bucketName: z.string().min(1),
|
|
32
|
+
previewBucketName: z.string().min(1).optional(),
|
|
33
|
+
jurisdiction: z.string().min(1).optional(),
|
|
34
|
+
})
|
|
35
|
+
.strict();
|
|
36
|
+
|
|
37
|
+
const schedulerConfigSchema = z
|
|
38
|
+
.object({
|
|
39
|
+
enabled: z.boolean().optional(),
|
|
40
|
+
binding: z.string().min(1).optional(),
|
|
41
|
+
queue: z.string().min(1).optional(),
|
|
42
|
+
})
|
|
43
|
+
.strict();
|
|
44
|
+
|
|
45
|
+
const realtimeConfigSchema = z
|
|
46
|
+
.object({
|
|
47
|
+
enabled: z.boolean().optional(),
|
|
48
|
+
binding: z.string().min(1).optional(),
|
|
49
|
+
className: z.string().min(1).optional(),
|
|
50
|
+
objectName: z.string().min(1).optional(),
|
|
51
|
+
subscribePath: z.string().min(1).optional(),
|
|
52
|
+
websocketPath: z.string().min(1).optional(),
|
|
53
|
+
protocol: z.string().min(1).optional(),
|
|
54
|
+
})
|
|
55
|
+
.strict();
|
|
56
|
+
|
|
57
|
+
const appflareConfigSchema = z
|
|
58
|
+
.object({
|
|
59
|
+
scanDir: z.string().min(1),
|
|
60
|
+
outDir: z.string().min(1),
|
|
61
|
+
wranglerOutDir: z.string().min(1).optional(),
|
|
62
|
+
wranglerOutPath: z.string().min(1).optional(),
|
|
63
|
+
schema: z.array(z.string()).min(1),
|
|
64
|
+
schemaDsl: z
|
|
65
|
+
.object({
|
|
66
|
+
entry: z.string().min(1),
|
|
67
|
+
exportName: z.string().min(1).optional(),
|
|
68
|
+
outFile: z.string().min(1).optional(),
|
|
69
|
+
typesOutFile: z.string().min(1).optional(),
|
|
70
|
+
zodOutFile: z.string().min(1).optional(),
|
|
71
|
+
namingStrategy: z.literal("camelToSnake").optional(),
|
|
72
|
+
})
|
|
73
|
+
.strict()
|
|
74
|
+
.optional(),
|
|
75
|
+
database: z.union([databaseSchema, z.array(databaseSchema).min(1)]),
|
|
76
|
+
kv: z.union([kvSchema, z.array(kvSchema)]).optional(),
|
|
77
|
+
r2: z.union([r2Schema, z.array(r2Schema)]).optional(),
|
|
78
|
+
auth: z
|
|
79
|
+
.object({
|
|
80
|
+
enabled: z.boolean(),
|
|
81
|
+
basePath: z.string().min(1),
|
|
82
|
+
options: z.custom<AppflareConfig["auth"]["options"]>((value) => {
|
|
83
|
+
return typeof value === "object" && value !== null;
|
|
84
|
+
}),
|
|
85
|
+
clientOptions: z.custom<AppflareConfig["auth"]["clientOptions"]>(
|
|
86
|
+
(value) => {
|
|
87
|
+
return typeof value === "object" && value !== null;
|
|
88
|
+
},
|
|
89
|
+
),
|
|
90
|
+
})
|
|
91
|
+
.strict(),
|
|
92
|
+
scheduler: schedulerConfigSchema.optional(),
|
|
93
|
+
realtime: realtimeConfigSchema.optional(),
|
|
94
|
+
wranglerOverrides: z.record(z.string(), z.unknown()).optional(),
|
|
95
|
+
})
|
|
96
|
+
.strict();
|
|
97
|
+
|
|
98
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
99
|
+
return typeof value === "object" && value !== null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readLegacySchedulerConfig(
|
|
103
|
+
input: AppflareConfig,
|
|
104
|
+
): Partial<NonNullable<AppflareConfig["scheduler"]>> {
|
|
105
|
+
const raw = isRecord(input.wranglerOverrides)
|
|
106
|
+
? input.wranglerOverrides.scheduler
|
|
107
|
+
: undefined;
|
|
108
|
+
const parsed = schedulerConfigSchema.safeParse(raw);
|
|
109
|
+
if (!parsed.success) {
|
|
110
|
+
return {};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return parsed.data;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function removeLegacySchedulerOverride(
|
|
117
|
+
overrides: AppflareConfig["wranglerOverrides"],
|
|
118
|
+
): AppflareConfig["wranglerOverrides"] {
|
|
119
|
+
if (!isRecord(overrides) || !("scheduler" in overrides)) {
|
|
120
|
+
return overrides;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { scheduler: _scheduler, ...rest } = overrides;
|
|
124
|
+
return rest;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function normalizeConfig(input: AppflareConfig): NormalizedAppflareConfig {
|
|
128
|
+
const legacyScheduler = readLegacySchedulerConfig(input);
|
|
129
|
+
const scheduler = {
|
|
130
|
+
...(legacyScheduler ?? {}),
|
|
131
|
+
...(input.scheduler ?? {}),
|
|
132
|
+
};
|
|
133
|
+
const realtime = input.realtime ?? {};
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
...input,
|
|
137
|
+
database: Array.isArray(input.database) ? input.database : [input.database],
|
|
138
|
+
kv: input.kv ? (Array.isArray(input.kv) ? input.kv : [input.kv]) : [],
|
|
139
|
+
r2: input.r2 ? (Array.isArray(input.r2) ? input.r2 : [input.r2]) : [],
|
|
140
|
+
scheduler: {
|
|
141
|
+
enabled: scheduler.enabled ?? true,
|
|
142
|
+
binding: scheduler.binding ?? "APPFLARE_SCHEDULER_QUEUE",
|
|
143
|
+
queue: scheduler.queue,
|
|
144
|
+
},
|
|
145
|
+
realtime: {
|
|
146
|
+
enabled: realtime.enabled ?? true,
|
|
147
|
+
binding: realtime.binding ?? "APPFLARE_REALTIME",
|
|
148
|
+
className: realtime.className ?? "AppflareRealtimeDurableObject",
|
|
149
|
+
objectName: realtime.objectName ?? "global",
|
|
150
|
+
subscribePath: realtime.subscribePath ?? "/realtime/subscribe",
|
|
151
|
+
websocketPath: realtime.websocketPath ?? "/realtime/ws",
|
|
152
|
+
protocol: realtime.protocol ?? "appflare.realtime.v1",
|
|
153
|
+
},
|
|
154
|
+
wranglerOverrides: removeLegacySchedulerOverride(input.wranglerOverrides),
|
|
155
|
+
wranglerOutDir:
|
|
156
|
+
input.wranglerOutDir ?? input.wranglerOutPath ?? input.outDir,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function loadConfig(
|
|
161
|
+
configPathArg?: string,
|
|
162
|
+
): Promise<LoadedAppflareConfig> {
|
|
163
|
+
const configPath = isAbsolute(configPathArg ?? "")
|
|
164
|
+
? (configPathArg as string)
|
|
165
|
+
: resolve(process.cwd(), configPathArg ?? "appflare.config.ts");
|
|
166
|
+
|
|
167
|
+
const configDir = dirname(configPath);
|
|
168
|
+
const moduleUrl = pathToFileURL(configPath).href;
|
|
169
|
+
const configModule = await import(moduleUrl);
|
|
170
|
+
const raw = configModule.default;
|
|
171
|
+
const parsed = appflareConfigSchema.parse(raw) as AppflareConfig;
|
|
172
|
+
const config = normalizeConfig(parsed);
|
|
173
|
+
|
|
174
|
+
return {
|
|
175
|
+
configPath,
|
|
176
|
+
configDir,
|
|
177
|
+
scanDirAbs: resolve(configDir, config.scanDir),
|
|
178
|
+
outDirAbs: resolve(configDir, config.outDir),
|
|
179
|
+
wranglerOutDirAbs: resolve(configDir, config.wranglerOutDir),
|
|
180
|
+
config,
|
|
181
|
+
};
|
|
182
|
+
}
|