alchemy 2.0.0-beta.82 → 2.0.0-beta.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/Cloudflare/Containers/Container.d.ts +128 -40
- package/lib/Cloudflare/Containers/Container.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/Container.js +124 -24
- package/lib/Cloudflare/Containers/Container.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerApplication.d.ts +59 -50
- package/lib/Cloudflare/Containers/ContainerApplication.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerApplication.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerClient.d.ts +20 -7
- package/lib/Cloudflare/Containers/ContainerClient.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerClient.js +22 -5
- package/lib/Cloudflare/Containers/ContainerClient.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerConfiguration.d.ts +132 -13
- package/lib/Cloudflare/Containers/ContainerConfiguration.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerConfiguration.js +219 -35
- package/lib/Cloudflare/Containers/ContainerConfiguration.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerPlatform.d.ts +1 -1
- package/lib/Cloudflare/Containers/ContainerPlatform.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerPlatform.js +21 -2
- package/lib/Cloudflare/Containers/ContainerPlatform.js.map +1 -1
- package/lib/Cloudflare/Containers/ContainerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/ContainerProvider.js +74 -177
- package/lib/Cloudflare/Containers/ContainerProvider.js.map +1 -1
- package/lib/Cloudflare/Containers/DurableObjectApplication.d.ts +84 -0
- package/lib/Cloudflare/Containers/DurableObjectApplication.d.ts.map +1 -0
- package/lib/Cloudflare/Containers/DurableObjectApplication.js +249 -0
- package/lib/Cloudflare/Containers/DurableObjectApplication.js.map +1 -0
- package/lib/Cloudflare/Containers/LocalContainerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/LocalContainerProvider.js +20 -41
- package/lib/Cloudflare/Containers/LocalContainerProvider.js.map +1 -1
- package/lib/Cloudflare/Containers/index.d.ts +2 -1
- package/lib/Cloudflare/Containers/index.d.ts.map +1 -1
- package/lib/Cloudflare/Containers/index.js +2 -1
- package/lib/Cloudflare/Containers/index.js.map +1 -1
- package/lib/Cloudflare/Providers.d.ts.map +1 -1
- package/lib/Cloudflare/Providers.js +2 -1
- package/lib/Cloudflare/Providers.js.map +1 -1
- package/lib/Cloudflare/StateStore/Api.d.ts +3 -0
- package/lib/Cloudflare/Workers/DurableObject.d.ts +75 -1
- package/lib/Cloudflare/Workers/DurableObject.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/DurableObject.js +50 -0
- package/lib/Cloudflare/Workers/DurableObject.js.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js +29 -23
- package/lib/Cloudflare/Workers/LocalWorkerProvider.js.map +1 -1
- package/lib/Cloudflare/Workers/Worker.d.ts +2 -0
- package/lib/Cloudflare/Workers/Worker.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/Worker.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js +67 -4
- package/lib/Cloudflare/Workers/WorkerAsyncBindings.js.map +1 -1
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts +6 -0
- package/lib/Cloudflare/Workers/WorkerBinding.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.d.ts.map +1 -1
- package/lib/Cloudflare/Workers/WorkerProvider.js +54 -8
- package/lib/Cloudflare/Workers/WorkerProvider.js.map +1 -1
- package/lib/State/HttpStateApi.d.ts +13 -13
- package/package.json +6 -6
- package/src/Cloudflare/Containers/Container.ts +126 -56
- package/src/Cloudflare/Containers/ContainerApplication.ts +60 -53
- package/src/Cloudflare/Containers/ContainerClient.ts +47 -13
- package/src/Cloudflare/Containers/ContainerConfiguration.ts +316 -44
- package/src/Cloudflare/Containers/ContainerPlatform.ts +27 -3
- package/src/Cloudflare/Containers/ContainerProvider.ts +96 -256
- package/src/Cloudflare/Containers/DurableObjectApplication.ts +382 -0
- package/src/Cloudflare/Containers/LocalContainerProvider.ts +20 -48
- package/src/Cloudflare/Containers/index.ts +10 -0
- package/src/Cloudflare/Providers.ts +2 -0
- package/src/Cloudflare/Workers/DurableObject.ts +77 -1
- package/src/Cloudflare/Workers/LocalWorkerProvider.ts +32 -21
- package/src/Cloudflare/Workers/Worker.ts +2 -0
- package/src/Cloudflare/Workers/WorkerAsyncBindings.ts +95 -4
- package/src/Cloudflare/Workers/WorkerProvider.ts +78 -13
|
@@ -50,6 +50,10 @@ import { sha256 } from "../../Util/sha256.ts";
|
|
|
50
50
|
import { ANSI_RESET, ansiFg, colorsEnabled } from "../../Util/Terminal.ts";
|
|
51
51
|
import { theme } from "../../Util/Theme.ts";
|
|
52
52
|
import type { DevContainerImage } from "../Containers/ContainerApplication.ts";
|
|
53
|
+
import {
|
|
54
|
+
assertBindingContainersOnAsyncClasses,
|
|
55
|
+
mergeWorkerContainers,
|
|
56
|
+
} from "../Containers/ContainerConfiguration.ts";
|
|
53
57
|
import { localAccountId } from "../LocalAccount.ts";
|
|
54
58
|
import {
|
|
55
59
|
isLiveId,
|
|
@@ -61,6 +65,7 @@ import type { ConsumerSettings } from "../Queues/Consumer.ts";
|
|
|
61
65
|
import type { WorkerAssetsConfig, WorkerProps } from "../Workers/Worker.ts";
|
|
62
66
|
import { readAssetsConfigFiles } from "./Assets.ts";
|
|
63
67
|
import { getCompatibility } from "./Compatibility.ts";
|
|
68
|
+
import { isDurableObjectExport } from "./DurableObject.ts";
|
|
64
69
|
import { LocalEdge } from "./LocalEdge.ts";
|
|
65
70
|
import { routePatternHost } from "./RoutePattern.ts";
|
|
66
71
|
import { materializeRuntimeBindings, WorkerValidationError } from "./RuntimeBindings.ts";
|
|
@@ -361,6 +366,7 @@ export const LocalWorkerProvider = () =>
|
|
|
361
366
|
// change (a Dockerfile edit, a rebuilt bundle) would never change
|
|
362
367
|
// the config and the running container would serve stale code.
|
|
363
368
|
const containerHashes: Record<string, string> = {};
|
|
369
|
+
const containerRows: NonNullable<Worker["Binding"]["containers"]> = [];
|
|
364
370
|
const boundEnv: Record<string, any> = { ...props.env };
|
|
365
371
|
for (const { data } of bindings) {
|
|
366
372
|
if (data.env) Object.assign(boundEnv, data.env);
|
|
@@ -415,28 +421,33 @@ export const LocalWorkerProvider = () =>
|
|
|
415
421
|
};
|
|
416
422
|
}
|
|
417
423
|
}
|
|
418
|
-
if (data.containers)
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (container.hash !== undefined) {
|
|
437
|
-
containerHashes[container.className] = container.hash;
|
|
438
|
-
}
|
|
424
|
+
if (data.containers) containerRows.push(...data.containers);
|
|
425
|
+
}
|
|
426
|
+
// A Durable Object-managed class merges the images of every bound
|
|
427
|
+
// Container (keyed by Container id); a fleet class has one image.
|
|
428
|
+
const toRuntimeImage = (image: DevContainerImage) => ({
|
|
429
|
+
...image,
|
|
430
|
+
env: unwrapRedacted(image.env),
|
|
431
|
+
});
|
|
432
|
+
const exportMap = (props.exports ?? {}) as Record<string, unknown>;
|
|
433
|
+
yield* assertBindingContainersOnAsyncClasses(
|
|
434
|
+
containerRows,
|
|
435
|
+
Object.keys(exportMap).filter((name) => isDurableObjectExport(exportMap[name])),
|
|
436
|
+
);
|
|
437
|
+
for (const container of yield* mergeWorkerContainers(containerRows)) {
|
|
438
|
+
if (container.devImages !== undefined) {
|
|
439
|
+
const images: Record<string, RuntimeContainerImage> = {};
|
|
440
|
+
for (const [key, image] of Object.entries(container.devImages)) {
|
|
441
|
+
images[key] = toRuntimeImage(image);
|
|
439
442
|
}
|
|
443
|
+
containers[container.className] = { images };
|
|
444
|
+
} else if (container.dev) {
|
|
445
|
+
containers[container.className] = toRuntimeImage(container.dev);
|
|
446
|
+
} else {
|
|
447
|
+
return yield* Effect.die(`Container ${container.className} has no dev image`);
|
|
448
|
+
}
|
|
449
|
+
if (container.hash !== undefined) {
|
|
450
|
+
containerHashes[container.className] = container.hash;
|
|
440
451
|
}
|
|
441
452
|
}
|
|
442
453
|
for (const [className, dev] of Object.entries(containers)) {
|
|
@@ -1318,6 +1318,8 @@ export type Worker<Bindings = any> = Resource<
|
|
|
1318
1318
|
observability?: Pick<WorkerObservability, "traces">;
|
|
1319
1319
|
containers?: {
|
|
1320
1320
|
className: string;
|
|
1321
|
+
/** Set when an async `Cloudflare.DurableObject(name, { containers })` binding contributed the row. */
|
|
1322
|
+
attachedBy?: "binding";
|
|
1321
1323
|
/** Application name and prepared images for the native container API. */
|
|
1322
1324
|
name?: string;
|
|
1323
1325
|
images?: Record<string, string>;
|
|
@@ -19,7 +19,14 @@ import { isDataset } from "../AnalyticsEngine/Dataset.ts";
|
|
|
19
19
|
import { isNamespace } from "../Artifacts/Namespace.ts";
|
|
20
20
|
import type { Container } from "../Containers/Container.ts";
|
|
21
21
|
import type { ContainerApplication } from "../Containers/ContainerApplication.ts";
|
|
22
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
containerApplicationSettings,
|
|
24
|
+
DurableObjectContainerBindingError,
|
|
25
|
+
durableObjectContainerBinding,
|
|
26
|
+
isDurableObjectContainerResource,
|
|
27
|
+
workerContainerBinding,
|
|
28
|
+
} from "../Containers/ContainerConfiguration.ts";
|
|
29
|
+
import { declareDurableObjectApplication } from "../Containers/DurableObjectApplication.ts";
|
|
23
30
|
import { isDatabase } from "../D1/Database.ts";
|
|
24
31
|
import { isSendEmail } from "../Email/SendEmail.ts";
|
|
25
32
|
import { isApp } from "../Flagship/App.ts";
|
|
@@ -48,7 +55,11 @@ import { isAI } from "./AI.ts";
|
|
|
48
55
|
import { isAssets } from "./Assets.ts";
|
|
49
56
|
import { isBinding as isWorkerOnlyBinding } from "./Binding.ts";
|
|
50
57
|
import { isBrowser } from "./Browser.ts";
|
|
51
|
-
import {
|
|
58
|
+
import {
|
|
59
|
+
type DurableObjectLike,
|
|
60
|
+
isDurableObjectLike,
|
|
61
|
+
normalizeTransferredFrom,
|
|
62
|
+
} from "./DurableObject.ts";
|
|
52
63
|
import { isRateLimit } from "./RateLimit.ts";
|
|
53
64
|
import { isSecretKey } from "./SecretKey.ts";
|
|
54
65
|
import { isVersionMetadata } from "./VersionMetadata.ts";
|
|
@@ -264,8 +275,16 @@ export const bindWorkerAsyncBindings = Effect.fn(function* (
|
|
|
264
275
|
} satisfies WorkflowBinding;
|
|
265
276
|
}
|
|
266
277
|
|
|
278
|
+
// Durable Object-managed Containers attached to a hosted class. Both
|
|
279
|
+
// halves describe this env entry, so they share its `sid`.
|
|
280
|
+
const containers =
|
|
281
|
+
isDurableObjectLike(binding) && binding.containers?.length
|
|
282
|
+
? yield* bindDurableObjectContainers(resource, bindingName, binding)
|
|
283
|
+
: undefined;
|
|
284
|
+
|
|
267
285
|
yield* resource.bind`${bindingName}`({
|
|
268
286
|
bindings: [resolvedBindingMeta],
|
|
287
|
+
containers,
|
|
269
288
|
hyperdrives: isHyperdriveConnection(binding)
|
|
270
289
|
? yield* getHyperdriveDevOriginForHost(binding, resource)
|
|
271
290
|
: undefined,
|
|
@@ -317,8 +336,11 @@ const isContainerApplicationResource = (value: unknown): value is ContainerAppli
|
|
|
317
336
|
(value as { Type?: unknown }).Type === "Cloudflare.Container";
|
|
318
337
|
|
|
319
338
|
/**
|
|
320
|
-
* Wire a Container bound directly on an async Worker's `env`
|
|
321
|
-
* (`env: { Sandbox: Cloudflare.Container("Sandbox", { image }) }`).
|
|
339
|
+
* Wire a fleet Container bound directly on an async Worker's `env`
|
|
340
|
+
* (`env: { Sandbox: Cloudflare.Container("Sandbox", { image }) }`). A
|
|
341
|
+
* Durable Object-managed Container fails with
|
|
342
|
+
* {@link DurableObjectContainerBindingError}: it attaches through
|
|
343
|
+
* `Cloudflare.DurableObject(className, { containers })` instead. Mirrors
|
|
322
344
|
* v1 alchemy, where a Container binding is a Durable Object namespace plus
|
|
323
345
|
* its ContainerApplication in one declaration:
|
|
324
346
|
*
|
|
@@ -358,6 +380,14 @@ const bindContainerClass = Effect.fn(function* (
|
|
|
358
380
|
`Worker binding '${bindingName}' is a Container without a deployable image. Declare the container with props (image, or context/dockerfile) to bind it on an async Worker — effectful (main) containers require an Effect-native Durable Object host.`,
|
|
359
381
|
);
|
|
360
382
|
}
|
|
383
|
+
if (isDurableObjectContainerResource(application)) {
|
|
384
|
+
return yield* Effect.die(
|
|
385
|
+
new DurableObjectContainerBindingError({
|
|
386
|
+
bindingName,
|
|
387
|
+
message: `Worker binding '${bindingName}' is a Durable Object-managed Container ('${application.LogicalId}'). Attach it to the Durable Object class that starts it: env: { ${bindingName}: Cloudflare.DurableObject("${className}", { containers: [${application.LogicalId}] }) }.`,
|
|
388
|
+
}),
|
|
389
|
+
);
|
|
390
|
+
}
|
|
361
391
|
// Both halves of the Worker's side describe the same env entry, so they
|
|
362
392
|
// share one `sid`. Binding rows are collapsed by sid (last write wins), so
|
|
363
393
|
// splitting them across two `bind` calls silently drops the namespace
|
|
@@ -384,6 +414,67 @@ const bindContainerClass = Effect.fn(function* (
|
|
|
384
414
|
});
|
|
385
415
|
});
|
|
386
416
|
|
|
417
|
+
/**
|
|
418
|
+
* Binding mistakes die with a {@link DurableObjectContainerBindingError}: the
|
|
419
|
+
* Worker declaration hook cannot fail, and these are programmer errors.
|
|
420
|
+
*
|
|
421
|
+
* Attach the Durable Object-managed Containers of
|
|
422
|
+
* `Cloudflare.DurableObject(className, { containers })` to the class: each
|
|
423
|
+
* adds its image to the class's one container application (declared once per
|
|
424
|
+
* class, deduped by id) and returns the Worker's `containers` row for it. The
|
|
425
|
+
* same machinery as an Effect-native Durable Object's `yield* Container`.
|
|
426
|
+
*/
|
|
427
|
+
const bindDurableObjectContainers = Effect.fn(function* (
|
|
428
|
+
resource: Worker,
|
|
429
|
+
bindingName: string,
|
|
430
|
+
binding: DurableObjectLike,
|
|
431
|
+
) {
|
|
432
|
+
const className = binding.className ?? binding.name;
|
|
433
|
+
if (binding.scriptName !== undefined) {
|
|
434
|
+
return yield* Effect.die(
|
|
435
|
+
new DurableObjectContainerBindingError({
|
|
436
|
+
bindingName,
|
|
437
|
+
message: `Worker binding '${bindingName}' attaches containers to Durable Object class '${className}' hosted by another Worker. Attach containers where the class is hosted.`,
|
|
438
|
+
}),
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
const classApplication = yield* declareDurableObjectApplication(resource, className);
|
|
442
|
+
const namespaceId = resource.durableObjectNamespaces.pipe(
|
|
443
|
+
Output.map((namespaces) => namespaces?.[className]),
|
|
444
|
+
);
|
|
445
|
+
const rows = [];
|
|
446
|
+
for (const decl of binding.containers ?? []) {
|
|
447
|
+
const declaration = (decl as { Application?: unknown }).Application;
|
|
448
|
+
const application =
|
|
449
|
+
isYieldableEffectLike(declaration) && !Output.isOutput(declaration)
|
|
450
|
+
? yield* declaration as Effect.Effect<unknown>
|
|
451
|
+
: undefined;
|
|
452
|
+
if (
|
|
453
|
+
!isContainerApplicationResource(application) ||
|
|
454
|
+
!isDurableObjectContainerResource(application)
|
|
455
|
+
) {
|
|
456
|
+
return yield* Effect.die(
|
|
457
|
+
new DurableObjectContainerBindingError({
|
|
458
|
+
bindingName,
|
|
459
|
+
message: `Cloudflare.DurableObject("${className}", { containers }) lists a Container that is not Durable Object-managed. Declare it with schedulingPolicy: "durable_object" and an image source (image, or context/dockerfile).`,
|
|
460
|
+
}),
|
|
461
|
+
);
|
|
462
|
+
}
|
|
463
|
+
yield* classApplication.bind`${application.LogicalId}`({
|
|
464
|
+
durableObjects: { namespaceId },
|
|
465
|
+
container: {
|
|
466
|
+
id: application.LogicalId,
|
|
467
|
+
settings: containerApplicationSettings(application),
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
rows.push({
|
|
471
|
+
...durableObjectContainerBinding(className, classApplication, application),
|
|
472
|
+
attachedBy: "binding" as const,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
return rows;
|
|
476
|
+
});
|
|
477
|
+
|
|
387
478
|
type BindingSpec = InputProps<WorkerBinding>;
|
|
388
479
|
|
|
389
480
|
/**
|
|
@@ -28,6 +28,10 @@ import { cachedFunction } from "../../Util/cached-function.ts";
|
|
|
28
28
|
import { initialCwd } from "../../Util/Node.ts";
|
|
29
29
|
import { sha256Object } from "../../Util/sha256.ts";
|
|
30
30
|
import { CloudflareEnvironment } from "../CloudflareEnvironment.ts";
|
|
31
|
+
import {
|
|
32
|
+
assertBindingContainersOnAsyncClasses,
|
|
33
|
+
mergeWorkerContainers,
|
|
34
|
+
} from "../Containers/ContainerConfiguration.ts";
|
|
31
35
|
import { localRuntimeServices } from "../LocalRuntime.ts";
|
|
32
36
|
import { CloudflareLogs } from "../Logs.ts";
|
|
33
37
|
import { detachQueueConsumersOfScript } from "../Queues/Consumer.ts";
|
|
@@ -3449,9 +3453,16 @@ export const LiveWorkerProvider = () =>
|
|
|
3449
3453
|
// Compute delete-class candidates. Candidates are validated against
|
|
3450
3454
|
// observed namespace ownership below — a class may already have been
|
|
3451
3455
|
// transferred to another script by its new host's deploy.
|
|
3456
|
+
// Several bindings may name one class (e.g. two Containers sharing a
|
|
3457
|
+
// Durable Object class): a class stays while any binding hosts it.
|
|
3458
|
+
const currentDoClasses = new Set(currentDoBindings.map((binding) => binding.className));
|
|
3452
3459
|
const deletedClassCandidates: string[] = [];
|
|
3453
3460
|
for (const [logicalId, className] of Object.entries(oldDoClassNameByLogicalId)) {
|
|
3454
|
-
if (
|
|
3461
|
+
if (
|
|
3462
|
+
!currentDoClassNameByLogicalId[logicalId] &&
|
|
3463
|
+
!currentDoClasses.has(className) &&
|
|
3464
|
+
!deletedClassCandidates.includes(className)
|
|
3465
|
+
) {
|
|
3455
3466
|
deletedClassCandidates.push(className);
|
|
3456
3467
|
}
|
|
3457
3468
|
}
|
|
@@ -3469,7 +3480,9 @@ export const LiveWorkerProvider = () =>
|
|
|
3469
3480
|
"className" in oldBinding &&
|
|
3470
3481
|
oldBinding.className &&
|
|
3471
3482
|
ownedLocally &&
|
|
3472
|
-
!currentDoBindings.some((binding) => binding.bindingName === oldBinding.name)
|
|
3483
|
+
!currentDoBindings.some((binding) => binding.bindingName === oldBinding.name) &&
|
|
3484
|
+
!currentDoClasses.has(oldBinding.className) &&
|
|
3485
|
+
!deletedClassCandidates.includes(oldBinding.className)
|
|
3473
3486
|
) {
|
|
3474
3487
|
deletedClassCandidates.push(oldBinding.className);
|
|
3475
3488
|
}
|
|
@@ -3607,15 +3620,31 @@ export const LiveWorkerProvider = () =>
|
|
|
3607
3620
|
}
|
|
3608
3621
|
|
|
3609
3622
|
// Collect container-backed classes so we can send container metadata.
|
|
3610
|
-
//
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3623
|
+
// A Durable Object-managed class merges the images of every bound
|
|
3624
|
+
// Container under one entry; `name` and `images` are only set for it.
|
|
3625
|
+
const containerRows = bindings.flatMap((b) => b.data.containers ?? []);
|
|
3626
|
+
yield* assertBindingContainersOnAsyncClasses(
|
|
3627
|
+
containerRows,
|
|
3628
|
+
effectNativeDurableObjectClasses(news.exports),
|
|
3629
|
+
);
|
|
3630
|
+
const containerClasses = (yield* mergeWorkerContainers(containerRows)).map(
|
|
3631
|
+
({ className, name, images }) => ({ className, name, images }),
|
|
3617
3632
|
);
|
|
3618
3633
|
|
|
3634
|
+
// Classes the deployed script hosts locally, by migration tag or by
|
|
3635
|
+
// an observed local Durable Object binding.
|
|
3636
|
+
const deployedDoClasses = new Set([
|
|
3637
|
+
...Object.values(oldDoClassNameByLogicalId),
|
|
3638
|
+
...oldBindings.flatMap((old) =>
|
|
3639
|
+
old.type === "durable_object_namespace" &&
|
|
3640
|
+
"className" in old &&
|
|
3641
|
+
old.className &&
|
|
3642
|
+
(!("scriptName" in old) || old.scriptName === undefined || old.scriptName === name)
|
|
3643
|
+
? [old.className]
|
|
3644
|
+
: [],
|
|
3645
|
+
),
|
|
3646
|
+
]);
|
|
3647
|
+
|
|
3619
3648
|
// Compute new, renamed, and transferred classes
|
|
3620
3649
|
const newClasses: string[] = [];
|
|
3621
3650
|
const newSqliteClasses: string[] = [];
|
|
@@ -3653,6 +3682,24 @@ export const LiveWorkerProvider = () =>
|
|
|
3653
3682
|
previousClassName = observed.className;
|
|
3654
3683
|
}
|
|
3655
3684
|
}
|
|
3685
|
+
// Another binding already hosts this class on the deployed script
|
|
3686
|
+
// (several bindings may name one class): the class exists, so a
|
|
3687
|
+
// binding added for it creates nothing.
|
|
3688
|
+
if (
|
|
3689
|
+
(!previousClassName || previousClassName !== binding.className) &&
|
|
3690
|
+
deployedDoClasses.has(binding.className)
|
|
3691
|
+
) {
|
|
3692
|
+
continue;
|
|
3693
|
+
}
|
|
3694
|
+
// A logical id that moved to another class while its old class is
|
|
3695
|
+
// still hosted by another binding is a new class, not a rename.
|
|
3696
|
+
if (
|
|
3697
|
+
previousClassName !== undefined &&
|
|
3698
|
+
previousClassName !== binding.className &&
|
|
3699
|
+
currentDoClasses.has(previousClassName)
|
|
3700
|
+
) {
|
|
3701
|
+
previousClassName = undefined;
|
|
3702
|
+
}
|
|
3656
3703
|
if (!previousClassName) {
|
|
3657
3704
|
// A class new to this script is a host move when the declaration
|
|
3658
3705
|
// says so: `transferredFrom` lists the former host(s) — moves
|
|
@@ -3685,9 +3732,17 @@ export const LiveWorkerProvider = () =>
|
|
|
3685
3732
|
}
|
|
3686
3733
|
// Default all new Durable Object classes to SQLite. Cloudflare
|
|
3687
3734
|
// recommends SQLite for new namespaces, and container-backed
|
|
3688
|
-
// Durable Objects require it.
|
|
3689
|
-
|
|
3690
|
-
|
|
3735
|
+
// Durable Objects require it. Several bindings may name one class
|
|
3736
|
+
// (e.g. two Containers sharing a class); create it once.
|
|
3737
|
+
if (!newSqliteClasses.includes(binding.className)) {
|
|
3738
|
+
newSqliteClasses.push(binding.className);
|
|
3739
|
+
}
|
|
3740
|
+
} else if (
|
|
3741
|
+
previousClassName !== binding.className &&
|
|
3742
|
+
!renamedClasses.some(
|
|
3743
|
+
(rename) => rename.from === previousClassName && rename.to === binding.className,
|
|
3744
|
+
)
|
|
3745
|
+
) {
|
|
3691
3746
|
renamedClasses.push({ from: previousClassName, to: binding.className });
|
|
3692
3747
|
}
|
|
3693
3748
|
}
|
|
@@ -3732,7 +3787,7 @@ export const LiveWorkerProvider = () =>
|
|
|
3732
3787
|
);
|
|
3733
3788
|
const transferringIn = new Set(transferredClasses.map((transfer) => transfer.to));
|
|
3734
3789
|
|
|
3735
|
-
const metadataContainers =
|
|
3790
|
+
const metadataContainers = containerClasses;
|
|
3736
3791
|
|
|
3737
3792
|
const compatibility = getCompatibility(news);
|
|
3738
3793
|
const tailConsumers = resolveTailConsumers(news.tailConsumers);
|
|
@@ -4791,6 +4846,10 @@ export const LiveWorkerProvider = () =>
|
|
|
4791
4846
|
// Container binding (mirrors reconcile's `containerClasses`).
|
|
4792
4847
|
// Mapping every DO class to a container would wrongly mark plain DOs
|
|
4793
4848
|
// as container-backed in the placeholder.
|
|
4849
|
+
yield* assertBindingContainersOnAsyncClasses(
|
|
4850
|
+
bindings.flatMap((b) => b.data.containers ?? []),
|
|
4851
|
+
exportDerived.map((binding) => binding.className),
|
|
4852
|
+
);
|
|
4794
4853
|
const containers = Array.from(
|
|
4795
4854
|
new Set(bindings.flatMap((b) => (b.data.containers ?? []).map((c) => c.className))),
|
|
4796
4855
|
).map((className) => ({ className }));
|
|
@@ -5776,6 +5835,12 @@ ${worker}${doClasses.map((className) => `export class ${className} extends Alche
|
|
|
5776
5835
|
* binding-derived entry wins on a collision so the `alchemy:dos:` tag keys off
|
|
5777
5836
|
* the same logical id (the binding sid) that `reconcile` writes.
|
|
5778
5837
|
*/
|
|
5838
|
+
/** The Durable Object classes a Worker implements as Effect-native exports. */
|
|
5839
|
+
function effectNativeDurableObjectClasses(exports: WorkerProps["exports"] | undefined) {
|
|
5840
|
+
const exportMap = (exports ?? {}) as Record<string, unknown>;
|
|
5841
|
+
return Object.keys(exportMap).filter((name) => isDurableObjectExport(exportMap[name]));
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5779
5844
|
function mergeDurableObjectClasses(
|
|
5780
5845
|
exportDerived: ReadonlyArray<{
|
|
5781
5846
|
logicalId: string;
|