Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 4f15e08

Browse files
authored
In-Memory Durable Objects (#391)
* Add support for in-memory Durable Objects * Rename Durable Object `klass` to `designator`
1 parent a5a5d3a commit 4f15e08

File tree

6 files changed

+97
-20
lines changed

6 files changed

+97
-20
lines changed

packages/tre/src/index.ts

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,9 @@ import {
2626
Plugins,
2727
SERVICE_ENTRY,
2828
SOCKET_ENTRY,
29+
normaliseDurableObject,
2930
} from "./plugins";
30-
import { HEADER_CUSTOM_SERVICE } from "./plugins/core";
31+
import { HEADER_CUSTOM_SERVICE, getUserServiceName } from "./plugins/core";
3132
import {
3233
Config,
3334
Runtime,
@@ -86,6 +87,33 @@ function validateOptions(
8687
return [pluginSharedOpts, pluginWorkerOpts];
8788
}
8889

90+
// When creating user worker services, we need to know which Durable Objects
91+
// they export. Rather than parsing JavaScript to search for class exports
92+
// (which would have to be recursive because of `export * from ...`), we collect
93+
// all Durable Object bindings, noting that bindings may be defined for objects
94+
// in other services.
95+
function getDurableObjectClassNames(
96+
allWorkerOpts: PluginWorkerOptions[]
97+
): Map<string, string[]> {
98+
const serviceClassNames = new Map<string, string[]>();
99+
for (const workerOpts of allWorkerOpts) {
100+
const workerServiceName = getUserServiceName(workerOpts.core.name);
101+
for (const designator of Object.values(
102+
workerOpts.do.durableObjects ?? {}
103+
)) {
104+
// Fallback to current worker service if name not defined
105+
const [className, serviceName = workerServiceName] =
106+
normaliseDurableObject(designator);
107+
let classNames = serviceClassNames.get(serviceName);
108+
if (classNames === undefined) {
109+
serviceClassNames.set(serviceName, (classNames = []));
110+
}
111+
classNames.push(className);
112+
}
113+
}
114+
return serviceClassNames;
115+
}
116+
89117
// ===== `Miniflare` Internal Storage & Routing =====
90118
type OptionalGatewayFactoryType<
91119
Gateway extends GatewayConstructor<any> | undefined
@@ -343,6 +371,8 @@ export class Miniflare {
343371
},
344372
];
345373

374+
const durableObjectClassNames = getDurableObjectClassNames(allWorkerOpts);
375+
346376
// Dedupe services by name
347377
const serviceNames = new Set<string>();
348378

@@ -365,6 +395,7 @@ export class Miniflare {
365395
sharedOptions: sharedOpts[key],
366396
workerBindings,
367397
workerIndex: i,
398+
durableObjectClassNames,
368399
});
369400
if (pluginServices !== undefined) {
370401
for (const service of pluginServices) {

packages/tre/src/plugins/core/index.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import fs from "fs/promises";
33
import { Request, Response } from "undici";
44
import { z } from "zod";
55
import { Awaitable, JsonSchema } from "../../helpers";
6-
import { Service, Worker_Binding, Worker_Module } from "../../runtime";
6+
import { Service, Worker_Binding, Worker_Module, kVoid } from "../../runtime";
77
import { BINDING_SERVICE_LOOPBACK, Plugin } from "../shared";
88
import {
99
ModuleDefinitionSchema,
@@ -107,6 +107,10 @@ const SERVICE_USER_PREFIX = `${CORE_PLUGIN_NAME}:user`;
107107
// Service prefix for custom fetch functions defined in `serviceBindings` option
108108
const SERVICE_CUSTOM_PREFIX = `${CORE_PLUGIN_NAME}:custom`;
109109

110+
export function getUserServiceName(name = "") {
111+
return `${SERVICE_USER_PREFIX}:${name}`;
112+
}
113+
110114
export const HEADER_PROBE = "MF-Probe";
111115
export const HEADER_CUSTOM_SERVICE = "MF-Custom-Service";
112116

@@ -141,12 +145,7 @@ export const SCRIPT_CUSTOM_SERVICE = `addEventListener("fetch", (event) => {
141145
event.respondWith(${BINDING_SERVICE_LOOPBACK}.fetch(request));
142146
})`;
143147

144-
const now = new Date();
145-
const fallbackCompatibilityDate = [
146-
now.getFullYear(),
147-
(now.getMonth() + 1).toString().padStart(2, "0"),
148-
now.getDate().toString().padStart(2, "0"),
149-
].join("-");
148+
const FALLBACK_COMPATIBILITY_DATE = "2000-01-01";
150149

151150
export const CORE_PLUGIN: Plugin<
152151
typeof CoreOptionsSchema,
@@ -208,6 +207,7 @@ export const CORE_PLUGIN: Plugin<
208207
workerBindings,
209208
workerIndex,
210209
sharedOptions,
210+
durableObjectClassNames,
211211
}) {
212212
// Define core/shared services.
213213
// Services get de-duped by name, so only the first worker's
@@ -224,23 +224,29 @@ export const CORE_PLUGIN: Plugin<
224224
serviceWorkerScript: SCRIPT_ENTRY,
225225
compatibilityDate: "2022-09-01",
226226
bindings: serviceEntryBindings,
227-
compatibilityDate: "2022-09-01",
228227
},
229228
},
230229
];
231230

