Skip to content
File

Blob: src/workerd/api/workers-module.h

cpp144 lines
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 
10namespace workerd::api {
11 
12class 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.
27class 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.
45class 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.
66class 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.
77class 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 
129template <class Registry>
130void registerWorkersModule(Registry& registry, CompatibilityFlags::Reader flags) {
131 registry.template addBuiltinModule<EntrypointsModule>(
132 "cloudflare-internal:workers", workerd::jsg::ModuleRegistry::Type::INTERNAL);
133}
134 
135template <typename TypeWrapper>
136kj::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