Skip to content
File

Blob: src/workerd/api/actor.h

cpp395 lines
1// Copyright (c) 2017-2022 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// "Actors" are the internal name for Durable Objects, because they implement a sort of actor
7// model. We ended up not calling the product "Actors" publicly because we found that people who
8// were familiar with actor-model programming were more confused than helped by it -- they tended
9// to expect something that looked more specifically like Erlang, whereas our actors are much more
10// abstractly related.
11 
12#include <workerd/api/http.h>
13#include <workerd/io/actor-id.h>
14#include <workerd/io/worker-interface.capnp.h>
15#include <workerd/jsg/jsg.h>
16 
17namespace workerd {
18template <typename T>
19class IoOwn;
20}
21 
22namespace workerd::api {
23 
24// A capability to an ephemeral Actor namespace.
25class ColoLocalActorNamespace: public jsg::Object {
26 public:
27 ColoLocalActorNamespace(uint channel): channel(channel) {}
28 
29 jsg::Ref<Fetcher> get(jsg::Lock& js, kj::String actorId);
30 
31 JSG_RESOURCE_TYPE(ColoLocalActorNamespace) {
32 JSG_METHOD(get);
33 }
34 
35 private:
36 uint channel;
37};
38 
39class DurableObjectNamespace;
40 
41// DurableObjectId type seen by JavaScript.
42class DurableObjectId: public jsg::Object {
43 public:
44 DurableObjectId(kj::Own<ActorIdFactory::ActorId> id): id(kj::mv(id)) {}
45 
46 const ActorIdFactory::ActorId& getInner() {
47 return *id;
48 }
49 
50 // ---------------------------------------------------------------------------
51 // JS API
52 
53 // Converts to a string which can be passed back to the constructor to reproduce the same ID.
54 kj::String toString();
55 
56 inline bool equals(DurableObjectId& other) {
57 return id->equals(*other.id);
58 }
59 
60 // Get the name, if known.
61 inline jsg::Optional<kj::StringPtr> getName() {
62 return id->getName();
63 }
64 
65 jsg::Optional<kj::StringPtr> getJurisdiction() {
66 return id->getJurisdiction();
67 }
68 
69 JSG_RESOURCE_TYPE(DurableObjectId) {
70 JSG_METHOD(toString);
71 JSG_METHOD(equals);
72 JSG_READONLY_INSTANCE_PROPERTY(name, getName);
73 JSG_READONLY_INSTANCE_PROPERTY(jurisdiction, getJurisdiction);
74 }
75 
76 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
77 tracker.trackFieldWithSize("id", sizeof(ActorIdFactory::ActorId));
78 }
79 
80 private:
81 kj::Own<ActorIdFactory::ActorId> id;
82 
83 friend class DurableObjectNamespace;
84};
85 
86// Stub object used to send messages to a remote durable object.
87class DurableObject final: public Fetcher {
88 public:
89 DurableObject(jsg::Ref<DurableObjectId> id,
90 IoOwn<OutgoingFactory> outgoingFactory,
91 RequiresHostAndProtocol requiresHost)
92 : Fetcher(kj::mv(outgoingFactory), requiresHost, true /* isInHouse */),
93 id(kj::mv(id)) {}
94 
95 jsg::Ref<DurableObjectId> getId() {
96 return id.addRef();
97 }
98 
99 jsg::Optional<kj::StringPtr> getName() {
100 return id->getName();
101 }
102 
103 JSG_RESOURCE_TYPE(DurableObject) {
104 JSG_INHERIT(Fetcher);
105 
106 JSG_READONLY_INSTANCE_PROPERTY(id, getId);
107 JSG_READONLY_INSTANCE_PROPERTY(name, getName);
108 
109 JSG_TS_DEFINE(interface DurableObject {
110 fetch(request: Request): Response | Promise<Response>;
111 connect?(socket: Socket): void | Promise<void>;
112 alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise<void>;
113 webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise<void>;
114 webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise<void>;
115 webSocketError?(ws: WebSocket, error: unknown): void | Promise<void>;
116 });
117 JSG_TS_OVERRIDE(
118 type DurableObjectStub<T extends Rpc.DurableObjectBranded | undefined = undefined> =
119 Fetcher<T, "alarm" | "connect" | "webSocketMessage" | "webSocketClose" | "webSocketError">
120 & {
121 readonly id: DurableObjectId;
122 readonly name?: string;
123 }
124 );
125 // Rename this resource type to DurableObjectStub, and make DurableObject
126 // the interface implemented by users' Durable Object classes.
127 }
128 
129 void visitForMemoryInfo(jsg::MemoryTracker& tracker) const {
130 tracker.trackField("id", id);
131 }
132 
133 private:
134 jsg::Ref<DurableObjectId> id;
135 
136 void visitForGc(jsg::GcVisitor& visitor) {
137 visitor.visit(id);
138 }
139};
140 
141// Global durable object class binding type.
142class DurableObjectNamespace: public jsg::Object {
143 public:
144 // Instead of providing a channel ID, the caller can pass a factory object. This is used in cases
145 // where a DurableObjectNamespace is constructed dynamically within an execution context, rather
146 // than being a long-lived binding.
147 class ActorChannelFactory: public kj::Refcounted {
148 public:
149 virtual kj::Own<IoChannelFactory::ActorChannel> getGlobalActor(
150 const ActorIdFactory::ActorId& id,
151 kj::Maybe<kj::String> locationHint,
152 ActorGetMode mode,
153 bool enableReplicaRouting,
154 ActorRoutingMode routingMode,
155 SpanParent parentSpan,
156 kj::Maybe<ActorVersion> version) = 0;
157 };
158 
159 DurableObjectNamespace(uint channel, kj::Own<ActorIdFactory> idFactory)
160 : channel(channel),
161 idFactory(kj::mv(idFactory)) {}
162 DurableObjectNamespace(IoOwn<ActorChannelFactory> factory, kj::Own<ActorIdFactory> idFactory)
163 : channel(kj::mv(factory)),
164 idFactory(kj::mv(idFactory)) {}
165 
166 struct NewUniqueIdOptions {
167 // Restricts the new unique ID to a set of colos within a jurisdiction.
168 jsg::Optional<kj::Maybe<kj::String>> jurisdiction;
169 
170 JSG_STRUCT(jurisdiction);
171 
172 JSG_STRUCT_TS_DEFINE(type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high");
173 // Possible values from https://developers.cloudflare.com/workers/runtime-apis/durable-objects/#restricting-objects-to-a-jurisdiction
174 JSG_STRUCT_TS_OVERRIDE({
175 jurisdiction?: DurableObjectJurisdiction;
176 });
177 };
178 
179 // Create a new unique ID for a durable object that will be allocated nearby the calling colo.
180 jsg::Ref<DurableObjectId> newUniqueId(jsg::Lock& js, jsg::Optional<NewUniqueIdOptions> options);
181 
182 // Create a name-derived ID. Passing in the same `name` (to the same class) will always
183 // produce the same ID.
184 jsg::Ref<DurableObjectId> idFromName(jsg::Lock& js, kj::String name);
185 
186 // Create a DurableObjectId from the stringified form of the ID (as produced by calling
187 // `toString()` on a durable object ID). Throws if the ID is not a 64-digit hex number, or if the
188 // ID was not originally created for this class.
189 //
190 // The ID may be one that was originally created using either `newUniqueId()` or `idFromName()`.
191 jsg::Ref<DurableObjectId> idFromString(jsg::Lock& js, kj::String id);
192 
193 struct GetDurableObjectOptions {
194 jsg::Optional<kj::String> locationHint;
195 // `routingMode` may be be of interest to applications using Durable Objects replicas. It can be
196 // one of the following options:
197 // - none: the default, indicates we will pick for the application.
198 // - "primary-only": guarantees we route directly to the primary (skip any replicas).
199 jsg::Optional<kj::String> routingMode;
200 
201 struct VersionOptions {
202 jsg::Optional<kj::String> cohort;
203 JSG_STRUCT(cohort);
204 JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {
205 if (!flags.getWorkerdExperimental()) {
206 JSG_TS_OVERRIDE(type VersionOptions = never);
207 }
208 }
209 };
210 jsg::Optional<VersionOptions> version;
211 
212 JSG_STRUCT(locationHint, routingMode, version);
213 
214 // DurableObjectLocationHint values from https://developers.cloudflare.com/workers/runtime-apis/durable-objects/#providing-a-location-hint
215 JSG_STRUCT_TS_DEFINE(
216 type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me";
217 type DurableObjectRoutingMode = "primary-only");
218 
219 JSG_STRUCT_TS_OVERRIDE_DYNAMIC(CompatibilityFlags::Reader flags) {
220 if (flags.getWorkerdExperimental()) {
221 JSG_TS_OVERRIDE({
222 locationHint?: DurableObjectLocationHint;
223 routingMode?: DurableObjectRoutingMode;
224 version?: { cohort?: string };
225 });
226 } else {
227 JSG_TS_OVERRIDE({
228 locationHint?: DurableObjectLocationHint;
229 routingMode?: DurableObjectRoutingMode;
230 version: never;
231 });
232 }
233 }
234 };
235 
236 // Gets a durable object by ID or creates it if it doesn't already exist.
237 jsg::Ref<DurableObject> get(
238 jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options);
239 
240 // Gets a durable object by name or creates it if it doesn't already exist.
241 //
242 // Short for `idFromName()` followed by `get()`.
243 jsg::Ref<DurableObject> getByName(
244 jsg::Lock& js, kj::String name, jsg::Optional<GetDurableObjectOptions> options);
245 
246 // Experimental. Gets a durable object by ID if it already exists. Currently, gated for use
247 // by cloudflare only.
248 jsg::Ref<DurableObject> getExisting(
249 jsg::Lock& js, jsg::Ref<DurableObjectId> id, jsg::Optional<GetDurableObjectOptions> options);
250 
251 // Creates a subnamespace with the jurisdiction hardcoded.
252 jsg::Ref<DurableObjectNamespace> jurisdiction(
253 jsg::Lock& js, jsg::Optional<kj::Maybe<kj::String>> maybeJurisdiction);
254 
255 JSG_RESOURCE_TYPE(DurableObjectNamespace, CompatibilityFlags::Reader flags) {
256 JSG_METHOD(newUniqueId);
257 JSG_METHOD(idFromName);
258 JSG_METHOD(idFromString);
259 JSG_METHOD(get);
260 JSG_METHOD(getByName);
261 if (flags.getDurableObjectGetExisting()) {
262 JSG_METHOD(getExisting);
263 }
264 JSG_METHOD(jurisdiction);
265 
266 JSG_TS_ROOT();
267 if (flags.getDurableObjectGetExisting()) {
268 JSG_TS_OVERRIDE(<T extends Rpc.DurableObjectBranded | undefined = undefined> {
269 get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
270 getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
271 getExisting(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
272 jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;
273 });
274 } else {
275 JSG_TS_OVERRIDE(<T extends Rpc.DurableObjectBranded | undefined = undefined> {
276 get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
277 getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub<T>;
278 jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace<T>;
279 });
280 }
281 }
282 
283 private:
284 kj::OneOf<uint, IoOwn<ActorChannelFactory>> channel;
285 kj::Own<ActorIdFactory> idFactory;
286 
287 jsg::Ref<DurableObject> getImpl(jsg::Lock& js,
288 ActorGetMode mode,
289 jsg::Ref<DurableObjectId> id,
290 jsg::Optional<GetDurableObjectOptions> options);
291};
292 
293class GlobalActorOutgoingFactory final: public Fetcher::OutgoingFactory {
294 public:
295 using ChannelIdOrFactory = kj::OneOf<uint, kj::Own<DurableObjectNamespace::ActorChannelFactory>>;
296 
297 GlobalActorOutgoingFactory(ChannelIdOrFactory channelIdOrFactory,
298 jsg::Ref<DurableObjectId> id,
299 kj::Maybe<kj::String> locationHint,
300 ActorGetMode mode,
301 bool enableReplicaRouting,
302 ActorRoutingMode routingMode,
303 kj::Maybe<ActorVersion> version)
304 : channelIdOrFactory(kj::mv(channelIdOrFactory)),
305 id(kj::mv(id)),
306 locationHint(kj::mv(locationHint)),
307 mode(mode),
308 enableReplicaRouting(enableReplicaRouting),
309 routingMode(routingMode),
310 version(kj::mv(version)) {}
311 
312 kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;
313 
314 private:
315 ChannelIdOrFactory channelIdOrFactory;
316 jsg::Ref<DurableObjectId> id;
317 kj::Maybe<kj::String> locationHint;
318 ActorGetMode mode;
319 bool enableReplicaRouting;
320 ActorRoutingMode routingMode;
321 kj::Maybe<ActorVersion> version;
322 kj::Maybe<kj::Own<IoChannelFactory::ActorChannel>> actorChannel;
323};
324 
325// Like `GlobalActorOutgoingFactory`, but for colo-local actors
326class LocalActorOutgoingFactory final: public Fetcher::OutgoingFactory {
327 public:
328 LocalActorOutgoingFactory(uint channelId, kj::String actorId)
329 : channelId(channelId),
330 actorId(kj::mv(actorId)) {}
331 
332 kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;
333 
334 private:
335 uint channelId;
336 kj::String actorId;
337 kj::Maybe<kj::Own<IoChannelFactory::ActorChannel>> actorChannel;
338};
339 
340// Like `GlobalActorOutgoingFactory`, but only used for creating a stub to the primary DO so the
341// stub can be given to a replica.
342//
343// The main distinction here is we already have the capability to the primary, so we don't need to
344// make an outgoing request to set things up.
345class ReplicaActorOutgoingFactory final: public Fetcher::OutgoingFactory {
346 public:
347 ReplicaActorOutgoingFactory(kj::Own<IoChannelFactory::ActorChannel> channel, kj::String actorId)
348 : actorChannel(kj::mv(channel)),
349 actorId(kj::mv(actorId)) {}
350 
351 kj::Own<WorkerInterface> newSingleUseClient(kj::Maybe<kj::String> cfStr) override;
352 
353 private:
354 kj::Own<IoChannelFactory::ActorChannel> actorChannel;
355 kj::String actorId;
356};
357 
358// DurableObjectClass represents a binding to a Durable Object class that can be used
359// as a facet. The only use of this type is to pass to `ctx.facets.get()`.
360class DurableObjectClass: public jsg::Object {
361 public:
362 DurableObjectClass(uint channel): channel(channel) {}
363 DurableObjectClass(IoOwn<IoChannelFactory::ActorClassChannel> channel)
364 : channel(kj::mv(channel)) {}
365 
366 kj::Own<IoChannelFactory::ActorClassChannel> getChannel(IoContext& ioctx);
367 
368 JSG_RESOURCE_TYPE(DurableObjectClass) {
369 // No methods - this is just a handle that gets passed to ctx.facets.get()
370 
371 JSG_TS_OVERRIDE(
372 interface DurableObjectClass<
373 _T extends Rpc.DurableObjectBranded | undefined = undefined
374 > {}
375 );
376 }
377 
378 void serialize(jsg::Lock& js, jsg::Serializer& serializer);
379 static jsg::Ref<DurableObjectClass> deserialize(
380 jsg::Lock& js, rpc::SerializationTag tag, jsg::Deserializer& deserializer);
381 
382 JSG_SERIALIZABLE(rpc::SerializationTag::ACTOR_CLASS);
383 
384 private:
385 kj::OneOf<uint, IoOwn<IoChannelFactory::ActorClassChannel>> channel;
386};
387 
388#define EW_ACTOR_ISOLATE_TYPES \
389 api::ColoLocalActorNamespace, api::DurableObject, api::DurableObjectId, \
390 api::DurableObjectNamespace, api::DurableObjectNamespace::NewUniqueIdOptions, \
391 api::DurableObjectNamespace::GetDurableObjectOptions, api::DurableObjectClass, \
392 api::DurableObjectNamespace::GetDurableObjectOptions::VersionOptions
393 
394} // namespace workerd::api