@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,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage glue for the durable queue Durable Object. The constructor must stay
|
|
3
|
+
* cheap: open SQLite, ensure the queue table, and re-arm the single alarm from
|
|
4
|
+
* the earliest pending lease expiry or initial visibility deadline.
|
|
5
|
+
*
|
|
6
|
+
* Completed rows are retained (not deleted) so custom-id deduplication
|
|
7
|
+
* survives completion, matching the SQL-backed store. A failed item moves to
|
|
8
|
+
* the back of the queue, so a poisoned item cannot hot-loop the head while
|
|
9
|
+
* still being retried ahead of items offered after its failure.
|
|
10
|
+
*
|
|
11
|
+
* `completed` holds the row state: 0 while pending, processing, or waiting to
|
|
12
|
+
* retry, 1 once completed, and 2 once its final attempt failed. An
|
|
13
|
+
* `"untilExecuted"` offer replaces a row in state 1 or 2.
|
|
14
|
+
*
|
|
15
|
+
* Each lease records its taker in `lease_owner` and the taker's
|
|
16
|
+
* `max_attempts`. Settling or extending a lease only applies while that taker
|
|
17
|
+
* still owns the row, so a taker whose lease expired cannot settle work another
|
|
18
|
+
* taker now holds or a row an `"untilExecuted"` offer re-created.
|
|
19
|
+
*
|
|
20
|
+
* Leasing a row counts an attempt, so a taker that crashes still consumes one.
|
|
21
|
+
* An expired lease keeps its owner until another taker leases the row; if it
|
|
22
|
+
* used the row's last attempt the row moves to the failed state (2). A release
|
|
23
|
+
* refunds the attempt. A recorded failure or release clears the owner, so a
|
|
24
|
+
* retried `fail` or `release` cannot settle the same attempt twice.
|
|
25
|
+
* A recorded failure that leaves the row pending stores its retry delay in
|
|
26
|
+
* `lease_until` with no owner, the same delayed visibility an offer uses.
|
|
27
|
+
* Lookups by taker go through `lease_owner`, so a retried take or a cancel
|
|
28
|
+
* finds its row after the object restarts.
|
|
29
|
+
*
|
|
30
|
+
* @internal
|
|
31
|
+
*/
|
|
32
|
+
import type { SqlStorage } from "@cloudflare/workers-types"
|
|
33
|
+
import type { DurableQueueItem } from "../CloudflareDurableObjectPrograms.ts"
|
|
34
|
+
|
|
35
|
+
const ddl = [
|
|
36
|
+
`CREATE TABLE IF NOT EXISTS queue_items (
|
|
37
|
+
id TEXT PRIMARY KEY,
|
|
38
|
+
element TEXT NOT NULL,
|
|
39
|
+
position INTEGER NOT NULL,
|
|
40
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
41
|
+
completed INTEGER NOT NULL DEFAULT 0,
|
|
42
|
+
lease_until INTEGER,
|
|
43
|
+
lease_owner TEXT,
|
|
44
|
+
max_attempts INTEGER,
|
|
45
|
+
last_failure TEXT
|
|
46
|
+
)`,
|
|
47
|
+
`CREATE INDEX IF NOT EXISTS queue_items_take_idx
|
|
48
|
+
ON queue_items (completed, position)`,
|
|
49
|
+
`CREATE INDEX IF NOT EXISTS queue_items_lease_idx
|
|
50
|
+
ON queue_items (lease_until)`,
|
|
51
|
+
// MAX(position) in offerItem/failItem needs a bare position index; the
|
|
52
|
+
// (completed, position) index cannot serve it and completed rows are
|
|
53
|
+
// retained forever for dedup.
|
|
54
|
+
`CREATE INDEX IF NOT EXISTS queue_items_position_idx
|
|
55
|
+
ON queue_items (position)`,
|
|
56
|
+
// A retried take and a cancel look the taker's row up by owner.
|
|
57
|
+
`CREATE INDEX IF NOT EXISTS queue_items_owner_idx
|
|
58
|
+
ON queue_items (lease_owner)`
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
/** @internal */
|
|
62
|
+
export const ensureQueueStorage = (sql: SqlStorage): void => {
|
|
63
|
+
for (const statement of ddl) {
|
|
64
|
+
sql.exec(statement)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** @internal */
|
|
69
|
+
export type QueueItem = DurableQueueItem
|
|
70
|
+
|
|
71
|
+
type QueueItemRow = {
|
|
72
|
+
readonly id: string
|
|
73
|
+
readonly element: string
|
|
74
|
+
readonly attempts: number
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
type LeaseExpiryRow = {
|
|
78
|
+
readonly lease_until: number | null
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** @internal */
|
|
82
|
+
export const offerItem = (
|
|
83
|
+
sql: SqlStorage,
|
|
84
|
+
id: string,
|
|
85
|
+
element: string,
|
|
86
|
+
visibleAt: number | null,
|
|
87
|
+
deduplicate?: "untilExecuted" | "untilRemoved" | undefined
|
|
88
|
+
): void => {
|
|
89
|
+
if (deduplicate === "untilExecuted") {
|
|
90
|
+
// A completed or failed row releases the id. Deleting it lets the insert
|
|
91
|
+
// below store the offer as new work after every existing row. Both
|
|
92
|
+
// statements run synchronously, so no other offer can interleave.
|
|
93
|
+
sql.exec("DELETE FROM queue_items WHERE id = ? AND completed <> 0", id)
|
|
94
|
+
}
|
|
95
|
+
// Reuse lease_until for initial visibility; INSERT OR IGNORE preserves the
|
|
96
|
+
// original deadline and payload when an id is offered again.
|
|
97
|
+
sql.exec(
|
|
98
|
+
`INSERT OR IGNORE INTO queue_items (id, element, position, lease_until)
|
|
99
|
+
VALUES (?, ?, (SELECT IFNULL(MAX(position), 0) + 1 FROM queue_items), ?)`,
|
|
100
|
+
id,
|
|
101
|
+
element,
|
|
102
|
+
visibleAt
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Leases the next available item and counts the attempt. The returned
|
|
108
|
+
* `attempts` is the number of earlier attempts, not counting this one.
|
|
109
|
+
*
|
|
110
|
+
* @internal
|
|
111
|
+
*/
|
|
112
|
+
export const leaseNextItem = (
|
|
113
|
+
sql: SqlStorage,
|
|
114
|
+
now: number,
|
|
115
|
+
leaseUntil: number,
|
|
116
|
+
maxAttempts: number,
|
|
117
|
+
owner: string
|
|
118
|
+
): QueueItem | undefined => {
|
|
119
|
+
const row = sql.exec<QueueItemRow>(
|
|
120
|
+
`SELECT id, element, attempts FROM queue_items
|
|
121
|
+
WHERE completed = 0 AND attempts < ? AND (lease_until IS NULL OR lease_until <= ?)
|
|
122
|
+
ORDER BY position ASC LIMIT 1`,
|
|
123
|
+
maxAttempts,
|
|
124
|
+
now
|
|
125
|
+
).toArray()[0]
|
|
126
|
+
if (row === undefined) return undefined
|
|
127
|
+
sql.exec(
|
|
128
|
+
`UPDATE queue_items SET attempts = attempts + 1, lease_until = ?, lease_owner = ?, max_attempts = ?
|
|
129
|
+
WHERE id = ?`,
|
|
130
|
+
leaseUntil,
|
|
131
|
+
owner,
|
|
132
|
+
maxAttempts,
|
|
133
|
+
row.id
|
|
134
|
+
)
|
|
135
|
+
return row
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The item still leased to `owner` under a live lease, so a retried take can
|
|
140
|
+
* return it. `attempts` counts earlier attempts, as `leaseNextItem` returns.
|
|
141
|
+
*
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
export const leasedItem = (sql: SqlStorage, owner: string, now: number): QueueItem | undefined =>
|
|
145
|
+
sql.exec<QueueItemRow>(
|
|
146
|
+
`SELECT id, element, attempts - 1 AS attempts FROM queue_items
|
|
147
|
+
WHERE lease_owner = ? AND completed = 0 AND lease_until > ?
|
|
148
|
+
LIMIT 1`,
|
|
149
|
+
owner,
|
|
150
|
+
now
|
|
151
|
+
).toArray()[0]
|
|
152
|
+
|
|
153
|
+
/** @internal */
|
|
154
|
+
export const completeItem = (sql: SqlStorage, id: string, owner: string): void => {
|
|
155
|
+
sql.exec("UPDATE queue_items SET completed = 1, lease_until = NULL WHERE id = ? AND lease_owner = ?", id, owner)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Records a failed attempt, which the lease already counted. When the attempt
|
|
160
|
+
* reaches the `max_attempts` stored with the lease, or the failure is
|
|
161
|
+
* `terminal`, the row moves to the failed state (2). A row that is already
|
|
162
|
+
* completed is left alone, even when the caller was its last owner.
|
|
163
|
+
* A row that stays pending is hidden until `visibleAt` (null for no delay).
|
|
164
|
+
* Clearing `lease_owner` makes a replayed call a no-op until the next lease
|
|
165
|
+
* assigns a new owner.
|
|
166
|
+
*
|
|
167
|
+
* @internal
|
|
168
|
+
*/
|
|
169
|
+
export const failItem = (
|
|
170
|
+
sql: SqlStorage,
|
|
171
|
+
id: string,
|
|
172
|
+
lastFailure: string,
|
|
173
|
+
owner: string,
|
|
174
|
+
terminal: boolean,
|
|
175
|
+
visibleAt: number | null
|
|
176
|
+
): void => {
|
|
177
|
+
const failed = "? OR attempts >= max_attempts"
|
|
178
|
+
sql.exec(
|
|
179
|
+
`UPDATE queue_items
|
|
180
|
+
SET lease_until = CASE WHEN ${failed} THEN NULL ELSE ? END, lease_owner = NULL, last_failure = ?,
|
|
181
|
+
completed = CASE WHEN ${failed} THEN 2 ELSE 0 END,
|
|
182
|
+
position = (SELECT IFNULL(MAX(position), 0) + 1 FROM queue_items)
|
|
183
|
+
WHERE id = ? AND lease_owner = ? AND completed = 0`,
|
|
184
|
+
terminal ? 1 : 0,
|
|
185
|
+
visibleAt,
|
|
186
|
+
lastFailure,
|
|
187
|
+
terminal ? 1 : 0,
|
|
188
|
+
id,
|
|
189
|
+
owner
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Returns the item to the queue and refunds the attempt its lease counted.
|
|
195
|
+
*
|
|
196
|
+
* @internal
|
|
197
|
+
*/
|
|
198
|
+
export const releaseItem = (sql: SqlStorage, id: string, owner: string): void => {
|
|
199
|
+
sql.exec(
|
|
200
|
+
`UPDATE queue_items SET attempts = attempts - 1, lease_until = NULL, lease_owner = NULL
|
|
201
|
+
WHERE id = ? AND lease_owner = ? AND completed = 0`,
|
|
202
|
+
id,
|
|
203
|
+
owner
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Releases the item `owner` holds under a live lease, as `releaseItem` does.
|
|
209
|
+
* An expired lease can still carry the same owner until its alarm runs; it
|
|
210
|
+
* keeps its spent attempt so it can fail on its own.
|
|
211
|
+
*
|
|
212
|
+
* @internal
|
|
213
|
+
*/
|
|
214
|
+
export const releaseOwnedItem = (sql: SqlStorage, owner: string, now: number): void => {
|
|
215
|
+
sql.exec(
|
|
216
|
+
`UPDATE queue_items SET attempts = attempts - 1, lease_until = NULL, lease_owner = NULL
|
|
217
|
+
WHERE lease_owner = ? AND completed = 0 AND lease_until > ?`,
|
|
218
|
+
owner,
|
|
219
|
+
now
|
|
220
|
+
)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** @internal */
|
|
224
|
+
export const extendLease = (sql: SqlStorage, id: string, leaseUntil: number, owner: string): void => {
|
|
225
|
+
sql.exec(
|
|
226
|
+
"UPDATE queue_items SET lease_until = ? WHERE id = ? AND lease_owner = ? AND lease_until IS NOT NULL",
|
|
227
|
+
leaseUntil,
|
|
228
|
+
id,
|
|
229
|
+
owner
|
|
230
|
+
)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Expires due leases and initial visibility deadlines. A lease that used the
|
|
235
|
+
* row's last attempt moves the row to the failed state (2).
|
|
236
|
+
*
|
|
237
|
+
* @internal
|
|
238
|
+
*/
|
|
239
|
+
export const expireLeases = (sql: SqlStorage, now: number): void => {
|
|
240
|
+
sql.exec(
|
|
241
|
+
`UPDATE queue_items
|
|
242
|
+
SET lease_until = NULL,
|
|
243
|
+
completed = CASE WHEN lease_owner IS NOT NULL AND attempts >= max_attempts THEN 2 ELSE 0 END
|
|
244
|
+
WHERE lease_until IS NOT NULL AND lease_until <= ? AND completed = 0`,
|
|
245
|
+
now
|
|
246
|
+
)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** @internal */
|
|
250
|
+
export const earliestLeaseExpiry = (sql: SqlStorage): number | undefined => {
|
|
251
|
+
const row = sql.exec<LeaseExpiryRow>(
|
|
252
|
+
"SELECT min(lease_until) AS lease_until FROM queue_items WHERE lease_until IS NOT NULL"
|
|
253
|
+
).toArray()[0]
|
|
254
|
+
return row?.lease_until ?? undefined
|
|
255
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A module-level registration map shared between the Worker layer and the
|
|
3
|
+
* Durable Object instances of the same isolate. Unregistering only removes
|
|
4
|
+
* the exact registration that was added, so a finalizer racing a re-register
|
|
5
|
+
* cannot drop the replacement.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** @internal */
|
|
11
|
+
export interface Registry<A> {
|
|
12
|
+
readonly get: (key: string) => A | undefined
|
|
13
|
+
readonly register: (key: string, value: A) => boolean
|
|
14
|
+
readonly unregister: (key: string, value: A) => void
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export const makeRegistry = <A>(): Registry<A> => {
|
|
19
|
+
const registrations = new Map<string, A>()
|
|
20
|
+
return {
|
|
21
|
+
get: (key) => registrations.get(key),
|
|
22
|
+
register: (key, value) => {
|
|
23
|
+
if (registrations.has(key)) return false
|
|
24
|
+
registrations.set(key, value)
|
|
25
|
+
return true
|
|
26
|
+
},
|
|
27
|
+
unregister: (key, value) => {
|
|
28
|
+
if (registrations.get(key) === value) registrations.delete(key)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
import type * as Context from "effect/Context"
|
|
3
|
+
import type * as Effect from "effect/Effect"
|
|
4
|
+
import { makeRegistry } from "./registry.ts"
|
|
5
|
+
|
|
6
|
+
/** @internal */
|
|
7
|
+
export interface SingletonRegistration {
|
|
8
|
+
readonly run: Effect.Effect<void, unknown, never>
|
|
9
|
+
readonly context: Context.Context<never>
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const registry = makeRegistry<SingletonRegistration>()
|
|
13
|
+
|
|
14
|
+
/** @internal */
|
|
15
|
+
export const getSingletonRegistration: (name: string) => SingletonRegistration | undefined = registry.get
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export const registerSingleton: (name: string, registration: SingletonRegistration) => boolean = registry.register
|
|
19
|
+
|
|
20
|
+
/** @internal */
|
|
21
|
+
export const unregisterSingleton: (name: string, registration: SingletonRegistration) => void = registry.unregister
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runs one singleton effect for each Worker Cron Trigger wake. Concurrent
|
|
3
|
+
* duplicate wakes are ignored while the accepted wake is still running; once
|
|
4
|
+
* it returns, the object has no live work and Cloudflare may hibernate it.
|
|
5
|
+
*
|
|
6
|
+
* While a wake runs, a watchdog alarm stays armed in the future. If it fires
|
|
7
|
+
* mid-run it is re-armed, so losing the isolate never leaves the pending wake
|
|
8
|
+
* without an alarm to resume it.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
import type { DurableObjectStorage, SqlStorage } from "@cloudflare/workers-types"
|
|
13
|
+
import * as Effect from "effect/Effect"
|
|
14
|
+
import { armAlarm } from "./entityStorage.ts"
|
|
15
|
+
import {
|
|
16
|
+
beginSingletonWake,
|
|
17
|
+
completeSingletonWake,
|
|
18
|
+
ensureSingletonStorage,
|
|
19
|
+
loadSingletonState
|
|
20
|
+
} from "./singletonStorage.ts"
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* How far ahead the watchdog alarm is armed while a wake is running.
|
|
24
|
+
*/
|
|
25
|
+
const watchdogMillis = 60_000
|
|
26
|
+
|
|
27
|
+
/** @internal */
|
|
28
|
+
export interface SingletonRuntimeOptions {
|
|
29
|
+
readonly sql: SqlStorage
|
|
30
|
+
readonly alarm: Pick<DurableObjectStorage, "deleteAlarm" | "getAlarm" | "setAlarm">
|
|
31
|
+
readonly now: () => number
|
|
32
|
+
readonly run: Effect.Effect<void, unknown>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** @internal */
|
|
36
|
+
export interface SingletonRuntime {
|
|
37
|
+
readonly wake: () => Promise<void>
|
|
38
|
+
readonly runAlarm: () => Promise<void>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @internal */
|
|
42
|
+
export const makeSingletonRuntime = (options: SingletonRuntimeOptions): SingletonRuntime => {
|
|
43
|
+
ensureSingletonStorage(options.sql)
|
|
44
|
+
let inFlight: Promise<void> | undefined
|
|
45
|
+
|
|
46
|
+
const armWatchdog = (): Effect.Effect<void> =>
|
|
47
|
+
Effect.suspend(() => armAlarm(options.alarm, options.now() + watchdogMillis))
|
|
48
|
+
|
|
49
|
+
const runPending = (arm: boolean): Promise<void> => {
|
|
50
|
+
const run = options.run.pipe(
|
|
51
|
+
Effect.ensuring(
|
|
52
|
+
Effect.promise(() => options.alarm.deleteAlarm()).pipe(
|
|
53
|
+
Effect.ensuring(Effect.sync(() => completeSingletonWake(options.sql)))
|
|
54
|
+
)
|
|
55
|
+
)
|
|
56
|
+
)
|
|
57
|
+
const operation = Effect.runPromise(arm ? Effect.andThen(armWatchdog(), run) : run)
|
|
58
|
+
inFlight = operation
|
|
59
|
+
void operation.then(
|
|
60
|
+
() => {
|
|
61
|
+
inFlight = undefined
|
|
62
|
+
},
|
|
63
|
+
() => {
|
|
64
|
+
inFlight = undefined
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
return operation
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
wake: () => {
|
|
72
|
+
if (inFlight !== undefined) return Promise.resolve()
|
|
73
|
+
const pending = loadSingletonState(options.sql).wakeAt
|
|
74
|
+
if (pending !== undefined) return runPending(false)
|
|
75
|
+
const now = options.now()
|
|
76
|
+
if (!beginSingletonWake(options.sql, now)) return Promise.resolve()
|
|
77
|
+
return runPending(true)
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
// Cloudflare consumes the alarm before invoking this handler, so an alarm
|
|
81
|
+
// that fires during a run must arm the next watchdog before returning.
|
|
82
|
+
runAlarm: () => {
|
|
83
|
+
if (loadSingletonState(options.sql).wakeAt === undefined) return Promise.resolve()
|
|
84
|
+
if (inFlight !== undefined) return Effect.runPromise(armWatchdog())
|
|
85
|
+
return runPending(false)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage glue for the singleton Durable Object. A pending row is a watchdog:
|
|
3
|
+
* a normal wake clears it after the effect returns, while an isolate crash
|
|
4
|
+
* leaves it for the constructor to re-arm on the next alarm wake.
|
|
5
|
+
*
|
|
6
|
+
* @internal
|
|
7
|
+
*/
|
|
8
|
+
import type { SqlStorage } from "@cloudflare/workers-types"
|
|
9
|
+
|
|
10
|
+
const ddl = `CREATE TABLE IF NOT EXISTS singleton_state (
|
|
11
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
12
|
+
name TEXT,
|
|
13
|
+
wake_at INTEGER
|
|
14
|
+
)`
|
|
15
|
+
|
|
16
|
+
/** @internal */
|
|
17
|
+
export const ensureSingletonStorage = (sql: SqlStorage): void => {
|
|
18
|
+
sql.exec(ddl)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** @internal */
|
|
22
|
+
export interface SingletonState {
|
|
23
|
+
readonly name: string | undefined
|
|
24
|
+
readonly wakeAt: number | undefined
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type SingletonStateRow = {
|
|
28
|
+
readonly name: string | null
|
|
29
|
+
readonly wake_at: number | null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** @internal */
|
|
33
|
+
export const loadSingletonState = (sql: SqlStorage): SingletonState => {
|
|
34
|
+
const row = sql.exec<SingletonStateRow>("SELECT name, wake_at FROM singleton_state WHERE id = 1").toArray()[0]
|
|
35
|
+
return {
|
|
36
|
+
name: row?.name ?? undefined,
|
|
37
|
+
wakeAt: row?.wake_at ?? undefined
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @internal */
|
|
42
|
+
export const rememberSingletonName = (sql: SqlStorage, name: string): void => {
|
|
43
|
+
sql.exec(
|
|
44
|
+
`INSERT INTO singleton_state (id, name) VALUES (1, ?)
|
|
45
|
+
ON CONFLICT(id) DO UPDATE SET name = excluded.name`,
|
|
46
|
+
name
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @internal */
|
|
51
|
+
export const beginSingletonWake = (sql: SqlStorage, wakeAt: number): boolean => {
|
|
52
|
+
if (loadSingletonState(sql).wakeAt !== undefined) return false
|
|
53
|
+
sql.exec(
|
|
54
|
+
`INSERT INTO singleton_state (id, wake_at) VALUES (1, ?)
|
|
55
|
+
ON CONFLICT(id) DO UPDATE SET wake_at = excluded.wake_at`,
|
|
56
|
+
wakeAt
|
|
57
|
+
)
|
|
58
|
+
return true
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @internal */
|
|
62
|
+
export const completeSingletonWake = (sql: SqlStorage): void => {
|
|
63
|
+
sql.exec("UPDATE singleton_state SET wake_at = NULL WHERE id = 1")
|
|
64
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** @internal */
|
|
2
|
+
import { PersistenceError } from "effect/cluster/ClusterError"
|
|
3
|
+
import * as Effect from "effect/Effect"
|
|
4
|
+
import * as Schedule from "effect/Schedule"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The flags Cloudflare sets on the Error a failed Durable Object call rejects
|
|
8
|
+
* with.
|
|
9
|
+
*
|
|
10
|
+
* @internal
|
|
11
|
+
*/
|
|
12
|
+
export interface TransportErrorFlags {
|
|
13
|
+
readonly retryable?: boolean | undefined
|
|
14
|
+
readonly overloaded?: boolean | undefined
|
|
15
|
+
readonly remote?: boolean | undefined
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Whether a failed Durable Object call may be retried. Cloudflare marks
|
|
20
|
+
* transient failures `retryable`, and an `overloaded` object must not be
|
|
21
|
+
* retried, as retrying only adds load.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export const isRetryable = (error: PersistenceError): boolean => {
|
|
26
|
+
const cause = error.cause as TransportErrorFlags | null | undefined
|
|
27
|
+
return typeof cause === "object" && cause !== null && cause.retryable === true && cause.overloaded !== true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The bounded retry policy for idempotent calls outside a workflow: a few
|
|
32
|
+
* quick attempts, then the caller sees the typed error.
|
|
33
|
+
*
|
|
34
|
+
* @internal
|
|
35
|
+
*/
|
|
36
|
+
export const boundedRetry: Schedule.Schedule<unknown> = Schedule.max([
|
|
37
|
+
Schedule.exponential(100, 2),
|
|
38
|
+
Schedule.recurs(4)
|
|
39
|
+
])
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Makes one Worker-to-Durable Object stub call.
|
|
43
|
+
*
|
|
44
|
+
* **Details**
|
|
45
|
+
*
|
|
46
|
+
* A rejection becomes a `PersistenceError` whose `cause` is the rejected
|
|
47
|
+
* value, so Cloudflare's `retryable`, `overloaded` and `remote` flags stay
|
|
48
|
+
* readable. Each attempt resolves a fresh stub with `resolve`, because
|
|
49
|
+
* Cloudflare advises against reusing a stub after it throws. With `retry`, the
|
|
50
|
+
* call is retried on that schedule while the error is retryable; only pass it
|
|
51
|
+
* for idempotent calls.
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export const callDurableObject = <Stub, A>(options: {
|
|
56
|
+
readonly resolve: () => Stub
|
|
57
|
+
readonly call: (stub: Stub) => Promise<A>
|
|
58
|
+
readonly retry?: Schedule.Schedule<unknown, PersistenceError> | undefined
|
|
59
|
+
}): Effect.Effect<A, PersistenceError> => {
|
|
60
|
+
const attempt = Effect.tryPromise({
|
|
61
|
+
try: () => options.call(options.resolve()),
|
|
62
|
+
catch: (cause) => new PersistenceError({ cause })
|
|
63
|
+
})
|
|
64
|
+
return options.retry === undefined
|
|
65
|
+
? attempt
|
|
66
|
+
: Effect.retry(attempt, { schedule: options.retry, while: isRetryable })
|
|
67
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module-level workflow state shared between the Worker layer and the workflow
|
|
3
|
+
* Durable Object instances of the same isolate. Registrations are recorded at
|
|
4
|
+
* Worker init; a running execution provides its own handle through
|
|
5
|
+
* `CurrentExecutionHandle` so engine operations inside the run hit local
|
|
6
|
+
* SQLite instead of a self-RPC.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
import * as Context from "effect/Context"
|
|
11
|
+
import type * as Effect from "effect/Effect"
|
|
12
|
+
import type * as Workflow from "effect/workflow/Workflow"
|
|
13
|
+
import * as WorkflowEngine from "effect/workflow/WorkflowEngine"
|
|
14
|
+
import type { ClusterWorkflowRunOptions, ClusterWorkflowWakeMode } from "../CloudflareDurableObjectPrograms.ts"
|
|
15
|
+
import { makeRegistry } from "./registry.ts"
|
|
16
|
+
|
|
17
|
+
/** @internal */
|
|
18
|
+
export interface WorkflowRegistration {
|
|
19
|
+
readonly workflow: Workflow.Any
|
|
20
|
+
readonly execute: (
|
|
21
|
+
payload: object,
|
|
22
|
+
executionId: string
|
|
23
|
+
) => Effect.Effect<unknown, unknown, WorkflowEngine.WorkflowInstance | WorkflowEngine.WorkflowEngine>
|
|
24
|
+
readonly context: Context.Context<never>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const registry = makeRegistry<WorkflowRegistration>()
|
|
28
|
+
|
|
29
|
+
/** @internal */
|
|
30
|
+
export const getWorkflowRegistration: (name: string) => WorkflowRegistration | undefined = registry.get
|
|
31
|
+
|
|
32
|
+
/** @internal */
|
|
33
|
+
export const registerWorkflow: (name: string, registration: WorkflowRegistration) => boolean = registry.register
|
|
34
|
+
|
|
35
|
+
/** @internal */
|
|
36
|
+
export const unregisterWorkflow: (name: string, registration: WorkflowRegistration) => void = registry.unregister
|
|
37
|
+
|
|
38
|
+
/** @internal */
|
|
39
|
+
export type WorkflowRunOptions = ClusterWorkflowRunOptions
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The transport shared by workflow Durable Object stubs and same-isolate
|
|
43
|
+
* execution handles. All payloads are JSON text.
|
|
44
|
+
*
|
|
45
|
+
* @internal
|
|
46
|
+
*/
|
|
47
|
+
export interface WorkflowStub {
|
|
48
|
+
readonly run: (payload: string, options: WorkflowRunOptions) => Promise<string>
|
|
49
|
+
readonly poll: () => Promise<string | undefined>
|
|
50
|
+
readonly resume: (mode?: ClusterWorkflowWakeMode) => Promise<void>
|
|
51
|
+
readonly interrupt: () => Promise<void>
|
|
52
|
+
readonly interruptUnsafe: () => Promise<void>
|
|
53
|
+
readonly deferredDone: (name: string, exit: string) => Promise<void>
|
|
54
|
+
readonly scheduleClock: (name: string, deferredName: string, wakeUp: number) => Promise<void>
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** @internal */
|
|
58
|
+
export interface WorkflowExecutionHandle extends WorkflowStub {
|
|
59
|
+
readonly executionId: string
|
|
60
|
+
readonly loadActivity: (key: string) => string | undefined
|
|
61
|
+
readonly saveActivity: (key: string, exit: string) => void
|
|
62
|
+
readonly loadDeferred: (name: string) => string | undefined
|
|
63
|
+
/** Completes a deferred of this execution from inside its own run. */
|
|
64
|
+
readonly completeOwnDeferred: (name: string, exit: string) => Effect.Effect<void>
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The handle of the workflow execution currently running in this fiber,
|
|
69
|
+
* provided by the workflow Durable Object runtime around each run attempt.
|
|
70
|
+
*
|
|
71
|
+
* @internal
|
|
72
|
+
*/
|
|
73
|
+
export class CurrentExecutionHandle extends Context.Service<CurrentExecutionHandle, WorkflowExecutionHandle>()(
|
|
74
|
+
"@effect/platform-cloudflare/CloudflareWorkflowEngine/CurrentExecutionHandle"
|
|
75
|
+
) {}
|
|
76
|
+
|
|
77
|
+
/** @internal */
|
|
78
|
+
export const deferredState = WorkflowEngine.makeDeferredState()
|