File
Blob: src/workerd/api/workers-module.h
| 1 | // Copyright (c) 2017-2025 Cloudflare, Inc. |
| 2 | // Licensed under the Apache 2.0 license found in the LICENSE file or at: |
| 3 | // https://opensource.org/licenses/Apache-2.0 |
| 4 | |
| 5 | #pragma once |
| 6 | |
| 7 | #include <workerd/api/http.h> |
| 8 | #include <workerd/api/worker-rpc.h> |
| 9 | |
| 10 | namespace workerd::api { |
| 11 | |
| 12 | class CacheContext; |
| 13 | |
| 14 | // Base class for exported RPC services. |
| 15 | // |
| 16 | // When the worker's top-level module exports a class that extends this class, it means that it |
| 17 | // is a stateless service. |
| 18 | // |
| 19 | // import {WorkerEntrypoint} from "cloudflare:workers"; |
| 20 | // export class MyService extends WorkerEntrypoint { |
| 21 | // async fetch(req) { ... } |
| 22 | // async someRpcMethod(a, b) { ... } |
| 23 | // } |
| 24 | // |
| 25 | // `env` and `ctx` are automatically available as `this.env` and `this.ctx`, without the need to |
| 26 | // define a constructor. |
| 27 | class WorkerEntrypoint: public jsg::Object { |
| 28 | public: |
| 29 | static jsg::Ref<WorkerEntrypoint> constructor( |
| 30 | const v8::FunctionCallbackInfo<v8::Value>& args, jsg::JsObject ctx, jsg::JsObject env); |
| 31 | |
| 32 | JSG_RESOURCE_TYPE(WorkerEntrypoint) {} |
| 33 | }; |
| 34 | |
| 35 | // Like WorkerEntrypoint, but this is the base class for Durable Object classes. |
| 36 | // |
| 37 | // Note that the name of this class as seen by JavaScript is `DurableObject`, but using that name |
| 38 | // in C++ would conflict with the type name currently used by DO stubs. |
| 39 | // TODO(cleanup): Rename DO stubs to `DurableObjectStub`? |
| 40 | // |
| 41 | // Historically, DO classes were not expected to inherit anything. However, this made it impossible |
| 42 | // to tell whether an exported class was intended to be a DO class vs. something else. Originally |
| 43 | // there were no other kinds of exported classes so this was fine. Going forward, we encourage |
| 44 | // everyone to be explicit by inheriting this, and we require it if you want to use RPC. |
| 45 | class DurableObjectBase: public jsg::Object { |
| 46 | public: |
| 47 | static jsg::Ref<DurableObjectBase> constructor(const v8::FunctionCallbackInfo<v8::Value>& args, |
| 48 | jsg::Ref<DurableObjectState> ctx, |
| 49 | jsg::JsObject env); |
| 50 | |
| 51 | JSG_RESOURCE_TYPE(DurableObjectBase) {} |
| 52 | }; |
| 53 | |
| 54 | // Base class for Workflows |
| 55 | // |
| 56 | // When the worker's top-level module exports a class that extends this class, it means that it |
| 57 | // is a Workflow. |
| 58 | // |
| 59 | // import { WorkflowEntrypoint } from "cloudflare:workers"; |
| 60 | // export class MyWorkflow extends WorkflowEntrypoint { |
| 61 | // async run(batch, fns) { ... } |
| 62 | // } |
| 63 | // |
| 64 | // `env` and `ctx` are automatically available as `this.env` and `this.ctx`, without the need to |
| 65 | // define a constructor. |
| 66 | class WorkflowEntrypoint: public jsg::Object { |
| 67 | public: |
| 68 | static jsg::Ref<WorkflowEntrypoint> constructor(const v8::FunctionCallbackInfo<v8::Value>& args, |
| 69 | jsg::Ref<ExecutionContext> ctx, |
| 70 | jsg::JsObject env); |
| 71 | |
| 72 | JSG_RESOURCE_TYPE(WorkflowEntrypoint) {} |
| 73 | }; |
| 74 | |
| 75 | // The "cloudflare:workers" module, which exposes the WorkerEntrypoint, WorkflowEntrypoint and DurableObject types |
| 76 | // for extending. |
| 77 | class EntrypointsModule: public jsg::Object { |
| 78 | public: |
| 79 | EntrypointsModule() = default; |
| 80 | EntrypointsModule(jsg::Lock&, const jsg::Url&) {} |
| 81 | |
| 82 | void waitUntil(kj::Promise<void> promise); |
| 83 | |
| 84 | // Returns the current request's CacheContext (ctx.cache), or kj::none if there is no active |
| 85 | // IoContext or cache is not available. Used by the cloudflare:workers TypeScript wrapper to |
| 86 | // expose an importable `cache` proxy. |
| 87 | jsg::Optional<jsg::Ref<CacheContext>> getCtxCache(jsg::Lock& js); |
| 88 | |
| 89 | // Immediately condemn and terminate the current isolate. In workerd for now this just aborts the |
| 90 | // process. |
| 91 | void abortIsolate(jsg::Lock& js, jsg::Optional<kj::String> reason); |
| 92 | |
| 93 | // Returns whether the workerd_experimental compat flag is enabled. Exposed on the internal |
| 94 | // module so user-facing wrappers in cloudflare:workers can gate experimental APIs without |
| 95 | // relying on Cloudflare.compatibilityFlags (which filters out experimental flags themselves). |
| 96 | bool getIsExperimental(jsg::Lock& js); |
| 97 | |
| 98 | JSG_RESOURCE_TYPE(EntrypointsModule, CompatibilityFlags::Reader flags) { |
| 99 | JSG_NESTED_TYPE(WorkerEntrypoint); |
| 100 | JSG_NESTED_TYPE(WorkflowEntrypoint); |
| 101 | JSG_NESTED_TYPE_NAMED(DurableObjectBase, DurableObject); |
| 102 | JSG_NESTED_TYPE_NAMED(JsRpcPromise, RpcPromise); |
| 103 | JSG_NESTED_TYPE_NAMED(JsRpcProperty, RpcProperty); |
| 104 | JSG_NESTED_TYPE_NAMED(JsRpcStub, RpcStub); |
| 105 | JSG_NESTED_TYPE_NAMED(JsRpcTarget, RpcTarget); |
| 106 | JSG_NESTED_TYPE_NAMED(Fetcher, ServiceStub); |
| 107 | |
| 108 | JSG_METHOD(waitUntil); |
| 109 | JSG_METHOD(getCtxCache); |
| 110 | |
| 111 | // abortIsolate: |
| 112 | // |
| 113 | // From user code only usable with experimental set for now. |
| 114 | // The Python runtime wants to use it directly. |
| 115 | // |
| 116 | // So we always expose it to internal JS for the Python runtime, but the |
| 117 | // version exposed to user code checks this isExperimental flag and throws |
| 118 | // if it returns false. |
| 119 | // |
| 120 | // TODO: Clean up when we remove the experimental gate on abortIsolate. |
| 121 | JSG_METHOD(abortIsolate); |
| 122 | JSG_READONLY_PROTOTYPE_PROPERTY(isExperimental, getIsExperimental); |
| 123 | } |
| 124 | }; |
| 125 | |
| 126 | #define EW_WORKERS_MODULE_ISOLATE_TYPES \ |
| 127 | api::WorkerEntrypoint, api::WorkflowEntrypoint, api::DurableObjectBase, api::EntrypointsModule |
| 128 | |
| 129 | template <class Registry> |
| 130 | void registerWorkersModule(Registry& registry, CompatibilityFlags::Reader flags) { |
| 131 | registry.template addBuiltinModule<EntrypointsModule>( |
| 132 | "cloudflare-internal:workers", workerd::jsg::ModuleRegistry::Type::INTERNAL); |
| 133 | } |
| 134 | |
| 135 | template <typename TypeWrapper> |
| 136 | kj::Own<jsg::modules::ModuleBundle> getInternalRpcModuleBundle(auto featureFlags) { |
| 137 | jsg::modules::ModuleBundle::BuiltinBuilder builder( |
| 138 | jsg::modules::ModuleBundle::BuiltinBuilder::Type::BUILTIN_ONLY); |
| 139 | static const auto kSpecifier = "cloudflare-internal:workers"_url; |
| 140 | builder.addObject<EntrypointsModule, TypeWrapper>(kSpecifier); |
| 141 | return builder.finish(); |
| 142 | } |
| 143 | }; // namespace workerd::api |