232231
// Define regular user worker if script is set
233232
const workerScript = getWorkerScript(options);
234233
if (workerScript !== undefined) {
235-
const name = `${SERVICE_USER_PREFIX}:${options.name ?? ""}`;
234+
const name = getUserServiceName(options.name);
235+
const classNames = durableObjectClassNames.get(name) ?? [];
236+
236237
services.push({
237238
name,
238239
worker: {
239240
...workerScript,
240241
compatibilityDate:
241-
options.compatibilityDate ?? fallbackCompatibilityDate,
242+
options.compatibilityDate ?? FALLBACK_COMPATIBILITY_DATE,
242243
compatibilityFlags: options.compatibilityFlags,
243244
bindings: workerBindings,
245+
durableObjectNamespaces: classNames.map((className) => ({
246+
className,
247+
uniqueKey: className,
248+
})),
249+
durableObjectStorage: { inMemory: kVoid },
244250
},
245251
});
246252
serviceEntryBindings.push({

packages/tre/src/plugins/do/index.ts

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { z } from "zod";
2+
import { MiniflareError } from "../../helpers";
3+
import { Worker_Binding } from "../../runtime";
4+
import { getUserServiceName } from "../core";
25
import { PersistenceSchema, Plugin } from "../shared";
36
import { DurableObjectsStorageGateway } from "./gateway";
47
import { DurableObjectsStorageRouter } from "./router";
58

9+
export type DurableObjectsErrorCode = "ERR_PERSIST_UNSUPPORTED"; // Durable Object persistence is not yet supported
10+
export class DurableObjectsError extends MiniflareError<DurableObjectsErrorCode> {}
11+
612
export const DurableObjectsOptionsSchema = z.object({
713
durableObjects: z
814
.record(
@@ -20,6 +26,20 @@ export const DurableObjectsSharedOptionsSchema = z.object({
2026
durableObjectsPersist: PersistenceSchema,
2127
});
2228

29+
export function normaliseDurableObject(
30+
designator: NonNullable<
31+
z.infer<typeof DurableObjectsOptionsSchema>["durableObjects"]
32+
>[string]
33+
): [className: string, serviceName: string | undefined] {
34+
const isObject = typeof designator === "object";
35+
const className = isObject ? designator.className : designator;
36+
const serviceName =
37+
isObject && designator.scriptName !== undefined
38+
? getUserServiceName(designator.scriptName)
39+
: undefined;
40+
return [className, serviceName];
41+
}
42+
2343
export const DURABLE_OBJECTS_PLUGIN_NAME = "do";
2444
export const DURABLE_OBJECTS_PLUGIN: Plugin<
2545
typeof DurableObjectsOptionsSchema,
@@ -31,10 +51,29 @@ export const DURABLE_OBJECTS_PLUGIN: Plugin<
3151
options: DurableObjectsOptionsSchema,
3252
sharedOptions: DurableObjectsSharedOptionsSchema,
3353
getBindings(options) {
34-
return undefined;
54+
return Object.entries(options.durableObjects ?? {}).map<Worker_Binding>(
55+
([name, klass]) => {
56+
const [className, serviceName] = normaliseDurableObject(klass);
57+
return {
58+
name,
59+
durableObjectNamespace: { className, serviceName },
60+
};
61+
}
62+
);
3563
},
36-
getServices(options) {
37-
return undefined;
64+
getServices({ options, sharedOptions }) {
65+
if (
66+
// If we have Durable Object bindings...
67+
Object.keys(options.durableObjects ?? {}).length > 0 &&
68+
// ...and persistence is enabled...
69+
sharedOptions.durableObjectsPersist
70+
) {
71+
// ...throw, as Durable-Durable Objects are not yet supported
72+
throw new DurableObjectsError(
73+
"ERR_PERSIST_UNSUPPORTED",
74+
"Persisted Durable Objects are not yet supported"
75+
);
76+
}
3877
},
3978
};
4079

packages/tre/src/plugins/kv/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ export const KV_PLUGIN: Plugin<
4646
sharedOptions: KVSharedOptionsSchema,
4747
async getBindings(options) {
4848
const bindings = Object.entries(
49-
options.kvNamespaces ?? []
49+
options.kvNamespaces ?? {}
5050
).map<Worker_Binding>(([name, id]) => ({
5151
name,
5252
kvNamespace: { name: `${SERVICE_NAMESPACE_PREFIX}:${id}` },
@@ -75,7 +75,6 @@ export const KV_PLUGIN: Plugin<
7575
service: { name: SERVICE_LOOPBACK },
7676
},
7777
],
78-
compatibilityDate: "2022-09-01",
7978
},
8079
})
8180
);

packages/tre/src/plugins/shared/index.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import { Service, Worker_Binding } from "../../runtime";
44
import { GatewayConstructor } from "./gateway";
55
import { RouterConstructor } from "./router";
66

7+
export type DurableObjectClassNames = Map<string, string[]>;
8+
79
export interface PluginServicesOptions<
810
Options extends z.ZodType,
911
SharedOptions extends z.ZodType | undefined
@@ -13,19 +15,18 @@ export interface PluginServicesOptions<
1315
sharedOptions: OptionalZodTypeOf<SharedOptions>;
1416
workerBindings: Worker_Binding[];
1517
workerIndex: number;
18+
durableObjectClassNames: DurableObjectClassNames;
1619
}
1720

1821
export interface PluginBase<
1922
Options extends z.ZodType,
2023
SharedOptions extends z.ZodType | undefined
2124
> {
2225
options: Options;
23-
getBindings(
24-
options: z.infer<Options>
25-
): Awaitable<Worker_Binding[] | undefined>;
26+
getBindings(options: z.infer<Options>): Awaitable<Worker_Binding[] | void>;
2627
getServices(
2728
options: PluginServicesOptions<Options, SharedOptions>
28-
): Awaitable<Service[] | undefined>;
29+
): Awaitable<Service[] | void>;
2930
}
3031

3132
export type Plugin<
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# TODO: remove prior to release
22
sserve-conf*
3+
workerd*

0 commit comments

Comments
 (0)