@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.
Files changed (237) hide show
  1. package/AGENTS.md +434 -0
  2. package/CLAUDE.md +434 -0
  3. package/LICENSE +21 -0
  4. package/README.md +506 -0
  5. package/ai-docs/README.md +44 -0
  6. package/ai-docs/package.json +36 -0
  7. package/ai-docs/src/01_effect/01_basics/01_effect-gen.ts +30 -0
  8. package/ai-docs/src/01_effect/01_basics/02_effect-fn.ts +52 -0
  9. package/ai-docs/src/01_effect/01_basics/10_creating-effects.ts +74 -0
  10. package/ai-docs/src/01_effect/01_basics/index.md +7 -0
  11. package/ai-docs/src/01_effect/02_schema/10_schema-basics.ts +43 -0
  12. package/ai-docs/src/01_effect/02_schema/index.md +7 -0
  13. package/ai-docs/src/01_effect/03_services/01_service.ts +45 -0
  14. package/ai-docs/src/01_effect/03_services/10_reference.ts +10 -0
  15. package/ai-docs/src/01_effect/03_services/20_layer-composition.ts +70 -0
  16. package/ai-docs/src/01_effect/03_services/20_layer-unwrap.ts +66 -0
  17. package/ai-docs/src/01_effect/03_services/index.md +5 -0
  18. package/ai-docs/src/01_effect/04_errors/01_error-handling.ts +30 -0
  19. package/ai-docs/src/01_effect/04_errors/10_catch-tags.ts +24 -0
  20. package/ai-docs/src/01_effect/04_errors/20_reason-errors.ts +64 -0
  21. package/ai-docs/src/01_effect/04_errors/index.md +1 -0
  22. package/ai-docs/src/01_effect/05_resources/10_acquire-release.ts +105 -0
  23. package/ai-docs/src/01_effect/05_resources/20_layer-side-effects.ts +31 -0
  24. package/ai-docs/src/01_effect/05_resources/30_layer-map.ts +86 -0
  25. package/ai-docs/src/01_effect/05_resources/index.md +3 -0
  26. package/ai-docs/src/01_effect/06_running/10_run-main.ts +30 -0
  27. package/ai-docs/src/01_effect/06_running/20_layer-launch.ts +27 -0
  28. package/ai-docs/src/01_effect/06_running/index.md +1 -0
  29. package/ai-docs/src/01_effect/07_pubsub/10_pubsub.ts +56 -0
  30. package/ai-docs/src/01_effect/07_pubsub/index.md +3 -0
  31. package/ai-docs/src/03_stream/10_creating-streams.ts +103 -0
  32. package/ai-docs/src/03_stream/20_consuming-streams.ts +137 -0
  33. package/ai-docs/src/03_stream/30_encoding.ts +163 -0
  34. package/ai-docs/src/03_stream/index.md +4 -0
  35. package/ai-docs/src/04_integration/10_managed-runtime.ts +129 -0
  36. package/ai-docs/src/04_integration/index.md +5 -0
  37. package/ai-docs/src/05_batching/10_request-resolver.ts +89 -0
  38. package/ai-docs/src/05_batching/index.md +3 -0
  39. package/ai-docs/src/06_schedule/10_schedules.ts +111 -0
  40. package/ai-docs/src/06_schedule/index.md +3 -0
  41. package/ai-docs/src/07_datetime/10_creating-and-formatting.ts +30 -0
  42. package/ai-docs/src/07_datetime/20_time-zones.ts +44 -0
  43. package/ai-docs/src/07_datetime/index.md +5 -0
  44. package/ai-docs/src/08_observability/10_logging.ts +66 -0
  45. package/ai-docs/src/08_observability/20_otlp-tracing.ts +95 -0
  46. package/ai-docs/src/08_observability/index.md +7 -0
  47. package/ai-docs/src/09_testing/10_effect-tests.ts +55 -0
  48. package/ai-docs/src/09_testing/20_layer-tests.ts +138 -0
  49. package/ai-docs/src/09_testing/index.md +1 -0
  50. package/ai-docs/src/10_predicate/01_basics.ts +14 -0
  51. package/ai-docs/src/10_predicate/index.md +9 -0
  52. package/ai-docs/src/40_sql/10_basics.ts +175 -0
  53. package/ai-docs/src/40_sql/index.md +6 -0
  54. package/ai-docs/src/50_http-client/10_basics.ts +102 -0
  55. package/ai-docs/src/50_http-client/index.md +3 -0
  56. package/ai-docs/src/51_http-server/10_basics.ts +118 -0
  57. package/ai-docs/src/51_http-server/20_testing.ts +100 -0
  58. package/ai-docs/src/51_http-server/fixtures/api/Api.ts +14 -0
  59. package/ai-docs/src/51_http-server/fixtures/api/Authorization.ts +36 -0
  60. package/ai-docs/src/51_http-server/fixtures/api/System.ts +10 -0
  61. package/ai-docs/src/51_http-server/fixtures/api/Users.ts +107 -0
  62. package/ai-docs/src/51_http-server/fixtures/domain/User.ts +21 -0
  63. package/ai-docs/src/51_http-server/fixtures/domain/UserErrors.ts +22 -0
  64. package/ai-docs/src/51_http-server/fixtures/server/Authorization.ts +37 -0
  65. package/ai-docs/src/51_http-server/fixtures/server/Users/http.ts +80 -0
  66. package/ai-docs/src/51_http-server/fixtures/server/Users.ts +183 -0
  67. package/ai-docs/src/51_http-server/index.md +3 -0
  68. package/ai-docs/src/52_graphql/10_basics.ts +117 -0
  69. package/ai-docs/src/52_graphql/20_testing.ts +45 -0
  70. package/ai-docs/src/52_graphql/30_batching.ts +118 -0
  71. package/ai-docs/src/52_graphql/40_aliased-batching.ts +124 -0
  72. package/ai-docs/src/52_graphql/50_errors-and-retries.ts +74 -0
  73. package/ai-docs/src/52_graphql/index.md +6 -0
  74. package/ai-docs/src/60_child-process/10_working-with-child-processes.ts +117 -0
  75. package/ai-docs/src/60_child-process/index.md +3 -0
  76. package/ai-docs/src/70_cli/10_basics.ts +162 -0
  77. package/ai-docs/src/70_cli/index.md +5 -0
  78. package/ai-docs/src/71_ai/10_language-model.ts +156 -0
  79. package/ai-docs/src/71_ai/20_tools.ts +226 -0
  80. package/ai-docs/src/71_ai/30_chat.ts +158 -0
  81. package/ai-docs/src/71_ai/fixtures/domain/LaunchPlan.ts +9 -0
  82. package/ai-docs/src/71_ai/index.md +5 -0
  83. package/ai-docs/src/80_cluster/10_entities.ts +97 -0
  84. package/ai-docs/src/80_cluster/index.md +4 -0
  85. package/ai-docs/src/index.md +10 -0
  86. package/ai-docs/tsconfig.json +28 -0
  87. package/dist/AlchemyCloudflareCluster.d.ts +134 -0
  88. package/dist/AlchemyCloudflareCluster.d.ts.map +1 -0
  89. package/dist/AlchemyCloudflareCluster.js +111 -0
  90. package/dist/AlchemyCloudflareCluster.js.map +1 -0
  91. package/dist/CloudflareCluster.d.ts +196 -0
  92. package/dist/CloudflareCluster.d.ts.map +1 -0
  93. package/dist/CloudflareCluster.js +533 -0
  94. package/dist/CloudflareCluster.js.map +1 -0
  95. package/dist/CloudflareDurableObjectPrograms.d.ts +279 -0
  96. package/dist/CloudflareDurableObjectPrograms.d.ts.map +1 -0
  97. package/dist/CloudflareDurableObjectPrograms.js +295 -0
  98. package/dist/CloudflareDurableObjectPrograms.js.map +1 -0
  99. package/dist/CloudflareDurableObjects.d.ts +149 -0
  100. package/dist/CloudflareDurableObjects.d.ts.map +1 -0
  101. package/dist/CloudflareDurableObjects.js +337 -0
  102. package/dist/CloudflareDurableObjects.js.map +1 -0
  103. package/dist/CloudflarePersistedQueue.d.ts +36 -0
  104. package/dist/CloudflarePersistedQueue.d.ts.map +1 -0
  105. package/dist/CloudflarePersistedQueue.js +122 -0
  106. package/dist/CloudflarePersistedQueue.js.map +1 -0
  107. package/dist/CloudflareWorkflowEngine.d.ts +72 -0
  108. package/dist/CloudflareWorkflowEngine.d.ts.map +1 -0
  109. package/dist/CloudflareWorkflowEngine.js +187 -0
  110. package/dist/CloudflareWorkflowEngine.js.map +1 -0
  111. package/dist/index.d.ts +29 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +33 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/internal/alchemyCluster.d.ts +2 -0
  116. package/dist/internal/alchemyCluster.d.ts.map +1 -0
  117. package/dist/internal/alchemyCluster.js +75 -0
  118. package/dist/internal/alchemyCluster.js.map +1 -0
  119. package/dist/internal/boundedMap.d.ts +3 -0
  120. package/dist/internal/boundedMap.d.ts.map +1 -0
  121. package/dist/internal/boundedMap.js +15 -0
  122. package/dist/internal/boundedMap.js.map +1 -0
  123. package/dist/internal/cluster.d.ts +57 -0
  124. package/dist/internal/cluster.d.ts.map +1 -0
  125. package/dist/internal/cluster.js +2 -0
  126. package/dist/internal/cluster.js.map +1 -0
  127. package/dist/internal/clusterName.d.ts +2 -0
  128. package/dist/internal/clusterName.d.ts.map +1 -0
  129. package/dist/internal/clusterName.js +16 -0
  130. package/dist/internal/clusterName.js.map +1 -0
  131. package/dist/internal/entityKeepAlive.d.ts +2 -0
  132. package/dist/internal/entityKeepAlive.d.ts.map +1 -0
  133. package/dist/internal/entityKeepAlive.js +73 -0
  134. package/dist/internal/entityKeepAlive.js.map +1 -0
  135. package/dist/internal/entityMailbox.d.ts +2 -0
  136. package/dist/internal/entityMailbox.d.ts.map +1 -0
  137. package/dist/internal/entityMailbox.js +160 -0
  138. package/dist/internal/entityMailbox.js.map +1 -0
  139. package/dist/internal/entityRegistry.d.ts +16 -0
  140. package/dist/internal/entityRegistry.d.ts.map +1 -0
  141. package/dist/internal/entityRegistry.js +22 -0
  142. package/dist/internal/entityRegistry.js.map +1 -0
  143. package/dist/internal/entityReply.d.ts +2 -0
  144. package/dist/internal/entityReply.d.ts.map +1 -0
  145. package/dist/internal/entityReply.js +35 -0
  146. package/dist/internal/entityReply.js.map +1 -0
  147. package/dist/internal/entityRuntime.d.ts +2 -0
  148. package/dist/internal/entityRuntime.d.ts.map +1 -0
  149. package/dist/internal/entityRuntime.js +823 -0
  150. package/dist/internal/entityRuntime.js.map +1 -0
  151. package/dist/internal/entitySql.d.ts +2 -0
  152. package/dist/internal/entitySql.d.ts.map +1 -0
  153. package/dist/internal/entitySql.js +29 -0
  154. package/dist/internal/entitySql.js.map +1 -0
  155. package/dist/internal/entityStorage.d.ts +2 -0
  156. package/dist/internal/entityStorage.d.ts.map +1 -0
  157. package/dist/internal/entityStorage.js +98 -0
  158. package/dist/internal/entityStorage.js.map +1 -0
  159. package/dist/internal/entityWire.d.ts +2 -0
  160. package/dist/internal/entityWire.d.ts.map +1 -0
  161. package/dist/internal/entityWire.js +146 -0
  162. package/dist/internal/entityWire.js.map +1 -0
  163. package/dist/internal/queueRuntime.d.ts +2 -0
  164. package/dist/internal/queueRuntime.d.ts.map +1 -0
  165. package/dist/internal/queueRuntime.js +88 -0
  166. package/dist/internal/queueRuntime.js.map +1 -0
  167. package/dist/internal/queueStorage.d.ts +2 -0
  168. package/dist/internal/queueStorage.d.ts.map +1 -0
  169. package/dist/internal/queueStorage.js +129 -0
  170. package/dist/internal/queueStorage.js.map +1 -0
  171. package/dist/internal/registry.d.ts +10 -0
  172. package/dist/internal/registry.d.ts.map +1 -0
  173. package/dist/internal/registry.js +24 -0
  174. package/dist/internal/registry.js.map +1 -0
  175. package/dist/internal/singletonRegistry.d.ts +2 -0
  176. package/dist/internal/singletonRegistry.d.ts.map +1 -0
  177. package/dist/internal/singletonRegistry.js +9 -0
  178. package/dist/internal/singletonRegistry.js.map +1 -0
  179. package/dist/internal/singletonRuntime.d.ts +2 -0
  180. package/dist/internal/singletonRuntime.d.ts.map +1 -0
  181. package/dist/internal/singletonRuntime.js +42 -0
  182. package/dist/internal/singletonRuntime.js.map +1 -0
  183. package/dist/internal/singletonStorage.d.ts +2 -0
  184. package/dist/internal/singletonStorage.d.ts.map +1 -0
  185. package/dist/internal/singletonStorage.js +34 -0
  186. package/dist/internal/singletonStorage.js.map +1 -0
  187. package/dist/internal/transport.d.ts +2 -0
  188. package/dist/internal/transport.d.ts.map +1 -0
  189. package/dist/internal/transport.js +49 -0
  190. package/dist/internal/transport.js.map +1 -0
  191. package/dist/internal/workflowRegistry.d.ts +2 -0
  192. package/dist/internal/workflowRegistry.d.ts.map +1 -0
  193. package/dist/internal/workflowRegistry.js +29 -0
  194. package/dist/internal/workflowRegistry.js.map +1 -0
  195. package/dist/internal/workflowRuntime.d.ts +2 -0
  196. package/dist/internal/workflowRuntime.d.ts.map +1 -0
  197. package/dist/internal/workflowRuntime.js +322 -0
  198. package/dist/internal/workflowRuntime.js.map +1 -0
  199. package/dist/internal/workflowStorage.d.ts +2 -0
  200. package/dist/internal/workflowStorage.d.ts.map +1 -0
  201. package/dist/internal/workflowStorage.js +113 -0
  202. package/dist/internal/workflowStorage.js.map +1 -0
  203. package/dist/internal/workflowWire.d.ts +2 -0
  204. package/dist/internal/workflowWire.d.ts.map +1 -0
  205. package/dist/internal/workflowWire.js +52 -0
  206. package/dist/internal/workflowWire.js.map +1 -0
  207. package/package.json +77 -1
  208. package/src/AlchemyCloudflareCluster.ts +200 -0
  209. package/src/CloudflareCluster.ts +704 -0
  210. package/src/CloudflareDurableObjectPrograms.ts +543 -0
  211. package/src/CloudflareDurableObjects.ts +430 -0
  212. package/src/CloudflarePersistedQueue.ts +189 -0
  213. package/src/CloudflareWorkflowEngine.ts +220 -0
  214. package/src/index.ts +38 -0
  215. package/src/internal/alchemyCluster.ts +120 -0
  216. package/src/internal/boundedMap.ts +15 -0
  217. package/src/internal/cluster.ts +57 -0
  218. package/src/internal/clusterName.ts +19 -0
  219. package/src/internal/entityKeepAlive.ts +121 -0
  220. package/src/internal/entityMailbox.ts +324 -0
  221. package/src/internal/entityRegistry.ts +46 -0
  222. package/src/internal/entityReply.ts +54 -0
  223. package/src/internal/entityRuntime.ts +1318 -0
  224. package/src/internal/entitySql.ts +60 -0
  225. package/src/internal/entityStorage.ts +168 -0
  226. package/src/internal/entityWire.ts +204 -0
  227. package/src/internal/queueRuntime.ts +174 -0
  228. package/src/internal/queueStorage.ts +255 -0
  229. package/src/internal/registry.ts +31 -0
  230. package/src/internal/singletonRegistry.ts +21 -0
  231. package/src/internal/singletonRuntime.ts +88 -0
  232. package/src/internal/singletonStorage.ts +64 -0
  233. package/src/internal/transport.ts +67 -0
  234. package/src/internal/workflowRegistry.ts +78 -0
  235. package/src/internal/workflowRuntime.ts +443 -0
  236. package/src/internal/workflowStorage.ts +234 -0
  237. 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
+ })