@effect-agent/platform-cloudflare 0.1.0-beta.6 → 0.1.0-beta.60
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/dist/Alarm.d.mts +168 -0
- package/dist/Alarm.mjs +415 -0
- package/dist/Alarm.mjs.map +1 -0
- package/dist/BrowserRestCapture.d.mts +35 -0
- package/dist/BrowserRestCapture.mjs +238 -0
- package/dist/BrowserRestCapture.mjs.map +1 -0
- package/dist/BrowserRestCrawl.d.mts +17 -0
- package/dist/BrowserRestCrawl.mjs +244 -0
- package/dist/BrowserRestCrawl.mjs.map +1 -0
- package/dist/CloudflareBindings.d.mts +105 -0
- package/dist/CloudflareBindings.mjs +98 -0
- package/dist/CloudflareBindings.mjs.map +1 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs +466 -0
- package/dist/CloudflareBrowser-Bj22nNUT.mjs.map +1 -0
- package/dist/CloudflareBrowser-DJq53niJ.d.mts +80 -0
- package/dist/CloudflareBrowser.d.mts +2 -0
- package/dist/CloudflareBrowser.mjs +2 -0
- package/dist/CloudflareCodeMode.d.mts +44 -0
- package/dist/CloudflareCodeMode.mjs +611 -0
- package/dist/CloudflareCodeMode.mjs.map +1 -0
- package/dist/CloudflareConfig-f3CqTel1.d.mts +101 -0
- package/dist/CloudflareConfig.d.mts +2 -0
- package/dist/CloudflareConfig.mjs +122 -0
- package/dist/CloudflareConfig.mjs.map +1 -0
- package/dist/CloudflareMemory.d.mts +140 -0
- package/dist/CloudflareMemory.mjs +168 -0
- package/dist/CloudflareMemory.mjs.map +1 -0
- package/dist/CloudflareScheduling.d.mts +52 -0
- package/dist/CloudflareScheduling.mjs +389 -0
- package/dist/CloudflareScheduling.mjs.map +1 -0
- package/dist/CloudflareSubscriptions.d.mts +87 -0
- package/dist/CloudflareSubscriptions.mjs +524 -0
- package/dist/CloudflareSubscriptions.mjs.map +1 -0
- package/dist/CloudflareThreadClient.d.mts +1214 -0
- package/dist/CloudflareThreadClient.mjs +386 -0
- package/dist/CloudflareThreadClient.mjs.map +1 -0
- package/dist/InteractiveBrowser.d.mts +144 -0
- package/dist/InteractiveBrowser.mjs +1090 -0
- package/dist/InteractiveBrowser.mjs.map +1 -0
- package/dist/ProtectedBrowser.d.mts +65 -0
- package/dist/ProtectedBrowser.mjs +859 -0
- package/dist/ProtectedBrowser.mjs.map +1 -0
- package/dist/ThreadObject-CvnEnVQH.d.mts +772 -0
- package/dist/ThreadObject-s5w3V_co.mjs +675 -0
- package/dist/ThreadObject-s5w3V_co.mjs.map +1 -0
- package/dist/ThreadObject.d.mts +2 -0
- package/dist/ThreadObject.mjs +3 -0
- package/dist/WakeScheduler.d.mts +23 -0
- package/dist/WakeScheduler.mjs +60 -0
- package/dist/WakeScheduler.mjs.map +1 -0
- package/dist/boundary-BguazVkh.mjs +42 -0
- package/dist/boundary-BguazVkh.mjs.map +1 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs +84 -0
- package/dist/browser-session-lifecycle-CUbVfEgP.mjs.map +1 -0
- package/dist/browser-session-lifecycle-DJpqLXy_.d.mts +21 -0
- package/dist/index.d.mts +12 -1543
- package/dist/index.mjs +12 -1636
- package/dist/prepared-admission-G7N4DDqS.mjs +73 -0
- package/dist/prepared-admission-G7N4DDqS.mjs.map +1 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +1 -53
- package/src/Alarm.ts +862 -0
- package/src/BrowserRestCapture.ts +477 -0
- package/src/BrowserRestCrawl.ts +540 -0
- package/src/CloudflareBindings.ts +171 -0
- package/src/CloudflareBrowser.ts +15 -0
- package/src/{code-mode-executor.ts → CloudflareCodeMode.ts} +367 -204
- package/src/{config.ts → CloudflareConfig.ts} +9 -8
- package/src/CloudflareMemory.ts +359 -0
- package/src/CloudflareScheduling.ts +733 -0
- package/src/CloudflareSubscriptions.ts +1045 -0
- package/src/CloudflareThreadClient.ts +807 -0
- package/src/InteractiveBrowser.ts +2357 -0
- package/src/ProtectedBrowser.ts +2 -0
- package/src/{conversation-object.ts → ThreadObject.ts} +423 -239
- package/src/{wake-scheduler.ts → WakeScheduler.ts} +23 -23
- package/src/index.ts +11 -23
- package/src/internal/boundary.ts +55 -0
- package/src/internal/browser-quick-action.ts +818 -0
- package/src/internal/browser-session-lifecycle.ts +160 -0
- package/src/internal/layers.ts +615 -0
- package/src/internal/message-delivery.ts +90 -0
- package/src/internal/prepared-admission.ts +116 -0
- package/src/internal/progress-wait.ts +121 -0
- package/src/internal/transport.ts +42 -0
- package/src/protected-browser/binding.ts +187 -0
- package/src/protected-browser/inspect-frame.ts +125 -0
- package/src/protected-browser/native.ts +429 -0
- package/src/protected-browser/policy.ts +719 -0
- package/dist/index.mjs.map +0 -1
- package/src/alarm.ts +0 -334
- package/src/bindings.ts +0 -145
- package/src/client.ts +0 -646
- package/src/layers.ts +0 -376
- package/src/transport.ts +0 -38
|
@@ -1,35 +1,49 @@
|
|
|
1
|
-
import type
|
|
2
|
-
import { SubmissionId } from "@effect-agent/core";
|
|
1
|
+
import { type AgentId } from "@effect-agent/core/Identifiers";
|
|
2
|
+
import { SubmissionId } from "@effect-agent/core/Identifiers";
|
|
3
|
+
import {
|
|
4
|
+
decodePortRequest,
|
|
5
|
+
encodePortResponse,
|
|
6
|
+
type PortRequest,
|
|
7
|
+
} from "@effect-agent/storage-cloudflare/PortProtocol";
|
|
3
8
|
import {
|
|
4
|
-
AppendConflict,
|
|
5
|
-
ConversationNotMaterialized,
|
|
6
|
-
ConversationRead,
|
|
7
|
-
ConversationStore,
|
|
8
|
-
ConversationStoreError,
|
|
9
|
-
DigestError,
|
|
10
|
-
DurableAgentRuntime,
|
|
11
|
-
DurableRuntimeFailpointError,
|
|
12
|
-
FenceRejected,
|
|
13
9
|
IntegrityReport,
|
|
14
|
-
LedgerError,
|
|
15
10
|
ObligationReport,
|
|
16
11
|
ObligationThresholds,
|
|
12
|
+
RecoveryExplanation,
|
|
13
|
+
RetryCommand,
|
|
14
|
+
RetryRefused,
|
|
15
|
+
} from "@effect-agent/thread/Admin";
|
|
16
|
+
import { DigestError } from "@effect-agent/thread/Digest";
|
|
17
|
+
import {
|
|
18
|
+
DurableAgentRuntime,
|
|
19
|
+
RecoveryReport,
|
|
20
|
+
type DurableSubmitAgent,
|
|
21
|
+
} from "@effect-agent/thread/DurableAgentRuntime";
|
|
22
|
+
import { DurableRuntimeFailpointError } from "@effect-agent/thread/DurableFailpoint";
|
|
23
|
+
import {
|
|
17
24
|
OperationAuthorizationRequest,
|
|
18
25
|
OperationAuthorizer,
|
|
19
26
|
OperationDenied,
|
|
27
|
+
} from "@effect-agent/thread/OperationAuthorizer";
|
|
28
|
+
import { PersistedJson } from "@effect-agent/thread/Records";
|
|
29
|
+
import { RunJournalError } from "@effect-agent/thread/RunJournal";
|
|
30
|
+
import {
|
|
31
|
+
AdmissionPolicyError,
|
|
32
|
+
LedgerError,
|
|
20
33
|
OwnershipLost,
|
|
21
|
-
PersistedJson,
|
|
22
|
-
RecoveryExplanation,
|
|
23
|
-
RecoveryReport,
|
|
24
|
-
RetryCommand,
|
|
25
|
-
RetryRefused,
|
|
26
|
-
RunJournalError,
|
|
27
34
|
SettlementConflict,
|
|
28
35
|
SubmissionLedger,
|
|
29
36
|
SubmissionLookupByKey,
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
37
|
+
} from "@effect-agent/thread/SubmissionLedger";
|
|
38
|
+
import {
|
|
39
|
+
AppendConflict,
|
|
40
|
+
ThreadNotMaterialized,
|
|
41
|
+
ThreadRead,
|
|
42
|
+
ThreadStore,
|
|
43
|
+
ThreadStoreError,
|
|
44
|
+
FenceRejected,
|
|
45
|
+
} from "@effect-agent/thread/ThreadStore";
|
|
46
|
+
import { WakeScheduler } from "@effect-agent/thread/WakeScheduler";
|
|
33
47
|
import { Effect, Layer, Option, Schema, Stream } from "effect";
|
|
34
48
|
import {
|
|
35
49
|
DurableObject as EffectCfDurableObject,
|
|
@@ -38,29 +52,35 @@ import {
|
|
|
38
52
|
} from "effect-cf";
|
|
39
53
|
|
|
40
54
|
import {
|
|
41
|
-
|
|
55
|
+
ThreadMaintenance,
|
|
42
56
|
DurableAlarmError,
|
|
43
57
|
DurableAlarmService,
|
|
44
58
|
type MaintenancePassFailure,
|
|
45
|
-
} from "./
|
|
59
|
+
} from "./Alarm.ts";
|
|
46
60
|
import {
|
|
47
|
-
|
|
61
|
+
ThreadObjectIdentity,
|
|
48
62
|
DurableObjectContext,
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
ThreadObjectNamespace,
|
|
64
|
+
threadNamespaceFromEnv,
|
|
51
65
|
type CloudflareBindingError,
|
|
52
|
-
} from "./
|
|
66
|
+
} from "./CloudflareBindings.ts";
|
|
67
|
+
import { AdmissionLimitExceeded, CloudflareDurableRuntimeConfig } from "./CloudflareConfig.ts";
|
|
53
68
|
import {
|
|
54
69
|
AbortRecorded,
|
|
55
70
|
ApprovalRecorded,
|
|
56
71
|
HostFailed,
|
|
57
72
|
HostProtocolError,
|
|
58
73
|
ObservedPage,
|
|
74
|
+
ProgressObserved,
|
|
75
|
+
ProgressCancelled,
|
|
59
76
|
SettlementReached,
|
|
77
|
+
SubmissionStatusResponse,
|
|
60
78
|
SubmitSucceeded,
|
|
61
79
|
UnknownResolutionRecorded,
|
|
62
80
|
boundHostDiagnostic,
|
|
63
81
|
decodeAbortCommand,
|
|
82
|
+
decodeAwaitProgressRequest,
|
|
83
|
+
decodeCancelProgressRequest,
|
|
64
84
|
decodeApprovalDecisionCommand,
|
|
65
85
|
decodeObservePageRequest,
|
|
66
86
|
decodeReceipt,
|
|
@@ -69,25 +89,39 @@ import {
|
|
|
69
89
|
encodeHostResponse,
|
|
70
90
|
type HostFailure,
|
|
71
91
|
type HostResponse,
|
|
72
|
-
} from "./
|
|
73
|
-
import { AdmissionLimitExceeded, CloudflareDurableRuntimeConfig } from "./config.ts";
|
|
92
|
+
} from "./CloudflareThreadClient.ts";
|
|
74
93
|
import {
|
|
75
|
-
|
|
76
|
-
|
|
94
|
+
layerConfig,
|
|
95
|
+
ThreadObjectPorts,
|
|
77
96
|
type CloudflareDurableRuntimeInitializationError,
|
|
78
97
|
type CloudflareDurableRuntimeOptions,
|
|
79
98
|
type CloudflareDurableRuntimeServices,
|
|
80
|
-
|
|
99
|
+
type CloudflareBootstrapServices,
|
|
100
|
+
} from "./internal/layers.ts";
|
|
101
|
+
import { ProgressWaitRegistry } from "./internal/progress-wait.ts";
|
|
102
|
+
|
|
103
|
+
export {
|
|
104
|
+
layer,
|
|
105
|
+
layerConfig,
|
|
106
|
+
type ThreadPublicationOptions as PublicationOptions,
|
|
107
|
+
type CloudflareDurableRuntimeOptions as RuntimeOptions,
|
|
108
|
+
type CloudflareDurableRuntimeServices as Services,
|
|
109
|
+
type CloudflareDurableRuntimeInitializationError as InitializationError,
|
|
110
|
+
type CloudflareBootstrapServices as BootstrapServices,
|
|
111
|
+
} from "./internal/layers.ts";
|
|
81
112
|
|
|
82
113
|
/**
|
|
83
|
-
* `
|
|
114
|
+
* `ThreadObject.make(application, options)` — the Thread Durable Object
|
|
84
115
|
* (plan §1.4,
|
|
85
116
|
* D-P6-1): a factory returning a class that applications export from their Worker entry.
|
|
86
|
-
* One SQLite-backed Object per
|
|
117
|
+
* One SQLite-backed Object per Thread is the serialized owner (durability §6); the
|
|
87
118
|
* Object never runs `runResolvedWorker`'s infinite loop — each ingress event or alarm runs
|
|
88
|
-
* ONE bounded `runRecovery` + `
|
|
119
|
+
* ONE bounded `runRecovery` + `processThreadHead` pass, and the persisted alarm
|
|
89
120
|
* (the single multiplexed slot, D-P6-2) finishes accepted work across evictions WITHOUT any
|
|
90
121
|
* incoming request.
|
|
122
|
+
* `Services` exposes the same owner `SqlClient` used by the Thread stores. Compose optional
|
|
123
|
+
* local repositories after `ThreadObject.layer`; never acquire another independently locked
|
|
124
|
+
* SQL client for the same Object. Exposing the client installs no additional storage schemas.
|
|
91
125
|
*
|
|
92
126
|
* Constructor gate (`blockConcurrencyWhile`) is LOCAL-ONLY: schema migration and the
|
|
93
127
|
* exact-version check, configuration decode, and the defensive ensure-alarm half of the
|
|
@@ -97,39 +131,62 @@ import {
|
|
|
97
131
|
* `runRecovery` BEFORE any claim, so reconciliation still strictly precedes new work.
|
|
98
132
|
*/
|
|
99
133
|
|
|
100
|
-
/** Construction options for one deployed
|
|
101
|
-
export interface
|
|
134
|
+
/** Construction options for one deployed Thread Object class. */
|
|
135
|
+
export interface Options<
|
|
136
|
+
ApplicationServices = never,
|
|
137
|
+
EventServices = never,
|
|
138
|
+
EventLayerError = never,
|
|
139
|
+
> extends CloudflareDurableRuntimeOptions {
|
|
140
|
+
/** Accept transient native RPC tracing through effect-cf; disabled by default. */
|
|
141
|
+
readonly rpcTracing?: boolean;
|
|
102
142
|
/**
|
|
103
143
|
* Name of the Worker `env` binding carrying THIS class's `DurableObjectNamespace` — the
|
|
104
|
-
* Object's route back to sibling
|
|
144
|
+
* Object's route back to sibling Thread Objects for the WP2 cross-Object port calls
|
|
105
145
|
* and remote wakes (DEPLOY-010: the binding enters through a Layer, never ambiently).
|
|
106
146
|
*/
|
|
107
147
|
readonly namespaceBinding: string;
|
|
148
|
+
/** Acquired and finalized per native event, with access to the complete application runtime. */
|
|
149
|
+
readonly eventLayer?: Layer.Layer<
|
|
150
|
+
EventServices,
|
|
151
|
+
EventLayerError,
|
|
152
|
+
| RuntimeServices
|
|
153
|
+
| ApplicationServices
|
|
154
|
+
| EffectCfDurableObjectState.DurableObjectState
|
|
155
|
+
| WorkerEnvironment
|
|
156
|
+
>;
|
|
108
157
|
}
|
|
109
158
|
|
|
110
|
-
type EndpointServices =
|
|
111
|
-
|
|
112
|
-
|
|
159
|
+
type EndpointServices =
|
|
160
|
+
| CloudflareDurableRuntimeServices
|
|
161
|
+
| CloudflareBootstrapServices
|
|
162
|
+
| DurableObjectContext;
|
|
163
|
+
type RuntimeServices = EndpointServices | ThreadObjectNamespace;
|
|
164
|
+
type ThreadObjectInitializationError =
|
|
113
165
|
| CloudflareDurableRuntimeInitializationError
|
|
114
166
|
| CloudflareBindingError
|
|
115
167
|
| MaintenancePassFailure;
|
|
116
168
|
|
|
117
|
-
/**
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
169
|
+
/** Classify only a decoded port request so new protocol members cannot bypass pre-arming. */
|
|
170
|
+
const isMutatingPortRequest = (request: PortRequest): boolean => {
|
|
171
|
+
switch (request._tag) {
|
|
172
|
+
case "LedgerAdmit":
|
|
173
|
+
case "LedgerMarkReady":
|
|
174
|
+
case "LedgerRequestAbort":
|
|
175
|
+
case "LedgerRecordChildSettled":
|
|
176
|
+
case "StoreMaterialize":
|
|
177
|
+
case "StoreAppend":
|
|
178
|
+
return true;
|
|
179
|
+
case "LedgerLookup":
|
|
180
|
+
case "LedgerResolveAdmission":
|
|
181
|
+
case "StoreReadPage":
|
|
182
|
+
case "StoreInspectTail":
|
|
183
|
+
case "StoreExport":
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
request satisfies never;
|
|
126
187
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
encoded !== null &&
|
|
130
|
-
"_tag" in encoded &&
|
|
131
|
-
typeof encoded._tag === "string" &&
|
|
132
|
-
MUTATING_PORT_TAGS.has(encoded._tag);
|
|
188
|
+
return false;
|
|
189
|
+
};
|
|
133
190
|
|
|
134
191
|
/** The literal encoded `PortFailed(PortProtocolError)` fallback (same shape as WP2's). */
|
|
135
192
|
const encodedPortProtocolFailure = (message: string): unknown => ({
|
|
@@ -165,7 +222,7 @@ const encodeResponse = (response: HostResponse): Effect.Effect<unknown> =>
|
|
|
165
222
|
),
|
|
166
223
|
);
|
|
167
224
|
|
|
168
|
-
const utf8Bytes = (value:
|
|
225
|
+
const utf8Bytes = (value: PersistedJson): number =>
|
|
169
226
|
new TextEncoder().encode(JSON.stringify(value)).length;
|
|
170
227
|
|
|
171
228
|
/**
|
|
@@ -174,55 +231,57 @@ const utf8Bytes = (value: unknown): number =>
|
|
|
174
231
|
* exempt: its accepted-work obligation already exists, and returning the original Receipt
|
|
175
232
|
* consumes no new quota. Refusals are typed `AdmissionLimitExceeded` and nothing is written.
|
|
176
233
|
*/
|
|
177
|
-
const gateAdmissionLimits = Effect.fn("
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
);
|
|
195
|
-
if (Option.isSome(existing)) return;
|
|
196
|
-
|
|
197
|
-
const inputBytes = utf8Bytes(request.inputPayload);
|
|
198
|
-
if (inputBytes > config.limits.maxInputBytes) {
|
|
199
|
-
return yield* AdmissionLimitExceeded.make({
|
|
200
|
-
limit: "input-bytes",
|
|
201
|
-
actual: inputBytes,
|
|
202
|
-
maximum: config.limits.maxInputBytes,
|
|
203
|
-
});
|
|
204
|
-
}
|
|
234
|
+
const gateAdmissionLimits = Effect.fn("ThreadObject.gateAdmissionLimits")(function* (request: {
|
|
235
|
+
readonly principal: SubmissionLookupByKey["principal"];
|
|
236
|
+
readonly idempotencyKey: SubmissionLookupByKey["idempotencyKey"];
|
|
237
|
+
readonly inputPayload: PersistedJson;
|
|
238
|
+
}) {
|
|
239
|
+
const identity = yield* ThreadObjectIdentity;
|
|
240
|
+
const config = yield* CloudflareDurableRuntimeConfig;
|
|
241
|
+
const ledger = yield* SubmissionLedger;
|
|
242
|
+
const { ctx } = yield* DurableObjectContext;
|
|
243
|
+
|
|
244
|
+
const existing = yield* ledger.lookup(
|
|
245
|
+
SubmissionLookupByKey.make({
|
|
246
|
+
threadId: identity.threadId,
|
|
247
|
+
principal: request.principal,
|
|
248
|
+
idempotencyKey: request.idempotencyKey,
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
205
251
|
|
|
206
|
-
|
|
207
|
-
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
208
|
-
if (nonterminal.length >= config.limits.maxQueueDepthPerLane) {
|
|
209
|
-
return yield* AdmissionLimitExceeded.make({
|
|
210
|
-
limit: "queue-depth",
|
|
211
|
-
actual: nonterminal.length,
|
|
212
|
-
maximum: config.limits.maxQueueDepthPerLane,
|
|
213
|
-
});
|
|
214
|
-
}
|
|
252
|
+
if (Option.isSome(existing)) return;
|
|
215
253
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
254
|
+
const inputBytes = utf8Bytes(request.inputPayload);
|
|
255
|
+
|
|
256
|
+
if (inputBytes > config.limits.maxInputBytes) {
|
|
257
|
+
return yield* AdmissionLimitExceeded.make({
|
|
258
|
+
limit: "input-bytes",
|
|
259
|
+
actual: inputBytes,
|
|
260
|
+
maximum: config.limits.maxInputBytes,
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// One Thread per Object (durability §5): the local scan IS this lane's queue.
|
|
265
|
+
const nonterminal = yield* Stream.runCollect(ledger.scanNonterminal);
|
|
266
|
+
|
|
267
|
+
if (nonterminal.length >= config.limits.maxQueueDepthPerLane) {
|
|
268
|
+
return yield* AdmissionLimitExceeded.make({
|
|
269
|
+
limit: "queue-depth",
|
|
270
|
+
actual: nonterminal.length,
|
|
271
|
+
maximum: config.limits.maxQueueDepthPerLane,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const databaseBytes = yield* Effect.sync(() => ctx.storage.sql.databaseSize);
|
|
276
|
+
|
|
277
|
+
if (databaseBytes > config.limits.maxDatabaseBytes) {
|
|
278
|
+
return yield* AdmissionLimitExceeded.make({
|
|
279
|
+
limit: "database-bytes",
|
|
280
|
+
actual: databaseBytes,
|
|
281
|
+
maximum: config.limits.maxDatabaseBytes,
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
});
|
|
226
285
|
|
|
227
286
|
/**
|
|
228
287
|
* The submit-capable projection of an Agent Binding on the OBJECT side: the input arrived
|
|
@@ -242,22 +301,35 @@ const submitEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoin
|
|
|
242
301
|
Effect.mapError(protocolFailure("The submit request could not be decoded")),
|
|
243
302
|
Effect.flatMap((request) =>
|
|
244
303
|
Effect.gen(function* () {
|
|
245
|
-
const identity = yield*
|
|
246
|
-
const maintenance = yield*
|
|
304
|
+
const identity = yield* ThreadObjectIdentity;
|
|
305
|
+
const maintenance = yield* ThreadMaintenance;
|
|
247
306
|
const runtime = yield* DurableAgentRuntime;
|
|
307
|
+
|
|
248
308
|
yield* gateAdmissionLimits(request);
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
request.inputPayload,
|
|
254
|
-
|
|
255
|
-
conversationId: identity.conversationId,
|
|
309
|
+
|
|
310
|
+
// Alarm invariant: the generation + alarm commit BEFORE the admission, and maintenance
|
|
311
|
+
// cannot acknowledge that generation until this mutation leaves its public RPC seam.
|
|
312
|
+
const receipt = yield* maintenance.withMutation(
|
|
313
|
+
runtime.submit(passthroughSubmitAgent(request.agentId), request.inputPayload, {
|
|
314
|
+
threadId: identity.threadId,
|
|
256
315
|
principal: request.principal,
|
|
257
316
|
idempotencyKey: request.idempotencyKey,
|
|
317
|
+
...(request.admissionGroup === undefined
|
|
318
|
+
? {}
|
|
319
|
+
: { admissionGroup: request.admissionGroup }),
|
|
320
|
+
...(request.admissionFence === undefined
|
|
321
|
+
? {}
|
|
322
|
+
: { admissionFence: request.admissionFence }),
|
|
323
|
+
...(request.workerAdmission === undefined
|
|
324
|
+
? {}
|
|
325
|
+
: { workerAdmission: request.workerAdmission }),
|
|
326
|
+
...(request.messageAdmission === undefined
|
|
327
|
+
? {}
|
|
328
|
+
: { messageAdmission: request.messageAdmission }),
|
|
258
329
|
definitions: request.definitions,
|
|
259
|
-
},
|
|
330
|
+
}),
|
|
260
331
|
);
|
|
332
|
+
|
|
261
333
|
return SubmitSucceeded.make({ receipt });
|
|
262
334
|
}),
|
|
263
335
|
),
|
|
@@ -265,6 +337,22 @@ const submitEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoin
|
|
|
265
337
|
Effect.flatMap(encodeResponse),
|
|
266
338
|
);
|
|
267
339
|
|
|
340
|
+
const submissionStatusEndpoint = (
|
|
341
|
+
encoded: unknown,
|
|
342
|
+
): Effect.Effect<unknown, never, EndpointServices> =>
|
|
343
|
+
decodeReceipt(encoded).pipe(
|
|
344
|
+
Effect.mapError(protocolFailure("The receipt could not be decoded")),
|
|
345
|
+
Effect.flatMap((receipt) =>
|
|
346
|
+
Effect.gen(function* () {
|
|
347
|
+
const runtime = yield* DurableAgentRuntime;
|
|
348
|
+
|
|
349
|
+
return SubmissionStatusResponse.make({ status: yield* runtime.submissionStatus(receipt) });
|
|
350
|
+
}),
|
|
351
|
+
),
|
|
352
|
+
respond,
|
|
353
|
+
Effect.flatMap(encodeResponse),
|
|
354
|
+
);
|
|
355
|
+
|
|
268
356
|
const awaitSettlementEndpoint = (
|
|
269
357
|
encoded: unknown,
|
|
270
358
|
): Effect.Effect<unknown, never, EndpointServices> =>
|
|
@@ -274,6 +362,7 @@ const awaitSettlementEndpoint = (
|
|
|
274
362
|
Effect.gen(function* () {
|
|
275
363
|
const runtime = yield* DurableAgentRuntime;
|
|
276
364
|
const settlement = yield* runtime.awaitSettlement(receipt);
|
|
365
|
+
|
|
277
366
|
return SettlementReached.make({ settlement });
|
|
278
367
|
}),
|
|
279
368
|
),
|
|
@@ -281,28 +370,73 @@ const awaitSettlementEndpoint = (
|
|
|
281
370
|
Effect.flatMap(encodeResponse),
|
|
282
371
|
);
|
|
283
372
|
|
|
373
|
+
const awaitProgressEndpoint = (encoded: unknown): Effect.Effect<unknown, never, EndpointServices> =>
|
|
374
|
+
decodeAwaitProgressRequest(encoded).pipe(
|
|
375
|
+
Effect.mapError(protocolFailure("The progress request could not be decoded")),
|
|
376
|
+
Effect.flatMap((request) =>
|
|
377
|
+
Effect.gen(function* () {
|
|
378
|
+
const identity = yield* ThreadObjectIdentity;
|
|
379
|
+
const runtime = yield* DurableAgentRuntime;
|
|
380
|
+
const registry = yield* ProgressWaitRegistry;
|
|
381
|
+
|
|
382
|
+
yield* Effect.scoped(
|
|
383
|
+
Effect.gen(function* () {
|
|
384
|
+
const cancelled = yield* registry.subscribe(request.waiterId);
|
|
385
|
+
|
|
386
|
+
yield* Effect.raceFirst(
|
|
387
|
+
runtime.awaitProgress(identity.threadId, request.afterSequence),
|
|
388
|
+
cancelled,
|
|
389
|
+
);
|
|
390
|
+
}),
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
return ProgressObserved.make();
|
|
394
|
+
}),
|
|
395
|
+
),
|
|
396
|
+
respond,
|
|
397
|
+
Effect.flatMap(encodeResponse),
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const cancelProgressEndpoint = (
|
|
401
|
+
encoded: unknown,
|
|
402
|
+
): Effect.Effect<unknown, never, EndpointServices> =>
|
|
403
|
+
decodeCancelProgressRequest(encoded).pipe(
|
|
404
|
+
Effect.mapError(protocolFailure("The progress cancellation could not be decoded")),
|
|
405
|
+
Effect.flatMap((request) =>
|
|
406
|
+
Effect.gen(function* () {
|
|
407
|
+
const registry = yield* ProgressWaitRegistry;
|
|
408
|
+
|
|
409
|
+
yield* registry.cancel(request.waiterId);
|
|
410
|
+
|
|
411
|
+
return ProgressCancelled.make();
|
|
412
|
+
}),
|
|
413
|
+
),
|
|
414
|
+
respond,
|
|
415
|
+
Effect.flatMap(encodeResponse),
|
|
416
|
+
);
|
|
417
|
+
|
|
284
418
|
const observePageEndpoint = (encoded: unknown): Effect.Effect<unknown, never, EndpointServices> =>
|
|
285
419
|
decodeObservePageRequest(encoded).pipe(
|
|
286
420
|
Effect.mapError(protocolFailure("The observe request could not be decoded")),
|
|
287
421
|
Effect.flatMap((request) =>
|
|
288
422
|
Effect.gen(function* () {
|
|
289
|
-
const identity = yield*
|
|
290
|
-
const store = yield*
|
|
423
|
+
const identity = yield* ThreadObjectIdentity;
|
|
424
|
+
const store = yield* ThreadStore;
|
|
291
425
|
// The same fail-closed authorization seam the runtime's `observe` consults (P7 WP1);
|
|
292
426
|
// the default reference preserves the possession behavior.
|
|
293
427
|
const authorizer = yield* OperationAuthorizer;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
428
|
+
|
|
429
|
+
yield* authorizer.authorize(
|
|
430
|
+
OperationAuthorizationRequest.make({
|
|
431
|
+
operation: "observe",
|
|
432
|
+
threadId: identity.threadId,
|
|
433
|
+
}),
|
|
434
|
+
);
|
|
435
|
+
|
|
302
436
|
const records = yield* Stream.runCollect(
|
|
303
437
|
store.read(
|
|
304
|
-
|
|
305
|
-
|
|
438
|
+
ThreadRead.make({
|
|
439
|
+
threadId: identity.threadId,
|
|
306
440
|
...(request.afterSequence === undefined
|
|
307
441
|
? {}
|
|
308
442
|
: { afterSequence: request.afterSequence }),
|
|
@@ -310,6 +444,7 @@ const observePageEndpoint = (encoded: unknown): Effect.Effect<unknown, never, En
|
|
|
310
444
|
}),
|
|
311
445
|
),
|
|
312
446
|
);
|
|
447
|
+
|
|
313
448
|
return ObservedPage.make({ records: [...records] });
|
|
314
449
|
}),
|
|
315
450
|
),
|
|
@@ -322,10 +457,10 @@ const abortEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoint
|
|
|
322
457
|
Effect.mapError(protocolFailure("The abort command could not be decoded")),
|
|
323
458
|
Effect.flatMap((command) =>
|
|
324
459
|
Effect.gen(function* () {
|
|
325
|
-
const maintenance = yield*
|
|
460
|
+
const maintenance = yield* ThreadMaintenance;
|
|
326
461
|
const runtime = yield* DurableAgentRuntime;
|
|
327
|
-
yield* maintenance.
|
|
328
|
-
|
|
462
|
+
const intent = yield* maintenance.withMutation(runtime.abort(command));
|
|
463
|
+
|
|
329
464
|
return AbortRecorded.make({ intent });
|
|
330
465
|
}),
|
|
331
466
|
),
|
|
@@ -333,24 +468,6 @@ const abortEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoint
|
|
|
333
468
|
Effect.flatMap(encodeResponse),
|
|
334
469
|
);
|
|
335
470
|
|
|
336
|
-
/**
|
|
337
|
-
* The pre-P7 host protocol's failure union does not carry `OperationDenied` (the Worker client
|
|
338
|
-
* predates the authorizer). This assembly always runs the default possession authorizer — no
|
|
339
|
-
* `CloudflareDurableRuntimeOptions` authorizer lever exists yet — so a denial here is
|
|
340
|
-
* unreachable today; if one ever surfaces it degrades to the protocol failure instead of an
|
|
341
|
-
* out-of-contract throw. The four P7 admin entry points below carry `OperationDenied` typed.
|
|
342
|
-
*/
|
|
343
|
-
const deniedToProtocolFailure = (
|
|
344
|
-
denied: OperationDenied,
|
|
345
|
-
): Effect.Effect<never, HostProtocolError> =>
|
|
346
|
-
Effect.fail(
|
|
347
|
-
HostProtocolError.make({
|
|
348
|
-
message: boundHostDiagnostic(
|
|
349
|
-
`The ${denied.operation} operation was denied: ${denied.reason}`,
|
|
350
|
-
),
|
|
351
|
-
}),
|
|
352
|
-
);
|
|
353
|
-
|
|
354
471
|
const resolveApprovalEndpoint = (
|
|
355
472
|
encoded: unknown,
|
|
356
473
|
): Effect.Effect<unknown, never, EndpointServices> =>
|
|
@@ -358,12 +475,10 @@ const resolveApprovalEndpoint = (
|
|
|
358
475
|
Effect.mapError(protocolFailure("The approval command could not be decoded")),
|
|
359
476
|
Effect.flatMap((command) =>
|
|
360
477
|
Effect.gen(function* () {
|
|
361
|
-
const maintenance = yield*
|
|
478
|
+
const maintenance = yield* ThreadMaintenance;
|
|
362
479
|
const runtime = yield* DurableAgentRuntime;
|
|
363
|
-
yield* maintenance.
|
|
364
|
-
|
|
365
|
-
.resolveApproval(command)
|
|
366
|
-
.pipe(Effect.catchTag("OperationDenied", deniedToProtocolFailure));
|
|
480
|
+
const intent = yield* maintenance.withMutation(runtime.resolveApproval(command));
|
|
481
|
+
|
|
367
482
|
return ApprovalRecorded.make({ intent });
|
|
368
483
|
}),
|
|
369
484
|
),
|
|
@@ -378,12 +493,10 @@ const resolveUnknownEndpoint = (
|
|
|
378
493
|
Effect.mapError(protocolFailure("The resolution command could not be decoded")),
|
|
379
494
|
Effect.flatMap((command) =>
|
|
380
495
|
Effect.gen(function* () {
|
|
381
|
-
const maintenance = yield*
|
|
496
|
+
const maintenance = yield* ThreadMaintenance;
|
|
382
497
|
const runtime = yield* DurableAgentRuntime;
|
|
383
|
-
yield* maintenance.
|
|
384
|
-
|
|
385
|
-
.resolveUnknown(command)
|
|
386
|
-
.pipe(Effect.catchTag("OperationDenied", deniedToProtocolFailure));
|
|
498
|
+
const intent = yield* maintenance.withMutation(runtime.resolveUnknown(command));
|
|
499
|
+
|
|
387
500
|
return UnknownResolutionRecorded.make({ intent });
|
|
388
501
|
}),
|
|
389
502
|
),
|
|
@@ -413,6 +526,7 @@ export class AdminVerifyRequest extends Schema.Class<AdminVerifyRequest>(
|
|
|
413
526
|
|
|
414
527
|
/** Every typed failure of the four admin entry points, plus the protocol's own errors. */
|
|
415
528
|
export const AdminFailure = Schema.Union([
|
|
529
|
+
AdmissionPolicyError,
|
|
416
530
|
OperationDenied,
|
|
417
531
|
RetryRefused,
|
|
418
532
|
LedgerError,
|
|
@@ -420,14 +534,15 @@ export const AdminFailure = Schema.Union([
|
|
|
420
534
|
DigestError,
|
|
421
535
|
OwnershipLost,
|
|
422
536
|
SettlementConflict,
|
|
423
|
-
|
|
424
|
-
|
|
537
|
+
ThreadStoreError,
|
|
538
|
+
ThreadNotMaterialized,
|
|
425
539
|
AppendConflict,
|
|
426
540
|
FenceRejected,
|
|
427
541
|
DurableRuntimeFailpointError,
|
|
428
542
|
DurableAlarmError,
|
|
429
543
|
HostProtocolError,
|
|
430
544
|
]);
|
|
545
|
+
|
|
431
546
|
export type AdminFailure = typeof AdminFailure.Type;
|
|
432
547
|
|
|
433
548
|
export class ExplainedRecovery extends Schema.TaggedClass<ExplainedRecovery>(
|
|
@@ -469,6 +584,7 @@ export const AdminResponse = Schema.Union([
|
|
|
469
584
|
ObligationsScanned,
|
|
470
585
|
AdminFailed,
|
|
471
586
|
]);
|
|
587
|
+
|
|
472
588
|
export type AdminResponse = typeof AdminResponse.Type;
|
|
473
589
|
|
|
474
590
|
export const decodeAdminExplainRequest = Schema.decodeUnknownEffect(AdminExplainRequest);
|
|
@@ -506,12 +622,14 @@ const explainEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoi
|
|
|
506
622
|
Effect.mapError(protocolFailure("The explain request could not be decoded")),
|
|
507
623
|
Effect.flatMap((request) =>
|
|
508
624
|
Effect.gen(function* () {
|
|
509
|
-
const identity = yield*
|
|
625
|
+
const identity = yield* ThreadObjectIdentity;
|
|
510
626
|
const runtime = yield* DurableAgentRuntime;
|
|
627
|
+
|
|
511
628
|
const explanations =
|
|
512
629
|
request.submissionId === undefined
|
|
513
|
-
? yield* runtime.
|
|
630
|
+
? yield* runtime.explainThread(identity.threadId)
|
|
514
631
|
: [yield* runtime.explain(request.submissionId)];
|
|
632
|
+
|
|
515
633
|
return ExplainedRecovery.make({ explanations });
|
|
516
634
|
}),
|
|
517
635
|
),
|
|
@@ -524,9 +642,10 @@ const verifyEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoin
|
|
|
524
642
|
Effect.mapError(protocolFailure("The verify request could not be decoded")),
|
|
525
643
|
Effect.flatMap(() =>
|
|
526
644
|
Effect.gen(function* () {
|
|
527
|
-
const identity = yield*
|
|
645
|
+
const identity = yield* ThreadObjectIdentity;
|
|
528
646
|
const runtime = yield* DurableAgentRuntime;
|
|
529
|
-
const report = yield* runtime.verify(identity.
|
|
647
|
+
const report = yield* runtime.verify(identity.threadId);
|
|
648
|
+
|
|
530
649
|
return VerifiedIntegrity.make({ report });
|
|
531
650
|
}),
|
|
532
651
|
),
|
|
@@ -539,12 +658,11 @@ const retryEndpoint = (encoded: unknown): Effect.Effect<unknown, never, Endpoint
|
|
|
539
658
|
Effect.mapError(protocolFailure("The retry command could not be decoded")),
|
|
540
659
|
Effect.flatMap((command) =>
|
|
541
660
|
Effect.gen(function* () {
|
|
542
|
-
const maintenance = yield*
|
|
661
|
+
const maintenance = yield* ThreadMaintenance;
|
|
543
662
|
const runtime = yield* DurableAgentRuntime;
|
|
544
|
-
//
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
const report = yield* runtime.retry(command);
|
|
663
|
+
// Retry may repair durable state, so its generation + alarm commit before the mutation.
|
|
664
|
+
const report = yield* maintenance.withMutation(runtime.retry(command));
|
|
665
|
+
|
|
548
666
|
return RetryExecuted.make({ report });
|
|
549
667
|
}),
|
|
550
668
|
),
|
|
@@ -559,6 +677,7 @@ const obligationsEndpoint = (encoded: unknown): Effect.Effect<unknown, never, En
|
|
|
559
677
|
Effect.gen(function* () {
|
|
560
678
|
const runtime = yield* DurableAgentRuntime;
|
|
561
679
|
const report = yield* runtime.scanObligations(thresholds);
|
|
680
|
+
|
|
562
681
|
return ObligationsScanned.make({ report });
|
|
563
682
|
}),
|
|
564
683
|
),
|
|
@@ -567,62 +686,90 @@ const obligationsEndpoint = (encoded: unknown): Effect.Effect<unknown, never, En
|
|
|
567
686
|
);
|
|
568
687
|
|
|
569
688
|
/**
|
|
570
|
-
* Owner-side `portCall`:
|
|
571
|
-
* THIS Object must already carry the alarm that will
|
|
572
|
-
* (never the routed decorators), then arm an immediate
|
|
573
|
-
* processed promptly. Protocol anomalies answer
|
|
689
|
+
* Owner-side `portCall`: wrap a mutating envelope in the same pre-armed generation protocol as
|
|
690
|
+
* public RPC (a routed mutation committed by THIS Object must already carry the alarm that will
|
|
691
|
+
* finish it), execute on the LOCAL facets (never the routed decorators), then arm an immediate
|
|
692
|
+
* alarm so the mutated lane is processed promptly. Protocol anomalies answer
|
|
693
|
+
* `PortFailed(PortProtocolError)`.
|
|
574
694
|
*/
|
|
575
695
|
const portCallEndpoint = (encoded: unknown): Effect.Effect<unknown, never, EndpointServices> =>
|
|
576
696
|
Effect.gen(function* () {
|
|
577
|
-
const ports = yield*
|
|
578
|
-
const maintenance = yield*
|
|
697
|
+
const ports = yield* ThreadObjectPorts;
|
|
698
|
+
const maintenance = yield* ThreadMaintenance;
|
|
579
699
|
const alarm = yield* DurableAlarmService;
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
700
|
+
|
|
701
|
+
const decoded = yield* decodePortRequest(encoded).pipe(
|
|
702
|
+
Effect.map((request) => ({ _tag: "success" as const, request })),
|
|
703
|
+
Effect.catch((error) => Effect.succeed({ _tag: "failure" as const, message: error.message })),
|
|
704
|
+
);
|
|
705
|
+
|
|
706
|
+
if (decoded._tag === "failure") {
|
|
707
|
+
return encodedPortProtocolFailure(
|
|
708
|
+
`The port request could not be decoded: ${decoded.message}`,
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
const mutating = isMutatingPortRequest(decoded.request);
|
|
712
|
+
|
|
713
|
+
const handled = yield* (
|
|
714
|
+
mutating
|
|
715
|
+
? maintenance.withMutation(ports.handle(decoded.request))
|
|
716
|
+
: ports.handle(decoded.request)
|
|
717
|
+
).pipe(Effect.exit);
|
|
718
|
+
|
|
719
|
+
if (handled._tag === "Failure") {
|
|
720
|
+
// Without the committed generation/alarm the invariant cannot be promised; refuse before
|
|
721
|
+
// the port mutation runs. `ports.handle` itself is total, so this is the maintenance error.
|
|
722
|
+
return encodedPortProtocolFailure(
|
|
723
|
+
"The owner Object could not arm its maintenance alarm before the mutation.",
|
|
724
|
+
);
|
|
589
725
|
}
|
|
590
|
-
|
|
726
|
+
|
|
727
|
+
const response = yield* encodePortResponse(handled.value).pipe(
|
|
728
|
+
Effect.catch((error) =>
|
|
729
|
+
Effect.succeed(
|
|
730
|
+
encodedPortProtocolFailure(`The port response could not be encoded: ${error.message}`),
|
|
731
|
+
),
|
|
732
|
+
),
|
|
733
|
+
);
|
|
734
|
+
|
|
591
735
|
if (mutating) {
|
|
592
736
|
// Prompt processing hint; the pre-armed alarm already guarantees convergence.
|
|
593
737
|
yield* alarm.scheduleNow.pipe(
|
|
594
738
|
Effect.catch((error) =>
|
|
595
|
-
Effect.logWarning("
|
|
739
|
+
Effect.logWarning("ThreadObject.portCall: immediate re-arm failed", error),
|
|
596
740
|
),
|
|
597
741
|
);
|
|
598
742
|
}
|
|
743
|
+
|
|
599
744
|
return response;
|
|
600
745
|
});
|
|
601
746
|
|
|
602
747
|
const wakeEndpoint: Effect.Effect<void, never, EndpointServices> = Effect.gen(function* () {
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
);
|
|
748
|
+
const identity = yield* ThreadObjectIdentity;
|
|
749
|
+
const wake = yield* WakeScheduler;
|
|
750
|
+
|
|
751
|
+
// Route the remote hint through this incarnation's scheduler so scoped progress waiters and
|
|
752
|
+
// the alarm receive the same hint. Delivery remains droppable; canonical storage is authority.
|
|
753
|
+
yield* wake.notify(identity.threadId);
|
|
609
754
|
});
|
|
610
755
|
|
|
611
756
|
const alarmEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
|
|
612
757
|
function* () {
|
|
613
|
-
const maintenance = yield*
|
|
758
|
+
const maintenance = yield* ThreadMaintenance;
|
|
759
|
+
|
|
614
760
|
// Typed pass failures propagate: the rejected promise makes workerd retry the alarm
|
|
615
|
-
// (at-least-once delivery), and the
|
|
761
|
+
// (at-least-once delivery), and the dirty generation retains a committed slot meanwhile.
|
|
616
762
|
yield* maintenance.pass;
|
|
617
763
|
},
|
|
618
764
|
);
|
|
619
765
|
|
|
620
766
|
const gateEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices> = Effect.gen(
|
|
621
767
|
function* () {
|
|
622
|
-
// Forcing
|
|
768
|
+
// Forcing ThreadMaintenance forces the whole Layer stack: migration + exact-version
|
|
623
769
|
// check + configuration decode (DEPLOY-008 fails typed here, before any mutation), then
|
|
624
770
|
// the defensive local ensure-alarm half of the invariant. LOCAL-ONLY by construction.
|
|
625
|
-
const maintenance = yield*
|
|
771
|
+
const maintenance = yield* ThreadMaintenance;
|
|
772
|
+
|
|
626
773
|
yield* maintenance.ensureAlarm;
|
|
627
774
|
},
|
|
628
775
|
);
|
|
@@ -634,8 +781,9 @@ const gateEndpoint: Effect.Effect<void, MaintenancePassFailure, EndpointServices
|
|
|
634
781
|
*/
|
|
635
782
|
const effectCfPlatformLayer = (
|
|
636
783
|
namespaceBinding: string,
|
|
784
|
+
rpcTracing = false,
|
|
637
785
|
): Layer.Layer<
|
|
638
|
-
DurableObjectContext |
|
|
786
|
+
DurableObjectContext | ThreadObjectNamespace,
|
|
639
787
|
CloudflareBindingError,
|
|
640
788
|
EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
|
|
641
789
|
> => {
|
|
@@ -643,27 +791,39 @@ const effectCfPlatformLayer = (
|
|
|
643
791
|
Effect.gen(function* () {
|
|
644
792
|
const state = yield* EffectCfDurableObjectState.DurableObjectState;
|
|
645
793
|
const env = yield* WorkerEnvironment;
|
|
794
|
+
|
|
646
795
|
return DurableObjectContext.of({ ctx: state.raw, env });
|
|
647
796
|
}),
|
|
648
797
|
);
|
|
649
|
-
|
|
798
|
+
|
|
799
|
+
const namespace = Layer.effect(ThreadObjectNamespace)(
|
|
650
800
|
Effect.gen(function* () {
|
|
651
801
|
const env = yield* WorkerEnvironment;
|
|
652
|
-
const binding = yield*
|
|
653
|
-
|
|
802
|
+
const binding = yield* threadNamespaceFromEnv(env, namespaceBinding);
|
|
803
|
+
|
|
804
|
+
return ThreadObjectNamespace.of({
|
|
805
|
+
namespace: binding,
|
|
806
|
+
...(rpcTracing === true ? { rpcTracing: namespaceBinding } : {}),
|
|
807
|
+
});
|
|
654
808
|
}),
|
|
655
809
|
);
|
|
810
|
+
|
|
656
811
|
return Layer.merge(context, namespace);
|
|
657
812
|
};
|
|
658
813
|
|
|
659
|
-
/** The public
|
|
660
|
-
export interface
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
814
|
+
/** The public endpoints and effect-cf invocation hook of one Thread Object instance. */
|
|
815
|
+
export interface Instance<EventServices = never> extends InstanceType<
|
|
816
|
+
EffectCfDurableObject.DurableObjectClass<Record<never, never>, RuntimeServices | EventServices>
|
|
817
|
+
> {
|
|
818
|
+
submitEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
819
|
+
submissionStatusEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
820
|
+
awaitSettlementEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
821
|
+
awaitProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
822
|
+
cancelProgressEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
823
|
+
observePage(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
824
|
+
abortEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
825
|
+
resolveApprovalEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
826
|
+
resolveUnknownEncoded(encoded: unknown, traceContext?: unknown): Promise<unknown>;
|
|
667
827
|
explainEncoded(encoded: unknown): Promise<unknown>;
|
|
668
828
|
verifyEncoded(encoded: unknown): Promise<unknown>;
|
|
669
829
|
retryEncoded(encoded: unknown): Promise<unknown>;
|
|
@@ -673,52 +833,60 @@ export interface ConversationObjectInstance extends CloudflareDurableObject {
|
|
|
673
833
|
alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void;
|
|
674
834
|
}
|
|
675
835
|
|
|
676
|
-
/** The constructor shape workerd instantiates for each
|
|
677
|
-
export interface
|
|
678
|
-
new (ctx: DurableObjectState, env: Cloudflare.Env):
|
|
836
|
+
/** The constructor shape workerd instantiates for each Thread Object. */
|
|
837
|
+
export interface Class<EventServices = never> {
|
|
838
|
+
new (ctx: DurableObjectState, env: Cloudflare.Env): Instance<EventServices>;
|
|
679
839
|
}
|
|
680
840
|
|
|
681
841
|
/**
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
685
|
-
*
|
|
686
|
-
*
|
|
842
|
+
* Export a composed application Layer as a native Durable Object class.
|
|
843
|
+
* Bootstrap services are provided to the whole graph before it acquires, so application Layers
|
|
844
|
+
* can yield effect-cf's WorkerEnvironment and DurableObjectState, derived identity, and Crypto.
|
|
845
|
+
* Application dependencies remain visible until Layer.provide satisfies them. effect-cf owns the
|
|
846
|
+
* cached ManagedRuntime, native RPC methods, event scopes, and telemetry flushing.
|
|
847
|
+
* Initialization is local and bounded inside the constructor gate. Cloudflare eviction does not
|
|
848
|
+
* guarantee finalizers; put resources requiring timely release in scoped operations or eventLayer.
|
|
687
849
|
*/
|
|
688
|
-
export const
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
850
|
+
export const make = <
|
|
851
|
+
ApplicationServices,
|
|
852
|
+
ApplicationError,
|
|
853
|
+
EventServices = never,
|
|
854
|
+
EventLayerError = never,
|
|
855
|
+
>(
|
|
856
|
+
applicationLayer: Layer.Layer<
|
|
857
|
+
CloudflareDurableRuntimeServices | ApplicationServices,
|
|
858
|
+
ApplicationError,
|
|
859
|
+
| CloudflareBootstrapServices
|
|
695
860
|
| EffectCfDurableObjectState.DurableObjectState
|
|
696
861
|
| WorkerEnvironment
|
|
862
|
+
| DurableObjectContext
|
|
863
|
+
| ThreadObjectNamespace
|
|
697
864
|
>,
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
> = CloudflareDurableRuntime.layer(options).pipe(
|
|
704
|
-
Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding)),
|
|
865
|
+
options: Options<ApplicationServices, EventServices, EventLayerError>,
|
|
866
|
+
): Class<ApplicationServices | EventServices> => {
|
|
867
|
+
const application = applicationLayer.pipe(
|
|
868
|
+
Layer.provideMerge(layerConfig(options)),
|
|
869
|
+
Layer.provideMerge(effectCfPlatformLayer(options.namespaceBinding, options.rpcTracing)),
|
|
705
870
|
);
|
|
706
871
|
|
|
707
872
|
// The storage/config Layer must acquire inside Cloudflare's constructor gate. effect-cf owns
|
|
708
873
|
// the ManagedRuntime, while this effectContext ensures its first Layer build enters the gate
|
|
709
874
|
// before migration, compatibility checks, or alarm inspection touch Object storage.
|
|
710
875
|
const runtime: Layer.Layer<
|
|
711
|
-
RuntimeServices,
|
|
712
|
-
|
|
876
|
+
RuntimeServices | ApplicationServices,
|
|
877
|
+
ThreadObjectInitializationError | ApplicationError,
|
|
713
878
|
EffectCfDurableObjectState.DurableObjectState | WorkerEnvironment
|
|
714
879
|
> = Layer.effectContext(
|
|
715
880
|
Effect.gen(function* () {
|
|
716
881
|
const state = yield* EffectCfDurableObjectState.DurableObjectState;
|
|
717
882
|
const scope = yield* Effect.scope;
|
|
883
|
+
|
|
718
884
|
return yield* state.blockConcurrencyWhile(
|
|
719
885
|
Effect.gen(function* () {
|
|
720
886
|
const services = yield* Layer.buildWithScope(application, scope);
|
|
887
|
+
|
|
721
888
|
yield* gateEndpoint.pipe(Effect.provide(services));
|
|
889
|
+
|
|
722
890
|
return services;
|
|
723
891
|
}),
|
|
724
892
|
);
|
|
@@ -727,7 +895,10 @@ export const makeConversationObjectClass = <EventLayerError = never, EventServic
|
|
|
727
895
|
|
|
728
896
|
const rpc = {
|
|
729
897
|
submitEncoded: (encoded: unknown) => submitEndpoint(encoded),
|
|
898
|
+
submissionStatusEncoded: (encoded: unknown) => submissionStatusEndpoint(encoded),
|
|
730
899
|
awaitSettlementEncoded: (encoded: unknown) => awaitSettlementEndpoint(encoded),
|
|
900
|
+
awaitProgressEncoded: (encoded: unknown) => awaitProgressEndpoint(encoded),
|
|
901
|
+
cancelProgressEncoded: (encoded: unknown) => cancelProgressEndpoint(encoded),
|
|
731
902
|
observePage: (encoded: unknown) => observePageEndpoint(encoded),
|
|
732
903
|
abortEncoded: (encoded: unknown) => abortEndpoint(encoded),
|
|
733
904
|
resolveApprovalEncoded: (encoded: unknown) => resolveApprovalEndpoint(encoded),
|
|
@@ -738,31 +909,44 @@ export const makeConversationObjectClass = <EventLayerError = never, EventServic
|
|
|
738
909
|
obligationsEncoded: (encoded: unknown) => obligationsEndpoint(encoded),
|
|
739
910
|
portCall: (encoded: unknown) => portCallEndpoint(encoded),
|
|
740
911
|
wake: () => wakeEndpoint,
|
|
741
|
-
} satisfies EffectCfDurableObject.DurableObjectRpc<
|
|
912
|
+
} satisfies EffectCfDurableObject.DurableObjectRpc<
|
|
913
|
+
RuntimeServices | ApplicationServices | EventServices
|
|
914
|
+
>;
|
|
742
915
|
|
|
743
|
-
|
|
744
|
-
RuntimeServices,
|
|
745
|
-
|
|
916
|
+
type NativeOptions = EffectCfDurableObject.DurableObjectOptions<
|
|
917
|
+
RuntimeServices | ApplicationServices,
|
|
918
|
+
EventServices,
|
|
919
|
+
EventLayerError,
|
|
920
|
+
typeof rpc
|
|
921
|
+
>;
|
|
922
|
+
|
|
923
|
+
const EffectCfThreadObject = EffectCfDurableObject.make<
|
|
924
|
+
RuntimeServices | ApplicationServices,
|
|
925
|
+
ThreadObjectInitializationError | ApplicationError,
|
|
746
926
|
EventServices,
|
|
747
927
|
EventLayerError,
|
|
748
928
|
typeof rpc
|
|
749
929
|
>(runtime, {
|
|
750
|
-
...(
|
|
930
|
+
...(options.rpcTracing === true ? { rpcTracing: { service: options.namespaceBinding } } : {}),
|
|
931
|
+
...(options.eventLayer === undefined ? {} : { eventLayer: options.eventLayer }),
|
|
751
932
|
// Force the gated runtime Layer when Cloudflare loads this Object incarnation. Recovery stays
|
|
752
933
|
// in each bounded pass so cross-Object initialization cannot deadlock.
|
|
753
934
|
initialize: Effect.void,
|
|
754
935
|
rpc,
|
|
755
936
|
alarm: () => alarmEndpoint,
|
|
756
|
-
|
|
937
|
+
// This host owns the raw alarm and supplies event services through options.eventLayer.
|
|
938
|
+
// Upstream's conditional alarm-registration check cannot reduce over generic application
|
|
939
|
+
// services. Options and the rpc satisfies check above retain their Effect requirements.
|
|
940
|
+
} as NativeOptions);
|
|
757
941
|
|
|
758
942
|
// effect-cf's class type keeps `alarm` optional even when the handler option is present. This
|
|
759
943
|
// concrete override reflects this factory's stronger contract while delegating execution to
|
|
760
944
|
// the effect-cf runtime unchanged.
|
|
761
|
-
class
|
|
945
|
+
class ThreadObject extends EffectCfThreadObject {
|
|
762
946
|
override alarm(alarmInfo?: AlarmInvocationInfo): Promise<void> | void {
|
|
763
947
|
return super.alarm?.(alarmInfo);
|
|
764
948
|
}
|
|
765
949
|
}
|
|
766
950
|
|
|
767
|
-
return
|
|
951
|
+
return ThreadObject;
|
|
768
952
|
};
|