appflare 0.2.48 โ†’ 0.2.49

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 (139) hide show
  1. package/Documentation.md +898 -898
  2. package/cli/commands/index.ts +247 -247
  3. package/cli/generate.ts +360 -360
  4. package/cli/index.ts +120 -120
  5. package/cli/load-config.ts +184 -184
  6. package/cli/schema-compiler.ts +1366 -1366
  7. package/cli/templates/auth/README.md +156 -156
  8. package/cli/templates/auth/config.ts +61 -61
  9. package/cli/templates/auth/route-config.ts +1 -1
  10. package/cli/templates/auth/route-handler.ts +1 -1
  11. package/cli/templates/auth/route-request-utils.ts +5 -5
  12. package/cli/templates/auth/route.config.ts +18 -18
  13. package/cli/templates/auth/route.handler.ts +18 -18
  14. package/cli/templates/auth/route.request-utils.ts +55 -55
  15. package/cli/templates/auth/route.ts +14 -14
  16. package/cli/templates/core/README.md +266 -266
  17. package/cli/templates/core/app-creation.ts +19 -19
  18. package/cli/templates/core/client/appflare.ts +112 -112
  19. package/cli/templates/core/client/handlers/index.ts +763 -763
  20. package/cli/templates/core/client/handlers.ts +1 -1
  21. package/cli/templates/core/client/index.ts +7 -7
  22. package/cli/templates/core/client/storage.ts +195 -195
  23. package/cli/templates/core/client/types.ts +187 -187
  24. package/cli/templates/core/client-modules/appflare.ts +1 -1
  25. package/cli/templates/core/client-modules/handlers.ts +1 -1
  26. package/cli/templates/core/client-modules/index.ts +1 -1
  27. package/cli/templates/core/client-modules/storage.ts +1 -1
  28. package/cli/templates/core/client-modules/types.ts +1 -1
  29. package/cli/templates/core/client.artifacts.ts +39 -39
  30. package/cli/templates/core/client.ts +4 -4
  31. package/cli/templates/core/drizzle.ts +15 -15
  32. package/cli/templates/core/export.ts +14 -14
  33. package/cli/templates/core/handlers.route.ts +24 -24
  34. package/cli/templates/core/handlers.ts +1 -1
  35. package/cli/templates/core/imports.ts +9 -9
  36. package/cli/templates/core/server.ts +38 -38
  37. package/cli/templates/core/types.ts +6 -6
  38. package/cli/templates/core/wrangler.ts +109 -109
  39. package/cli/templates/dashboard/builders/functions/index.ts +17 -17
  40. package/cli/templates/dashboard/builders/functions/render-page/header.ts +20 -20
  41. package/cli/templates/dashboard/builders/functions/render-page/index.ts +33 -33
  42. package/cli/templates/dashboard/builders/functions/render-page/request-panel.ts +271 -271
  43. package/cli/templates/dashboard/builders/functions/render-page/result-panel.ts +85 -85
  44. package/cli/templates/dashboard/builders/functions/render-page/scripts.ts +703 -703
  45. package/cli/templates/dashboard/builders/functions/tree-builder.ts +47 -47
  46. package/cli/templates/dashboard/builders/navigation.ts +155 -155
  47. package/cli/templates/dashboard/builders/storage/index.ts +13 -13
  48. package/cli/templates/dashboard/builders/storage/routes/create-directory-route.ts +29 -29
  49. package/cli/templates/dashboard/builders/storage/routes/delete-route.ts +18 -18
  50. package/cli/templates/dashboard/builders/storage/routes/download-route.ts +23 -23
  51. package/cli/templates/dashboard/builders/storage/routes/index.ts +22 -22
  52. package/cli/templates/dashboard/builders/storage/routes/list-route.ts +25 -25
  53. package/cli/templates/dashboard/builders/storage/routes/preview-route.ts +21 -21
  54. package/cli/templates/dashboard/builders/storage/routes/upload-route.ts +21 -21
  55. package/cli/templates/dashboard/builders/storage/runtime/helpers.ts +72 -72
  56. package/cli/templates/dashboard/builders/storage/runtime/storage-page.ts +130 -130
  57. package/cli/templates/dashboard/builders/table-routes/common/drawer-panel.ts +27 -27
  58. package/cli/templates/dashboard/builders/table-routes/common/pagination.ts +30 -30
  59. package/cli/templates/dashboard/builders/table-routes/common/search-bar.ts +23 -23
  60. package/cli/templates/dashboard/builders/table-routes/fragments.ts +257 -217
  61. package/cli/templates/dashboard/builders/table-routes/helpers.ts +45 -45
  62. package/cli/templates/dashboard/builders/table-routes/index.ts +8 -8
  63. package/cli/templates/dashboard/builders/table-routes/table/actions-cell.ts +71 -71
  64. package/cli/templates/dashboard/builders/table-routes/table/get-route.ts +291 -291
  65. package/cli/templates/dashboard/builders/table-routes/table/index.ts +80 -80
  66. package/cli/templates/dashboard/builders/table-routes/table/post-routes.ts +163 -163
  67. package/cli/templates/dashboard/builders/table-routes/table-route.ts +7 -7
  68. package/cli/templates/dashboard/builders/table-routes/users/get-route.ts +69 -69
  69. package/cli/templates/dashboard/builders/table-routes/users/html/modals.ts +57 -57
  70. package/cli/templates/dashboard/builders/table-routes/users/html/page.ts +27 -27
  71. package/cli/templates/dashboard/builders/table-routes/users/html/table.ts +128 -128
  72. package/cli/templates/dashboard/builders/table-routes/users/index.ts +32 -32
  73. package/cli/templates/dashboard/builders/table-routes/users/post-routes.ts +150 -150
  74. package/cli/templates/dashboard/builders/table-routes/users/redirect.ts +14 -14
  75. package/cli/templates/dashboard/builders/table-routes/users-route.ts +10 -10
  76. package/cli/templates/dashboard/components/dashboard-home.ts +23 -23
  77. package/cli/templates/dashboard/components/layout.ts +420 -420
  78. package/cli/templates/dashboard/components/login-page.ts +65 -65
  79. package/cli/templates/dashboard/index.ts +61 -61
  80. package/cli/templates/dashboard/types.ts +9 -9
  81. package/cli/templates/handlers/README.md +353 -353
  82. package/cli/templates/handlers/auth.ts +37 -37
  83. package/cli/templates/handlers/execution.ts +44 -42
  84. package/cli/templates/handlers/generators/context/context-creation.ts +101 -101
  85. package/cli/templates/handlers/generators/context/error-helpers.ts +11 -11
  86. package/cli/templates/handlers/generators/context/scheduler.ts +24 -24
  87. package/cli/templates/handlers/generators/context/storage-api.ts +82 -82
  88. package/cli/templates/handlers/generators/context/storage-helpers.ts +59 -59
  89. package/cli/templates/handlers/generators/context/types.ts +40 -40
  90. package/cli/templates/handlers/generators/context.ts +43 -43
  91. package/cli/templates/handlers/generators/execution.ts +15 -15
  92. package/cli/templates/handlers/generators/handlers.ts +14 -14
  93. package/cli/templates/handlers/generators/registration/modules/cron.ts +35 -35
  94. package/cli/templates/handlers/generators/registration/modules/realtime/auth.ts +75 -75
  95. package/cli/templates/handlers/generators/registration/modules/realtime/durable-object.ts +144 -144
  96. package/cli/templates/handlers/generators/registration/modules/realtime/index.ts +14 -14
  97. package/cli/templates/handlers/generators/registration/modules/realtime/publisher.ts +102 -102
  98. package/cli/templates/handlers/generators/registration/modules/realtime/routes.ts +164 -164
  99. package/cli/templates/handlers/generators/registration/modules/realtime/types.ts +30 -30
  100. package/cli/templates/handlers/generators/registration/modules/realtime/utils.ts +510 -510
  101. package/cli/templates/handlers/generators/registration/modules/scheduler.ts +65 -65
  102. package/cli/templates/handlers/generators/registration/modules/storage.ts +199 -199
  103. package/cli/templates/handlers/generators/registration/sections.ts +210 -210
  104. package/cli/templates/handlers/generators/types/context.ts +121 -121
  105. package/cli/templates/handlers/generators/types/core.ts +108 -108
  106. package/cli/templates/handlers/generators/types/operations.ts +135 -135
  107. package/cli/templates/handlers/generators/types/query-definitions/filter-and-where-types.ts +291 -291
  108. package/cli/templates/handlers/generators/types/query-definitions/query-api-types.ts +135 -135
  109. package/cli/templates/handlers/generators/types/query-definitions/query-helper-functions.ts +1382 -1382
  110. package/cli/templates/handlers/generators/types/query-definitions/schema-and-table-types.ts +278 -278
  111. package/cli/templates/handlers/generators/types/query-definitions.ts +13 -13
  112. package/cli/templates/handlers/generators/types/query-runtime/handled-error.ts +13 -13
  113. package/cli/templates/handlers/generators/types/query-runtime/runtime-aggregate-and-footer.ts +174 -174
  114. package/cli/templates/handlers/generators/types/query-runtime/runtime-read.ts +156 -156
  115. package/cli/templates/handlers/generators/types/query-runtime/runtime-setup.ts +45 -45
  116. package/cli/templates/handlers/generators/types/query-runtime/runtime-write.ts +958 -958
  117. package/cli/templates/handlers/generators/types/query-runtime.ts +15 -15
  118. package/cli/templates/handlers/index.ts +47 -47
  119. package/cli/templates/handlers/operations.ts +116 -116
  120. package/cli/templates/handlers/registration.ts +91 -91
  121. package/cli/templates/handlers/types.ts +17 -17
  122. package/cli/templates/handlers/utils.ts +48 -48
  123. package/cli/types.ts +110 -110
  124. package/cli/utils/handler-discovery.ts +501 -501
  125. package/cli/utils/json-utils.ts +24 -24
  126. package/cli/utils/path-utils.ts +19 -19
  127. package/cli/utils/schema-discovery.ts +402 -399
  128. package/dist/cli/index.js +77 -55
  129. package/dist/cli/index.mjs +77 -55
  130. package/index.ts +18 -18
  131. package/package.json +58 -58
  132. package/react/index.ts +5 -5
  133. package/react/use-infinite-query.ts +255 -255
  134. package/react/use-mutation.ts +89 -89
  135. package/react/use-query.ts +210 -210
  136. package/schema.ts +641 -641
  137. package/test-better-auth-hash.ts +2 -2
  138. package/tsconfig.json +6 -6
  139. package/tsup.config.ts +82 -82
