| 1 | import { |
| 2 | DEFAULT_COMPATIBILITY_DATE, |
| 3 | NODEJS_COMPAT_DEFAULT_ON, |
| 4 | withDefaultFlags, |
| 5 | } from "@alchemy.run/cloudflare-runtime/core/internal/constants"; |
| 6 | import { isPythonMain } from "./Sources/Python.ts"; |
| 7 | import 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 | */ |
| 21 | const 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 | */ |
| 30 | export 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 | |
| 47 | export 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 | }; |