File
Blob: src/workerd/api/actor.h
| 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 | |
| 17 | namespace workerd { |
| 18 | template <typename T> |
| 19 | class IoOwn; |
| 20 | } |
| 21 | |
| 22 | namespace workerd::api { |
| 23 | |
| 24 | // A capability to an ephemeral Actor namespace. |
| 25 | class 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 | |
| 39 | class DurableObjectNamespace; |
| 40 | |
| 41 | // DurableObjectId type seen by JavaScript. |
| 42 | class 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. |
| 87 | class 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. |
| 142 | class 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 | |
| 293 | class 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 |
| 326 | class 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. |
| 345 | class 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()`. |
| 360 | class 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 |