package/cli/generate.ts CHANGED
@@ -1,360 +1,360 @@
1
- import { mkdir, readdir, rm } from "node:fs/promises";
2
- import { existsSync } from "node:fs";
3
- import { dirname, join, relative, resolve } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { generateAuthConfigSource } from "./templates/auth/config";
6
- import { generateClientArtifacts } from "./templates/core/client.artifacts";
7
- import { generateDrizzleConfigSource } from "./templates/core/drizzle";
8
- import { generateHandlersArtifacts } from "./templates/core/handlers";
9
- import { generateServerSource } from "./templates/core/server";
10
- import { generateWranglerJson } from "./templates/core/wrangler";
11
- import { generateDashboardSource } from "./templates/dashboard/index";
12
- import { compileSchemaDsl } from "./schema-compiler";
13
- import type { LoadedAppflareConfig } from "./types";
14
- import { discoverHandlerOperations } from "./utils/handler-discovery";
15
- import { discoverSchema } from "./utils/schema-discovery";
16
- import { ensureRelativeImportPath } from "./utils/path-utils";
17
-
18
- function extractRolesFromConfig(config: LoadedAppflareConfig["config"]): string[] {
19
- const plugins = (config.auth.options as Record<string, unknown>).plugins;
20
- if (!Array.isArray(plugins)) return [];
21
-
22
- for (const plugin of plugins) {
23
- if (plugin && typeof plugin === "object") {
24
- const pluginObj = plugin as Record<string, unknown>;
25
- if (pluginObj.id === "admin" && "options" in pluginObj) {
26
- const options = (pluginObj as Record<string, unknown>).options as Record<string, unknown> | undefined;
27
- if (options && typeof options === "object" && "roles" in options) {
28
- const rolesObj = options.roles as Record<string, unknown>;
29
- if (rolesObj && typeof rolesObj === "object") {
30
- return Object.keys(rolesObj);
31
- }
32
- }
33
- }
34
- }
35
- }
36
- return [];
37
- }
38
-
39
- type AdditionalField = { name: string; tsType: string };
40
-
41
- function extractAdditionalFields(config: LoadedAppflareConfig["config"]): AdditionalField[] {
42
- const options = config.auth.options as Record<string, unknown> | undefined;
43
- const userSection = options?.user as Record<string, unknown> | undefined;
44
- const raw = userSection?.additionalFields as Record<string, { type: string }> | undefined;
45
- if (!raw || typeof raw !== "object") return [];
46
-
47
- const typeMap: Record<string, string> = {
48
- string: "string",
49
- number: "number",
50
- boolean: "boolean",
51
- date: "Date",
52
- };
53
-
54
- return Object.entries(raw)
55
- .filter(([, v]) => v && typeof v === "object" && "type" in v)
56
- .map(([name, v]) => ({
57
- name,
58
- tsType: typeMap[(v as { type: string }).type] || "unknown",
59
- }));
60
- }
61
-
62
- async function patchAuthSchemaRole(schemaPath: string, roles: string[]): Promise<void> {
63
- const content = await Bun.file(schemaPath).text();
64
- const roleType = roles.map((r) => `"${r}"`).join(" | ");
65
-
66
- const updated = content
67
- .replace(
68
- /(import.*?from\s+["']drizzle-orm\/sqlite-core["'])/,
69
- "$1\nimport { customType } from \"drizzle-orm/sqlite-core\"",
70
- )
71
- .replace(
72
- /role:\s*text\(["']role["']\)/,
73
- `role: customType<{ data: ${roleType}; dataNotNull: ${roleType} }>({ dataType: () => "text" })("role")`,
74
- );
75
-
76
- await Bun.write(schemaPath, updated);
77
- }
78
-
79
- function toConfigRelativePath(configDir: string, absolutePath: string): string {
80
- const relativePath = relative(configDir, absolutePath).replace(/\\/g, "/");
81
- return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
82
- }
83
-
84
- export async function generateArtifacts(
85
- loadedConfig: LoadedAppflareConfig,
86
- ): Promise<void> {
87
- const { outDirAbs, wranglerOutDirAbs, config, configPath, configDir } =
88
- loadedConfig;
89
- const configImport = ensureRelativeImportPath(outDirAbs, configPath);
90
- const clientOutDirAbs = resolve(outDirAbs, "client");
91
- const clientConfigImport = ensureRelativeImportPath(
92
- clientOutDirAbs,
93
- configPath,
94
- );
95
-
96
- const t0 = performance.now();
97
-
98
- await Promise.all([
99
- mkdir(outDirAbs, { recursive: true }),
100
- mkdir(clientOutDirAbs, { recursive: true }),
101
- mkdir(wranglerOutDirAbs, { recursive: true }),
102
- ]);
103
- process.stdout.write(`๐Ÿ“ Directories (${(performance.now() - t0).toFixed(0)}ms)\n`);
104
-
105
- const moduleDir = dirname(fileURLToPath(import.meta.url));
106
- const tscBin = join(
107
- dirname(Bun.resolveSync("typescript/package.json", moduleDir)),
108
- "bin",
109
- "tsc",
110
- );
111
- const betterAuthBin = join(
112
- dirname(Bun.resolveSync("@better-auth/cli/package.json", moduleDir)),
113
- "dist",
114
- "index.mjs",
115
- );
116
-
117
- const serverPath = resolve(outDirAbs, "server.ts");
118
- const clientPath = resolve(outDirAbs, "client.ts");
119
- const authConfigPath = resolve(outDirAbs, "auth.config.ts");
120
- const authSchemaPath = resolve(outDirAbs, "auth.schema.ts");
121
- const drizzleConfigPath = resolve(outDirAbs, "drizzle.config.ts");
122
- const wranglerPath = resolve(wranglerOutDirAbs, "wrangler.json");
123
- const compiledSchema = await compileSchemaDsl(loadedConfig);
124
-
125
- const discoveredSchema = await discoverSchema(
126
- loadedConfig,
127
- compiledSchema ? [compiledSchema.schemaPath] : [],
128
- );
129
- const schemaImport = ensureRelativeImportPath(
130
- outDirAbs,
131
- discoveredSchema.schemaPath,
132
- );
133
- const discoveredHandlers = await discoverHandlerOperations(loadedConfig);
134
- process.stdout.write(
135
- `๐Ÿ” ${discoveredHandlers.length} handler(s) (${(performance.now() - t0).toFixed(0)}ms)\n`,
136
- );
137
-
138
- const roles = extractRolesFromConfig(config);
139
- const additionalFields = extractAdditionalFields(config);
140
-
141
- const serverSource = generateServerSource(
142
- config.auth.basePath,
143
- config.database[0].binding,
144
- config.kv[0]?.binding,
145
- config.scheduler.binding,
146
- config.r2[0]?.binding,
147
- config.realtime.binding,
148
- config.realtime.objectName,
149
- config.realtime.subscribePath,
150
- config.realtime.websocketPath,
151
- config.realtime.protocol,
152
- );
153
- const clientArtifacts = generateClientArtifacts(
154
- clientConfigImport,
155
- discoveredHandlers,
156
- );
157
- const handlerArtifacts = generateHandlersArtifacts(
158
- schemaImport,
159
- discoveredHandlers,
160
- config.r2[0]?.binding,
161
- roles,
162
- additionalFields,
163
- );
164
- const authConfigSource = generateAuthConfigSource(configImport);
165
- const drizzleSchemaPaths = compiledSchema
166
- ? [
167
- toConfigRelativePath(configDir, compiledSchema.schemaPath),
168
- ...config.schema.filter((schemaPath) => {
169
- return !/(^|\/)schema\.ts$/.test(schemaPath);
170
- }),
171
- ]
172
- : config.schema;
173
- const drizzleConfigSource = generateDrizzleConfigSource(drizzleSchemaPaths);
174
- const wranglerJson = generateWranglerJson(loadedConfig, discoveredHandlers);
175
- const dashboardSource = generateDashboardSource(
176
- schemaImport,
177
- discoveredSchema,
178
- discoveredHandlers,
179
- );
180
- const dashboardPath = resolve(outDirAbs, "admin.routes.ts");
181
- const handlerWriteOperations = handlerArtifacts.map((artifact) => {
182
- return Bun.write(
183
- resolve(outDirAbs, artifact.relativePath),
184
- artifact.source,
185
- );
186
- });
187
- const clientWriteOperations = clientArtifacts.map((artifact) => {
188
- return Bun.write(
189
- resolve(outDirAbs, artifact.relativePath),
190
- artifact.source,
191
- );
192
- });
193
-
194
- await Promise.all([
195
- Bun.write(serverPath, serverSource),
196
- Bun.write(clientPath, 'export * from "./client/index";\n'),
197
- ...clientWriteOperations,
198
- ...handlerWriteOperations,
199
- Bun.write(authConfigPath, authConfigSource),
200
- Bun.write(authSchemaPath, ""),
201
- Bun.write(drizzleConfigPath, drizzleConfigSource),
202
- Bun.write(wranglerPath, `${JSON.stringify(wranglerJson, null, 2)}\n`),
203
- Bun.write(dashboardPath, dashboardSource),
204
- ]);
205
- process.stdout.write(`๐Ÿ“ Source artifacts (${(performance.now() - t0).toFixed(0)}ms)\n`);
206
-
207
- const authConfigPathFromConfigDir = relative(
208
- configDir,
209
- authConfigPath,
210
- ).replace(/\\/g, "/");
211
- const authConfigCliPath = authConfigPathFromConfigDir.startsWith(".")
212
- ? authConfigPathFromConfigDir
213
- : `./${authConfigPathFromConfigDir}`;
214
- const authSchemaPathFromConfigDir = relative(
215
- configDir,
216
- authSchemaPath,
217
- ).replace(/\\/g, "/");
218
- const authSchemaCliPath = authSchemaPathFromConfigDir.startsWith(".")
219
- ? authSchemaPathFromConfigDir
220
- : `./${authSchemaPathFromConfigDir}`;
221
-
222
- const betterAuthGenerate = Bun.spawn(
223
- [
224
- process.execPath,
225
- betterAuthBin,
226
- "generate",
227
- "--config",
228
- authConfigCliPath,
229
- "--output",
230
- authSchemaCliPath,
231
- "--yes",
232
- ],
233
- {
234
- cwd: configDir,
235
- stdout: "inherit",
236
- stderr: "inherit",
237
- },
238
- );
239
-
240
- const exitCode = await betterAuthGenerate.exited;
241
- if (exitCode !== 0) {
242
- throw new Error(`better-auth generation failed with exit code ${exitCode}`);
243
- }
244
- process.stdout.write(`๐Ÿ” Auth schema (${(performance.now() - t0).toFixed(0)}ms)\n`);
245
-
246
- if (roles.length > 0) {
247
- await patchAuthSchemaRole(authSchemaPath, roles);
248
- process.stdout.write(`๐Ÿ”ง Patched role type (${(performance.now() - t0).toFixed(0)}ms)\n`);
249
- }
250
-
251
- function generatedTsconfig(overrides: Record<string, unknown> = {}) {
252
- return {
253
- compilerOptions: {
254
- lib: ["es2024"],
255
- types: ["@types/node", "@cloudflare/workers-types/2023-07-01"],
256
- module: "es2022",
257
- target: "es2024",
258
- moduleResolution: "bundler",
259
- strictNullChecks: true,
260
- skipLibCheck: true,
261
- declaration: true,
262
- emitDeclarationOnly: true,
263
- noCheck: true,
264
- jsx: "react-jsx",
265
- paths: {
266
- "_generated/*": ["./*"],
267
- },
268
- ...overrides,
269
- },
270
- include: ["./*.ts", "./client/*.ts"],
271
- };
272
- }
273
-
274
- const jsTsconfigPath = resolve(outDirAbs, "tsconfig.js.json");
275
-
276
- await Promise.all([
277
- Bun.write(
278
- jsTsconfigPath,
279
- `${JSON.stringify(generatedTsconfig({ declaration:true,emitDeclarationOnly: false }), null, 2)}\n`,
280
- ),
281
- ]);
282
- process.stdout.write(`๐Ÿ“„ TS configs (${(performance.now() - t0).toFixed(0)}ms)\n`);
283
-
284
- async function runTsc(configPath: string, label: string): Promise<void> {
285
- process.stdout.write(`โš™๏ธ ${label}... (${(performance.now() - t0).toFixed(0)}ms)\n`);
286
- const p = Bun.spawn([process.execPath, tscBin, "-p", configPath], {
287
- cwd: outDirAbs,
288
- stdout: "inherit",
289
- stderr: "inherit",
290
- });
291
- const code = await p.exited;
292
- if (code !== 0) throw new Error(`tsc ${label} failed with exit code ${code}`);
293
- }
294
-
295
- await runTsc(jsTsconfigPath, "JS files");
296
- process.stdout.write(`โœ… JS + declarations (${(performance.now() - t0).toFixed(0)}ms)\n`);
297
- async function removeTsFiles(dir: string): Promise<void> {
298
- const entries = await readdir(dir, { withFileTypes: true });
299
- for (const entry of entries) {
300
- const fullPath = join(dir, entry.name);
301
- if (entry.isDirectory()) {
302
- if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
303
- await removeTsFiles(fullPath);
304
- } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
305
- await rm(fullPath);
306
- }
307
- }
308
- }
309
- await removeTsFiles(outDirAbs);
310
- process.stdout.write(`๐Ÿ“ฆ Cleaned .ts files (${(performance.now() - t0).toFixed(0)}ms)\n`);
311
-
312
- const generatedDirPrefix = outDirAbs + "/";
313
- async function cleanDtsOutside(dir: string): Promise<void> {
314
- const entries = await readdir(dir, { withFileTypes: true });
315
- for (const entry of entries) {
316
- const fullPath = join(dir, entry.name);
317
- if (entry.isDirectory()) {
318
- if (
319
- entry.name === "node_modules" ||
320
- entry.name.startsWith(".") ||
321
- fullPath === outDirAbs
322
- ) {
323
- continue;
324
- }
325
- await cleanDtsOutside(fullPath);
326
- } else if (
327
- !fullPath.startsWith(generatedDirPrefix) && (
328
- entry.name.endsWith(".d.ts") || (
329
- entry.name.endsWith(".js"))
330
- )
331
- ) {
332
- await rm(fullPath);
333
- }
334
- }
335
- }
336
- await cleanDtsOutside(configDir);
337
- process.stdout.write(`๐Ÿงน Cleanup (${(performance.now() - t0).toFixed(0)}ms)\n`);
338
-
339
-
340
- const tsconfigPath = resolve(configDir, "tsconfig.json");
341
- if (loadedConfig.config.build && existsSync(tsconfigPath)) {
342
- process.stdout.write(`โš™๏ธ Building project... (${(performance.now() - t0).toFixed(0)}ms)\n`);
343
- const tsBuild = Bun.spawn(
344
- [process.execPath, tscBin, "--build"],
345
- {
346
- cwd: configDir,
347
- stdout: "inherit",
348
- stderr: "inherit",
349
- },
350
- );
351
-
352
- const tsBuildExitCode = await tsBuild.exited;
353
- if (tsBuildExitCode !== 0) {
354
- throw new Error(
355
- `TypeScript build failed with exit code ${tsBuildExitCode}`,
356
- );
357
- }
358
- process.stdout.write(`โœ… Build complete (${(performance.now() - t0).toFixed(0)}ms)\n`);
359
- }
360
- }
1
+ import { mkdir, readdir, rm } from "node:fs/promises";
2
+ import { existsSync } from "node:fs";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { generateAuthConfigSource } from "./templates/auth/config";
6
+ import { generateClientArtifacts } from "./templates/core/client.artifacts";
7
+ import { generateDrizzleConfigSource } from "./templates/core/drizzle";
8
+ import { generateHandlersArtifacts } from "./templates/core/handlers";
9
+ import { generateServerSource } from "./templates/core/server";
10
+ import { generateWranglerJson } from "./templates/core/wrangler";
11
+ import { generateDashboardSource } from "./templates/dashboard/index";
12
+ import { compileSchemaDsl } from "./schema-compiler";
13
+ import type { LoadedAppflareConfig } from "./types";
14
+ import { discoverHandlerOperations } from "./utils/handler-discovery";
15
+ import { discoverSchema } from "./utils/schema-discovery";
16
+ import { ensureRelativeImportPath } from "./utils/path-utils";
17
+
18
+ function extractRolesFromConfig(config: LoadedAppflareConfig["config"]): string[] {
19
+ const plugins = (config.auth.options as Record<string, unknown>).plugins;
20
+ if (!Array.isArray(plugins)) return [];
21
+
22
+ for (const plugin of plugins) {
23
+ if (plugin && typeof plugin === "object") {
24
+ const pluginObj = plugin as Record<string, unknown>;
25
+ if (pluginObj.id === "admin" && "options" in pluginObj) {
26
+ const options = (pluginObj as Record<string, unknown>).options as Record<string, unknown> | undefined;
27
+ if (options && typeof options === "object" && "roles" in options) {
28
+ const rolesObj = options.roles as Record<string, unknown>;
29
+ if (rolesObj && typeof rolesObj === "object") {
30
+ return Object.keys(rolesObj);
31
+ }
32
+ }
33
+ }
34
+ }
35
+ }
36
+ return [];
37
+ }
38
+
39
+ type AdditionalField = { name: string; tsType: string };
40
+
41
+ function extractAdditionalFields(config: LoadedAppflareConfig["config"]): AdditionalField[] {
42
+ const options = config.auth.options as Record<string, unknown> | undefined;
43
+ const userSection = options?.user as Record<string, unknown> | undefined;
44
+ const raw = userSection?.additionalFields as Record<string, { type: string }> | undefined;
45
+ if (!raw || typeof raw !== "object") return [];
46
+
47
+ const typeMap: Record<string, string> = {
48
+ string: "string",
49
+ number: "number",
50
+ boolean: "boolean",
51
+ date: "Date",
52
+ };
53
+
54
+ return Object.entries(raw)
55
+ .filter(([, v]) => v && typeof v === "object" && "type" in v)
56
+ .map(([name, v]) => ({
57
+ name,
58
+ tsType: typeMap[(v as { type: string }).type] || "unknown",
59
+ }));
60
+ }
61
+
62
+ async function patchAuthSchemaRole(schemaPath: string, roles: string[]): Promise<void> {
63
+ const content = await Bun.file(schemaPath).text();
64
+ const roleType = roles.map((r) => `"${r}"`).join(" | ");
65
+
66
+ const updated = content
67
+ .replace(
68
+ /(import.*?from\s+["']drizzle-orm\/sqlite-core["'])/,
69
+ "$1\nimport { customType } from \"drizzle-orm/sqlite-core\"",
70
+ )
71
+ .replace(
72
+ /role:\s*text\(["']role["']\)/,
73
+ `role: customType<{ data: ${roleType}; dataNotNull: ${roleType} }>({ dataType: () => "text" })("role")`,
74
+ );
75
+
76
+ await Bun.write(schemaPath, updated);
77
+ }
78
+
79
+ function toConfigRelativePath(configDir: string, absolutePath: string): string {
80
+ const relativePath = relative(configDir, absolutePath).replace(/\\/g, "/");
81
+ return relativePath.startsWith(".") ? relativePath : `./${relativePath}`;
82
+ }
83
+
84
+ export async function generateArtifacts(
85
+ loadedConfig: LoadedAppflareConfig,
86
+ ): Promise<void> {
87
+ const { outDirAbs, wranglerOutDirAbs, config, configPath, configDir } =
88
+ loadedConfig;
89
+ const configImport = ensureRelativeImportPath(outDirAbs, configPath);
90
+ const clientOutDirAbs = resolve(outDirAbs, "client");
91
+ const clientConfigImport = ensureRelativeImportPath(
92
+ clientOutDirAbs,
93
+ configPath,
94
+ );
95
+
96
+ const t0 = performance.now();
97
+
98
+ await Promise.all([
99
+ mkdir(outDirAbs, { recursive: true }),
100
+ mkdir(clientOutDirAbs, { recursive: true }),
101
+ mkdir(wranglerOutDirAbs, { recursive: true }),
102
+ ]);
103
+ process.stdout.write(`๐Ÿ“ Directories (${(performance.now() - t0).toFixed(0)}ms)\n`);
104
+
105
+ const moduleDir = dirname(fileURLToPath(import.meta.url));
106
+ const tscBin = join(
107
+ dirname(Bun.resolveSync("typescript/package.json", moduleDir)),
108
+ "bin",
109
+ "tsc",
110
+ );
111
+ const betterAuthBin = join(
112
+ dirname(Bun.resolveSync("@better-auth/cli/package.json", moduleDir)),
113
+ "dist",
114
+ "index.mjs",
115
+ );
116
+
117
+ const serverPath = resolve(outDirAbs, "server.ts");
118
+ const clientPath = resolve(outDirAbs, "client.ts");
119
+ const authConfigPath = resolve(outDirAbs, "auth.config.ts");
120
+ const authSchemaPath = resolve(outDirAbs, "auth.schema.ts");
121
+ const drizzleConfigPath = resolve(outDirAbs, "drizzle.config.ts");
122
+ const wranglerPath = resolve(wranglerOutDirAbs, "wrangler.json");
123
+ const compiledSchema = await compileSchemaDsl(loadedConfig);
124
+
125
+ const discoveredSchema = await discoverSchema(
126
+ loadedConfig,
127
+ compiledSchema ? [compiledSchema.schemaPath] : [],
128
+ );
129
+ const schemaImport = ensureRelativeImportPath(
130
+ outDirAbs,
131
+ discoveredSchema.schemaPath,
132
+ );
133
+ const discoveredHandlers = await discoverHandlerOperations(loadedConfig);
134
+ process.stdout.write(
135
+ `๐Ÿ” ${discoveredHandlers.length} handler(s) (${(performance.now() - t0).toFixed(0)}ms)\n`,
136
+ );
137
+
138
+ const roles = extractRolesFromConfig(config);
139
+ const additionalFields = extractAdditionalFields(config);
140
+
141
+ const serverSource = generateServerSource(
142
+ config.auth.basePath,
143
+ config.database[0].binding,
144
+ config.kv[0]?.binding,
145
+ config.scheduler.binding,
146
+ config.r2[0]?.binding,
147
+ config.realtime.binding,
148
+ config.realtime.objectName,
149
+ config.realtime.subscribePath,
150
+ config.realtime.websocketPath,
151
+ config.realtime.protocol,
152
+ );
153
+ const clientArtifacts = generateClientArtifacts(
154
+ clientConfigImport,
155
+ discoveredHandlers,
156
+ );
157
+ const handlerArtifacts = generateHandlersArtifacts(
158
+ schemaImport,
159
+ discoveredHandlers,
160
+ config.r2[0]?.binding,
161
+ roles,
162
+ additionalFields,
163
+ );
164
+ const authConfigSource = generateAuthConfigSource(configImport);
165
+ const drizzleSchemaPaths = compiledSchema
166
+ ? [
167
+ toConfigRelativePath(configDir, compiledSchema.schemaPath),
168
+ ...config.schema.filter((schemaPath) => {
169
+ return !/(^|\/)schema\.ts$/.test(schemaPath);
170
+ }),
171
+ ]
172
+ : config.schema;
173
+ const drizzleConfigSource = generateDrizzleConfigSource(drizzleSchemaPaths);
174
+ const wranglerJson = generateWranglerJson(loadedConfig, discoveredHandlers);
175
+ const dashboardSource = generateDashboardSource(
176
+ schemaImport,
177
+ discoveredSchema,
178
+ discoveredHandlers,
179
+ );
180
+ const dashboardPath = resolve(outDirAbs, "admin.routes.ts");
181
+ const handlerWriteOperations = handlerArtifacts.map((artifact) => {
182
+ return Bun.write(
183
+ resolve(outDirAbs, artifact.relativePath),
184
+ artifact.source,
185
+ );
186
+ });
187
+ const clientWriteOperations = clientArtifacts.map((artifact) => {
188
+ return Bun.write(
189
+ resolve(outDirAbs, artifact.relativePath),
190
+ artifact.source,
191
+ );
192
+ });
193
+
194
+ await Promise.all([
195
+ Bun.write(serverPath, serverSource),
196
+ Bun.write(clientPath, 'export * from "./client/index";\n'),
197
+ ...clientWriteOperations,
198
+ ...handlerWriteOperations,
199
+ Bun.write(authConfigPath, authConfigSource),
200
+ Bun.write(authSchemaPath, ""),
201
+ Bun.write(drizzleConfigPath, drizzleConfigSource),
202
+ Bun.write(wranglerPath, `${JSON.stringify(wranglerJson, null, 2)}\n`),
203
+ Bun.write(dashboardPath, dashboardSource),
204
+ ]);
205
+ process.stdout.write(`๐Ÿ“ Source artifacts (${(performance.now() - t0).toFixed(0)}ms)\n`);
206
+
207
+ const authConfigPathFromConfigDir = relative(
208
+ configDir,
209
+ authConfigPath,
210
+ ).replace(/\\/g, "/");
211
+ const authConfigCliPath = authConfigPathFromConfigDir.startsWith(".")
212
+ ? authConfigPathFromConfigDir
213
+ : `./${authConfigPathFromConfigDir}`;
214
+ const authSchemaPathFromConfigDir = relative(
215
+ configDir,
216
+ authSchemaPath,
217
+ ).replace(/\\/g, "/");
218
+ const authSchemaCliPath = authSchemaPathFromConfigDir.startsWith(".")
219
+ ? authSchemaPathFromConfigDir
220
+ : `./${authSchemaPathFromConfigDir}`;
221
+
222
+ const betterAuthGenerate = Bun.spawn(
223
+ [
224
+ process.execPath,
225
+ betterAuthBin,
226
+ "generate",
227
+ "--config",
228
+ authConfigCliPath,
229
+ "--output",
230
+ authSchemaCliPath,
231
+ "--yes",
232
+ ],
233
+ {
234
+ cwd: configDir,
235
+ stdout: "inherit",
236
+ stderr: "inherit",
237
+ },
238
+ );
239
+
240
+ const exitCode = await betterAuthGenerate.exited;
241
+ if (exitCode !== 0) {
242
+ throw new Error(`better-auth generation failed with exit code ${exitCode}`);
243
+ }
244
+ process.stdout.write(`๐Ÿ” Auth schema (${(performance.now() - t0).toFixed(0)}ms)\n`);
245
+
246
+ if (roles.length > 0) {
247
+ await patchAuthSchemaRole(authSchemaPath, roles);
248
+ process.stdout.write(`๐Ÿ”ง Patched role type (${(performance.now() - t0).toFixed(0)}ms)\n`);
249
+ }
250
+
251
+ function generatedTsconfig(overrides: Record<string, unknown> = {}) {
252
+ return {
253
+ compilerOptions: {
254
+ lib: ["es2024"],
255
+ types: ["@types/node", "@cloudflare/workers-types/2023-07-01"],
256
+ module: "es2022",
257
+ target: "es2024",
258
+ moduleResolution: "bundler",
259
+ strictNullChecks: true,
260
+ skipLibCheck: true,
261
+ declaration: true,
262
+ emitDeclarationOnly: true,
263
+ noCheck: true,
264
+ jsx: "react-jsx",
265
+ paths: {
266
+ "_generated/*": ["./*"],
267
+ },
268
+ ...overrides,
269
+ },
270
+ include: ["./*.ts", "./client/*.ts"],
271
+ };
272
+ }
273
+
274
+ const jsTsconfigPath = resolve(outDirAbs, "tsconfig.js.json");
275
+
276
+ await Promise.all([
277
+ Bun.write(
278
+ jsTsconfigPath,
279
+ `${JSON.stringify(generatedTsconfig({ declaration:true,emitDeclarationOnly: false }), null, 2)}\n`,
280
+ ),
281
+ ]);
282
+ process.stdout.write(`๐Ÿ“„ TS configs (${(performance.now() - t0).toFixed(0)}ms)\n`);
283
+
284
+ async function runTsc(configPath: string, label: string): Promise<void> {
285
+ process.stdout.write(`โš™๏ธ ${label}... (${(performance.now() - t0).toFixed(0)}ms)\n`);
286
+ const p = Bun.spawn([process.execPath, tscBin, "-p", configPath], {
287
+ cwd: outDirAbs,
288
+ stdout: "inherit",
289
+ stderr: "inherit",
290
+ });
291
+ const code = await p.exited;
292
+ if (code !== 0) throw new Error(`tsc ${label} failed with exit code ${code}`);
293
+ }
294
+
295
+ await runTsc(jsTsconfigPath, "JS files");
296
+ process.stdout.write(`โœ… JS + declarations (${(performance.now() - t0).toFixed(0)}ms)\n`);
297
+ async function removeTsFiles(dir: string): Promise<void> {
298
+ const entries = await readdir(dir, { withFileTypes: true });
299
+ for (const entry of entries) {
300
+ const fullPath = join(dir, entry.name);
301
+ if (entry.isDirectory()) {
302
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
303
+ await removeTsFiles(fullPath);
304
+ } else if (entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
305
+ await rm(fullPath);
306
+ }
307
+ }
308
+ }
309
+ await removeTsFiles(outDirAbs);
310
+ process.stdout.write(`๐Ÿ“ฆ Cleaned .ts files (${(performance.now() - t0).toFixed(0)}ms)\n`);
311
+
312
+ const generatedDirPrefix = outDirAbs + "/";
313
+ async function cleanDtsOutside(dir: string): Promise<void> {
314
+ const entries = await readdir(dir, { withFileTypes: true });
315
+ for (const entry of entries) {
316
+ const fullPath = join(dir, entry.name);
317
+ if (entry.isDirectory()) {
318
+ if (
319
+ entry.name === "node_modules" ||
320
+ entry.name.startsWith(".") ||
321
+ fullPath === outDirAbs
322
+ ) {
323
+ continue;
324
+ }
325
+ await cleanDtsOutside(fullPath);
326
+ } else if (
327
+ !fullPath.startsWith(generatedDirPrefix) && (
328
+ entry.name.endsWith(".d.ts") || (
329
+ entry.name.endsWith(".js"))
330
+ )
331
+ ) {
332
+ await rm(fullPath);
333
+ }
334
+ }
335
+ }
336
+ await cleanDtsOutside(configDir);
337
+ process.stdout.write(`๐Ÿงน Cleanup (${(performance.now() - t0).toFixed(0)}ms)\n`);
338
+
339
+
340
+ const tsconfigPath = resolve(configDir, "tsconfig.json");
341
+ if (loadedConfig.config.build && existsSync(tsconfigPath)) {
342
+ process.stdout.write(`โš™๏ธ Building project... (${(performance.now() - t0).toFixed(0)}ms)\n`);
343
+ const tsBuild = Bun.spawn(
344
+ [process.execPath, tscBin, "--build"],
345
+ {
346
+ cwd: configDir,
347
+ stdout: "inherit",
348
+ stderr: "inherit",
349
+ },
350
+ );
351
+
352
+ const tsBuildExitCode = await tsBuild.exited;
353
+ if (tsBuildExitCode !== 0) {
354
+ throw new Error(
355
+ `TypeScript build failed with exit code ${tsBuildExitCode}`,
356
+ );
357
+ }
358
+ process.stdout.write(`โœ… Build complete (${(performance.now() - t0).toFixed(0)}ms)\n`);
359
+ }
360
+ }