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/index.ts CHANGED
@@ -1,120 +1,120 @@
1
- import { Command } from "commander";
2
- import { runBuild, runDev, runMigrate, runAddAdmin } from "./commands/index";
3
-
4
- const program = new Command();
5
-
6
- program
7
- .name("appflare")
8
- .description(
9
- "Appflare compiler/bundler for Cloudflare-native backends and SDK generation",
10
- )
11
- .version("0.0.28");
12
-
13
- program
14
- .command("build")
15
- .description(
16
- "Generate server.ts, client.ts, auth.config.js, drizzle.config.js, and wrangler.json artifacts",
17
- )
18
- .option(
19
- "-c, --config <path>",
20
- "Path to appflare.config.ts",
21
- "appflare.config.ts",
22
- )
23
- .option("--no-build", "Skip TypeScript build step")
24
- .action(async (options: { config: string; build: boolean }) => {
25
- await runBuild(options.config, { build: options.build });
26
- });
27
-
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
- .option("--no-build", "Skip TypeScript build step")
38
- .action(
39
- async (options: { config: string; watch: boolean; build: boolean }) => {
40
- await runDev(options.config, { watch: options.watch, build: options.build });
41
- },
42
- );
43
-
44
- program
45
- .command("migrate")
46
- .description(
47
- "Generate drizzle migration files from outDir/auth.schema.ts and apply them to the configured D1 database",
48
- )
49
- .option(
50
- "-c, --config <path>",
51
- "Path to appflare.config.ts",
52
- "appflare.config.ts",
53
- )
54
- .option(
55
- "--local",
56
- "Execute commands/files against a local DB for use with wrangler dev",
57
- false,
58
- )
59
- .option(
60
- "--remote",
61
- "Execute commands/files against a remote DB for use with wrangler dev --remote",
62
- false,
63
- )
64
- .option("--preview", "Execute commands/files against a preview D1 DB", false)
65
- .action(
66
- async (options: {
67
- config: string;
68
- local: boolean;
69
- remote: boolean;
70
- preview: boolean;
71
- }) => {
72
- await runMigrate(options.config, {
73
- local: options.local,
74
- remote: options.remote,
75
- preview: options.preview,
76
- });
77
- },
78
- );
79
-
80
- program
81
- .command("add-admin")
82
- .description("Add an admin user to the database")
83
- .requiredOption("-n, --name <name>", "Admin's display name")
84
- .requiredOption("-e, --email <email>", "Admin's email address")
85
- .requiredOption("-p, --password <password>", "Admin's password")
86
- .option(
87
- "-c, --config <path>",
88
- "Path to appflare.config.ts",
89
- "appflare.config.ts",
90
- )
91
- .option(
92
- "--local",
93
- "Execute command against a local DB for use with wrangler dev",
94
- false,
95
- )
96
- .option(
97
- "--remote",
98
- "Execute command against a remote DB for use with wrangler dev --remote",
99
- false,
100
- )
101
- .action(async (options: any) => {
102
- await runAddAdmin(options.config, {
103
- name: options.name,
104
- email: options.email,
105
- password: options.password,
106
- local: options.local,
107
- remote: options.remote,
108
- });
109
- });
110
-
111
- (async () => {
112
- if (!process.versions.bun) {
113
- console.error("Appflare CLI must be run with Bun.");
114
- process.exit(1);
115
- }
116
- await program.parseAsync(process.argv);
117
- })().catch((error) => {
118
- console.error(error);
119
- process.exit(1);
120
- });
1
+ import { Command } from "commander";
2
+ import { runBuild, runDev, runMigrate, runAddAdmin } from "./commands/index";
3
+
4
+ const program = new Command();
5
+
6
+ program
7
+ .name("appflare")
8
+ .description(
9
+ "Appflare compiler/bundler for Cloudflare-native backends and SDK generation",
10
+ )
11
+ .version("0.0.28");
12
+
13
+ program
14
+ .command("build")
15
+ .description(
16
+ "Generate server.ts, client.ts, auth.config.js, drizzle.config.js, and wrangler.json artifacts",
17
+ )
18
+ .option(
19
+ "-c, --config <path>",
20
+ "Path to appflare.config.ts",
21
+ "appflare.config.ts",
22
+ )
23
+ .option("--no-build", "Skip TypeScript build step")
24
+ .action(async (options: { config: string; build: boolean }) => {
25
+ await runBuild(options.config, { build: options.build });
26
+ });
27
+
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
+ .option("--no-build", "Skip TypeScript build step")
38
+ .action(
39
+ async (options: { config: string; watch: boolean; build: boolean }) => {
40
+ await runDev(options.config, { watch: options.watch, build: options.build });
41
+ },
42
+ );
43
+
44
+ program
45
+ .command("migrate")
46
+ .description(
47
+ "Generate drizzle migration files from outDir/auth.schema.ts and apply them to the configured D1 database",
48
+ )
49
+ .option(
50
+ "-c, --config <path>",
51
+ "Path to appflare.config.ts",
52
+ "appflare.config.ts",
53
+ )
54
+ .option(
55
+ "--local",
56
+ "Execute commands/files against a local DB for use with wrangler dev",
57
+ false,
58
+ )
59
+ .option(
60
+ "--remote",
61
+ "Execute commands/files against a remote DB for use with wrangler dev --remote",
62
+ false,
63
+ )
64
+ .option("--preview", "Execute commands/files against a preview D1 DB", false)
65
+ .action(
66
+ async (options: {
67
+ config: string;
68
+ local: boolean;
69
+ remote: boolean;
70
+ preview: boolean;
71
+ }) => {
72
+ await runMigrate(options.config, {
73
+ local: options.local,
74
+ remote: options.remote,
75
+ preview: options.preview,
76
+ });
77
+ },
78
+ );
79
+
80
+ program
81
+ .command("add-admin")
82
+ .description("Add an admin user to the database")
83
+ .requiredOption("-n, --name <name>", "Admin's display name")
84
+ .requiredOption("-e, --email <email>", "Admin's email address")
85
+ .requiredOption("-p, --password <password>", "Admin's password")
86
+ .option(
87
+ "-c, --config <path>",
88
+ "Path to appflare.config.ts",
89
+ "appflare.config.ts",
90
+ )
91
+ .option(
92
+ "--local",
93
+ "Execute command against a local DB for use with wrangler dev",
94
+ false,
95
+ )
96
+ .option(
97
+ "--remote",
98
+ "Execute command against a remote DB for use with wrangler dev --remote",
99
+ false,
100
+ )
101
+ .action(async (options: any) => {
102
+ await runAddAdmin(options.config, {
103
+ name: options.name,
104
+ email: options.email,
105
+ password: options.password,
106
+ local: options.local,
107
+ remote: options.remote,
108
+ });
109
+ });
110
+
111
+ (async () => {
112
+ if (!process.versions.bun) {
113
+ console.error("Appflare CLI must be run with Bun.");
114
+ process.exit(1);
115
+ }
116
+ await program.parseAsync(process.argv);
117
+ })().catch((error) => {
118
+ console.error(error);
119
+ process.exit(1);
120
+ });
@@ -1,184 +1,184 @@
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
- build: z.boolean().optional(),
96
- })
97
- .strict();
98
-
99
- function isRecord(value: unknown): value is Record<string, unknown> {
100
- return typeof value === "object" && value !== null;
101
- }
102
-
103
- function readLegacySchedulerConfig(
104
- input: AppflareConfig,
105
- ): Partial<NonNullable<AppflareConfig["scheduler"]>> {
106
- const raw = isRecord(input.wranglerOverrides)
107
- ? input.wranglerOverrides.scheduler
108
- : undefined;
109
- const parsed = schedulerConfigSchema.safeParse(raw);
110
- if (!parsed.success) {
111
- return {};
112
- }
113
-
114
- return parsed.data;
115
- }
116
-
117
- function removeLegacySchedulerOverride(
118
- overrides: AppflareConfig["wranglerOverrides"],
119
- ): AppflareConfig["wranglerOverrides"] {
120
- if (!isRecord(overrides) || !("scheduler" in overrides)) {
121
- return overrides;
122
- }
123
-
124
- const { scheduler: _scheduler, ...rest } = overrides;
125
- return rest;
126
- }
127
-
128
- function normalizeConfig(input: AppflareConfig): NormalizedAppflareConfig {
129
- const legacyScheduler = readLegacySchedulerConfig(input);
130
- const scheduler = {
131
- ...(legacyScheduler ?? {}),
132
- ...(input.scheduler ?? {}),
133
- };
134
- const realtime = input.realtime ?? {};
135
-
136
- return {
137
- ...input,
138
- database: Array.isArray(input.database) ? input.database : [input.database],
139
- kv: input.kv ? (Array.isArray(input.kv) ? input.kv : [input.kv]) : [],
140
- r2: input.r2 ? (Array.isArray(input.r2) ? input.r2 : [input.r2]) : [],
141
- scheduler: {
142
- enabled: scheduler.enabled ?? true,
143
- binding: scheduler.binding ?? "APPFLARE_SCHEDULER_QUEUE",
144
- queue: scheduler.queue,
145
- },
146
- realtime: {
147
- enabled: realtime.enabled ?? true,
148
- binding: realtime.binding ?? "APPFLARE_REALTIME",
149
- className: realtime.className ?? "AppflareRealtimeDurableObject",
150
- objectName: realtime.objectName ?? "global",
151
- subscribePath: realtime.subscribePath ?? "/realtime/subscribe",
152
- websocketPath: realtime.websocketPath ?? "/realtime/ws",
153
- protocol: realtime.protocol ?? "appflare.realtime.v1",
154
- },
155
- wranglerOverrides: removeLegacySchedulerOverride(input.wranglerOverrides),
156
- wranglerOutDir:
157
- input.wranglerOutDir ?? input.wranglerOutPath ?? input.outDir,
158
- build: input.build ?? true,
159
- };
160
- }
161
-
162
- export async function loadConfig(
163
- configPathArg?: string,
164
- ): Promise<LoadedAppflareConfig> {
165
- const configPath = isAbsolute(configPathArg ?? "")
166
- ? (configPathArg as string)
167
- : resolve(process.cwd(), configPathArg ?? "appflare.config.ts");
168
-
169
- const configDir = dirname(configPath);
170
- const moduleUrl = pathToFileURL(configPath).href;
171
- const configModule = await import(moduleUrl);
172
- const raw = configModule.default;
173
- const parsed = appflareConfigSchema.parse(raw) as AppflareConfig;
174
- const config = normalizeConfig(parsed);
175
-
176
- return {
177
- configPath,
178
- configDir,
179
- scanDirAbs: resolve(configDir, config.scanDir),
180
- outDirAbs: resolve(configDir, config.outDir),
181
- wranglerOutDirAbs: resolve(configDir, config.wranglerOutDir),
182
- config,
183
- };
184
- }
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
+ build: z.boolean().optional(),
96
+ })
97
+ .strict();
98
+
99
+ function isRecord(value: unknown): value is Record<string, unknown> {
100
+ return typeof value === "object" && value !== null;
101
+ }
102
+
103
+ function readLegacySchedulerConfig(
104
+ input: AppflareConfig,
105
+ ): Partial<NonNullable<AppflareConfig["scheduler"]>> {
106
+ const raw = isRecord(input.wranglerOverrides)
107
+ ? input.wranglerOverrides.scheduler
108
+ : undefined;
109
+ const parsed = schedulerConfigSchema.safeParse(raw);
110
+ if (!parsed.success) {
111
+ return {};
112
+ }
113
+
114
+ return parsed.data;
115
+ }
116
+
117
+ function removeLegacySchedulerOverride(
118
+ overrides: AppflareConfig["wranglerOverrides"],
119
+ ): AppflareConfig["wranglerOverrides"] {
120
+ if (!isRecord(overrides) || !("scheduler" in overrides)) {
121
+ return overrides;
122
+ }
123
+
124
+ const { scheduler: _scheduler, ...rest } = overrides;
125
+ return rest;
126
+ }
127
+
128
+ function normalizeConfig(input: AppflareConfig): NormalizedAppflareConfig {
129
+ const legacyScheduler = readLegacySchedulerConfig(input);
130
+ const scheduler = {
131
+ ...(legacyScheduler ?? {}),
132
+ ...(input.scheduler ?? {}),
133
+ };
134
+ const realtime = input.realtime ?? {};
135
+
136
+ return {
137
+ ...input,
138
+ database: Array.isArray(input.database) ? input.database : [input.database],
139
+ kv: input.kv ? (Array.isArray(input.kv) ? input.kv : [input.kv]) : [],
140
+ r2: input.r2 ? (Array.isArray(input.r2) ? input.r2 : [input.r2]) : [],
141
+ scheduler: {
142
+ enabled: scheduler.enabled ?? true,
143
+ binding: scheduler.binding ?? "APPFLARE_SCHEDULER_QUEUE",
144
+ queue: scheduler.queue,
145
+ },
146
+ realtime: {
147
+ enabled: realtime.enabled ?? true,
148
+ binding: realtime.binding ?? "APPFLARE_REALTIME",
149
+ className: realtime.className ?? "AppflareRealtimeDurableObject",
150
+ objectName: realtime.objectName ?? "global",
151
+ subscribePath: realtime.subscribePath ?? "/realtime/subscribe",
152
+ websocketPath: realtime.websocketPath ?? "/realtime/ws",
153
+ protocol: realtime.protocol ?? "appflare.realtime.v1",
154
+ },
155
+ wranglerOverrides: removeLegacySchedulerOverride(input.wranglerOverrides),
156
+ wranglerOutDir:
157
+ input.wranglerOutDir ?? input.wranglerOutPath ?? input.outDir,
158
+ build: input.build ?? true,
159
+ };
160
+ }
161
+
162
+ export async function loadConfig(
163
+ configPathArg?: string,
164
+ ): Promise<LoadedAppflareConfig> {
165
+ const configPath = isAbsolute(configPathArg ?? "")
166
+ ? (configPathArg as string)
167
+ : resolve(process.cwd(), configPathArg ?? "appflare.config.ts");
168
+
169
+ const configDir = dirname(configPath);
170
+ const moduleUrl = pathToFileURL(configPath).href;
171
+ const configModule = await import(moduleUrl);
172
+ const raw = configModule.default;
173
+ const parsed = appflareConfigSchema.parse(raw) as AppflareConfig;
174
+ const config = normalizeConfig(parsed);
175
+
176
+ return {
177
+ configPath,
178
+ configDir,
179
+ scanDirAbs: resolve(configDir, config.scanDir),
180
+ outDirAbs: resolve(configDir, config.outDir),
181
+ wranglerOutDirAbs: resolve(configDir, config.wranglerOutDir),
182
+ config,
183
+ };
184
+ }