Skip to content

Commit d68e8c9

Browse files
Inject latest Durable Object migration state into Miniflare (#6647)
* Update workerd.capnp and generated code to make use of enableSql * Inject latest Durable Object migration state into Miniflare * Add warnings in remote mode --------- Co-authored-by: Samuel Macleod <[email protected]>
1 parent 0737e0f commit d68e8c9

File tree

21 files changed

+284
-37
lines changed

21 files changed

+284
-37
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"miniflare": minor
3+
"wrangler": minor
4+
---
5+
6+
feat: Configure SQLite backed Durable Objects in local dev

packages/miniflare/src/index.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ function getDurableObjectClassNames(
312312
className,
313313
// Fallback to current worker service if name not defined
314314
serviceName = workerServiceName,
315+
enableSql,
315316
unsafeUniqueKey,
316317
unsafePreventEviction,
317318
} = normaliseDurableObject(designator);
@@ -325,6 +326,14 @@ function getDurableObjectClassNames(
325326
// If we've already seen this class in this service, make sure the
326327
// unsafe unique keys and unsafe prevent eviction values match
327328
const existingInfo = classNames.get(className);
329+
if (existingInfo?.enableSql !== enableSql) {
330+
throw new MiniflareCoreError(
331+
"ERR_DIFFERENT_STORAGE_BACKEND",
332+
`Different storage backends defined for Durable Object "${className}" in "${serviceName}": ${JSON.stringify(
333+
enableSql
334+
)} and ${JSON.stringify(existingInfo?.enableSql)}`
335+
);
336+
}
328337
if (existingInfo?.unsafeUniqueKey !== unsafeUniqueKey) {
329338
throw new MiniflareCoreError(
330339
"ERR_DIFFERENT_UNIQUE_KEYS",
@@ -343,7 +352,11 @@ function getDurableObjectClassNames(
343352
}
344353
} else {
345354
// Otherwise, just add it
346-
classNames.set(className, { unsafeUniqueKey, unsafePreventEviction });
355+
classNames.set(className, {
356+
enableSql,
357+
unsafeUniqueKey,
358+
unsafePreventEviction,
359+
});
347360
}
348361
}
349362
}

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,16 +612,21 @@ export const CORE_PLUGIN: Plugin<
612612
bindings: workerBindings,
613613
durableObjectNamespaces:
614614
classNamesEntries.map<Worker_DurableObjectNamespace>(
615-
([className, { unsafeUniqueKey, unsafePreventEviction }]) => {
615+
([
616+
className,
617+
{ enableSql, unsafeUniqueKey, unsafePreventEviction },
618+
]) => {
616619
if (unsafeUniqueKey === kUnsafeEphemeralUniqueKey) {
617620
return {
618621
className,
622+
enableSql,
619623
ephemeralLocal: kVoid,
620624
preventEviction: unsafePreventEviction,
621625
};
622626
} else {
623627
return {
624628
className,
629+
enableSql,
625630
// This `uniqueKey` will (among other things) be used as part of the
626631
// path when persisting to the file-system. `-` is invalid in
627632
// JavaScript class names, but safe on filesystems (incl. Windows).

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const DurableObjectsOptionsSchema = z.object({
1919
z.object({
2020
className: z.string(),
2121
scriptName: z.string().optional(),
22+
useSQLite: z.boolean().optional(),
2223
// Allow `uniqueKey` to be customised. We use in Wrangler when setting
2324
// up stub Durable Objects that proxy requests to Durable Objects in
2425
// another `workerd` process, to ensure the IDs created by the stub
@@ -44,6 +45,7 @@ export function normaliseDurableObject(
4445
): {
4546
className: string;
4647
serviceName?: string;
48+
enableSql?: boolean;
4749
unsafeUniqueKey?: UnsafeUniqueKey;
4850
unsafePreventEviction?: boolean;
4951
} {
@@ -53,11 +55,18 @@ export function normaliseDurableObject(
5355
isObject && designator.scriptName !== undefined
5456
? getUserServiceName(designator.scriptName)
5557
: undefined;
58+
const enableSql = isObject ? designator.useSQLite : undefined;
5659
const unsafeUniqueKey = isObject ? designator.unsafeUniqueKey : undefined;
5760
const unsafePreventEviction = isObject
5861
? designator.unsafePreventEviction
5962
: undefined;
60-
return { className, serviceName, unsafeUniqueKey, unsafePreventEviction };
63+
return {
64+
className,
65+
serviceName,
66+
enableSql,
67+
unsafeUniqueKey,
68+
unsafePreventEviction,
69+
};
6170
}
6271

6372
export const DURABLE_OBJECTS_PLUGIN_NAME = "do";

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type DurableObjectClassNames = Map<
4444
Map<
4545
/* className */ string,
4646
{
47+
enableSql?: boolean;
4748
unsafeUniqueKey?: UnsafeUniqueKey;
4849
unsafePreventEviction?: boolean;
4950
}

packages/miniflare/src/runtime/config/workerd.capnp

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
# to see and restrict what each Worker can access. Instead, the default is that a Worker has
2626
# access to no privileged resources at all, and you must explicitly declare "bindings" to give
2727
# it access to specific resources. A binding gives the Worker a JavaScript API object that points
28-
# to a specific resource. This means that by changing config alone, you can fully controll which
28+
# to a specific resource. This means that by changing config alone, you can fully control which
2929
# resources an Worker connects to. (You can even disallow access to the public internet, although
3030
# public internet access is granted by default.)
3131
#
@@ -39,6 +39,7 @@
3939
# 2. added to `tryImportBulitin` in workerd.c++ (grep for '"/workerd/workerd.capnp"').
4040
using Cxx = import "/capnp/c++.capnp";
4141
$Cxx.namespace("workerd::server::config");
42+
$Cxx.allowCancellation;
4243

4344
struct Config {
4445
# Top-level configuration for a workerd instance.
@@ -206,7 +207,7 @@ struct Worker {
206207
# event handlers.
207208
#
208209
# The value of this field is the raw source code. When using Cap'n Proto text format, use the
209-
# `embed` directive to read the code from an exnternal file:
210+
# `embed` directive to read the code from an external file:
210211
#
211212
# serviceWorkerScript = embed "worker.js"
212213

@@ -271,6 +272,10 @@ struct Worker {
271272
# Pyodide (https://pyodide.org/en/stable/usage/packages-in-pyodide.html). All packages listed
272273
# will be installed prior to the execution of the worker.
273274
}
275+
276+
namedExports @10 :List(Text);
277+
# For commonJsModule and nodeJsCompatModule, this is a list of named exports that the
278+
# module expects to be exported once the evaluation is complete.
274279
}
275280

276281
compatibilityDate @3 :Text;
@@ -345,7 +350,7 @@ struct Worker {
345350

346351
kvNamespace @11 :ServiceDesignator;
347352
# A KV namespace, implemented by the named service. The Worker sees a KvNamespace-typed
348-
# binding. Requests to the namespace will be converted into HTTP requests targetting the
353+
# binding. Requests to the namespace will be converted into HTTP requests targeting the
349354
# given service name.
350355

351356
r2Bucket @12 :ServiceDesignator;
@@ -358,7 +363,7 @@ struct Worker {
358363

359364
queue @15 :ServiceDesignator;
360365
# A Queue binding, implemented by the named service. Requests to the
361-
# namespace will be converted into HTTP requests targetting the given
366+
# namespace will be converted into HTTP requests targeting the given
362367
# service name.
363368

364369
fromEnvironment @16 :Text;
@@ -520,7 +525,7 @@ struct Worker {
520525
}
521526
}
522527

523-
globalOutbound @6 :ServiceDesignator = ( name = "internet" );
528+
globalOutbound @6 :ServiceDesignator = "internet";
524529
# Where should the global "fetch" go to? The default is the service called "internet", which
525530
# should usually be configured to talk to the public internet.
526531

@@ -555,10 +560,10 @@ struct Worker {
555560
# Instances of this class are ephemeral -- they have no durable storage at all. The
556561
# `state.storage` API will not be present. Additionally, this namespace will allow arbitrary
557562
# strings as IDs. There are no `idFromName()` nor `newUniqueId()` methods; `get()` takes any
558-
# string as a paremeter.
563+
# string as a parameter.
559564
#
560565
# Ephemeral objects are NOT globally unique, only "locally" unique, for some definition of
561-
# "local". For exmaple, on Cloudflare's network, these objects are unique per-colo.
566+
# "local". For example, on Cloudflare's network, these objects are unique per-colo.
562567
#
563568
# WARNING: Cloudflare Workers currently limits this feature to Cloudflare-internal users
564569
# only, because using them correctly requires deep understanding of Cloudflare network
@@ -574,6 +579,14 @@ struct Worker {
574579
# pinned to memory forever, so we provide this flag to change the default behavior.
575580
#
576581
# Note that this is only supported in Workerd; production Durable Objects cannot toggle eviction.
582+
583+
enableSql @4 :Bool;
584+
# Whether or not Durable Objects in this namespace can use the `storage.sql` API to execute SQL
585+
# queries.
586+
#
587+
# workerd uses SQLite to back all Durable Objects, but the SQL API is hidden by default to
588+
# emulate behavior of traditional DO namespaces on Cloudflare that aren't SQLite-backed. This
589+
# flag should be enabled when testing code that will run on a SQLite-backed namespace.
577590
}
578591

579592
durableObjectUniqueKeyModifier @8 :Text;
@@ -730,7 +743,7 @@ struct DiskDirectory {
730743
# particular, no attempt is made to guess the `Content-Type` header. You normally would wrap
731744
# this in a Worker that fills in the metadata in the way you want.
732745
#
733-
# A GET request targetting a directory (rather than a file) will return a basic JSAN directory
746+
# A GET request targeting a directory (rather than a file) will return a basic JSAN directory
734747
# listing like:
735748
#
736749
# [{"name":"foo","type":"file"},{"name":"bar","type":"directory"}]
@@ -921,4 +934,4 @@ struct Extension {
921934
esModule @2 :Text;
922935
# Raw source code of ES module.
923936
}
924-
}
937+
}

packages/miniflare/src/runtime/config/workerd.capnp.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ export declare class Worker_Module extends __S {
229229
getPythonRequirement(): string;
230230
isPythonRequirement(): boolean;
231231
setPythonRequirement(value: string): void;
232+
adoptNamedExports(value: capnp.Orphan<capnp.List<string>>): void;
233+
disownNamedExports(): capnp.Orphan<capnp.List<string>>;
234+
getNamedExports(): capnp.List<string>;
235+
hasNamedExports(): boolean;
236+
initNamedExports(length: number): capnp.List<string>;
237+
setNamedExports(value: capnp.List<string>): void;
232238
toString(): string;
233239
which(): Worker_Module_Which;
234240
}
@@ -671,6 +677,8 @@ export declare class Worker_DurableObjectNamespace extends __S {
671677
setEphemeralLocal(): void;
672678
getPreventEviction(): boolean;
673679
setPreventEviction(value: boolean): void;
680+
getEnableSql(): boolean;
681+
setEnableSql(value: boolean): void;
674682
toString(): string;
675683
which(): Worker_DurableObjectNamespace_Which;
676684
}

packages/miniflare/src/runtime/config/workerd.capnp.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,12 @@ class Worker_Module extends capnp_ts_1.Struct {
336336
capnp_ts_1.Struct.setUint16(0, 8, this);
337337
capnp_ts_1.Struct.setText(1, value, this);
338338
}
339+
adoptNamedExports(value) { capnp_ts_1.Struct.adopt(value, capnp_ts_1.Struct.getPointer(2, this)); }
340+
disownNamedExports() { return capnp_ts_1.Struct.disown(this.getNamedExports()); }
341+
getNamedExports() { return capnp_ts_1.Struct.getList(2, capnp.TextList, this); }
342+
hasNamedExports() { return !capnp_ts_1.Struct.isNull(capnp_ts_1.Struct.getPointer(2, this)); }
343+
initNamedExports(length) { return capnp_ts_1.Struct.initList(2, capnp.TextList, length, this); }
344+
setNamedExports(value) { capnp_ts_1.Struct.copyFrom(value, capnp_ts_1.Struct.getPointer(2, this)); }
339345
toString() { return "Worker_Module_" + super.toString(); }
340346
which() { return capnp_ts_1.Struct.getUint16(0, this); }
341347
}
@@ -349,7 +355,7 @@ Worker_Module.JSON = Worker_Module_Which.JSON;
349355
Worker_Module.NODE_JS_COMPAT_MODULE = Worker_Module_Which.NODE_JS_COMPAT_MODULE;
350356
Worker_Module.PYTHON_MODULE = Worker_Module_Which.PYTHON_MODULE;
351357
Worker_Module.PYTHON_REQUIREMENT = Worker_Module_Which.PYTHON_REQUIREMENT;
352-
Worker_Module._capnp = { displayName: "Module", id: "d9d87a63770a12f3", size: new capnp_ts_1.ObjectSize(8, 2) };
358+
Worker_Module._capnp = { displayName: "Module", id: "d9d87a63770a12f3", size: new capnp_ts_1.ObjectSize(8, 3) };
353359
var Worker_Binding_Type_Which;
354360
(function (Worker_Binding_Type_Which) {
355361
Worker_Binding_Type_Which[Worker_Binding_Type_Which["UNSPECIFIED"] = 0] = "UNSPECIFIED";
@@ -995,6 +1001,8 @@ class Worker_DurableObjectNamespace extends capnp_ts_1.Struct {
9951001
setEphemeralLocal() { capnp_ts_1.Struct.setUint16(0, 1, this); }
9961002
getPreventEviction() { return capnp_ts_1.Struct.getBit(16, this); }
9971003
setPreventEviction(value) { capnp_ts_1.Struct.setBit(16, value, this); }
1004+
getEnableSql() { return capnp_ts_1.Struct.getBit(17, this); }
1005+
setEnableSql(value) { capnp_ts_1.Struct.setBit(17, value, this); }
9981006
toString() { return "Worker_DurableObjectNamespace_" + super.toString(); }
9991007
which() { return capnp_ts_1.Struct.getUint16(0, this); }
10001008
}

packages/miniflare/src/runtime/config/workerd.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,7 @@ export interface Worker_Binding_MemoryCacheLimits {
174174
export type Worker_DurableObjectNamespace = {
175175
className?: string;
176176
preventEviction?: boolean;
177+
enableSql?: boolean;
177178
} & ({ uniqueKey?: string } | { ephemeralLocal?: Void });
178179

179180
export type ExternalServer = { address?: string } & (

packages/miniflare/src/shared/error.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ export type MiniflareCoreErrorCode =
2828
| "ERR_NO_WORKERS" // No workers defined
2929
| "ERR_VALIDATION" // Options failed to parse
3030
| "ERR_DUPLICATE_NAME" // Multiple workers defined with same name
31+
| "ERR_DIFFERENT_STORAGE_BACKEND" // Multiple Durable Object bindings declared for same class with different storage backends
3132
| "ERR_DIFFERENT_UNIQUE_KEYS" // Multiple Durable Object bindings declared for same class with different unsafe unique keys
3233
| "ERR_DIFFERENT_PREVENT_EVICTION" // Multiple Durable Object bindings declared for same class with different unsafe prevent eviction values
3334
| "ERR_MULTIPLE_OUTBOUNDS" // Both `outboundService` and `fetchMock` specified

0 commit comments

Comments
 (0)