Skip to content
File

Blob: src/workerd/api/export-loopback.h

cpp222 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 "actor.h"
8#include "http.h"
9 
10#include <workerd/io/io-channels.h>
11 
12namespace workerd::api {
13 
14// LoopbackServiceStub is the type of a property of `ctx.exports` which points back at a stateless
15// (non-actor) entrypoint of this Worker. It can be used as a regular Fetcher to make calls to that
16// entrypoint with empty props. It can also be invoked as a function in order to specialize it with
17// props and make it available for RPC.
18class LoopbackServiceStub: public Fetcher {
19 public:
20 // Loopback services are always represented by numbered subrequest channels.
21 explicit LoopbackServiceStub(uint channel)
22 : Fetcher(channel, RequiresHostAndProtocol::YES, /*isInHouse=*/true),
23 channel(channel) {}
24 
25 struct Options {
26 jsg::Optional<jsg::JsRef<jsg::JsObject>> props;
27 
28 JSG_STRUCT(props);
29 };
30 
31 struct OptionsWithVersion {
32 struct Version {
33 jsg::Optional<kj::Maybe<kj::String>> cohort;
34 
35 JSG_STRUCT(cohort);
36 };
37 
38 jsg::Optional<jsg::JsRef<jsg::JsObject>> props;
39 jsg::Optional<Version> version;
40 
41 JSG_STRUCT(props, version);
42 };
43 
44 // Create a specialized Fetcher which can be passed over RPC.
45 jsg::Ref<Fetcher> callImpl(jsg::Lock& js,
46 jsg::Optional<jsg::JsRef<jsg::JsObject>> propsMaybe,
47 jsg::Optional<OptionsWithVersion::Version> versionMaybe);
48 
49 jsg::Ref<Fetcher> call(jsg::Lock& js, Options options) {
50 return callImpl(js, kj::mv(options.props), kj::none);
51 }
52 
53 jsg::Ref<Fetcher> callWithVersion(jsg::Lock& js, OptionsWithVersion options) {
54 return callImpl(js, kj::mv(options.props), kj::mv(options.version));
55 }
56 
57 // Note that `LoopbackServiceStub` is intentionally NOT serializable, unlike its parent class
58 // Fetcher. We want people to explicitly specialize the entrypoint with props before sending
59 // it off to other services.
60 
61 JSG_RESOURCE_TYPE(LoopbackServiceStub, CompatibilityFlags::Reader flags) {
62 JSG_INHERIT(Fetcher);
63 
64 if (flags.getEnableVersionApi()) {
65 JSG_CALLABLE(callWithVersion);
66 } else {
67 JSG_CALLABLE(call);
68 }
69 
70 JSG_TS_ROOT();
71 
72 if (flags.getEnableVersionApi()) {
73 JSG_TS_OVERRIDE(
74 type LoopbackServiceStub<
75 T extends Rpc.WorkerEntrypointBranded | undefined = undefined
76 > = Fetcher<T> &
77 ( T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>
78 ? (opts: {props?: Props, version?: { cohort?: string | null }}) => Fetcher<T>
79 : (opts: {props?: any, version?: { cohort?: string | null }}) => Fetcher<T>);
80 );
81 } else {
82 JSG_TS_OVERRIDE(
83 type LoopbackServiceStub<
84 T extends Rpc.WorkerEntrypointBranded | undefined = undefined
85 > = Fetcher<T> &
86 ( T extends CloudflareWorkersModule.WorkerEntrypoint<any, infer Props>
87 ? (opts: {props?: Props}) => Fetcher<T>
88 : (opts: {props?: any}) => Fetcher<T>);
89 );
90 }
91 
92 // LoopbackForExport takes the type of an exported value and evaluates to the appropriate
93 // loopback stub for that export.
94 JSG_TS_DEFINE(
95 type LoopbackForExport<
96 T extends
97 | (new (...args: any[]) => Rpc.EntrypointBranded)
98 | ExportedHandler<any, any, any>
99 | undefined = undefined
100 > = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? LoopbackServiceStub<InstanceType<T>>
101 : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? LoopbackDurableObjectClass<InstanceType<T>>
102 : T extends ExportedHandler<any, any, any> ? LoopbackServiceStub<undefined>
103 : undefined;
104 );
105 }
106 
107 private:
108 uint channel;
109};
110 
111// Similar to LoopbackServiceStub, but for actor classes.
112//
113// Specifically, this is used for actor classes that do *not* have any storage configured. If you
114// simply export a class extending `DurableObject` but you don't configure storage for it, it shows
115// up in `ctx.exports` as this type. This can be used to create a Durable Object facet.
116class LoopbackDurableObjectClass: public DurableObjectClass {
117 public:
118 LoopbackDurableObjectClass(uint channel): DurableObjectClass(channel), channel(channel) {}
119 
120 struct Options {
121 jsg::Optional<jsg::JsRef<jsg::JsObject>> props;
122 
123 JSG_STRUCT(props);
124 };
125 
126 // Create a specialized DurableObjectClass which can be passed over RPC.
127 jsg::Ref<DurableObjectClass> call(jsg::Lock& js, Options options);
128 
129 JSG_RESOURCE_TYPE(LoopbackDurableObjectClass) {
130 JSG_INHERIT(DurableObjectClass);
131 JSG_CALLABLE(call);
132 
133 JSG_TS_ROOT();
134 JSG_TS_OVERRIDE(
135 type LoopbackDurableObjectClass<
136 T extends
137 | Rpc.DurableObjectBranded
138 | undefined = undefined
139 > = DurableObjectClass<T> &
140 ( T extends CloudflareWorkersModule.DurableObject<any, infer Props>
141 ? (opts: {props?: Props}) => DurableObjectClass<T>
142 : (opts: {props?: any}) => DurableObjectClass<T>);
143 );
144 }
145 
146 private:
147 uint channel;
148};
149 
150// LoopbackDurableObjectNamespace is similar to LoopbackDurableObjectClass, but used when the
151// class has storage configured. In this case, we want a binding that behaves *both* like a
152// LoopbackDurableObjectClass *and* like a DurableObjectNamespace binding. Easy enough, we'll
153// inherit DurableObjectNamespace, but also make the binding invokable as a function like
154// LoopbackDurableObjectClass.
155class LoopbackDurableObjectNamespace: public DurableObjectNamespace {
156 public:
157 LoopbackDurableObjectNamespace(uint nsChannel,
158 kj::Own<ActorIdFactory> idFactory,
159 jsg::Ref<LoopbackDurableObjectClass> loopbackClass)
160 : DurableObjectNamespace(nsChannel, kj::mv(idFactory)),
161 loopbackClass(kj::mv(loopbackClass)) {}
162 
163 // getClass() accessor for use from C++ only.
164 LoopbackDurableObjectClass& getClass() {
165 return *loopbackClass.get();
166 }
167 
168 // Invoking the binding creates a specialization of the class -- not the namespace.
169 jsg::Ref<DurableObjectClass> call(jsg::Lock& js, LoopbackDurableObjectClass::Options options) {
170 return loopbackClass->call(js, kj::mv(options));
171 }
172 
173 // If `DurableObjectNamespace` ever becomes serializable, we actually don't want to block
174 // serialization here, the way we want to for `LoopbackDurableObjectClass`, because actually
175 // serializing the loopback namespace would mean serializing the namespace stub, *not* the
176 // class stub. They are different things, and you might want to serialize either one.
177 
178 JSG_RESOURCE_TYPE(LoopbackDurableObjectNamespace) {
179 JSG_INHERIT(DurableObjectNamespace);
180 JSG_CALLABLE(call);
181 }
182 
183 private:
184 jsg::Ref<LoopbackDurableObjectClass> loopbackClass;
185};
186 
187// Like LoopbackDurableObjectNamespace, but for colo-local (ephemeral) actor namespaces.
188class LoopbackColoLocalActorNamespace: public ColoLocalActorNamespace {
189 public:
190 LoopbackColoLocalActorNamespace(
191 uint nsChannel, jsg::Ref<LoopbackDurableObjectClass> loopbackClass)
192 : ColoLocalActorNamespace(nsChannel),
193 loopbackClass(kj::mv(loopbackClass)) {}
194 
195 // getClass() accessor for use from C++ only.
196 LoopbackDurableObjectClass& getClass() {
197 return *loopbackClass.get();
198 }
199 
200 // Invoking the binding creates a specialization of the class -- not the namespace.
201 jsg::Ref<DurableObjectClass> call(jsg::Lock& js, LoopbackDurableObjectClass::Options options) {
202 return loopbackClass->call(js, kj::mv(options));
203 }
204 
205 JSG_RESOURCE_TYPE(LoopbackColoLocalActorNamespace) {
206 JSG_INHERIT(ColoLocalActorNamespace);
207 JSG_CALLABLE(call);
208 }
209 
210 private:
211 jsg::Ref<LoopbackDurableObjectClass> loopbackClass;
212};
213 
214#define EW_EXPORT_LOOPBACK_ISOLATE_TYPES \
215 api::LoopbackServiceStub, api::LoopbackServiceStub::Options, \
216 api::LoopbackServiceStub::OptionsWithVersion, \
217 api::LoopbackServiceStub::OptionsWithVersion::Version, api::LoopbackDurableObjectClass, \
218 api::LoopbackDurableObjectClass::Options, api::LoopbackDurableObjectNamespace, \
219 api::LoopbackColoLocalActorNamespace
220 
221} // namespace workerd::api