@effect/platform-cloudflare 0.0.0 → 4.1.3
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/AGENTS.md +434 -0
- package/CLAUDE.md +434 -0
- package/LICENSE +21 -0
- package/README.md +506 -0
- package/ai-docs/README.md +44 -0
- package/ai-docs/package.json +36 -0
- package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
- package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +52 -0
- package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
- package/ai-docs/src/01_effect/01_basics/index.md +7 -0
- package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
- package/ai-docs/src/01_effect/02_schema/index.md +7 -0
- package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
- package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
- package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
- package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
- package/ai-docs/src/01_effect/03_services/index.md +5 -0
- package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
- package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
- package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
- package/ai-docs/src/01_effect/04_errors/index.md +1 -0
- package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
- package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
- package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
- package/ai-docs/src/01_effect/05_resources/index.md +3 -0
- package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
- package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
- package/ai-docs/src/01_effect/06_running/index.md +1 -0
- package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
- package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
- package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
- package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
- package/ai-docs/src/03_stream/30_encoding.ts +163 -0
- package/ai-docs/src/03_stream/index.md +4 -0
- package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
- package/ai-docs/src/04_integration/index.md +5 -0
- package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
- package/ai-docs/src/05_batching/index.md +3 -0
- package/ai-docs/src/06_schedule/10_schedules.ts +111 -0
- package/ai-docs/src/06_schedule/index.md +3 -0
- package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
- package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
- package/ai-docs/src/07_datetime/index.md +5 -0
- package/ai-docs/src/08_observability/10_logging.ts +66 -0
- package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
- package/ai-docs/src/08_observability/index.md +7 -0
- package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
- package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
- package/ai-docs/src/09_testing/index.md +1 -0
- package/ai-docs/src/10_predicate/01_basics.ts +14 -0
- package/ai-docs/src/10_predicate/index.md +9 -0
- package/ai-docs/src/40_sql/10_basics.ts +175 -0
- package/ai-docs/src/40_sql/index.md +6 -0
- package/ai-docs/src/50_http-client/10_basics.ts +102 -0
- package/ai-docs/src/50_http-client/index.md +3 -0
- package/ai-docs/src/51_http-server/10_basics.ts +118 -0
- package/ai-docs/src/51_http-server/20_testing.ts +100 -0
- package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
- package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
- package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
- package/ai-docs/src/51_http-server/fixtures/api/Users.ts +107 -0
- package/ai-docs/src/51_http-server/fixtures/domain/User.ts +21 -0
- package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
- package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +37 -0
- package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +80 -0
- package/ai-docs/src/51_http-server/fixtures/server/Users.ts +183 -0
- package/ai-docs/src/51_http-server/index.md +3 -0
- package/ai-docs/src/52_graphql/10_basics.ts +117 -0
- package/ai-docs/src/52_graphql/20_testing.ts +45 -0
- package/ai-docs/src/52_graphql/30_batching.ts +118 -0
- package/ai-docs/src/52_graphql/40_aliased-batching.ts +124 -0
- package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
- package/ai-docs/src/52_graphql/index.md +6 -0
- package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
- package/ai-docs/src/60_child-process/index.md +3 -0
- package/ai-docs/src/70_cli/10_basics.ts +162 -0
- package/ai-docs/src/70_cli/index.md +5 -0
- package/ai-docs/src/71_ai/10_language-model.ts +156 -0
- package/ai-docs/src/71_ai/20_tools.ts +226 -0
- package/ai-docs/src/71_ai/30_chat.ts +158 -0
- package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
- package/ai-docs/src/71_ai/index.md +5 -0
- package/ai-docs/src/80_cluster/10_entities.ts +97 -0
- package/ai-docs/src/80_cluster/index.md +4 -0
- package/ai-docs/src/index.md +10 -0
- package/ai-docs/tsconfig.json +28 -0
- package/dist/AlchemyCloudflareCluster.d.ts +134 -0
- package/dist/AlchemyCloudflareCluster.d.ts.map +1 -0
- package/dist/AlchemyCloudflareCluster.js +111 -0
- package/dist/AlchemyCloudflareCluster.js.map +1 -0
- package/dist/CloudflareCluster.d.ts +196 -0
- package/dist/CloudflareCluster.d.ts.map +1 -0
- package/dist/CloudflareCluster.js +533 -0
- package/dist/CloudflareCluster.js.map +1 -0
- package/dist/CloudflareDurableObjectPrograms.d.ts +279 -0
- package/dist/CloudflareDurableObjectPrograms.d.ts.map +1 -0
- package/dist/CloudflareDurableObjectPrograms.js +295 -0
- package/dist/CloudflareDurableObjectPrograms.js.map +1 -0
- package/dist/CloudflareDurableObjects.d.ts +149 -0
- package/dist/CloudflareDurableObjects.d.ts.map +1 -0
- package/dist/CloudflareDurableObjects.js +337 -0
- package/dist/CloudflareDurableObjects.js.map +1 -0
- package/dist/CloudflarePersistedQueue.d.ts +36 -0
- package/dist/CloudflarePersistedQueue.d.ts.map +1 -0
- package/dist/CloudflarePersistedQueue.js +122 -0
- package/dist/CloudflarePersistedQueue.js.map +1 -0
- package/dist/CloudflareWorkflowEngine.d.ts +72 -0
- package/dist/CloudflareWorkflowEngine.d.ts.map +1 -0
- package/dist/CloudflareWorkflowEngine.js +187 -0
- package/dist/CloudflareWorkflowEngine.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/alchemyCluster.d.ts +2 -0
- package/dist/internal/alchemyCluster.d.ts.map +1 -0
- package/dist/internal/alchemyCluster.js +75 -0
- package/dist/internal/alchemyCluster.js.map +1 -0
- package/dist/internal/boundedMap.d.ts +3 -0
- package/dist/internal/boundedMap.d.ts.map +1 -0
- package/dist/internal/boundedMap.js +15 -0
- package/dist/internal/boundedMap.js.map +1 -0
- package/dist/internal/cluster.d.ts +57 -0
- package/dist/internal/cluster.d.ts.map +1 -0
- package/dist/internal/cluster.js +2 -0
- package/dist/internal/cluster.js.map +1 -0
- package/dist/internal/clusterName.d.ts +2 -0
- package/dist/internal/clusterName.d.ts.map +1 -0
- package/dist/internal/clusterName.js +16 -0
- package/dist/internal/clusterName.js.map +1 -0
- package/dist/internal/entityKeepAlive.d.ts +2 -0
- package/dist/internal/entityKeepAlive.d.ts.map +1 -0
- package/dist/internal/entityKeepAlive.js +73 -0
- package/dist/internal/entityKeepAlive.js.map +1 -0
- package/dist/internal/entityMailbox.d.ts +2 -0
- package/dist/internal/entityMailbox.d.ts.map +1 -0
- package/dist/internal/entityMailbox.js +160 -0
- package/dist/internal/entityMailbox.js.map +1 -0
- package/dist/internal/entityRegistry.d.ts +16 -0
- package/dist/internal/entityRegistry.d.ts.map +1 -0
- package/dist/internal/entityRegistry.js +22 -0
- package/dist/internal/entityRegistry.js.map +1 -0
- package/dist/internal/entityReply.d.ts +2 -0
- package/dist/internal/entityReply.d.ts.map +1 -0
- package/dist/internal/entityReply.js +35 -0
- package/dist/internal/entityReply.js.map +1 -0
- package/dist/internal/entityRuntime.d.ts +2 -0
- package/dist/internal/entityRuntime.d.ts.map +1 -0
- package/dist/internal/entityRuntime.js +823 -0
- package/dist/internal/entityRuntime.js.map +1 -0
- package/dist/internal/entitySql.d.ts +2 -0
- package/dist/internal/entitySql.d.ts.map +1 -0
- package/dist/internal/entitySql.js +29 -0
- package/dist/internal/entitySql.js.map +1 -0
- package/dist/internal/entityStorage.d.ts +2 -0
- package/dist/internal/entityStorage.d.ts.map +1 -0
- package/dist/internal/entityStorage.js +98 -0
- package/dist/internal/entityStorage.js.map +1 -0
- package/dist/internal/entityWire.d.ts +2 -0
- package/dist/internal/entityWire.d.ts.map +1 -0
- package/dist/internal/entityWire.js +146 -0
- package/dist/internal/entityWire.js.map +1 -0
- package/dist/internal/queueRuntime.d.ts +2 -0
- package/dist/internal/queueRuntime.d.ts.map +1 -0
- package/dist/internal/queueRuntime.js +88 -0
- package/dist/internal/queueRuntime.js.map +1 -0
- package/dist/internal/queueStorage.d.ts +2 -0
- package/dist/internal/queueStorage.d.ts.map +1 -0
- package/dist/internal/queueStorage.js +129 -0
- package/dist/internal/queueStorage.js.map +1 -0
- package/dist/internal/registry.d.ts +10 -0
- package/dist/internal/registry.d.ts.map +1 -0
- package/dist/internal/registry.js +24 -0
- package/dist/internal/registry.js.map +1 -0
- package/dist/internal/singletonRegistry.d.ts +2 -0
- package/dist/internal/singletonRegistry.d.ts.map +1 -0
- package/dist/internal/singletonRegistry.js +9 -0
- package/dist/internal/singletonRegistry.js.map +1 -0
- package/dist/internal/singletonRuntime.d.ts +2 -0
- package/dist/internal/singletonRuntime.d.ts.map +1 -0
- package/dist/internal/singletonRuntime.js +42 -0
- package/dist/internal/singletonRuntime.js.map +1 -0
- package/dist/internal/singletonStorage.d.ts +2 -0
- package/dist/internal/singletonStorage.d.ts.map +1 -0
- package/dist/internal/singletonStorage.js +34 -0
- package/dist/internal/singletonStorage.js.map +1 -0
- package/dist/internal/transport.d.ts +2 -0
- package/dist/internal/transport.d.ts.map +1 -0
- package/dist/internal/transport.js +49 -0
- package/dist/internal/transport.js.map +1 -0
- package/dist/internal/workflowRegistry.d.ts +2 -0
- package/dist/internal/workflowRegistry.d.ts.map +1 -0
- package/dist/internal/workflowRegistry.js +29 -0
- package/dist/internal/workflowRegistry.js.map +1 -0
- package/dist/internal/workflowRuntime.d.ts +2 -0
- package/dist/internal/workflowRuntime.d.ts.map +1 -0
- package/dist/internal/workflowRuntime.js +322 -0
- package/dist/internal/workflowRuntime.js.map +1 -0
- package/dist/internal/workflowStorage.d.ts +2 -0
- package/dist/internal/workflowStorage.d.ts.map +1 -0
- package/dist/internal/workflowStorage.js +113 -0
- package/dist/internal/workflowStorage.js.map +1 -0
- package/dist/internal/workflowWire.d.ts +2 -0
- package/dist/internal/workflowWire.d.ts.map +1 -0
- package/dist/internal/workflowWire.js +52 -0
- package/dist/internal/workflowWire.js.map +1 -0
- package/package.json +77 -1
- package/src/AlchemyCloudflareCluster.ts +200 -0
- package/src/CloudflareCluster.ts +704 -0
- package/src/CloudflareDurableObjectPrograms.ts +543 -0
- package/src/CloudflareDurableObjects.ts +430 -0
- package/src/CloudflarePersistedQueue.ts +189 -0
- package/src/CloudflareWorkflowEngine.ts +220 -0
- package/src/index.ts +38 -0
- package/src/internal/alchemyCluster.ts +120 -0
- package/src/internal/boundedMap.ts +15 -0
- package/src/internal/cluster.ts +57 -0
- package/src/internal/clusterName.ts +19 -0
- package/src/internal/entityKeepAlive.ts +121 -0
- package/src/internal/entityMailbox.ts +324 -0
- package/src/internal/entityRegistry.ts +46 -0
- package/src/internal/entityReply.ts +54 -0
- package/src/internal/entityRuntime.ts +1318 -0
- package/src/internal/entitySql.ts +60 -0
- package/src/internal/entityStorage.ts +168 -0
- package/src/internal/entityWire.ts +204 -0
- package/src/internal/queueRuntime.ts +174 -0
- package/src/internal/queueStorage.ts +255 -0
- package/src/internal/registry.ts +31 -0
- package/src/internal/singletonRegistry.ts +21 -0
- package/src/internal/singletonRuntime.ts +88 -0
- package/src/internal/singletonStorage.ts +64 -0
- package/src/internal/transport.ts +67 -0
- package/src/internal/workflowRegistry.ts +78 -0
- package/src/internal/workflowRuntime.ts +443 -0
- package/src/internal/workflowStorage.ts +234 -0
- package/src/internal/workflowWire.ts +123 -0
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Class-independent Durable Object programs behind the Cloudflare cluster
|
|
3
|
+
* integration.
|
|
4
|
+
*
|
|
5
|
+
* The bundled classes in `CloudflareDurableObjects` bind these behaviors to
|
|
6
|
+
* `cloudflare:workers` base classes for the Wrangler path. A framework that
|
|
7
|
+
* creates its own Durable Object classes (for example the Alchemy integration
|
|
8
|
+
* in `AlchemyCloudflareCluster`) runs these programs instead and forwards its
|
|
9
|
+
* class methods to the returned handles.
|
|
10
|
+
*
|
|
11
|
+
* @stability experimental
|
|
12
|
+
* @since 4.1.3
|
|
13
|
+
*/
|
|
14
|
+
import type { DurableObjectStorage } from "@cloudflare/workers-types"
|
|
15
|
+
import * as Cause from "effect/Cause"
|
|
16
|
+
import * as Clock from "effect/Clock"
|
|
17
|
+
import * as ClusterMetrics from "effect/cluster/ClusterMetrics"
|
|
18
|
+
import * as EntityAddress from "effect/cluster/EntityAddress"
|
|
19
|
+
import * as EntityId from "effect/cluster/EntityId"
|
|
20
|
+
import * as EntityType from "effect/cluster/EntityType"
|
|
21
|
+
import * as ShardId from "effect/cluster/ShardId"
|
|
22
|
+
import * as Effect from "effect/Effect"
|
|
23
|
+
import { decodeName, encodeName } from "./internal/clusterName.ts"
|
|
24
|
+
import { makeEntityKeepAlive } from "./internal/entityKeepAlive.ts"
|
|
25
|
+
import { keepAliveHeartbeatMillis } from "./internal/entityRegistry.ts"
|
|
26
|
+
import { makeEntityManager } from "./internal/entityRuntime.ts"
|
|
27
|
+
import {
|
|
28
|
+
armAlarm,
|
|
29
|
+
earliestDeliverAt,
|
|
30
|
+
ensureEntityStorage,
|
|
31
|
+
loadEntityState,
|
|
32
|
+
rememberEntityName
|
|
33
|
+
} from "./internal/entityStorage.ts"
|
|
34
|
+
import { makeQueueRuntime } from "./internal/queueRuntime.ts"
|
|
35
|
+
import { earliestLeaseExpiry } from "./internal/queueStorage.ts"
|
|
36
|
+
import { getSingletonRegistration } from "./internal/singletonRegistry.ts"
|
|
37
|
+
import { makeSingletonRuntime } from "./internal/singletonRuntime.ts"
|
|
38
|
+
import { ensureSingletonStorage, loadSingletonState, rememberSingletonName } from "./internal/singletonStorage.ts"
|
|
39
|
+
import type { WorkflowStub } from "./internal/workflowRegistry.ts"
|
|
40
|
+
import { makeWorkflowRuntime } from "./internal/workflowRuntime.ts"
|
|
41
|
+
import { earliestClockWakeUp, ensureWorkflowStorage, loadExecution } from "./internal/workflowStorage.ts"
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The native Durable Object capabilities a cluster program is built from.
|
|
45
|
+
*
|
|
46
|
+
* **Details**
|
|
47
|
+
*
|
|
48
|
+
* A `DurableObjectState` from `cloudflare:workers` satisfies this shape
|
|
49
|
+
* directly, so pass it as is. The program only reads the object name, opens
|
|
50
|
+
* SQLite storage, resolves same-Worker class exports, and extends the current
|
|
51
|
+
* event with `waitUntil`.
|
|
52
|
+
*
|
|
53
|
+
* @stability experimental
|
|
54
|
+
* @category models
|
|
55
|
+
* @since 4.1.3
|
|
56
|
+
*/
|
|
57
|
+
export interface DurableObjectProgramState {
|
|
58
|
+
readonly id: { readonly name?: string | undefined }
|
|
59
|
+
readonly storage: DurableObjectStorage
|
|
60
|
+
readonly exports: object
|
|
61
|
+
readonly waitUntil: (promise: Promise<unknown>) => void
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const exportedNamespace = <Stub>(
|
|
65
|
+
state: DurableObjectProgramState,
|
|
66
|
+
className: string
|
|
67
|
+
): { readonly getByName: (name: string) => Stub } | undefined =>
|
|
68
|
+
(state.exports as Record<string, unknown>)[className] as
|
|
69
|
+
| { readonly getByName: (name: string) => Stub }
|
|
70
|
+
| undefined
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Delivery options accepted by the cluster entity transport program.
|
|
74
|
+
*
|
|
75
|
+
* @stability experimental
|
|
76
|
+
* @category models
|
|
77
|
+
* @since 4.1.3
|
|
78
|
+
*/
|
|
79
|
+
export interface EntityDeliveryOptions {
|
|
80
|
+
readonly deliverAt?: number | undefined
|
|
81
|
+
readonly primaryKey?: string | null | undefined
|
|
82
|
+
readonly replyTo?: string | undefined
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Result returned by the cluster entity transport program.
|
|
87
|
+
*
|
|
88
|
+
* @stability experimental
|
|
89
|
+
* @category models
|
|
90
|
+
* @since 4.1.3
|
|
91
|
+
*/
|
|
92
|
+
export type EntityInvokeResult = {
|
|
93
|
+
readonly _tag: "Success"
|
|
94
|
+
readonly requestId: string
|
|
95
|
+
readonly replies: ReadonlyArray<string>
|
|
96
|
+
} | {
|
|
97
|
+
readonly _tag: "MailboxFull"
|
|
98
|
+
} | {
|
|
99
|
+
readonly _tag: "EncodedMessageTooLarge"
|
|
100
|
+
} | {
|
|
101
|
+
readonly _tag: "AskDeduplicatedToTell"
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Effect-valued program for one cluster entity Durable Object instance.
|
|
106
|
+
*
|
|
107
|
+
* **Gotchas**
|
|
108
|
+
*
|
|
109
|
+
* All program methods must keep their error channel `never`: Alchemy encodes
|
|
110
|
+
* typed failures as envelopes, but the native namespace transport expects
|
|
111
|
+
* plain results and rejected defects.
|
|
112
|
+
*
|
|
113
|
+
* @stability experimental
|
|
114
|
+
* @category models
|
|
115
|
+
* @since 4.1.3
|
|
116
|
+
*/
|
|
117
|
+
export type ClusterEntityProgram = {
|
|
118
|
+
readonly alarm: () => Effect.Effect<void>
|
|
119
|
+
readonly hold: () => Effect.Effect<void>
|
|
120
|
+
readonly invoke: (
|
|
121
|
+
envelopeText: string,
|
|
122
|
+
discard: boolean,
|
|
123
|
+
delivery?: EntityDeliveryOptions
|
|
124
|
+
) => Effect.Effect<EntityInvokeResult>
|
|
125
|
+
readonly acknowledge: (requestId: string, replyId: string) => Effect.Effect<ReadonlyArray<string>>
|
|
126
|
+
readonly interrupt: (storageRequestId: string, clientRequestId?: string) => Effect.Effect<void>
|
|
127
|
+
readonly reset: (requestId: string) => Effect.Effect<void>
|
|
128
|
+
readonly deliverReply: (requestId: string, reply: string) => Effect.Effect<boolean>
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates the program behind one cluster entity Durable Object.
|
|
133
|
+
*
|
|
134
|
+
* **When to use**
|
|
135
|
+
*
|
|
136
|
+
* Use when a framework creates the native Durable Object class itself and
|
|
137
|
+
* needs the entity mailbox, alarm, and same-Worker RPC behavior without
|
|
138
|
+
* extending the bundled `ClusterEntity` class.
|
|
139
|
+
*
|
|
140
|
+
* **Details**
|
|
141
|
+
*
|
|
142
|
+
* Run the returned Effect once per Durable Object activation, after the
|
|
143
|
+
* Worker application (the entity handler registrations) is initialized. It
|
|
144
|
+
* ensures the mailbox tables and re-arms the single alarm from the earliest
|
|
145
|
+
* pending `deliver_at`, or the keep-alive heartbeat when that alarm is
|
|
146
|
+
* missing, before returning the handler object. The host class must be
|
|
147
|
+
* exported from the Worker entry as `ClusterEntity`; keep-alive and reply
|
|
148
|
+
* delivery resolve that export through `state.exports`.
|
|
149
|
+
*
|
|
150
|
+
* @stability experimental
|
|
151
|
+
* @category constructors
|
|
152
|
+
* @since 4.1.3
|
|
153
|
+
*/
|
|
154
|
+
export const makeClusterEntityProgram = Effect.fnUntraced(function*(state: DurableObjectProgramState) {
|
|
155
|
+
const sql = state.storage.sql
|
|
156
|
+
ensureEntityStorage(sql)
|
|
157
|
+
const stored = loadEntityState(sql)
|
|
158
|
+
// An alarm wake may carry no `id.name`; fall back to the name stored by an
|
|
159
|
+
// earlier activation, so heartbeat and `deliverAt` alarms still run.
|
|
160
|
+
const entityName = state.id.name ?? stored.name ?? ""
|
|
161
|
+
const name = decodeName(entityName)
|
|
162
|
+
if (name === undefined) {
|
|
163
|
+
return yield* Effect.die(new Error("ClusterEntity requires a canonical entity Durable Object name"))
|
|
164
|
+
}
|
|
165
|
+
// Written only when it changes, so a repeat activation costs no write.
|
|
166
|
+
if (entityName !== stored.name) rememberEntityName(sql, entityName)
|
|
167
|
+
const keepAlive = makeEntityKeepAlive({
|
|
168
|
+
startHold: () => {
|
|
169
|
+
const namespace = exportedNamespace<{ readonly hold: () => Promise<void> }>(state, "ClusterEntity")
|
|
170
|
+
if (namespace === undefined) {
|
|
171
|
+
return Promise.reject(
|
|
172
|
+
new Error("CloudflareCluster: ClusterEntity export is unavailable for keep-alive")
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
return namespace.getByName(entityName).hold()
|
|
176
|
+
},
|
|
177
|
+
wanted: stored.keepAlive,
|
|
178
|
+
persist: (wanted) => manager.saveKeepAlive(wanted),
|
|
179
|
+
retryCapMillis: () => keepAliveHeartbeatMillis(name.type)
|
|
180
|
+
})
|
|
181
|
+
const manager = makeEntityManager({
|
|
182
|
+
storage: state.storage,
|
|
183
|
+
address: EntityAddress.make({
|
|
184
|
+
shardId: ShardId.make("default", 1),
|
|
185
|
+
entityType: EntityType.make(name.type),
|
|
186
|
+
entityId: EntityId.make(name.id)
|
|
187
|
+
}),
|
|
188
|
+
entityName,
|
|
189
|
+
keepAlive,
|
|
190
|
+
waitUntil: (effect) => state.waitUntil(Effect.runPromise(effect)),
|
|
191
|
+
getNamespace: () =>
|
|
192
|
+
exportedNamespace<{
|
|
193
|
+
readonly deliverReply: (requestId: string, reply: string) => Promise<boolean>
|
|
194
|
+
}>(state, "ClusterEntity")
|
|
195
|
+
})
|
|
196
|
+
// A stored keep-alive flag arms the heartbeat too. That repairs the chain
|
|
197
|
+
// after Cloudflare dropped an alarm whose retries all failed, or after a
|
|
198
|
+
// crash between the flag write and `setAlarm`. Arming at `now` on every
|
|
199
|
+
// activation would fire one wasted alarm per wake.
|
|
200
|
+
const deliverAt = earliestDeliverAt(sql)
|
|
201
|
+
const alarmAt = stored.keepAlive
|
|
202
|
+
? Math.min(deliverAt ?? Infinity, Date.now() + keepAliveHeartbeatMillis(name.type))
|
|
203
|
+
: deliverAt
|
|
204
|
+
if (alarmAt !== undefined) {
|
|
205
|
+
yield* armAlarm(state.storage, alarmAt)
|
|
206
|
+
}
|
|
207
|
+
const program: ClusterEntityProgram = {
|
|
208
|
+
alarm: () => manager.alarm,
|
|
209
|
+
hold: () => keepAlive.await,
|
|
210
|
+
invoke: (envelopeText, discard, delivery) => manager.invoke(envelopeText, discard, delivery),
|
|
211
|
+
acknowledge: manager.acknowledge,
|
|
212
|
+
interrupt: manager.interrupt,
|
|
213
|
+
reset: manager.reset,
|
|
214
|
+
deliverReply: manager.deliverReply
|
|
215
|
+
}
|
|
216
|
+
return program
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Execution options accepted by the workflow program's `run`.
|
|
221
|
+
*
|
|
222
|
+
* @stability experimental
|
|
223
|
+
* @category models
|
|
224
|
+
* @since 4.1.3
|
|
225
|
+
*/
|
|
226
|
+
export interface ClusterWorkflowRunOptions {
|
|
227
|
+
readonly discard: boolean
|
|
228
|
+
readonly parent?: { readonly workflowName: string; readonly executionId: string } | undefined
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* How a workflow resume was requested. `"explicit"` (the default) always
|
|
233
|
+
* replays the execution; `"automatic"`, used for a child's completion wake,
|
|
234
|
+
* leaves an execution suspended by `Workflow.SuspendOnFailure` parked until
|
|
235
|
+
* an explicit resume.
|
|
236
|
+
*
|
|
237
|
+
* @stability experimental
|
|
238
|
+
* @category models
|
|
239
|
+
* @since 4.1.3
|
|
240
|
+
*/
|
|
241
|
+
export type ClusterWorkflowWakeMode = "explicit" | "automatic"
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Effect-valued program for one workflow execution Durable Object.
|
|
245
|
+
*
|
|
246
|
+
* @stability experimental
|
|
247
|
+
* @category models
|
|
248
|
+
* @since 4.1.3
|
|
249
|
+
*/
|
|
250
|
+
export type ClusterWorkflowProgram = {
|
|
251
|
+
readonly run: (payload: string, options: ClusterWorkflowRunOptions) => Effect.Effect<string>
|
|
252
|
+
readonly poll: () => Effect.Effect<string | undefined>
|
|
253
|
+
readonly resume: (mode?: ClusterWorkflowWakeMode) => Effect.Effect<void>
|
|
254
|
+
readonly interrupt: () => Effect.Effect<void>
|
|
255
|
+
readonly interruptUnsafe: () => Effect.Effect<void>
|
|
256
|
+
readonly deferredDone: (name: string, exit: string) => Effect.Effect<void>
|
|
257
|
+
readonly scheduleClock: (name: string, deferredName: string, wakeUp: number) => Effect.Effect<void>
|
|
258
|
+
readonly alarm: () => Effect.Effect<void>
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Creates the program behind one workflow execution Durable Object.
|
|
263
|
+
*
|
|
264
|
+
* **When to use**
|
|
265
|
+
*
|
|
266
|
+
* Use when a framework creates the native Durable Object class itself and
|
|
267
|
+
* needs the Effect Workflow journal, alarm, and same-Worker RPC behavior
|
|
268
|
+
* without extending the bundled `ClusterWorkflow` class.
|
|
269
|
+
*
|
|
270
|
+
* **Details**
|
|
271
|
+
*
|
|
272
|
+
* Run the returned Effect once per Durable Object activation, after the
|
|
273
|
+
* Worker application (the workflow handler registrations) is initialized. It
|
|
274
|
+
* ensures the workflow tables and re-arms the single alarm from the earliest
|
|
275
|
+
* pending clock before returning the handler object. The host class must be
|
|
276
|
+
* exported from the Worker entry as `ClusterWorkflow`; child and parent
|
|
277
|
+
* workflow delivery resolves that export through `state.exports`.
|
|
278
|
+
*
|
|
279
|
+
* @stability experimental
|
|
280
|
+
* @category constructors
|
|
281
|
+
* @since 4.1.3
|
|
282
|
+
*/
|
|
283
|
+
export const makeClusterWorkflowProgram = Effect.fnUntraced(function*(state: DurableObjectProgramState) {
|
|
284
|
+
const clock = yield* Clock.Clock
|
|
285
|
+
const sql = state.storage.sql
|
|
286
|
+
ensureWorkflowStorage(sql)
|
|
287
|
+
let name: string | undefined
|
|
288
|
+
if (state.id.name !== undefined) {
|
|
289
|
+
if (decodeName(state.id.name) === undefined) {
|
|
290
|
+
return yield* Effect.die(new Error("ClusterWorkflow requires a canonical workflow Durable Object name"))
|
|
291
|
+
}
|
|
292
|
+
name = state.id.name
|
|
293
|
+
} else {
|
|
294
|
+
// An alarm wake carries no `id.name`; recover it from the stored
|
|
295
|
+
// execution so due clocks still fire after eviction.
|
|
296
|
+
const stored = loadExecution(sql)
|
|
297
|
+
name = stored === undefined ? undefined : encodeName(stored.workflowName, stored.executionId)
|
|
298
|
+
}
|
|
299
|
+
let runtime: ReturnType<typeof makeWorkflowRuntime> | undefined
|
|
300
|
+
const getRuntime = () => {
|
|
301
|
+
if (runtime !== undefined) return runtime
|
|
302
|
+
if (name === undefined) {
|
|
303
|
+
throw new Error("ClusterWorkflow requires a canonical workflow Durable Object name")
|
|
304
|
+
}
|
|
305
|
+
runtime = makeWorkflowRuntime({
|
|
306
|
+
name,
|
|
307
|
+
sql,
|
|
308
|
+
alarm: state.storage,
|
|
309
|
+
now: () => clock.currentTimeMillisUnsafe(),
|
|
310
|
+
waitUntil: (promise) => state.waitUntil(promise),
|
|
311
|
+
getStub: (stubName) => {
|
|
312
|
+
const namespace = exportedNamespace<WorkflowStub>(state, "ClusterWorkflow")
|
|
313
|
+
if (namespace === undefined) {
|
|
314
|
+
throw new Error("CloudflareCluster: ClusterWorkflow export is unavailable for workflow delivery")
|
|
315
|
+
}
|
|
316
|
+
return namespace.getByName(stubName)
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
return runtime
|
|
320
|
+
}
|
|
321
|
+
const wakeUp = earliestClockWakeUp(sql)
|
|
322
|
+
if (wakeUp !== undefined) {
|
|
323
|
+
yield* armAlarm(state.storage, wakeUp)
|
|
324
|
+
}
|
|
325
|
+
const program: ClusterWorkflowProgram = {
|
|
326
|
+
run: (payload, options) => Effect.promise(() => getRuntime().run(payload, options)),
|
|
327
|
+
poll: () => Effect.promise(() => getRuntime().poll()),
|
|
328
|
+
resume: (mode) => Effect.promise(() => getRuntime().resume(mode)),
|
|
329
|
+
interrupt: () => Effect.promise(() => getRuntime().interrupt()),
|
|
330
|
+
interruptUnsafe: () => Effect.promise(() => getRuntime().interruptUnsafe()),
|
|
331
|
+
deferredDone: (deferredName, exit) => Effect.promise(() => getRuntime().deferredDone(deferredName, exit)),
|
|
332
|
+
scheduleClock: (clockName, deferredName, wakeAt) =>
|
|
333
|
+
Effect.promise(() => getRuntime().scheduleClock(clockName, deferredName, wakeAt)),
|
|
334
|
+
// No stored execution means no clock could have armed this alarm.
|
|
335
|
+
alarm: () => name === undefined ? Effect.void : Effect.promise(() => getRuntime().runAlarm())
|
|
336
|
+
}
|
|
337
|
+
return program
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Item leased from the durable queue program.
|
|
342
|
+
*
|
|
343
|
+
* @stability experimental
|
|
344
|
+
* @category models
|
|
345
|
+
* @since 4.1.3
|
|
346
|
+
*/
|
|
347
|
+
export interface DurableQueueItem {
|
|
348
|
+
readonly id: string
|
|
349
|
+
readonly element: string
|
|
350
|
+
readonly attempts: number
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Effect-valued program for one durable queue Durable Object.
|
|
355
|
+
*
|
|
356
|
+
* @stability experimental
|
|
357
|
+
* @category models
|
|
358
|
+
* @since 4.1.3
|
|
359
|
+
*/
|
|
360
|
+
export type ClusterDurableQueueProgram = {
|
|
361
|
+
readonly offer: (
|
|
362
|
+
id: string,
|
|
363
|
+
element: string,
|
|
364
|
+
delayMillis?: number,
|
|
365
|
+
deduplicate?: "untilExecuted" | "untilRemoved"
|
|
366
|
+
) => Effect.Effect<void>
|
|
367
|
+
readonly take: (takerId: string, maxAttempts: number, leaseMillis: number) => Effect.Effect<DurableQueueItem>
|
|
368
|
+
readonly cancelTake: (takerId: string) => Effect.Effect<void>
|
|
369
|
+
readonly complete: (id: string, takerId: string) => Effect.Effect<void>
|
|
370
|
+
readonly fail: (
|
|
371
|
+
id: string,
|
|
372
|
+
lastFailure: string,
|
|
373
|
+
takerId: string,
|
|
374
|
+
terminal?: boolean,
|
|
375
|
+
retryDelayMillis?: number
|
|
376
|
+
) => Effect.Effect<void>
|
|
377
|
+
readonly release: (id: string, takerId: string) => Effect.Effect<void>
|
|
378
|
+
readonly extend: (id: string, leaseMillis: number, takerId: string) => Effect.Effect<void>
|
|
379
|
+
readonly alarm: () => Effect.Effect<void>
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* Creates the program behind one durable queue Durable Object.
|
|
384
|
+
*
|
|
385
|
+
* **When to use**
|
|
386
|
+
*
|
|
387
|
+
* Use when a framework creates the native Durable Object class itself and
|
|
388
|
+
* needs the queue persistence, leasing, and alarm behavior without extending
|
|
389
|
+
* the bundled `ClusterDurableQueue` class.
|
|
390
|
+
*
|
|
391
|
+
* **Details**
|
|
392
|
+
*
|
|
393
|
+
* Run the returned Effect once per Durable Object activation. It ensures the
|
|
394
|
+
* queue table and re-arms the single alarm from the earliest pending lease
|
|
395
|
+
* expiry before returning the handler object.
|
|
396
|
+
*
|
|
397
|
+
* @stability experimental
|
|
398
|
+
* @category constructors
|
|
399
|
+
* @since 4.1.3
|
|
400
|
+
*/
|
|
401
|
+
export const makeClusterDurableQueueProgram = Effect.fnUntraced(function*(state: DurableObjectProgramState) {
|
|
402
|
+
const clock = yield* Clock.Clock
|
|
403
|
+
if (state.id.name !== undefined && decodeName(state.id.name) === undefined) {
|
|
404
|
+
return yield* Effect.die(new Error("ClusterDurableQueue requires a canonical queue Durable Object name"))
|
|
405
|
+
}
|
|
406
|
+
const runtime = makeQueueRuntime({
|
|
407
|
+
sql: state.storage.sql,
|
|
408
|
+
alarm: state.storage,
|
|
409
|
+
now: () => clock.currentTimeMillisUnsafe()
|
|
410
|
+
})
|
|
411
|
+
const expiry = earliestLeaseExpiry(state.storage.sql)
|
|
412
|
+
if (expiry !== undefined) {
|
|
413
|
+
yield* armAlarm(state.storage, expiry)
|
|
414
|
+
}
|
|
415
|
+
const program: ClusterDurableQueueProgram = {
|
|
416
|
+
offer: (id, element, delayMillis, deduplicate) =>
|
|
417
|
+
Effect.promise(() => runtime.offer(id, element, delayMillis, deduplicate)),
|
|
418
|
+
take: (takerId, maxAttempts, leaseMillis) => Effect.promise(() => runtime.take(takerId, maxAttempts, leaseMillis)),
|
|
419
|
+
cancelTake: (takerId) => Effect.promise(() => runtime.cancelTake(takerId)),
|
|
420
|
+
complete: (id, takerId) => Effect.promise(() => runtime.complete(id, takerId)),
|
|
421
|
+
fail: (id, lastFailure, takerId, terminal, retryDelayMillis) =>
|
|
422
|
+
Effect.promise(() => runtime.fail(id, lastFailure, takerId, terminal, retryDelayMillis)),
|
|
423
|
+
release: (id, takerId) => Effect.promise(() => runtime.release(id, takerId)),
|
|
424
|
+
extend: (id, leaseMillis, takerId) => Effect.promise(() => runtime.extend(id, leaseMillis, takerId)),
|
|
425
|
+
alarm: () => Effect.promise(() => runtime.runAlarm())
|
|
426
|
+
}
|
|
427
|
+
return program
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Effect-valued program for one cluster singleton Durable Object.
|
|
432
|
+
*
|
|
433
|
+
* @stability experimental
|
|
434
|
+
* @category models
|
|
435
|
+
* @since 4.1.3
|
|
436
|
+
*/
|
|
437
|
+
export type ClusterSingletonProgram = {
|
|
438
|
+
readonly wake: () => Effect.Effect<SingletonWakeResult>
|
|
439
|
+
readonly alarm: () => Effect.Effect<void>
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* How one singleton wake ended.
|
|
444
|
+
*
|
|
445
|
+
* **Details**
|
|
446
|
+
*
|
|
447
|
+
* A failed singleton run resolves `wake()` with a `Failure` instead of
|
|
448
|
+
* rejecting it, so a rejected `wake()` always means the call itself failed in
|
|
449
|
+
* transit and is safe to retry. Retrying a failed run would rerun user code.
|
|
450
|
+
*
|
|
451
|
+
* @stability experimental
|
|
452
|
+
* @category models
|
|
453
|
+
* @since 4.1.3
|
|
454
|
+
*/
|
|
455
|
+
export type SingletonWakeResult =
|
|
456
|
+
| { readonly _tag: "Success" }
|
|
457
|
+
| { readonly _tag: "Failure"; readonly message: string }
|
|
458
|
+
|
|
459
|
+
const wakeSuccess: SingletonWakeResult = { _tag: "Success" }
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Creates the program behind one cluster singleton Durable Object.
|
|
463
|
+
*
|
|
464
|
+
* **When to use**
|
|
465
|
+
*
|
|
466
|
+
* Use when a framework creates the native Durable Object class itself and
|
|
467
|
+
* needs the singleton wake and watchdog behavior without extending the
|
|
468
|
+
* bundled `ClusterSingleton` class.
|
|
469
|
+
*
|
|
470
|
+
* **Details**
|
|
471
|
+
*
|
|
472
|
+
* Run the returned Effect once per Durable Object activation, after the
|
|
473
|
+
* Worker application (the singleton registrations) is initialized. It ensures
|
|
474
|
+
* the singleton state table and re-arms the watchdog alarm for a wake
|
|
475
|
+
* interrupted by isolate loss before returning the handler object.
|
|
476
|
+
*
|
|
477
|
+
* @stability experimental
|
|
478
|
+
* @category constructors
|
|
479
|
+
* @since 4.1.3
|
|
480
|
+
*/
|
|
481
|
+
export const makeClusterSingletonProgram = Effect.fnUntraced(function*(state: DurableObjectProgramState) {
|
|
482
|
+
const clock = yield* Clock.Clock
|
|
483
|
+
const sql = state.storage.sql
|
|
484
|
+
ensureSingletonStorage(sql)
|
|
485
|
+
if (state.id.name !== undefined) {
|
|
486
|
+
if (!state.id.name.startsWith("Singleton/")) {
|
|
487
|
+
return yield* Effect.die(new Error("ClusterSingleton requires a Singleton/<name> Durable Object name"))
|
|
488
|
+
}
|
|
489
|
+
rememberSingletonName(sql, state.id.name)
|
|
490
|
+
}
|
|
491
|
+
const stored = loadSingletonState(sql)
|
|
492
|
+
const name = state.id.name ?? stored.name
|
|
493
|
+
let runtime: ReturnType<typeof makeSingletonRuntime> | undefined
|
|
494
|
+
const getRuntime = () => {
|
|
495
|
+
if (runtime !== undefined) return runtime
|
|
496
|
+
if (name === undefined) {
|
|
497
|
+
throw new Error("ClusterSingleton requires a Singleton/<name> Durable Object name")
|
|
498
|
+
}
|
|
499
|
+
const singletonName = name.slice("Singleton/".length)
|
|
500
|
+
const registration = getSingletonRegistration(singletonName)
|
|
501
|
+
if (registration === undefined) {
|
|
502
|
+
throw new Error(`CloudflareCluster: no singleton registered under the name "${singletonName}"`)
|
|
503
|
+
}
|
|
504
|
+
const run = Effect.sync(() => {
|
|
505
|
+
ClusterMetrics.singletons.modifyUnsafe(BigInt(1), registration.context)
|
|
506
|
+
}).pipe(
|
|
507
|
+
Effect.andThen(registration.run),
|
|
508
|
+
Effect.scoped,
|
|
509
|
+
Effect.ensuring(Effect.sync(() => {
|
|
510
|
+
ClusterMetrics.singletons.modifyUnsafe(BigInt(-1), registration.context)
|
|
511
|
+
})),
|
|
512
|
+
Effect.provideContext(registration.context),
|
|
513
|
+
Effect.orDie
|
|
514
|
+
)
|
|
515
|
+
runtime = makeSingletonRuntime({
|
|
516
|
+
sql,
|
|
517
|
+
alarm: state.storage,
|
|
518
|
+
now: () => clock.currentTimeMillisUnsafe(),
|
|
519
|
+
run
|
|
520
|
+
})
|
|
521
|
+
return runtime
|
|
522
|
+
}
|
|
523
|
+
if (stored.wakeAt !== undefined) {
|
|
524
|
+
yield* armAlarm(state.storage, stored.wakeAt)
|
|
525
|
+
}
|
|
526
|
+
const program: ClusterSingletonProgram = {
|
|
527
|
+
wake: () =>
|
|
528
|
+
Effect.promise(() => getRuntime().wake()).pipe(
|
|
529
|
+
Effect.as(wakeSuccess),
|
|
530
|
+
Effect.catchCause((cause) =>
|
|
531
|
+
Effect.as(
|
|
532
|
+
Effect.logError("Singleton run failed", cause),
|
|
533
|
+
{ _tag: "Failure", message: Cause.pretty(cause) } as const
|
|
534
|
+
)
|
|
535
|
+
)
|
|
536
|
+
),
|
|
537
|
+
alarm: () =>
|
|
538
|
+
Effect.suspend(() =>
|
|
539
|
+
loadSingletonState(sql).wakeAt === undefined ? Effect.void : Effect.promise(() => getRuntime().runAlarm())
|
|
540
|
+
)
|
|
541
|
+
}
|
|
542
|
+
return program
|
|
543
|
+
})
|