appflare 0.0.8 → 0.0.10

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/core/build.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  generateHonoServer,
15
15
  generateWebsocketDurableObject,
16
16
  generateSchedulerHandlers,
17
+ generateCronHandlers,
17
18
  } from "./handlers";
18
19
 
19
20
  export async function buildFromConfig(params: {
@@ -100,6 +101,14 @@ export async function buildFromConfig(params: {
100
101
  schedulerTs
101
102
  );
102
103
 
104
+ const { code: cronTs } = generateCronHandlers({
105
+ handlers,
106
+ outDirAbs,
107
+ schemaPathAbs,
108
+ configPathAbs,
109
+ });
110
+ await fs.writeFile(path.join(outDirAbs, "server", "cron.ts"), cronTs);
111
+
103
112
  if (emit) {
104
113
  // Remove previous emit output to avoid stale files lingering.
105
114
  await fs.rm(path.join(outDirAbs, "dist"), { recursive: true, force: true });
@@ -1,5 +1,6 @@
1
1
  import { promises as fs } from "node:fs";
2
2
  import path from "node:path";
3
+ import * as ts from "typescript";
3
4
  import {
4
5
  DiscoveredHandler,
5
6
  HandlerKind,
@@ -29,15 +30,19 @@ export async function discoverHandlers(params: {
29
30
  if (path.resolve(fileAbs) === path.resolve(params.configPathAbs)) continue;
30
31
 
31
32
  const content = await fs.readFile(fileAbs, "utf8");
33
+ const cronTriggersByHandler = extractCronTriggers(content);
32
34
  const regex =
33
- /export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(query|mutation|internalQuery|internalMutation|scheduler)\s*\(/g;
35
+ /export\s+const\s+([A-Za-z_$][\w$]*)\s*=\s*(query|mutation|internalQuery|internalMutation|scheduler|cron)\s*\(/g;
34
36
  let match: RegExpExecArray | null;
35
37
  while ((match = regex.exec(content)) !== null) {
38
+ const kind = match[2] as HandlerKind;
36
39
  handlers.push({
37
40
  fileName: path.basename(fileAbs, ".ts"),
38
41
  name: match[1],
39
- kind: match[2] as HandlerKind,
42
+ kind,
40
43
  sourceFileAbs: fileAbs,
44
+ cronTriggers:
45
+ kind === "cron" ? cronTriggersByHandler.get(match[1]) : undefined,
41
46
  });
42
47
  }
43
48
  }
@@ -59,3 +64,70 @@ export async function discoverHandlers(params: {
59
64
 
60
65
  return unique;
61
66
  }
67
+
68
+ const cronPropertyNames = new Set(["cronTrigger", "cronTriggers"]);
69
+
70
+ const isExported = (node: ts.Node): boolean => {
71
+ return Boolean(
72
+ ts.canHaveModifiers(node) &&
73
+ node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword)
74
+ );
75
+ };
76
+
77
+ const extractCronTriggers = (sourceText: string): Map<string, string[]> => {
78
+ const sourceFile = ts.createSourceFile(
79
+ "cron-handlers.ts",
80
+ sourceText,
81
+ ts.ScriptTarget.Latest,
82
+ true
83
+ );
84
+ const triggersByHandler = new Map<string, string[]>();
85
+
86
+ const visit = (node: ts.Node): void => {
87
+ if (ts.isVariableStatement(node) && isExported(node)) {
88
+ for (const decl of node.declarationList.declarations) {
89
+ if (!ts.isIdentifier(decl.name)) continue;
90
+ const initializer = decl.initializer;
91
+ if (!initializer || !ts.isCallExpression(initializer)) continue;
92
+ const callee = initializer.expression;
93
+ if (!ts.isIdentifier(callee) || callee.text !== "cron") continue;
94
+ const cronTriggers = parseCronTriggerArg(initializer.arguments[0]);
95
+ if (cronTriggers && cronTriggers.length > 0) {
96
+ triggersByHandler.set(decl.name.text, cronTriggers);
97
+ }
98
+ }
99
+ }
100
+ ts.forEachChild(node, visit);
101
+ };
102
+
103
+ visit(sourceFile);
104
+ return triggersByHandler;
105
+ };
106
+
107
+ const parseCronTriggerArg = (arg?: ts.Expression): string[] | undefined => {
108
+ if (!arg || !ts.isObjectLiteralExpression(arg)) return undefined;
109
+ const cronProp = arg.properties.find((prop) => {
110
+ if (!ts.isPropertyAssignment(prop)) return false;
111
+ if (ts.isIdentifier(prop.name))
112
+ return cronPropertyNames.has(prop.name.text);
113
+ if (ts.isStringLiteralLike(prop.name))
114
+ return cronPropertyNames.has(prop.name.text);
115
+ return false;
116
+ });
117
+
118
+ if (!cronProp || !ts.isPropertyAssignment(cronProp)) return undefined;
119
+ const value = cronProp.initializer;
120
+ const triggers: string[] = [];
121
+ if (ts.isArrayLiteralExpression(value)) {
122
+ for (const element of value.elements) {
123
+ if (ts.isStringLiteralLike(element)) {
124
+ triggers.push(element.text.trim());
125
+ }
126
+ }
127
+ } else if (ts.isStringLiteralLike(value)) {
128
+ triggers.push(value.text.trim());
129
+ }
130
+
131
+ const unique = Array.from(new Set(triggers.filter(Boolean)));
132
+ return unique.length > 0 ? unique : undefined;
133
+ };
@@ -4,3 +4,4 @@ export { generateApiClient } from "../generators/generate-api-client";
4
4
  export { generateHonoServer } from "../generators/generate-hono-server";
5
5
  export { generateWebsocketDurableObject } from "../generators/generate-websocket-durable-object";
6
6
  export { generateSchedulerHandlers } from "../generators/generate-scheduler-handlers";
7
+ export { generateCronHandlers } from "../generators/generate-cron-handlers";
@@ -2,9 +2,16 @@ import { resolveAllowedOrigins } from "./helpers";
2
2
 
3
3
  export function generateCloudflareWorkerIndex(params: {
4
4
  allowedOrigins?: string[];
5
+ hasCronHandlers?: boolean;
5
6
  }): string {
6
7
  const allowedOrigins = resolveAllowedOrigins(params.allowedOrigins);
7
8
  const allowedOriginsCsv = allowedOrigins.join(",");
9
+ const cronImports = params.hasCronHandlers
10
+ ? 'import { handleCron } from "./cron";\n'
11
+ : "";
12
+ const scheduledBlock = params.hasCronHandlers
13
+ ? `\n\tasync scheduled(controller, env, ctx): Promise<void> {\n\t\tawait handleCron({ controller, env, ctx });\n\t},\n`
14
+ : "";
8
15
 
9
16
  return `/* eslint-disable */
10
17
  /**
@@ -15,7 +22,7 @@ export function generateCloudflareWorkerIndex(params: {
15
22
  import { createAppflareHonoServer } from "./server";
16
23
  import { WebSocketHibernationServer } from "./websocket-hibernation-server";
17
24
  import { createScheduler, handleSchedulerBatch } from "./scheduler";
18
- import { getDatabase } from "cloudflare-do-mongo";
25
+ ${cronImports}import { getDatabase } from "cloudflare-do-mongo";
19
26
  import { MONGO_DURABLE_OBJECT } from "cloudflare-do-mongo/do";
20
27
  import type { Hono } from "hono";
21
28
  import { cors } from "hono/cors";
@@ -130,9 +137,10 @@ export default {
130
137
  return response;
131
138
  },
132
139
 
133
- async queue(batch, env): Promise<void> {
134
- await handleSchedulerBatch({ batch, env });
135
- },
140
+ ${scheduledBlock}
141
+ async queue(batch, env): Promise<void> {
142
+ await handleSchedulerBatch({ batch, env });
143
+ },
136
144
  } satisfies ExportedHandler<Env>;
137
145
 
138
146
  export { MONGO_DURABLE_OBJECT, WebSocketHibernationServer };
@@ -11,6 +11,7 @@ export function generateWranglerJson(params: {
11
11
  config: AppflareConfig;
12
12
  configDirAbs: string;
13
13
  allowedOrigins?: string[];
14
+ cronTriggers?: string[];
14
15
  }): string {
15
16
  const allowedOrigins = resolveAllowedOrigins(params.allowedOrigins);
16
17
  const bucketBinding =
@@ -82,5 +83,11 @@ export function generateWranglerJson(params: {
82
83
  };
83
84
  }
84
85
 
86
+ if (params.cronTriggers && params.cronTriggers.length > 0) {
87
+ wrangler.triggers = {
88
+ crons: Array.from(new Set(params.cronTriggers)),
89
+ };
90
+ }
91
+
85
92
  return `${JSON.stringify(wrangler, null, 2)}\n`;
86
93
  }
@@ -0,0 +1,2 @@
1
+ export const buildCronHandlersBlock = (handlerEntries: string): string =>
2
+ `const cronHandlers = {\n${handlerEntries}\n} as const;`;
@@ -0,0 +1,29 @@
1
+ import type { DiscoveredHandler } from "../../utils/utils";
2
+
3
+ const stringifyTriggers = (triggers?: string[]): string => {
4
+ if (!triggers || triggers.length === 0) return "[]";
5
+ return `[${triggers.map((value) => JSON.stringify(value)).join(", ")}]`;
6
+ };
7
+
8
+ export const buildCronHandlerEntries = (params: {
9
+ handlers: DiscoveredHandler[];
10
+ localNameFor: (handler: DiscoveredHandler) => string;
11
+ }): string => {
12
+ if (params.handlers.length === 0) return "";
13
+
14
+ return params.handlers
15
+ .map((handler) => {
16
+ const local = params.localNameFor(handler);
17
+ const task = `${handler.fileName}/${handler.name}`;
18
+ const fallbackTriggers = stringifyTriggers(handler.cronTriggers);
19
+ return (
20
+ `\t${JSON.stringify(task)}: {\n` +
21
+ `\t\tfile: ${JSON.stringify(handler.fileName)},\n` +
22
+ `\t\tname: ${JSON.stringify(handler.name)},\n` +
23
+ `\t\tcronTrigger: ${local}.cronTrigger ?? ${fallbackTriggers},\n` +
24
+ `\t\trun: ${local}.handler,\n` +
25
+ `\t},`
26
+ );
27
+ })
28
+ .join("\n");
29
+ };
@@ -0,0 +1,61 @@
1
+ import { buildImportSection } from "../generate-hono-server/imports";
2
+ import type { DiscoveredHandler } from "../../utils/utils";
3
+ import {
4
+ schemaTypesImportPath,
5
+ serverImportPath,
6
+ filePreamble,
7
+ } from "../generate-scheduler-handlers/constants";
8
+ import { buildCronHandlerEntries } from "./handler-entries";
9
+ import { buildCronHandlersBlock } from "./cron-handlers-block";
10
+ import { buildTypeHelpersBlock } from "./type-helpers-block";
11
+ import { buildRuntimeBlock } from "./runtime-block";
12
+
13
+ export function generateCronHandlers(params: {
14
+ handlers: DiscoveredHandler[];
15
+ outDirAbs: string;
16
+ schemaPathAbs: string;
17
+ configPathAbs: string;
18
+ }): { code: string; cronTriggers: string[] } {
19
+ const cronHandlers = params.handlers.filter(
20
+ (handler) => handler.kind === "cron"
21
+ );
22
+
23
+ const imports = buildImportSection({
24
+ handlers: cronHandlers,
25
+ outDirAbs: params.outDirAbs,
26
+ schemaPathAbs: params.schemaPathAbs,
27
+ configPathAbs: params.configPathAbs,
28
+ });
29
+
30
+ const handlerImportBlock =
31
+ imports.handlerImports.length > 0
32
+ ? `${imports.handlerImports.join("\n")}\n\n`
33
+ : "";
34
+
35
+ const handlerEntries = buildCronHandlerEntries({
36
+ handlers: cronHandlers,
37
+ localNameFor: imports.localNameFor,
38
+ });
39
+
40
+ const importsBlock = `import { createAppflareDbContext, type AppflareDbContext } from ${JSON.stringify(serverImportPath)};\nimport type {\n\tScheduler,\n\tSchedulerEnqueueOptions,\n\tSchedulerPayload,\n\tSchedulerTaskName,\n\tAppflareAuthContext,\n\tAppflareAuthSession,\n\tAppflareAuthUser,\n} from ${JSON.stringify(schemaTypesImportPath)};\nimport { createScheduler } from "./scheduler";\nimport { getDatabase } from "cloudflare-do-mongo";\nimport { Db } from "mongodb";\n${handlerImportBlock}`;
41
+
42
+ const code = [
43
+ filePreamble,
44
+ "",
45
+ importsBlock,
46
+ buildCronHandlersBlock(handlerEntries),
47
+ buildTypeHelpersBlock(),
48
+ buildRuntimeBlock(),
49
+ "export type { CronTaskName };\n",
50
+ ].join("\n");
51
+
52
+ const cronTriggers = Array.from(
53
+ new Set(
54
+ cronHandlers
55
+ .flatMap((handler) => handler.cronTriggers ?? [])
56
+ .filter((value): value is string => Boolean(value))
57
+ )
58
+ );
59
+
60
+ return { code, cronTriggers };
61
+ }
@@ -0,0 +1,49 @@
1
+ export const buildRuntimeBlock = (): string => `
2
+ const normalizeCronTrigger = (value: CronTriggerValue): string[] => {
3
+ if (!value) return [];
4
+ if (typeof value === "string") return [value.trim()].filter(Boolean);
5
+ return value.map((entry) => String(entry).trim()).filter(Boolean);
6
+ };
7
+
8
+ export async function handleCron(params: {
9
+ controller: { cron: string };
10
+ env: Env;
11
+ ctx: { waitUntil(promise: Promise<unknown>): void };
12
+ }): Promise<void> {
13
+ const { controller, env, ctx } = params;
14
+ const db = createAppflareDbContext({
15
+ db: getDatabase(env.MONGO_DB) as unknown as Db,
16
+ });
17
+
18
+ const scheduler = env.APPFLARE_SCHEDULER_QUEUE
19
+ ? createScheduler(env.APPFLARE_SCHEDULER_QUEUE)
20
+ : undefined;
21
+
22
+ const baseContext: CronHandlerContext = {
23
+ ...emptyAuthContext,
24
+ db,
25
+ env,
26
+ scheduler,
27
+ controller: {
28
+ cron: controller.cron,
29
+ scheduledTime: (controller as { scheduledTime?: number }).scheduledTime,
30
+ nextScheduledTime: (controller as { nextScheduledTime?: number })
31
+ .nextScheduledTime,
32
+ },
33
+ ctx,
34
+ };
35
+
36
+ const handlerLookup = cronHandlers as unknown as CronHandlerLookup;
37
+ const cronValue = controller.cron;
38
+
39
+ for (const [task, handler] of Object.entries(handlerLookup)) {
40
+ const triggers = normalizeCronTrigger(handler.cronTrigger);
41
+ if (!triggers.includes(cronValue)) continue;
42
+ try {
43
+ await handler.run(baseContext);
44
+ } catch (err) {
45
+ console.error("Cron task failed", task, err);
46
+ }
47
+ }
48
+ }
49
+ `;
@@ -0,0 +1,60 @@
1
+ export const buildTypeHelpersBlock = (): string => `
2
+ type CronHandlers = typeof cronHandlers;
3
+
4
+ type CronTaskName = keyof CronHandlers extends never
5
+ ? string
6
+ : keyof CronHandlers;
7
+
8
+ type CronTriggerValue = string | readonly string[] | undefined;
9
+
10
+ type Env = {
11
+ MONGO_DB: unknown;
12
+ APPFLARE_SCHEDULER_QUEUE?: {
13
+ send: (body: unknown, options?: { delaySeconds?: number }) => Promise<void>;
14
+ };
15
+ } & Record<string, unknown>;
16
+
17
+ const emptyAuthContext: AppflareAuthContext = {
18
+ session: null as AppflareAuthSession,
19
+ user: null as AppflareAuthUser,
20
+ };
21
+
22
+ type SchedulerEnqueue = {
23
+ <TTask extends SchedulerTaskName>(
24
+ task: TTask,
25
+ ...args: SchedulerPayload<TTask> extends undefined
26
+ ? [payload?: SchedulerPayload<TTask>, options?: SchedulerEnqueueOptions]
27
+ : [payload: SchedulerPayload<TTask>, options?: SchedulerEnqueueOptions]
28
+ ): Promise<void>;
29
+ };
30
+
31
+ type TypedScheduler = Omit<Scheduler, "enqueue"> & {
32
+ enqueue: SchedulerEnqueue;
33
+ };
34
+
35
+ type ScheduledController = {
36
+ cron: string;
37
+ scheduledTime?: number;
38
+ nextScheduledTime?: number;
39
+ };
40
+
41
+ type ExecutionContext = {
42
+ waitUntil(promise: Promise<unknown>): void;
43
+ };
44
+
45
+ type CronHandlerContext = AppflareAuthContext & {
46
+ db: AppflareDbContext;
47
+ env: Env;
48
+ scheduler?: TypedScheduler;
49
+ controller: ScheduledController;
50
+ ctx: ExecutionContext;
51
+ };
52
+
53
+ type CronHandlerLookup = Record<
54
+ string,
55
+ {
56
+ cronTrigger: CronTriggerValue;
57
+ run: (ctx: CronHandlerContext) => Promise<void>;
58
+ }
59
+ >;
60
+ `;
@@ -13,6 +13,8 @@ export const buildHandlerFileContent = (
13
13
  ): string => {
14
14
  const findFn = `find${params.pascalName}`;
15
15
  const findOneFn = `findOne${params.pascalName}`;
16
+ const sumFn = `sum${params.pascalName}`;
17
+ const avgFn = `avg${params.pascalName}`;
16
18
  const insertFn = `insert${params.pascalName}`;
17
19
  const updateFn = `update${params.pascalName}`;
18
20
  const deleteFn = `delete${params.pascalName}`;
@@ -26,7 +28,9 @@ import { z } from "zod";
26
28
  import {
27
29
  internalMutation,
28
30
  internalQuery,
31
+ type AppflareInclude,
29
32
  type EditableDoc,
33
+ type Doc,
30
34
  type Id,
31
35
  type QuerySort,
32
36
  type QueryWhere,
@@ -40,6 +44,8 @@ export const ${findFn} = internalQuery({
40
44
  .optional(),
41
45
  limit: z.number().int().nonnegative().optional(),
42
46
  offset: z.number().int().nonnegative().optional(),
47
+ include: z.custom<AppflareInclude<Doc<${JSON.stringify(params.tableName)}>>>()
48
+ .optional(),
43
49
  },
44
50
  handler: async (ctx, args) => {
45
51
  return ctx.db[${JSON.stringify(params.tableName)} as any].findMany({
@@ -47,6 +53,7 @@ export const ${findFn} = internalQuery({
47
53
  orderBy: args.sort as any,
48
54
  skip: args.offset,
49
55
  take: args.limit,
56
+ include: args.include as any,
50
57
  });
51
58
  },
52
59
  });
@@ -58,6 +65,8 @@ export const ${findOneFn} = internalQuery({
58
65
  sort: z.custom<QuerySort<${JSON.stringify(params.tableName)}>>()
59
66
  .optional(),
60
67
  offset: z.number().int().nonnegative().optional(),
68
+ include: z.custom<AppflareInclude<Doc<${JSON.stringify(params.tableName)}>>>()
69
+ .optional(),
61
70
  },
62
71
  handler: async (ctx, args) => {
63
72
  return ctx.db[${JSON.stringify(params.tableName)} as any].findFirst({
@@ -65,6 +74,45 @@ export const ${findOneFn} = internalQuery({
65
74
  orderBy: args.sort as any,
66
75
  skip: args.offset,
67
76
  take: 1,
77
+ include: args.include as any,
78
+ });
79
+ },
80
+ });
81
+
82
+ export const ${sumFn} = internalQuery({
83
+ args: {
84
+ fields: z.array(z.string()).min(1),
85
+ where: z.custom<QueryWhere<${JSON.stringify(params.tableName)}>>()
86
+ .optional(),
87
+ groupBy: z.union([z.string(), z.array(z.string())]).optional(),
88
+ populate: z.custom<AppflareInclude<Doc<${JSON.stringify(params.tableName)}>>>()
89
+ .optional(),
90
+ },
91
+ handler: async (ctx, args) => {
92
+ return ctx.db[${JSON.stringify(params.tableName)} as any].aggregate({
93
+ where: args.where as any,
94
+ groupBy: args.groupBy as any,
95
+ sum: args.fields as any,
96
+ populate: args.populate as any,
97
+ });
98
+ },
99
+ });
100
+
101
+ export const ${avgFn} = internalQuery({
102
+ args: {
103
+ fields: z.array(z.string()).min(1),
104
+ where: z.custom<QueryWhere<${JSON.stringify(params.tableName)}>>()
105
+ .optional(),
106
+ groupBy: z.union([z.string(), z.array(z.string())]).optional(),
107
+ populate: z.custom<AppflareInclude<Doc<${JSON.stringify(params.tableName)}>>>()
108
+ .optional(),
109
+ },
110
+ handler: async (ctx, args) => {
111
+ return ctx.db[${JSON.stringify(params.tableName)} as any].aggregate({
112
+ where: args.where as any,
113
+ groupBy: args.groupBy as any,
114
+ avg: args.fields as any,
115
+ populate: args.populate as any,
68
116
  });
69
117
  },
70
118
  });
@@ -121,11 +169,13 @@ export const ${deleteFn} = internalMutation({
121
169
  export const buildExportLine = (params: ExportLineParams): string => {
122
170
  const findFn = `find${params.pascalName}`;
123
171
  const findOneFn = `findOne${params.pascalName}`;
172
+ const sumFn = `sum${params.pascalName}`;
173
+ const avgFn = `avg${params.pascalName}`;
124
174
  const insertFn = `insert${params.pascalName}`;
125
175
  const updateFn = `update${params.pascalName}`;
126
176
  const deleteFn = `delete${params.pascalName}`;
127
177
 
128
- return `export { ${findFn}, ${findOneFn}, ${insertFn}, ${updateFn}, ${deleteFn} } from "./${params.tableName}";`;
178
+ return `export { ${findFn}, ${findOneFn}, ${sumFn}, ${avgFn}, ${insertFn}, ${updateFn}, ${deleteFn} } from "./${params.tableName}";`;
129
179
  };
130
180
 
131
181
  export const buildIndexFileContent = (
package/cli/index.ts CHANGED
@@ -12,6 +12,7 @@ import {
12
12
  generateHonoServer,
13
13
  generateWebsocketDurableObject,
14
14
  generateSchedulerHandlers,
15
+ generateCronHandlers,
15
16
  } from "./core/handlers";
16
17
  import {
17
18
  generateCloudflareWorkerIndex,
@@ -19,22 +20,11 @@ import {
19
20
  } from "./generators/generate-cloudflare-worker";
20
21
  import { generateSchemaTypes, getSchemaTableNames } from "./schema/schema";
21
22
  import { runTscEmit, writeEmitTsconfig } from "./utils/tsc";
22
- import { assertDirExists, assertFileExists } from "./utils/utils";
23
-
24
- type AppflareConfig = {
25
- dir: string;
26
- schema: string;
27
- outDir: string;
28
- /** Optional override for where wrangler.json is written. Resolved relative to the config file. */
29
- wranglerOutPath?: string;
30
- /** Optional override for wrangler.json main entrypoint. Defaults to ./server/index.ts in the generated worker. */
31
- wranglerMain?: string;
32
- corsOrigin?: string | string[];
33
- auth?: {
34
- enabled?: boolean;
35
- basePath?: string;
36
- };
37
- };
23
+ import {
24
+ AppflareConfig,
25
+ assertDirExists,
26
+ assertFileExists,
27
+ } from "./utils/utils";
38
28
 
39
29
  type WatchConfig = {
40
30
  targets: string[];
@@ -212,18 +202,33 @@ async function buildFromConfig(params: {
212
202
  schedulerTs
213
203
  );
214
204
 
205
+ const cronHandlersPresent = handlers.some(
206
+ (handler) => handler.kind === "cron"
207
+ );
208
+ const { code: cronTs, cronTriggers } = generateCronHandlers({
209
+ handlers,
210
+ outDirAbs,
211
+ schemaPathAbs,
212
+ configPathAbs,
213
+ });
214
+ await fs.writeFile(path.join(outDirAbs, "server", "cron.ts"), cronTs);
215
+
215
216
  const allowedOrigins = normalizeAllowedOrigins(
216
217
  process.env.APPFLARE_ALLOWED_ORIGINS ??
217
218
  config.corsOrigin ??
218
219
  "http://localhost:3000"
219
220
  );
220
- const workerIndexTs = generateCloudflareWorkerIndex({ allowedOrigins });
221
+ const workerIndexTs = generateCloudflareWorkerIndex({
222
+ allowedOrigins,
223
+ hasCronHandlers: cronHandlersPresent,
224
+ });
221
225
  await fs.writeFile(path.join(outDirAbs, "server", "index.ts"), workerIndexTs);
222
226
 
223
227
  const wranglerJson = generateWranglerJson({
224
228
  config,
225
229
  configDirAbs,
226
230
  allowedOrigins,
231
+ cronTriggers,
227
232
  });
228
233
  const wranglerOutPath =
229
234
  config.wranglerOutPath ?? path.join(config.outDir, "wrangler.json");