1import {
2 DEFAULT_COMPATIBILITY_DATE,
3 NODEJS_COMPAT_DEFAULT_ON,
4 withDefaultFlags,
5} from "@alchemy.run/cloudflare-runtime/core/internal/constants";
6import { isPythonMain } from "./Sources/Python.ts";
7import type { WorkerProps } from "./Worker.ts";
8
9/**
10 * The Effect worker bridge builds its layer stack once per isolate and shares
11 * the in-flight build promise across concurrent events. Awaiting a promise
12 * created under another event's request context is only sound with workerd's
13 * corrected cross-request promise semantics (default-on since compatibility
14 * date 2024-10-14): continuations are scheduled back into the promise's
15 * origin context instead of running in whichever request happens to resolve
16 * them. A user pinning an older compatibility date must not silently revert
17 * the bridge to the broken semantics, so the flag is forced for
18 * alchemy-bundled workers — and explicitly disabling it is a deploy-time
19 * error.
20 */
21const CROSS_REQUEST_PROMISE_RESOLUTION =
22 "handle_cross_request_promise_resolution";
23
24/**
25 * Compatibility settings passed to build tools and framework adapters.
26 * Cloudflare rejects a redundant `nodejs_compat` flag after its default-on
27 * date, but downstream tools may still detect Node support from the explicit
28 * flag only, so internal build configuration materializes the effective flag.
29 */
30export const getToolingCompatibility = (
31 compatibility: { date: string; flags: string[] },
32 main: unknown,
33) => ({
34 date: compatibility.date,
35 // TODO: Stop materializing `nodejs_compat` once supported downstream tools
36 // consistently derive the default from the compatibility date.
37 flags:
38 isPythonMain(main) ||
39 compatibility.date < NODEJS_COMPAT_DEFAULT_ON ||
40 compatibility.flags.includes("nodejs_compat") ||
41 compatibility.flags.includes("nodejs_compat_v2") ||
42 compatibility.flags.includes("no_nodejs_compat")
43 ? compatibility.flags
44 : [...compatibility.flags, "nodejs_compat"],
45});
46
47export const getCompatibility = (props: WorkerProps) => {
48 const userFlags = props.compatibility?.flags ?? [];
49 const python = isPythonMain(props.main);
50 if (python && !props.isExternal) {
51 throw new Error(
52 "Python Workers cannot have an inline Effect implementation: the " +
53 "Effect runtime is a JavaScript bundle and cannot be injected into " +
54 "a Pyodide Worker. Declare the Worker with only its props (the " +
55 "handlers live in the Python entry module).",
56 );
57 }
58 if (
59 !props.isExternal &&
60 userFlags.includes(`no_${CROSS_REQUEST_PROMISE_RESOLUTION}`)
61 ) {
62 throw new Error(
63 `The "no_${CROSS_REQUEST_PROMISE_RESOLUTION}" compatibility flag is not supported: ` +
64 "the alchemy Worker runtime shares its layer build across concurrent " +
65 "requests, which requires workerd's corrected cross-request promise " +
66 "semantics. Remove the flag from `compatibility.flags`.",
67 );
68 }
69 const date = props.compatibility?.date ?? DEFAULT_COMPATIBILITY_DATE;
70 return {
71 date,
72 flags: withDefaultFlags(props.compatibility?.flags, {
73 date,
74 python,
75 isExternal: props.isExternal ?? false,
76 bundle: props.bundle,
77 }),
78 };
79};