Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/loose-glasses-poke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"miniflare": patch
---

Loosen validation around different configurations for Durable Object

Allow durable objects to have `enableSql`, `unsafeUniqueKey` and `unsafePreventEviction` configurations set to `undefined` even if they same durable objects are defined with those configurations set to a different value (this would allow workers using external durable objects not to have to duplicate such configurations in their options)
56 changes: 44 additions & 12 deletions packages/miniflare/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -320,50 +320,82 @@ function getDurableObjectClassNames(
className,
// Fallback to current worker service if name not defined
serviceName = workerServiceName,
enableSql,
unsafeUniqueKey,
unsafePreventEviction,
...doConfigs
} = normaliseDurableObject(designator);
// Get or create `Map` mapping class name to optional unsafe unique key
let classNames = serviceClassNames.get(serviceName);
if (classNames === undefined) {
classNames = new Map();
serviceClassNames.set(serviceName, classNames);
}

if (classNames.has(className)) {
// If we've already seen this class in this service, make sure the
// unsafe unique keys and unsafe prevent eviction values match
const existingInfo = classNames.get(className);
if (existingInfo?.enableSql !== enableSql) {

const isDoUnacceptableDiff = (
field: Extract<
keyof typeof doConfigs,
"enableSql" | "unsafeUniqueKey" | "unsafePreventEviction"
>
) => {
if (!existingInfo) {
return false;
}

const same = existingInfo[field] === doConfigs[field];
if (same) {
return false;
}

const oneIsUndefined =
existingInfo[field] === undefined || doConfigs[field] === undefined;

// If one of the configurations is `undefined` (either the current one or the existing one) then there we
// want to consider this as an acceptable difference since we might be in a potentially valid situation in
// which worker A defines a DO with a config, while worker B simply uses the DO from worker A but without
// providing the configuration (thus leaving it `undefined`) (this for example is exactly what Wrangler does
// with the implicitly defined `enableSql` flag)
if (oneIsUndefined) {
return false;
}

return true;
};

if (isDoUnacceptableDiff("enableSql")) {
throw new MiniflareCoreError(
"ERR_DIFFERENT_STORAGE_BACKEND",
`Different storage backends defined for Durable Object "${className}" in "${serviceName}": ${JSON.stringify(
enableSql
doConfigs.enableSql
)} and ${JSON.stringify(existingInfo?.enableSql)}`
);
}
if (existingInfo?.unsafeUniqueKey !== unsafeUniqueKey) {

if (isDoUnacceptableDiff("unsafeUniqueKey")) {
throw new MiniflareCoreError(
"ERR_DIFFERENT_UNIQUE_KEYS",
`Multiple unsafe unique keys defined for Durable Object "${className}" in "${serviceName}": ${JSON.stringify(
unsafeUniqueKey
doConfigs.unsafeUniqueKey
)} and ${JSON.stringify(existingInfo?.unsafeUniqueKey)}`
);
}
if (existingInfo?.unsafePreventEviction !== unsafePreventEviction) {

if (isDoUnacceptableDiff("unsafePreventEviction")) {
throw new MiniflareCoreError(
"ERR_DIFFERENT_PREVENT_EVICTION",
`Multiple unsafe prevent eviction values defined for Durable Object "${className}" in "${serviceName}": ${JSON.stringify(
unsafePreventEviction
doConfigs.unsafePreventEviction
)} and ${JSON.stringify(existingInfo?.unsafePreventEviction)}`
);
}
} else {
// Otherwise, just add it
classNames.set(className, {
enableSql,
unsafeUniqueKey,
unsafePreventEviction,
enableSql: doConfigs.enableSql,
unsafeUniqueKey: doConfigs.unsafeUniqueKey,
unsafePreventEviction: doConfigs.unsafePreventEviction,
});
}
}
Expand Down
109 changes: 109 additions & 0 deletions packages/miniflare/test/plugins/do/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,3 +482,112 @@ test("colo-local actors", async (t) => {
res = await stub.fetch("http://localhost");
t.is(await res.text(), "body:thing2");
});

test("multiple workers with DO conflicting useSQLite booleans cause options error", async (t) => {
const mf = new Miniflare({
workers: [
{
modules: true,
name: "worker-a",
script: "export default {}",
},
],
});

t.teardown(() => mf.dispose());

await t.throwsAsync(
async () => {
await mf.setOptions({
workers: [
{
modules: true,
name: "worker-a",
script: `
import { DurableObject } from "cloudflare:workers";

export class MyDo extends DurableObject {}

export default { }
`,
durableObjects: {
MY_DO: {
className: "MyDo",
scriptName: undefined,
useSQLite: true,
},
},
},
{
modules: true,
name: "worker-b",
script: "export default {}",
durableObjects: {
MY_DO: {
className: "MyDo",
scriptName: "worker-a",
useSQLite: false,
},
},
},
],
});
},
{
instanceOf: Error,
message:
'Different storage backends defined for Durable Object "MyDo" in "core:user:worker-a": false and true',
}
);
});

test("multiple workers with DO useSQLite true and undefined does not cause options error", async (t) => {
const mf = new Miniflare({
workers: [
{
modules: true,
name: "worker-a",
script: "export default {}",
},
],
});

t.teardown(() => mf.dispose());

await t.notThrowsAsync(async () => {
await mf.setOptions({
workers: [
{
modules: true,
name: "worker-a",
script: `
import { DurableObject } from "cloudflare:workers";

export class MyDo extends DurableObject {}

export default { }
`,
durableObjects: {
MY_DO: {
className: "MyDo",
scriptName: undefined,
useSQLite: true,
},
},
},
{
modules: true,
name: "worker-b",
script: "export default {}",
durableObjects: {
MY_DO: {
className: "MyDo",
scriptName: "worker-a",
useSQLite: undefined,
},
},
},
],
});
});
});
Loading