agents 0.22.0 → 0.23.0
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/README.md +20 -1
- package/dist/{agent-routing-CnkaHb-v.d.ts → agent-routing-DE5zmCQ8.d.ts} +1038 -394
- package/dist/agent-routing.d.ts +1 -1
- package/dist/agent-tool-types.d.ts +26 -26
- package/dist/{agent-tools-C0XJqWzB.d.ts → agent-tools-DtXMTDGM.d.ts} +7 -7
- package/dist/agent-tools.d.ts +21 -21
- package/dist/browser/ai.js +4 -1
- package/dist/browser/ai.js.map +1 -1
- package/dist/browser/tanstack-ai.js.map +1 -1
- package/dist/callable-decorator-DP__HhBA.d.ts +72 -0
- package/dist/callable-decorator.d.ts +18 -0
- package/dist/callable-decorator.js +71 -0
- package/dist/callable-decorator.js.map +1 -0
- package/dist/capability-BjSKYpzg.js.map +1 -1
- package/dist/capability-runner-Be_-PLR1.d.ts +459 -0
- package/dist/channel-Bnm4S7T2.d.ts +491 -0
- package/dist/channels/ai-sdk.d.ts +46 -0
- package/dist/channels/ai-sdk.js +120 -0
- package/dist/channels/ai-sdk.js.map +1 -0
- package/dist/channels/email.d.ts +95 -0
- package/dist/channels/email.js +323 -0
- package/dist/channels/email.js.map +1 -0
- package/dist/channels/index.d.ts +233 -0
- package/dist/channels/index.js +608 -0
- package/dist/channels/index.js.map +1 -0
- package/dist/channels/slack.d.ts +140 -0
- package/dist/channels/slack.js +614 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/tanstack-ai.d.ts +39 -0
- package/dist/channels/tanstack-ai.js +17 -0
- package/dist/channels/tanstack-ai.js.map +1 -0
- package/dist/channels/telegram.d.ts +106 -0
- package/dist/channels/telegram.js +427 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/voice.d.ts +45 -0
- package/dist/channels/voice.js +122 -0
- package/dist/channels/voice.js.map +1 -0
- package/dist/chat/index.d.ts +2325 -2019
- package/dist/chat/index.js +888 -518
- package/dist/chat/index.js.map +1 -1
- package/dist/chat-sdk/index.d.ts +7 -7
- package/dist/chat-sdk/index.js +1 -1
- package/dist/client.d.ts +1 -1
- package/dist/context/index.d.ts +216 -0
- package/dist/context/index.js +454 -0
- package/dist/context/index.js.map +1 -0
- package/dist/{current-agent-CuMErtly.d.ts → current-agent-Da_C9a3b.d.ts} +90 -107
- package/dist/current-agent-DhoDkSnH.js.map +1 -1
- package/dist/{diagnostics-CaBjfz4J.js → diagnostics-BzvaX2UT.js} +5 -1
- package/dist/diagnostics-BzvaX2UT.js.map +1 -0
- package/dist/diagnostics-C4jcz3VK.js +360 -0
- package/dist/diagnostics-C4jcz3VK.js.map +1 -0
- package/dist/index-BB0kqhIz.d.ts +101 -0
- package/dist/index-BVVgDSdq.d.ts +1 -0
- package/dist/index-XDkuQ7zm.d.ts +89 -0
- package/dist/{index-DcSAZKsB.d.ts → index-YSKgfgg9.d.ts} +3 -1
- package/dist/index.d.ts +91 -82
- package/dist/index.js +3 -2
- package/dist/ingress-BfetZbMO.js +83 -0
- package/dist/ingress-BfetZbMO.js.map +1 -0
- package/dist/internal-CYlgHl1l.js +59 -0
- package/dist/internal-CYlgHl1l.js.map +1 -0
- package/dist/internal_context.d.ts +1 -1
- package/dist/lifecycle/index.d.ts +35 -17
- package/dist/lifecycle/index.js +1 -1
- package/dist/lifecycle-CMRGjZdw.js +1299 -0
- package/dist/lifecycle-CMRGjZdw.js.map +1 -0
- package/dist/mcp/client/index.d.ts +20 -20
- package/dist/mcp/index.d.ts +35 -35
- package/dist/mcp/index.js +1 -1
- package/dist/observability/index.d.ts +1 -1
- package/dist/observability/index.js +1 -1
- package/dist/react.d.ts +4 -4
- package/dist/{retries-CAvxtG9d.d.ts → retries-D9Ds-1lz.d.ts} +17 -6
- package/dist/retries.d.ts +8 -6
- package/dist/retries.js +13 -1
- package/dist/retries.js.map +1 -1
- package/dist/routing/index.d.ts +137 -0
- package/dist/routing/index.js +244 -0
- package/dist/routing/index.js.map +1 -0
- package/dist/sanitize-D9TujEK8.js +79 -0
- package/dist/sanitize-D9TujEK8.js.map +1 -0
- package/dist/scheduler-DD9NdYbF.js +665 -0
- package/dist/scheduler-DD9NdYbF.js.map +1 -0
- package/dist/{scheduler-DQoTGoAW.d.ts → scheduler-Dwh85ZGl.d.ts} +21 -22
- package/dist/schedules/index.d.ts +1 -1
- package/dist/schedules/index.js +1 -1
- package/dist/sentence-chunker-BAidJ4DA.d.ts +68 -0
- package/dist/serializable.d.ts +1 -1
- package/dist/sessions/index.d.ts +441 -0
- package/dist/sessions/index.js +2063 -0
- package/dist/sessions/index.js.map +1 -0
- package/dist/skills/index.d.ts +99 -0
- package/dist/skills/index.js +254 -5
- package/dist/skills/index.js.map +1 -1
- package/dist/{src-5W6JNKVb.js → src-DlSHshb2.js} +1460 -1110
- package/dist/src-DlSHshb2.js.map +1 -0
- package/dist/streams/index.d.ts +120 -0
- package/dist/streams/index.js +107 -0
- package/dist/streams/index.js.map +1 -0
- package/dist/streams-D6tJ0NN9.d.ts +370 -0
- package/dist/streams-DZKgAj9b.js +709 -0
- package/dist/streams-DZKgAj9b.js.map +1 -0
- package/dist/sub-routing.d.ts +12 -12
- package/dist/surface-bZZJqBka.js +17 -0
- package/dist/surface-bZZJqBka.js.map +1 -0
- package/dist/tasks/index.d.ts +64 -0
- package/dist/tasks/index.js +2 -0
- package/dist/tasks-BRJ5zgya.d.ts +517 -0
- package/dist/tasks-ylZgBjhj.js +1656 -0
- package/dist/tasks-ylZgBjhj.js.map +1 -0
- package/dist/text-segment-joiner-BtAFQSA_.js +57 -0
- package/dist/text-segment-joiner-BtAFQSA_.js.map +1 -0
- package/dist/text-stream-CpdiKrJB.js +272 -0
- package/dist/text-stream-CpdiKrJB.js.map +1 -0
- package/dist/tokens-nHAKcN6M.js +52 -0
- package/dist/tokens-nHAKcN6M.js.map +1 -0
- package/dist/tool-schema-CBjGPrsQ.js +31 -0
- package/dist/tool-schema-CBjGPrsQ.js.map +1 -0
- package/dist/types-B7LojTe4.d.ts +202 -0
- package/dist/types-_Faxb570.d.ts +439 -0
- package/dist/voice/client.d.ts +226 -0
- package/dist/voice/client.js +932 -0
- package/dist/voice/client.js.map +1 -0
- package/dist/voice/errors.d.ts +43 -0
- package/dist/voice/errors.js +41 -0
- package/dist/voice/errors.js.map +1 -0
- package/dist/voice/index.d.ts +271 -0
- package/dist/voice/index.js +1812 -0
- package/dist/voice/index.js.map +1 -0
- package/dist/voice/react.d.ts +167 -0
- package/dist/voice/react.js +234 -0
- package/dist/voice/react.js.map +1 -0
- package/dist/voice/sfu.d.ts +71 -0
- package/dist/voice/sfu.js +157 -0
- package/dist/voice/sfu.js.map +1 -0
- package/dist/voice/text.d.ts +6 -0
- package/dist/voice/text.js +2 -0
- package/dist/voice/types.d.ts +58 -0
- package/dist/voice/types.js +18 -0
- package/dist/voice/types.js.map +1 -0
- package/dist/voice/workers-ai.d.ts +136 -0
- package/dist/voice/workers-ai.js +568 -0
- package/dist/voice/workers-ai.js.map +1 -0
- package/dist/websockets/index.d.ts +192 -0
- package/dist/websockets/index.js +2 -0
- package/dist/websockets-DUfRHPRq.js +502 -0
- package/dist/websockets-DUfRHPRq.js.map +1 -0
- package/dist/workflow-types.d.ts +25 -25
- package/dist/workflows.d.ts +21 -21
- package/dist/workflows.js +1 -1
- package/docs/agent-class.md +2 -2
- package/docs/agent-tools.md +2 -1
- package/docs/channels.md +323 -0
- package/docs/chat-agents.md +6 -13
- package/docs/context.md +131 -0
- package/docs/index.md +15 -12
- package/docs/lifecycle.md +102 -55
- package/docs/long-running-agents.md +2 -2
- package/docs/mcp-servers.md +5 -1
- package/docs/resumable-streaming.md +1 -1
- package/docs/routing.md +105 -0
- package/docs/sessions.md +237 -871
- package/docs/streams.md +213 -0
- package/docs/sub-agents.md +184 -124
- package/docs/tasks.md +246 -0
- package/docs/voice.md +745 -0
- package/package.json +115 -13
- package/dist/capability-runner-CvHGZqUu.d.ts +0 -150
- package/dist/compaction-helpers-iiKMr2TQ.js +0 -340
- package/dist/compaction-helpers-iiKMr2TQ.js.map +0 -1
- package/dist/compaction-helpers-wUz6M3us.d.ts +0 -621
- package/dist/diagnostics-CaBjfz4J.js.map +0 -1
- package/dist/durable-object-lifecycle-D6nNQJJd.js +0 -862
- package/dist/durable-object-lifecycle-D6nNQJJd.js.map +0 -1
- package/dist/experimental/memory/session/index.d.ts +0 -671
- package/dist/experimental/memory/session/index.js +0 -2379
- package/dist/experimental/memory/session/index.js.map +0 -1
- package/dist/experimental/memory/utils/index.d.ts +0 -96
- package/dist/experimental/memory/utils/index.js +0 -79
- package/dist/experimental/memory/utils/index.js.map +0 -1
- package/dist/scheduler-CR9RHGos.js +0 -857
- package/dist/scheduler-CR9RHGos.js.map +0 -1
- package/dist/src-5W6JNKVb.js.map +0 -1
- package/dist/tool-output-truncation-CNnnGZQ3.js +0 -98
- package/dist/tool-output-truncation-CNnnGZQ3.js.map +0 -1
|
@@ -0,0 +1,1299 @@
|
|
|
1
|
+
import { i as runWithoutCurrentAgent, r as runInLifecycleHostContext } from "./current-agent-DhoDkSnH.js";
|
|
2
|
+
import { i as _classPrivateFieldInitSpec, n as _classPrivateFieldSet2, r as _assertClassBrand, t as _classPrivateFieldGet2 } from "./classPrivateFieldGet2-DZBYAB34.js";
|
|
3
|
+
import { n as bindLifecycleCapability, r as lifecycleCapabilityId, t as LifecycleCapability } from "./capability-BjSKYpzg.js";
|
|
4
|
+
import { t as _classPrivateMethodInitSpec } from "./classPrivateMethodInitSpec-qMjJ6sHQ.js";
|
|
5
|
+
import { n as publishDiagnosticsEvent } from "./diagnostics-BzvaX2UT.js";
|
|
6
|
+
import { isDurableObjectCodeUpdateReset, isDurableObjectMemoryLimitReset, isPlatformFailure, tryN } from "./retries.js";
|
|
7
|
+
import { SqlError } from "./sql-error.js";
|
|
8
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
9
|
+
import "cloudflare:workers";
|
|
10
|
+
import { nanoid } from "nanoid";
|
|
11
|
+
//#region src/lifecycle/capability-runner.ts
|
|
12
|
+
var _resolveCapabilities = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
var _capabilities$1 = /* @__PURE__ */ new WeakMap();
|
|
14
|
+
var _startPromise = /* @__PURE__ */ new WeakMap();
|
|
15
|
+
var _started = /* @__PURE__ */ new WeakMap();
|
|
16
|
+
var _CapabilityRunner_brand = /* @__PURE__ */ new WeakSet();
|
|
17
|
+
/**
|
|
18
|
+
* Runs ordered lifecycle phases for capabilities installed in a Durable Object.
|
|
19
|
+
*
|
|
20
|
+
* Capabilities are resolved lazily on the first phase and retained for the
|
|
21
|
+
* lifetime of this runner. Startup runs in declaration order.
|
|
22
|
+
* Requests dispatch as a middleware chain: the first capability to return a
|
|
23
|
+
* response handles the request.
|
|
24
|
+
*/
|
|
25
|
+
var CapabilityRunner = class {
|
|
26
|
+
/**
|
|
27
|
+
* Create a lifecycle whose capabilities are resolved immediately before the
|
|
28
|
+
* first phase.
|
|
29
|
+
*
|
|
30
|
+
* @param resolveCapabilities - Returns capabilities in their startup order.
|
|
31
|
+
*/
|
|
32
|
+
constructor(resolveCapabilities) {
|
|
33
|
+
_classPrivateMethodInitSpec(this, _CapabilityRunner_brand);
|
|
34
|
+
_classPrivateFieldInitSpec(this, _resolveCapabilities, void 0);
|
|
35
|
+
_classPrivateFieldInitSpec(this, _capabilities$1, void 0);
|
|
36
|
+
_classPrivateFieldInitSpec(this, _startPromise, void 0);
|
|
37
|
+
_classPrivateFieldInitSpec(this, _started, false);
|
|
38
|
+
_classPrivateFieldSet2(_resolveCapabilities, this, resolveCapabilities);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Start every capability sequentially.
|
|
42
|
+
*
|
|
43
|
+
* Concurrent callers share one startup attempt. A failed attempt is not
|
|
44
|
+
* cached, allowing the host to retry its complete startup phase.
|
|
45
|
+
*
|
|
46
|
+
* @param context - Properties supplied while resolving the Durable Object.
|
|
47
|
+
*/
|
|
48
|
+
async start(context) {
|
|
49
|
+
if (_classPrivateFieldGet2(_started, this)) return;
|
|
50
|
+
const pending = _classPrivateFieldGet2(_startPromise, this);
|
|
51
|
+
if (pending) {
|
|
52
|
+
await pending;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const attempt = _assertClassBrand(_CapabilityRunner_brand, this, _runStart).call(this, context);
|
|
56
|
+
_classPrivateFieldSet2(_startPromise, this, attempt);
|
|
57
|
+
try {
|
|
58
|
+
await attempt;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
if (_classPrivateFieldGet2(_startPromise, this) === attempt) _classPrivateFieldSet2(_startPromise, this, void 0);
|
|
61
|
+
throw error;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Offer a request to each capability middleware in registration order.
|
|
66
|
+
*
|
|
67
|
+
* @param context - The request entering the Durable Object.
|
|
68
|
+
* @returns The first capability response, or `undefined` when no
|
|
69
|
+
* capability claimed the request.
|
|
70
|
+
*/
|
|
71
|
+
async request(context) {
|
|
72
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "handle a request");
|
|
73
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) {
|
|
74
|
+
const response = await capability.onRequest?.(context);
|
|
75
|
+
if (response !== void 0) return response;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Offer a WebSocket upgrade to each capability in declaration order.
|
|
80
|
+
*
|
|
81
|
+
* @param context - The upgrade request entering the Durable Object.
|
|
82
|
+
* @returns The first capability response, or `undefined` when unclaimed.
|
|
83
|
+
*/
|
|
84
|
+
async webSocketUpgrade(context) {
|
|
85
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "handle a WebSocket upgrade");
|
|
86
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) {
|
|
87
|
+
const response = await capability.onWebSocketUpgrade?.(context);
|
|
88
|
+
if (response !== void 0) return response;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Offer a platform `webSocketMessage` wake to each capability in
|
|
93
|
+
* declaration order.
|
|
94
|
+
*
|
|
95
|
+
* @returns Whether a capability consumed the event.
|
|
96
|
+
*/
|
|
97
|
+
async webSocketMessage(ws, message) {
|
|
98
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "handle a WebSocket message");
|
|
99
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) if (await capability.onWebSocketMessage?.(ws, message) === true) return true;
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
/** Offer a platform `webSocketClose` wake to each capability. */
|
|
103
|
+
async webSocketClose(ws, code, reason, wasClean) {
|
|
104
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "handle a WebSocket close");
|
|
105
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) if (await capability.onWebSocketClose?.(ws, code, reason, wasClean) === true) return true;
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
/** Offer a platform `webSocketError` wake to each capability. */
|
|
109
|
+
async webSocketError(ws, error) {
|
|
110
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "handle a WebSocket error");
|
|
111
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) if (await capability.onWebSocketError?.(ws, error) === true) return true;
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
/** Find one installed capability by its stable id. */
|
|
115
|
+
async findById(capabilityId) {
|
|
116
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "dispatch capability work");
|
|
117
|
+
return _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this).find((candidate) => lifecycleCapabilityId(candidate) === capabilityId);
|
|
118
|
+
}
|
|
119
|
+
/** Route one message to an installed named capability. */
|
|
120
|
+
async route(capabilityId, context) {
|
|
121
|
+
await _assertClassBrand(_CapabilityRunner_brand, this, _ensureReady).call(this, "route a capability message");
|
|
122
|
+
const capability = _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this).find((candidate) => lifecycleCapabilityId(candidate) === capabilityId);
|
|
123
|
+
if (!capability?.onRoute) throw new Error(`Lifecycle capability ${JSON.stringify(capabilityId)} cannot receive routed messages`);
|
|
124
|
+
return capability.onRoute(context);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Offer a memory-limit strike to every capability, best-effort.
|
|
128
|
+
*
|
|
129
|
+
* Deliberately not gated on startup: a strike can land while startup
|
|
130
|
+
* itself is the work that exceeded the memory limit, and the breaker's
|
|
131
|
+
* policy must still reach capabilities. One capability's failure does not
|
|
132
|
+
* stop the next — the isolate is about to reset either way.
|
|
133
|
+
*/
|
|
134
|
+
async memoryLimit(context) {
|
|
135
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) try {
|
|
136
|
+
await capability.onMemoryLimit?.(context);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.error("Lifecycle capability memory-limit policy failed", error);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Dispose installed capabilities in reverse registration order. */
|
|
142
|
+
async dispose() {
|
|
143
|
+
for (const capability of [..._assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)].reverse()) try {
|
|
144
|
+
await capability.dispose?.();
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error("Lifecycle capability disposal failed", error);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
async function _runStart(context) {
|
|
151
|
+
for (const capability of _assertClassBrand(_CapabilityRunner_brand, this, _getCapabilities).call(this)) await capability.onStart?.(context);
|
|
152
|
+
_classPrivateFieldSet2(_started, this, true);
|
|
153
|
+
}
|
|
154
|
+
async function _ensureReady(operation) {
|
|
155
|
+
const pending = _classPrivateFieldGet2(_startPromise, this);
|
|
156
|
+
if (pending) await pending;
|
|
157
|
+
if (!_classPrivateFieldGet2(_started, this)) throw new Error(`Cannot ${operation} before the Durable Object lifecycle has started`);
|
|
158
|
+
}
|
|
159
|
+
function _getCapabilities() {
|
|
160
|
+
if (!_classPrivateFieldGet2(_capabilities$1, this)) _classPrivateFieldSet2(_capabilities$1, this, Object.freeze([..._classPrivateFieldGet2(_resolveCapabilities, this).call(this)]));
|
|
161
|
+
return _classPrivateFieldGet2(_capabilities$1, this);
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/lifecycle/abort.ts
|
|
165
|
+
/**
|
|
166
|
+
* Reset the Durable Object instance without the platform retrying the alarm
|
|
167
|
+
* this invocation was handling. Never returns: `abort()` terminates
|
|
168
|
+
* execution.
|
|
169
|
+
*/
|
|
170
|
+
function abortWithoutAlarmRetry(ctx, reason) {
|
|
171
|
+
ctx.abort(reason, { retryAlarm: false });
|
|
172
|
+
}
|
|
173
|
+
//#endregion
|
|
174
|
+
//#region src/lifecycle/job-queue.ts
|
|
175
|
+
/**
|
|
176
|
+
* Durable job queue owned by Lifecycle.
|
|
177
|
+
*
|
|
178
|
+
* One timestamp-ordered table holds every pending job for the Durable
|
|
179
|
+
* Object. A job is a serialisable callback address — owning capability plus
|
|
180
|
+
* function name — with a due time and a payload. Capabilities and the host
|
|
181
|
+
* push jobs through their scoped `LifecycleJobs` surface; Lifecycle's alarm
|
|
182
|
+
* event loop drives due jobs and derives the physical alarm from queue
|
|
183
|
+
* state. Payloads are opaque to the queue.
|
|
184
|
+
*/
|
|
185
|
+
/** Capability id under which host-owned jobs are stored. */
|
|
186
|
+
const HOST_JOB_CAPABILITY = "host";
|
|
187
|
+
/** Seconds before an in-flight single-flight job is treated as hung. */
|
|
188
|
+
const DEFAULT_HUNG_TIMEOUT_SECONDS = 30;
|
|
189
|
+
/** @internal Convert one raw queue row into its public job shape. */
|
|
190
|
+
function jobFromRow(row) {
|
|
191
|
+
return {
|
|
192
|
+
id: row.id,
|
|
193
|
+
capability: row.capability,
|
|
194
|
+
fn: row.fn,
|
|
195
|
+
time: row.time,
|
|
196
|
+
payload: typeof row.payload === "string" ? JSON.parse(row.payload) : void 0,
|
|
197
|
+
retry: typeof row.retry_options === "string" ? JSON.parse(row.retry_options) : void 0,
|
|
198
|
+
singleflight: row.singleflight === 1,
|
|
199
|
+
exclusive: row.exclusive === 1,
|
|
200
|
+
recoveryLoop: row.recovery_loop === 1,
|
|
201
|
+
createdAt: row.created_at
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/** @internal One row's hung/slow-dispatch threshold in milliseconds. */
|
|
205
|
+
function hungTimeoutMs(row) {
|
|
206
|
+
return (row.hung_timeout_seconds ?? DEFAULT_HUNG_TIMEOUT_SECONDS) * 1e3;
|
|
207
|
+
}
|
|
208
|
+
/** Whether an in-flight single-flight job has crossed its hung timeout. */
|
|
209
|
+
function isHungRow(row, nowMs) {
|
|
210
|
+
return nowMs - (row.execution_started_at ?? 0) >= hungTimeoutMs(row);
|
|
211
|
+
}
|
|
212
|
+
var _storage = /* @__PURE__ */ new WeakMap();
|
|
213
|
+
var _tableEnsured = /* @__PURE__ */ new WeakMap();
|
|
214
|
+
var _JobQueue_brand = /* @__PURE__ */ new WeakSet();
|
|
215
|
+
/**
|
|
216
|
+
* @internal SQL-backed job queue. Lifecycle owns the single instance; the
|
|
217
|
+
* scoped `LifecycleJobs` surfaces delegate here with a fixed capability id.
|
|
218
|
+
*/
|
|
219
|
+
var JobQueue = class {
|
|
220
|
+
constructor(storage) {
|
|
221
|
+
_classPrivateMethodInitSpec(this, _JobQueue_brand);
|
|
222
|
+
_classPrivateFieldInitSpec(this, _storage, void 0);
|
|
223
|
+
_classPrivateFieldInitSpec(this, _tableEnsured, false);
|
|
224
|
+
_classPrivateFieldSet2(_storage, this, storage);
|
|
225
|
+
}
|
|
226
|
+
push(capability, options) {
|
|
227
|
+
if (!Number.isFinite(options.time) || options.time < 0) throw new Error(`Invalid job time: ${String(options.time)}`);
|
|
228
|
+
if (typeof options.fn !== "string" || options.fn.trim() === "") throw new Error("Jobs require a non-empty fn");
|
|
229
|
+
const id = options.id ?? nanoid(9);
|
|
230
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `INSERT INTO cf_agents_jobs
|
|
231
|
+
(id, capability, fn, time, payload, retry_options, singleflight,
|
|
232
|
+
hung_timeout_seconds, exclusive, recovery_loop, running,
|
|
233
|
+
execution_started_at)
|
|
234
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NULL)
|
|
235
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
236
|
+
fn = excluded.fn,
|
|
237
|
+
time = excluded.time,
|
|
238
|
+
payload = excluded.payload,
|
|
239
|
+
retry_options = excluded.retry_options,
|
|
240
|
+
singleflight = excluded.singleflight,
|
|
241
|
+
hung_timeout_seconds = excluded.hung_timeout_seconds,
|
|
242
|
+
exclusive = excluded.exclusive,
|
|
243
|
+
recovery_loop = excluded.recovery_loop,
|
|
244
|
+
running = 0,
|
|
245
|
+
execution_started_at = NULL
|
|
246
|
+
WHERE cf_agents_jobs.capability = excluded.capability`, id, capability, options.fn, Math.floor(options.time), options.payload === void 0 ? null : JSON.stringify(options.payload), options.retry ? JSON.stringify(options.retry) : null, options.singleflight ? 1 : 0, options.hungTimeoutSeconds ?? null, options.exclusive ? 1 : 0, options.recoveryLoop ? 1 : 0);
|
|
247
|
+
const job = this.get(capability, id);
|
|
248
|
+
if (!job) {
|
|
249
|
+
const owner = _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT capability FROM cf_agents_jobs WHERE id = ?", id)[0]?.capability;
|
|
250
|
+
throw new Error(owner !== void 0 ? `Job id ${JSON.stringify(id)} already belongs to ${JSON.stringify(owner)}; job ids are scoped to their owner` : `Failed to persist job ${id}`);
|
|
251
|
+
}
|
|
252
|
+
return job;
|
|
253
|
+
}
|
|
254
|
+
cancel(capability, id) {
|
|
255
|
+
if (_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT id FROM cf_agents_jobs WHERE id = ? AND capability = ?", id, capability).length === 0) return false;
|
|
256
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "DELETE FROM cf_agents_jobs WHERE id = ? AND capability = ?", id, capability);
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
reschedule(capability, id, time) {
|
|
260
|
+
if (!Number.isFinite(time) || time < 0) throw new Error(`Invalid job time: ${String(time)}`);
|
|
261
|
+
if (_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT id FROM cf_agents_jobs WHERE id = ? AND capability = ?", id, capability).length === 0) return false;
|
|
262
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
263
|
+
SET time = ?, running = 0, execution_started_at = NULL
|
|
264
|
+
WHERE id = ? AND capability = ?`, Math.floor(time), id, capability);
|
|
265
|
+
return true;
|
|
266
|
+
}
|
|
267
|
+
get(capability, id) {
|
|
268
|
+
const rows = _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT * FROM cf_agents_jobs WHERE id = ? AND capability = ?", id, capability);
|
|
269
|
+
return rows[0] ? jobFromRow(rows[0]) : void 0;
|
|
270
|
+
}
|
|
271
|
+
list(capability) {
|
|
272
|
+
return _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT * FROM cf_agents_jobs WHERE capability = ? ORDER BY time ASC", capability).map(jobFromRow);
|
|
273
|
+
}
|
|
274
|
+
/** Raw due rows at `nowMs`, ordered by due time. */
|
|
275
|
+
due(nowMs) {
|
|
276
|
+
return _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT * FROM cf_agents_jobs WHERE time <= ? ORDER BY time ASC", Math.floor(nowMs));
|
|
277
|
+
}
|
|
278
|
+
/** One job's current row when it still exists and is still due. */
|
|
279
|
+
dueRow(id, nowMs) {
|
|
280
|
+
return _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT * FROM cf_agents_jobs WHERE id = ? AND time <= ?", id, Math.floor(nowMs))[0];
|
|
281
|
+
}
|
|
282
|
+
markRunning(id, nowMs) {
|
|
283
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
284
|
+
SET running = 1, execution_started_at = ?
|
|
285
|
+
WHERE id = ?`, Math.floor(nowMs), id);
|
|
286
|
+
}
|
|
287
|
+
clearRunning(id) {
|
|
288
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
289
|
+
SET running = 0, execution_started_at = NULL
|
|
290
|
+
WHERE id = ?`, id);
|
|
291
|
+
}
|
|
292
|
+
delete(id) {
|
|
293
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "DELETE FROM cf_agents_jobs WHERE id = ?", id);
|
|
294
|
+
}
|
|
295
|
+
/**
|
|
296
|
+
* Unguarded retime for the alarm memory-limit breaker's backoff: the
|
|
297
|
+
* platform-failure path clears the dispatch marker before the breaker
|
|
298
|
+
* runs, so the guarded {@link applyOutcome} would no-op — and the
|
|
299
|
+
* breaker's backoff must land regardless, it is protecting the object.
|
|
300
|
+
*/
|
|
301
|
+
retime(id, time) {
|
|
302
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
303
|
+
SET time = ?, running = 0, execution_started_at = NULL
|
|
304
|
+
WHERE id = ?`, Math.floor(time), id);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Back off every pending recovery-loop job that would fire before the
|
|
308
|
+
* breaker's backoff time (#1825), so a doomed loop's sibling rows cannot
|
|
309
|
+
* re-trigger it on the next wake. Unguarded like {@link retime}: the
|
|
310
|
+
* breaker's backoff must land regardless of dispatch markers.
|
|
311
|
+
*/
|
|
312
|
+
delayRecoveryLoopJobs(time) {
|
|
313
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
314
|
+
SET time = ?, running = 0, execution_started_at = NULL
|
|
315
|
+
WHERE recovery_loop = 1 AND time <= ?`, Math.floor(time), Math.floor(time));
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Read every recovery-loop job before breaker sealing. The memory-limit
|
|
319
|
+
* policy phase uses this snapshot after the rows are purged so routed owners
|
|
320
|
+
* can terminalize their own durable recovery state.
|
|
321
|
+
*/
|
|
322
|
+
recoveryLoopJobs() {
|
|
323
|
+
return _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT * FROM cf_agents_jobs WHERE recovery_loop = 1 ORDER BY time ASC").map(jobFromRow);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Purge every recovery-loop job when the breaker seals at its strike
|
|
327
|
+
* budget (#1825). Unrelated jobs are untouched.
|
|
328
|
+
*/
|
|
329
|
+
purgeRecoveryLoopJobs() {
|
|
330
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "DELETE FROM cf_agents_jobs WHERE recovery_loop = 1");
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Apply one drive result, guarded on the dispatch marker: every driven
|
|
334
|
+
* job carries `running = 1` for the duration of its dispatch, and a
|
|
335
|
+
* same-id `push()` or `reschedule()` made meanwhile clears it. A cleared
|
|
336
|
+
* marker means newer durable intent exists, so the outcome quietly
|
|
337
|
+
* defers to it instead of deleting or retiming the fresher job.
|
|
338
|
+
*/
|
|
339
|
+
applyOutcome(id, outcome) {
|
|
340
|
+
if (outcome === void 0) {
|
|
341
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, "DELETE FROM cf_agents_jobs WHERE id = ? AND running = 1", id);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (outcome === "yield") {
|
|
345
|
+
this.clearRunning(id);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (typeof outcome === "object" && Number.isFinite(outcome.rescheduleAt) && outcome.rescheduleAt >= 0) {
|
|
349
|
+
_assertClassBrand(_JobQueue_brand, this, _sql).call(this, `UPDATE cf_agents_jobs
|
|
350
|
+
SET time = ?, running = 0, execution_started_at = NULL
|
|
351
|
+
WHERE id = ? AND running = 1`, Math.floor(outcome.rescheduleAt), id);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
throw new Error(`Invalid job outcome for ${id}`);
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* The next physical alarm time derived from queue state, or `null` when
|
|
358
|
+
* the queue holds nothing to wake for.
|
|
359
|
+
*
|
|
360
|
+
* Exclusive jobs suppress ordinary candidates. An ordinary candidate is
|
|
361
|
+
* the earliest ready job clamped to the future (overdue rows survive
|
|
362
|
+
* restarts and must re-fire immediately), merged with the earliest
|
|
363
|
+
* hung-timeout recheck for in-flight single-flight jobs.
|
|
364
|
+
*/
|
|
365
|
+
nextAlarmTime(nowMs) {
|
|
366
|
+
const exclusive = _assertClassBrand(_JobQueue_brand, this, _sql).call(this, "SELECT MIN(time) AS time FROM cf_agents_jobs WHERE exclusive = 1");
|
|
367
|
+
if (exclusive[0]?.time !== null && exclusive[0]?.time !== void 0) return exclusive[0].time;
|
|
368
|
+
const now = Math.floor(nowMs);
|
|
369
|
+
let candidate = null;
|
|
370
|
+
const ready = _assertClassBrand(_JobQueue_brand, this, _sql).call(this, `SELECT MIN(time) AS time FROM cf_agents_jobs
|
|
371
|
+
WHERE singleflight = 0
|
|
372
|
+
OR running = 0
|
|
373
|
+
OR coalesce(execution_started_at, 0) + coalesce(hung_timeout_seconds, ?) * 1000 <= ?`, DEFAULT_HUNG_TIMEOUT_SECONDS, now);
|
|
374
|
+
if (ready[0]?.time !== null && ready[0]?.time !== void 0) candidate = Math.max(ready[0].time, now + 1);
|
|
375
|
+
const recheck = _assertClassBrand(_JobQueue_brand, this, _sql).call(this, `SELECT MIN(coalesce(execution_started_at, 0) + coalesce(hung_timeout_seconds, ?) * 1000) AS recheck
|
|
376
|
+
FROM cf_agents_jobs
|
|
377
|
+
WHERE singleflight = 1
|
|
378
|
+
AND running = 1
|
|
379
|
+
AND coalesce(execution_started_at, 0) + coalesce(hung_timeout_seconds, ?) * 1000 > ?`, DEFAULT_HUNG_TIMEOUT_SECONDS, DEFAULT_HUNG_TIMEOUT_SECONDS, now)[0]?.recheck;
|
|
380
|
+
if (recheck !== null && recheck !== void 0) candidate = candidate === null ? recheck : Math.min(candidate, recheck);
|
|
381
|
+
return candidate;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
function _sql(query, ...params) {
|
|
385
|
+
_assertClassBrand(_JobQueue_brand, this, _ensureTable).call(this);
|
|
386
|
+
try {
|
|
387
|
+
return [..._classPrivateFieldGet2(_storage, this).sql.exec(query, ...params)];
|
|
388
|
+
} catch (cause) {
|
|
389
|
+
throw new SqlError(query, cause);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
function _ensureTable() {
|
|
393
|
+
if (_classPrivateFieldGet2(_tableEnsured, this)) return;
|
|
394
|
+
_classPrivateFieldGet2(_storage, this).sql.exec(`
|
|
395
|
+
CREATE TABLE IF NOT EXISTS cf_agents_jobs (
|
|
396
|
+
id TEXT PRIMARY KEY NOT NULL,
|
|
397
|
+
capability TEXT NOT NULL,
|
|
398
|
+
fn TEXT NOT NULL,
|
|
399
|
+
time INTEGER NOT NULL,
|
|
400
|
+
payload TEXT,
|
|
401
|
+
retry_options TEXT,
|
|
402
|
+
singleflight INTEGER NOT NULL DEFAULT 0,
|
|
403
|
+
hung_timeout_seconds INTEGER,
|
|
404
|
+
exclusive INTEGER NOT NULL DEFAULT 0,
|
|
405
|
+
recovery_loop INTEGER NOT NULL DEFAULT 0,
|
|
406
|
+
running INTEGER NOT NULL DEFAULT 0,
|
|
407
|
+
execution_started_at INTEGER,
|
|
408
|
+
created_at INTEGER NOT NULL DEFAULT (unixepoch())
|
|
409
|
+
) WITHOUT ROWID`);
|
|
410
|
+
_classPrivateFieldSet2(_tableEnsured, this, true);
|
|
411
|
+
}
|
|
412
|
+
//#endregion
|
|
413
|
+
//#region src/lifecycle/job-driver.ts
|
|
414
|
+
/**
|
|
415
|
+
* Alarm event loop for the Lifecycle job queue.
|
|
416
|
+
*
|
|
417
|
+
* The driver owns everything that happens when the physical alarm fires:
|
|
418
|
+
* the deadman pre-arm, driving due jobs in due order (single-flight skip and
|
|
419
|
+
* hung recovery, per-job retries, platform-failure deferral, terminal
|
|
420
|
+
* failure hooks), the backlog warning, and the alarm memory-limit circuit
|
|
421
|
+
* breaker (#1825). Lifecycle wires it to the host and capabilities through
|
|
422
|
+
* the narrow {@link JobDriverOptions} contract and keeps only the alarm
|
|
423
|
+
* entry point itself.
|
|
424
|
+
*/
|
|
425
|
+
/** Default consecutive memory-limit strikes tolerated before sealing. */
|
|
426
|
+
const DEFAULT_MAX_ALARM_MEMORY_LIMIT_STRIKES = 3;
|
|
427
|
+
/** Durable storage key for the alarm memory-limit strike counter (#1825). */
|
|
428
|
+
const OOM_ALARM_STRIKES_KEY = "cf_agents:oom_alarm_strikes";
|
|
429
|
+
/** Default retry policy applied to jobs pushed without one. */
|
|
430
|
+
const DEFAULT_JOB_RETRY = {
|
|
431
|
+
maxAttempts: 3,
|
|
432
|
+
baseDelayMs: 100,
|
|
433
|
+
maxDelayMs: 3e3
|
|
434
|
+
};
|
|
435
|
+
/** Due jobs for one capability above this count log a backlog warning. */
|
|
436
|
+
const JOB_BACKLOG_WARNING_THRESHOLD = 10;
|
|
437
|
+
/**
|
|
438
|
+
* Deadman pre-arm delay: armed before the event loop drives due jobs so an
|
|
439
|
+
* isolate death mid-drive still wakes this object to resume its queue.
|
|
440
|
+
*/
|
|
441
|
+
const DEADMAN_ALARM_DELAY_MS = 3e4;
|
|
442
|
+
/**
|
|
443
|
+
* Carries the row a platform failure escaped from out to `runAlarm`'s catch,
|
|
444
|
+
* across the rethrow from `#driveJob`. Attribution must not live in a shared
|
|
445
|
+
* instance field: overlapping `alarm()` invocations (a deadman or the
|
|
446
|
+
* platform's own re-fire racing a still-running invocation) dispatch jobs
|
|
447
|
+
* concurrently, and a field would let a later dispatch overwrite the row an
|
|
448
|
+
* earlier one is still unwinding for.
|
|
449
|
+
*/
|
|
450
|
+
var AttributedPlatformFailure = class {
|
|
451
|
+
constructor(row, cause) {
|
|
452
|
+
this.row = row;
|
|
453
|
+
this.cause = cause;
|
|
454
|
+
}
|
|
455
|
+
};
|
|
456
|
+
var _options = /* @__PURE__ */ new WeakMap();
|
|
457
|
+
var _alarmScope = /* @__PURE__ */ new WeakMap();
|
|
458
|
+
var _alarmsInFlight = /* @__PURE__ */ new WeakMap();
|
|
459
|
+
var _outstandingAlarmWork = /* @__PURE__ */ new WeakMap();
|
|
460
|
+
var _strike = /* @__PURE__ */ new WeakMap();
|
|
461
|
+
var _strikeRecordedThisIsolate = /* @__PURE__ */ new WeakMap();
|
|
462
|
+
var _JobDriver_brand = /* @__PURE__ */ new WeakSet();
|
|
463
|
+
/** @internal Drives the job queue when the Durable Object alarm fires. */
|
|
464
|
+
var JobDriver = class {
|
|
465
|
+
constructor(options) {
|
|
466
|
+
_classPrivateMethodInitSpec(this, _JobDriver_brand);
|
|
467
|
+
_classPrivateFieldInitSpec(this, _options, void 0);
|
|
468
|
+
_classPrivateFieldInitSpec(this, _alarmScope, new AsyncLocalStorage());
|
|
469
|
+
_classPrivateFieldInitSpec(this, _alarmsInFlight, 0);
|
|
470
|
+
_classPrivateFieldInitSpec(this, _outstandingAlarmWork, /* @__PURE__ */ new Set());
|
|
471
|
+
_classPrivateFieldInitSpec(this, _strike, void 0);
|
|
472
|
+
_classPrivateFieldInitSpec(this, _strikeRecordedThisIsolate, false);
|
|
473
|
+
_classPrivateFieldSet2(_options, this, options);
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Run one alarm invocation: initialize the lifecycle, drive due jobs, run
|
|
477
|
+
* the host's alarm callback, and re-arm the physical alarm from queue
|
|
478
|
+
* state — all inside the alarm memory-limit circuit breaker (#1825). A
|
|
479
|
+
* memory-limit reset that propagates here is intercepted — every other
|
|
480
|
+
* error re-throws unchanged so platform alarm-retry semantics hold — and
|
|
481
|
+
* broken from this outermost frame, where the heavy turn has unwound and
|
|
482
|
+
* small writes can land. Initialization runs inside the breaker because a
|
|
483
|
+
* severe reset can be thrown before any job runs (boot hydration, #1825);
|
|
484
|
+
* left unhandled it would re-throw to the platform, which auto-retries
|
|
485
|
+
* the alarm forever.
|
|
486
|
+
*/
|
|
487
|
+
async runAlarm(initialize, runHostAlarm) {
|
|
488
|
+
var _this$alarmsInFlight;
|
|
489
|
+
_classPrivateFieldSet2(_alarmsInFlight, this, (_this$alarmsInFlight = _classPrivateFieldGet2(_alarmsInFlight, this), _this$alarmsInFlight++, _this$alarmsInFlight));
|
|
490
|
+
let clean = false;
|
|
491
|
+
try {
|
|
492
|
+
try {
|
|
493
|
+
await initialize();
|
|
494
|
+
await _assertClassBrand(_JobDriver_brand, this, _driveDueJobs).call(this);
|
|
495
|
+
await _classPrivateFieldGet2(_alarmScope, this).run({ executing: void 0 }, runHostAlarm);
|
|
496
|
+
clean = true;
|
|
497
|
+
} catch (error) {
|
|
498
|
+
const attributed = error instanceof AttributedPlatformFailure;
|
|
499
|
+
const cause = attributed ? error.cause : error;
|
|
500
|
+
if (!isDurableObjectMemoryLimitReset(cause)) throw attributed ? cause : error;
|
|
501
|
+
await _assertClassBrand(_JobDriver_brand, this, _handleMemoryLimitReset).call(this, cause, attributed ? error.row : void 0);
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
} finally {
|
|
505
|
+
var _this$alarmsInFlight3;
|
|
506
|
+
_classPrivateFieldSet2(_alarmsInFlight, this, (_this$alarmsInFlight3 = _classPrivateFieldGet2(_alarmsInFlight, this), _this$alarmsInFlight3--, _this$alarmsInFlight3));
|
|
507
|
+
if (clean) await _assertClassBrand(_JobDriver_brand, this, _clearMemoryLimitStrikesWhenQuiescent).call(this);
|
|
508
|
+
}
|
|
509
|
+
await _classPrivateFieldGet2(_options, this).rearm();
|
|
510
|
+
}
|
|
511
|
+
/**
|
|
512
|
+
* Keep work a job handed off at a bounded return inside this alarm's
|
|
513
|
+
* memory-limit breaker domain (#1825). The alarm itself returns promptly,
|
|
514
|
+
* so other jobs stay live; the handoff is classified when it settles. A
|
|
515
|
+
* memory-limit reset it reports records a strike against the job that
|
|
516
|
+
* handed it off, exactly as an in-alarm reset would. Strikes clear only
|
|
517
|
+
* once no handed-off work is outstanding and the last of it settled clean.
|
|
518
|
+
*
|
|
519
|
+
* @returns True when called from an alarm-driven dispatch or the host
|
|
520
|
+
* alarm hook; false otherwise, in which case nothing is tracked. Tracking
|
|
521
|
+
* the same promise again (a claim-backstop wake for an attempt already
|
|
522
|
+
* handed off) is a no-op.
|
|
523
|
+
*/
|
|
524
|
+
trackAlarmWork(work) {
|
|
525
|
+
const scope = _classPrivateFieldGet2(_alarmScope, this).getStore();
|
|
526
|
+
if (!scope) return false;
|
|
527
|
+
if (_classPrivateFieldGet2(_outstandingAlarmWork, this).has(work)) return true;
|
|
528
|
+
_classPrivateFieldGet2(_outstandingAlarmWork, this).add(work);
|
|
529
|
+
work.then(() => _assertClassBrand(_JobDriver_brand, this, _settleAlarmWork).call(this, work, void 0, scope.executing), (error) => _assertClassBrand(_JobDriver_brand, this, _settleAlarmWork).call(this, work, error, scope.executing));
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
async function _settleAlarmWork(work, error, executing) {
|
|
534
|
+
try {
|
|
535
|
+
if (_classPrivateFieldGet2(_options, this).disabled()) return;
|
|
536
|
+
if (isDurableObjectMemoryLimitReset(error)) {
|
|
537
|
+
await _assertClassBrand(_JobDriver_brand, this, _handleMemoryLimitReset).call(this, error, executing);
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
} finally {
|
|
541
|
+
_classPrivateFieldGet2(_outstandingAlarmWork, this).delete(work);
|
|
542
|
+
}
|
|
543
|
+
await _assertClassBrand(_JobDriver_brand, this, _clearMemoryLimitStrikesWhenQuiescent).call(this);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Clear the strike counter once the alarm domain is fully quiescent: no
|
|
547
|
+
* alarm invocation in flight, no handed-off work outstanding, and no
|
|
548
|
+
* strike recorded in this isolate that a fresh isolate hasn't yet
|
|
549
|
+
* superseded. Called at every transition that could make either counter
|
|
550
|
+
* newly zero (an alarm ending, a handoff settling) — each call re-reads
|
|
551
|
+
* both fresh, so whichever transition happens last is the one that
|
|
552
|
+
* performs the clear. Idempotent: redundant calls no-op.
|
|
553
|
+
*/
|
|
554
|
+
async function _clearMemoryLimitStrikesWhenQuiescent() {
|
|
555
|
+
if (_classPrivateFieldGet2(_alarmsInFlight, this) > 0) return;
|
|
556
|
+
if (_classPrivateFieldGet2(_outstandingAlarmWork, this).size > 0) return;
|
|
557
|
+
if (_classPrivateFieldGet2(_strikeRecordedThisIsolate, this)) return;
|
|
558
|
+
_classPrivateFieldSet2(_strike, this, void 0);
|
|
559
|
+
await _assertClassBrand(_JobDriver_brand, this, _clearMemoryLimitStrikes).call(this);
|
|
560
|
+
}
|
|
561
|
+
/** Drive every due job once, in due-time order. */
|
|
562
|
+
async function _driveDueJobs() {
|
|
563
|
+
const nowMs = Date.now();
|
|
564
|
+
const due = _classPrivateFieldGet2(_options, this).queue.due(nowMs);
|
|
565
|
+
if (due.length === 0) return;
|
|
566
|
+
_assertClassBrand(_JobDriver_brand, this, _warnBacklog).call(this, due);
|
|
567
|
+
if (!_classPrivateFieldGet2(_options, this).disabled()) await _classPrivateFieldGet2(_options, this).storage.setAlarm(nowMs + DEADMAN_ALARM_DELAY_MS);
|
|
568
|
+
for (const stale of due) {
|
|
569
|
+
if (_classPrivateFieldGet2(_options, this).disabled()) return;
|
|
570
|
+
const row = _classPrivateFieldGet2(_options, this).queue.dueRow(stale.id, nowMs);
|
|
571
|
+
if (!row) continue;
|
|
572
|
+
if (row.singleflight === 1 && row.running === 1) {
|
|
573
|
+
if (!isHungRow(row, nowMs)) {
|
|
574
|
+
console.warn(`Skipping job ${row.id}: previous execution still running`);
|
|
575
|
+
continue;
|
|
576
|
+
}
|
|
577
|
+
console.warn(`Forcing reset of hung job ${row.id} (started ${Math.round((nowMs - (row.execution_started_at ?? 0)) / 1e3)}s ago)`);
|
|
578
|
+
}
|
|
579
|
+
_classPrivateFieldGet2(_options, this).queue.markRunning(row.id, nowMs);
|
|
580
|
+
await _assertClassBrand(_JobDriver_brand, this, _driveJob).call(this, row);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
/** Dispatch one due row to its owner with retry and failure policy. */
|
|
584
|
+
async function _driveJob(row) {
|
|
585
|
+
const { queue, resolveDispatch, disabled } = _classPrivateFieldGet2(_options, this);
|
|
586
|
+
const job = jobFromRow(row);
|
|
587
|
+
const dispatch = await resolveDispatch(row.capability);
|
|
588
|
+
if (!dispatch) {
|
|
589
|
+
console.error(`No installed capability or host handler for job ${row.id} (owner ${JSON.stringify(row.capability)}); dropping it`);
|
|
590
|
+
queue.delete(row.id);
|
|
591
|
+
return;
|
|
592
|
+
}
|
|
593
|
+
const maxAttempts = job.retry?.maxAttempts ?? DEFAULT_JOB_RETRY.maxAttempts;
|
|
594
|
+
const slowWatchdog = setTimeout(() => {
|
|
595
|
+
const seconds = Math.round(hungTimeoutMs(row) / 1e3);
|
|
596
|
+
console.warn(`Job ${row.id} (${row.capability}/${row.fn}) has been dispatching for over ${seconds}s. Long dispatches starve every other job on this object; onJob must detach unbounded work and return.`);
|
|
597
|
+
try {
|
|
598
|
+
_classPrivateFieldGet2(_options, this).emit("job:slow_dispatch", {
|
|
599
|
+
capability: row.capability,
|
|
600
|
+
fn: row.fn,
|
|
601
|
+
id: row.id,
|
|
602
|
+
thresholdMs: hungTimeoutMs(row)
|
|
603
|
+
});
|
|
604
|
+
} catch {}
|
|
605
|
+
}, hungTimeoutMs(row));
|
|
606
|
+
let outcome;
|
|
607
|
+
try {
|
|
608
|
+
outcome = await _classPrivateFieldGet2(_alarmScope, this).run({ executing: row }, () => tryN(maxAttempts, (attempt) => dispatch.onJob({
|
|
609
|
+
job,
|
|
610
|
+
attempt
|
|
611
|
+
}), {
|
|
612
|
+
baseDelayMs: job.retry?.baseDelayMs ?? DEFAULT_JOB_RETRY.baseDelayMs,
|
|
613
|
+
maxDelayMs: job.retry?.maxDelayMs ?? DEFAULT_JOB_RETRY.maxDelayMs,
|
|
614
|
+
shouldRetry: (error) => !isDurableObjectCodeUpdateReset(error) && !isDurableObjectMemoryLimitReset(error)
|
|
615
|
+
}));
|
|
616
|
+
} catch (error) {
|
|
617
|
+
if (disabled()) return;
|
|
618
|
+
if (isPlatformFailure(error)) {
|
|
619
|
+
try {
|
|
620
|
+
queue.clearRunning(row.id);
|
|
621
|
+
} catch {}
|
|
622
|
+
console.warn(`Deferring job ${row.id} to a fresh invocation after a platform failure; the job is preserved.`);
|
|
623
|
+
throw new AttributedPlatformFailure(row, error);
|
|
624
|
+
}
|
|
625
|
+
try {
|
|
626
|
+
outcome = await dispatch.onJobError?.({
|
|
627
|
+
job,
|
|
628
|
+
attempt: maxAttempts
|
|
629
|
+
}, error);
|
|
630
|
+
} catch (hookError) {
|
|
631
|
+
console.error(`Job failure hook threw for ${row.id}`, hookError);
|
|
632
|
+
}
|
|
633
|
+
} finally {
|
|
634
|
+
clearTimeout(slowWatchdog);
|
|
635
|
+
}
|
|
636
|
+
if (disabled()) return;
|
|
637
|
+
queue.applyOutcome(row.id, outcome ?? void 0);
|
|
638
|
+
}
|
|
639
|
+
function _warnBacklog(due) {
|
|
640
|
+
const counts = /* @__PURE__ */ new Map();
|
|
641
|
+
for (const row of due) counts.set(row.capability, (counts.get(row.capability) ?? 0) + 1);
|
|
642
|
+
for (const [owner, count] of counts) {
|
|
643
|
+
if (count < JOB_BACKLOG_WARNING_THRESHOLD) continue;
|
|
644
|
+
try {
|
|
645
|
+
console.warn(`Processing ${count} due jobs for ${JSON.stringify(owner)} in a single alarm cycle. This usually means one-shot jobs are pushed repeatedly without a stable id.`);
|
|
646
|
+
_classPrivateFieldGet2(_options, this).emit("job:backlog_warning", {
|
|
647
|
+
capability: owner,
|
|
648
|
+
count
|
|
649
|
+
});
|
|
650
|
+
} catch {}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Clear the durable memory-limit strike counter after a clean alarm so the
|
|
655
|
+
* breaker counts CONSECUTIVE resets rather than lifetime ones (#1825).
|
|
656
|
+
* Reads first and only writes when a strike is recorded. Best-effort.
|
|
657
|
+
*/
|
|
658
|
+
async function _clearMemoryLimitStrikes() {
|
|
659
|
+
const { storage } = _classPrivateFieldGet2(_options, this);
|
|
660
|
+
try {
|
|
661
|
+
const prior = await storage.get(OOM_ALARM_STRIKES_KEY);
|
|
662
|
+
if (typeof prior === "number" && prior > 0) await storage.delete(OOM_ALARM_STRIKES_KEY);
|
|
663
|
+
} catch {}
|
|
664
|
+
}
|
|
665
|
+
/**
|
|
666
|
+
* Alarm-boundary circuit breaker for Durable Object memory-limit resets
|
|
667
|
+
* (#1825). Unhandled, the platform would auto-retry the alarm forever,
|
|
668
|
+
* re-running the doomed work each cycle. A durable strike counter
|
|
669
|
+
* tolerates a few consecutive resets — backing off the executing job and
|
|
670
|
+
* every pending recovery-loop job so the retry is not a hot loop — then
|
|
671
|
+
* seals: those jobs are purged and the capability + host memory-limit
|
|
672
|
+
* policy hooks run.
|
|
673
|
+
*
|
|
674
|
+
* One reset is one event even when several flows observe it. The strike
|
|
675
|
+
* is recorded once per event ({@link #recordMemoryLimitStrike}); every
|
|
676
|
+
* observer then applies the per-job policy for the job it belongs to, and
|
|
677
|
+
* the first observer finishes the event by re-arming, syncing, and
|
|
678
|
+
* resetting the isolate. Each step is best-effort: even these small writes
|
|
679
|
+
* can OOM, but swallowing still halts the platform's auto-retry, and a
|
|
680
|
+
* later wake re-arms legitimate work.
|
|
681
|
+
*/
|
|
682
|
+
async function _handleMemoryLimitReset(error, executing) {
|
|
683
|
+
const { queue } = _classPrivateFieldGet2(_options, this);
|
|
684
|
+
const first = _classPrivateFieldGet2(_strike, this) === void 0;
|
|
685
|
+
_classPrivateFieldSet2(_strikeRecordedThisIsolate, this, true);
|
|
686
|
+
_classPrivateFieldGet2(_strike, this) ?? _classPrivateFieldSet2(_strike, this, _assertClassBrand(_JobDriver_brand, this, _recordMemoryLimitStrike).call(this, error));
|
|
687
|
+
const strike = await _classPrivateFieldGet2(_strike, this);
|
|
688
|
+
try {
|
|
689
|
+
if (executing) {
|
|
690
|
+
if (strike.sealed) queue.delete(executing.id);
|
|
691
|
+
else if (strike.nextTime !== void 0) queue.retime(executing.id, strike.nextTime);
|
|
692
|
+
}
|
|
693
|
+
} catch {}
|
|
694
|
+
try {
|
|
695
|
+
await _classPrivateFieldGet2(_options, this).onMemoryLimit({
|
|
696
|
+
sealed: strike.sealed,
|
|
697
|
+
nextTime: strike.nextTime,
|
|
698
|
+
executing: executing ? jobFromRow(executing) : void 0,
|
|
699
|
+
purgedRecoveryLoopJobs: first ? strike.purgedRecoveryLoopJobs : void 0
|
|
700
|
+
});
|
|
701
|
+
} catch {}
|
|
702
|
+
if (!first) return;
|
|
703
|
+
try {
|
|
704
|
+
await _classPrivateFieldGet2(_options, this).rearm();
|
|
705
|
+
} catch {}
|
|
706
|
+
try {
|
|
707
|
+
await _classPrivateFieldGet2(_options, this).storage.sync();
|
|
708
|
+
_classPrivateFieldGet2(_options, this).reset(`Alarm memory-limit strike ${strike.strikes}/${strike.limit}${strike.sealed ? " (sealed)" : ""}; resetting isolate (#1825)`);
|
|
709
|
+
} catch {}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Record one strike durably and apply the queue-wide policy that belongs
|
|
713
|
+
* to the event rather than to any one job: recovery-loop rows back off (or
|
|
714
|
+
* purge) as a pack, a sealing strike resets the counter, and the event is
|
|
715
|
+
* emitted once.
|
|
716
|
+
*/
|
|
717
|
+
async function _recordMemoryLimitStrike(error) {
|
|
718
|
+
const { queue, storage } = _classPrivateFieldGet2(_options, this);
|
|
719
|
+
let strikes = 1;
|
|
720
|
+
try {
|
|
721
|
+
const prior = await storage.get(OOM_ALARM_STRIKES_KEY);
|
|
722
|
+
strikes = (typeof prior === "number" ? prior : 0) + 1;
|
|
723
|
+
await storage.put(OOM_ALARM_STRIKES_KEY, strikes);
|
|
724
|
+
} catch {}
|
|
725
|
+
const limit = _classPrivateFieldGet2(_options, this).maxMemoryLimitStrikes() ?? DEFAULT_MAX_ALARM_MEMORY_LIMIT_STRIKES;
|
|
726
|
+
const sealed = strikes >= limit;
|
|
727
|
+
console.error(`Alarm hit a Durable Object memory-limit reset (strike ${strikes}/${limit}${sealed ? ", sealing recovery" : ", will retry with backoff"}). Breaking the platform alarm-retry loop (#1825).`, error instanceof Error ? error.message : String(error));
|
|
728
|
+
const nextTime = sealed ? void 0 : Date.now() + Math.min(300, 30 * strikes) * 1e3;
|
|
729
|
+
let purgedRecoveryLoopJobs;
|
|
730
|
+
try {
|
|
731
|
+
if (sealed) purgedRecoveryLoopJobs = queue.recoveryLoopJobs();
|
|
732
|
+
} catch {}
|
|
733
|
+
try {
|
|
734
|
+
if (sealed) queue.purgeRecoveryLoopJobs();
|
|
735
|
+
else if (nextTime !== void 0) queue.delayRecoveryLoopJobs(nextTime);
|
|
736
|
+
} catch {}
|
|
737
|
+
if (sealed) try {
|
|
738
|
+
await storage.delete(OOM_ALARM_STRIKES_KEY);
|
|
739
|
+
} catch {}
|
|
740
|
+
try {
|
|
741
|
+
_classPrivateFieldGet2(_options, this).emit("alarm:memory_limit_reset", {
|
|
742
|
+
strikes,
|
|
743
|
+
limit,
|
|
744
|
+
sealed,
|
|
745
|
+
error: error instanceof Error ? error.message : String(error)
|
|
746
|
+
});
|
|
747
|
+
} catch {}
|
|
748
|
+
return {
|
|
749
|
+
strikes,
|
|
750
|
+
limit,
|
|
751
|
+
sealed,
|
|
752
|
+
nextTime,
|
|
753
|
+
purgedRecoveryLoopJobs
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
//#endregion
|
|
757
|
+
//#region src/lifecycle/transport-errors.ts
|
|
758
|
+
/** Standard `WebSocket.readyState` values. */
|
|
759
|
+
const CLOSING = 2;
|
|
760
|
+
const CLOSED = 3;
|
|
761
|
+
/**
|
|
762
|
+
* A retryable transport-teardown error ("Network connection lost" /
|
|
763
|
+
* "WebSocket peer disconnected") that fires on a connection which is already
|
|
764
|
+
* CLOSING/CLOSED is just the socket going away during or right after the close
|
|
765
|
+
* handshake - not an application error. Surfacing it via `onError` spams logs
|
|
766
|
+
* on every abrupt client disconnect, and even on clean closes when the peer
|
|
767
|
+
* tears down its transport before our reciprocal Close frame lands. Suppress
|
|
768
|
+
* it in that specific case only; genuine mid-connection (OPEN) errors still
|
|
769
|
+
* reach `onError`.
|
|
770
|
+
*
|
|
771
|
+
* Detection prefers the structured `retryable` flag over message text so it
|
|
772
|
+
* stays correct across `enhanced-error-serialization` (compat date
|
|
773
|
+
* >= 2026-04-21), with a substring fallback for older error shapes.
|
|
774
|
+
*/
|
|
775
|
+
function isBenignTeardownError(ws, error) {
|
|
776
|
+
const state = ws.readyState;
|
|
777
|
+
if (state !== CLOSING && state !== CLOSED) return false;
|
|
778
|
+
if (typeof error !== "object" || error === null) return false;
|
|
779
|
+
const typed = error;
|
|
780
|
+
if (typed.retryable === true) return true;
|
|
781
|
+
const message = typeof typed.message === "string" ? typed.message : "";
|
|
782
|
+
return /Network connection lost|WebSocket peer disconnected/i.test(message);
|
|
783
|
+
}
|
|
784
|
+
//#endregion
|
|
785
|
+
//#region src/lifecycle/durable-object-lifecycle.ts
|
|
786
|
+
const LEGACY_NAME_STORAGE_KEY = "__ps_name";
|
|
787
|
+
function mutableRequest(request) {
|
|
788
|
+
return new Request(request);
|
|
789
|
+
}
|
|
790
|
+
/**
|
|
791
|
+
* Decode props from the internal lifecycle props header.
|
|
792
|
+
*
|
|
793
|
+
* Handles both base64-encoded lifecycle props and, for
|
|
794
|
+
* backwards compatibility with stubs/requests created by older versions,
|
|
795
|
+
* raw JSON. Base64 never starts with `{` or `[`, so a leading brace/bracket
|
|
796
|
+
* unambiguously identifies the legacy raw-JSON form.
|
|
797
|
+
*/
|
|
798
|
+
function decodeProps(header) {
|
|
799
|
+
const trimmed = header.trim();
|
|
800
|
+
if (trimmed.startsWith("{") || trimmed.startsWith("[")) return JSON.parse(trimmed);
|
|
801
|
+
const binary = atob(header);
|
|
802
|
+
const bytes = new Uint8Array(binary.length);
|
|
803
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
804
|
+
return JSON.parse(new TextDecoder().decode(bytes));
|
|
805
|
+
}
|
|
806
|
+
const lifecycleEventSinks = /* @__PURE__ */ new WeakMap();
|
|
807
|
+
const lifecycleRouteTransports = /* @__PURE__ */ new WeakMap();
|
|
808
|
+
const lifecycleHostInvokers = /* @__PURE__ */ new WeakMap();
|
|
809
|
+
/** @internal Adapt the host invocation boundary at a composition root. */
|
|
810
|
+
function setLifecycleHostInvoker(lifecycle, invoker) {
|
|
811
|
+
lifecycleHostInvokers.set(lifecycle, invoker);
|
|
812
|
+
}
|
|
813
|
+
/** @internal Supply a host's routed Lifecycle transport. */
|
|
814
|
+
function setLifecycleRouteTransport(lifecycle, transport) {
|
|
815
|
+
lifecycleRouteTransports.set(lifecycle, transport);
|
|
816
|
+
}
|
|
817
|
+
/** @internal Adapt Lifecycle's default diagnostics sink at a composition root. */
|
|
818
|
+
function setLifecycleEventSink(lifecycle, sink) {
|
|
819
|
+
lifecycleEventSinks.set(lifecycle, sink);
|
|
820
|
+
}
|
|
821
|
+
var _host = /* @__PURE__ */ new WeakMap();
|
|
822
|
+
var _ctx = /* @__PURE__ */ new WeakMap();
|
|
823
|
+
var _parentClassName = /* @__PURE__ */ new WeakMap();
|
|
824
|
+
var _capabilities = /* @__PURE__ */ new WeakMap();
|
|
825
|
+
var _capabilityRunner = /* @__PURE__ */ new WeakMap();
|
|
826
|
+
var _jobQueue = /* @__PURE__ */ new WeakMap();
|
|
827
|
+
var _jobDriver = /* @__PURE__ */ new WeakMap();
|
|
828
|
+
var _status = /* @__PURE__ */ new WeakMap();
|
|
829
|
+
var _alarmRearmQueue = /* @__PURE__ */ new WeakMap();
|
|
830
|
+
var _rearmRequestedDuringStart = /* @__PURE__ */ new WeakMap();
|
|
831
|
+
var _pendingEvents = /* @__PURE__ */ new WeakMap();
|
|
832
|
+
var _alarmsDisabled = /* @__PURE__ */ new WeakMap();
|
|
833
|
+
var _capabilitiesLocked = /* @__PURE__ */ new WeakMap();
|
|
834
|
+
var _fallbacks = /* @__PURE__ */ new WeakMap();
|
|
835
|
+
var _handlersInstalled = /* @__PURE__ */ new WeakMap();
|
|
836
|
+
var _Lifecycle_brand = /* @__PURE__ */ new WeakSet();
|
|
837
|
+
var _legacyName = /* @__PURE__ */ new WeakMap();
|
|
838
|
+
var _props = /* @__PURE__ */ new WeakMap();
|
|
839
|
+
/**
|
|
840
|
+
* Installs and coordinates the runtime lifecycle for a Durable Object.
|
|
841
|
+
*
|
|
842
|
+
* Construct this as an instance field on a class that directly extends
|
|
843
|
+
* `DurableObject`, then call {@link Lifecycle.installHandlers}
|
|
844
|
+
* from that class's constructor.
|
|
845
|
+
*
|
|
846
|
+
* @experimental The API surface may change before stabilizing.
|
|
847
|
+
*/
|
|
848
|
+
var Lifecycle = class Lifecycle {
|
|
849
|
+
/**
|
|
850
|
+
* Construct and install a lifecycle in one explicit operation.
|
|
851
|
+
*
|
|
852
|
+
* @param host - The Durable Object whose runtime handlers the lifecycle owns.
|
|
853
|
+
* @returns The installed lifecycle.
|
|
854
|
+
*/
|
|
855
|
+
static install(host, options) {
|
|
856
|
+
const lifecycle = new Lifecycle(host, options);
|
|
857
|
+
lifecycle.installHandlers();
|
|
858
|
+
return lifecycle;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Bind a lifecycle to a Durable Object instance without mutating its handlers.
|
|
862
|
+
*
|
|
863
|
+
* @param host - The Durable Object whose runtime lifecycle this object owns.
|
|
864
|
+
* @param options - Policy configuration for this lifecycle.
|
|
865
|
+
*/
|
|
866
|
+
constructor(host, options) {
|
|
867
|
+
_classPrivateMethodInitSpec(this, _Lifecycle_brand);
|
|
868
|
+
_classPrivateFieldInitSpec(this, _host, void 0);
|
|
869
|
+
_classPrivateFieldInitSpec(this, _ctx, void 0);
|
|
870
|
+
_classPrivateFieldInitSpec(this, _parentClassName, void 0);
|
|
871
|
+
_classPrivateFieldInitSpec(this, _capabilities, []);
|
|
872
|
+
_classPrivateFieldInitSpec(this, _capabilityRunner, new CapabilityRunner(() => _classPrivateFieldGet2(_capabilities, this)));
|
|
873
|
+
_classPrivateFieldInitSpec(this, _jobQueue, void 0);
|
|
874
|
+
_classPrivateFieldInitSpec(this, _jobDriver, void 0);
|
|
875
|
+
_classPrivateFieldInitSpec(this, _status, "zero");
|
|
876
|
+
_classPrivateFieldInitSpec(this, _alarmRearmQueue, Promise.resolve());
|
|
877
|
+
_classPrivateFieldInitSpec(this, _rearmRequestedDuringStart, false);
|
|
878
|
+
_classPrivateFieldInitSpec(this, _pendingEvents, []);
|
|
879
|
+
_classPrivateFieldInitSpec(this, _alarmsDisabled, false);
|
|
880
|
+
_classPrivateFieldInitSpec(this, _capabilitiesLocked, false);
|
|
881
|
+
_classPrivateFieldInitSpec(this, _fallbacks, /* @__PURE__ */ new Set());
|
|
882
|
+
_classPrivateFieldInitSpec(this, _handlersInstalled, false);
|
|
883
|
+
_classPrivateFieldInitSpec(this, _legacyName, void 0);
|
|
884
|
+
_classPrivateFieldInitSpec(this, _props, void 0);
|
|
885
|
+
_classPrivateFieldSet2(_host, this, host);
|
|
886
|
+
_classPrivateFieldSet2(_ctx, this, _classPrivateFieldGet2(_host, this).ctx);
|
|
887
|
+
_classPrivateFieldSet2(_parentClassName, this, _classPrivateFieldGet2(_host, this).constructor.name);
|
|
888
|
+
_classPrivateFieldSet2(_jobQueue, this, new JobQueue(_classPrivateFieldGet2(_ctx, this).storage));
|
|
889
|
+
_classPrivateFieldSet2(_jobDriver, this, new JobDriver({
|
|
890
|
+
queue: _classPrivateFieldGet2(_jobQueue, this),
|
|
891
|
+
storage: _classPrivateFieldGet2(_ctx, this).storage,
|
|
892
|
+
disabled: () => _classPrivateFieldGet2(_alarmsDisabled, this),
|
|
893
|
+
resolveDispatch: (owner) => _assertClassBrand(_Lifecycle_brand, this, _resolveJobDispatch).call(this, owner),
|
|
894
|
+
maxMemoryLimitStrikes: () => options?.maxAlarmMemoryLimitStrikes,
|
|
895
|
+
onMemoryLimit: async (context) => {
|
|
896
|
+
await _classPrivateFieldGet2(_capabilityRunner, this).memoryLimit(context);
|
|
897
|
+
await runInLifecycleHostContext({ host: _classPrivateFieldGet2(_host, this) }, () => _classPrivateFieldGet2(_host, this).onAlarmMemoryLimit?.(context));
|
|
898
|
+
},
|
|
899
|
+
emit: (type, payload) => _assertClassBrand(_Lifecycle_brand, this, _emitCapabilityEvent).call(this, {
|
|
900
|
+
source: "lifecycle",
|
|
901
|
+
type,
|
|
902
|
+
payload
|
|
903
|
+
}),
|
|
904
|
+
rearm: () => this.rearmAlarm(),
|
|
905
|
+
reset: (reason) => {
|
|
906
|
+
setTimeout(() => abortWithoutAlarmRetry(_classPrivateFieldGet2(_ctx, this), reason), 0);
|
|
907
|
+
}
|
|
908
|
+
}));
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Install platform fetch, alarm, and hibernating WebSocket handlers.
|
|
912
|
+
*
|
|
913
|
+
* Existing handlers are preserved for framework-owned dispatch such as the
|
|
914
|
+
* Agent's sub-agent router and alarm circuit breaker. Calling this method
|
|
915
|
+
* more than once is an error.
|
|
916
|
+
*/
|
|
917
|
+
installHandlers() {
|
|
918
|
+
if (_classPrivateFieldGet2(_handlersInstalled, this)) throw new Error("Durable Object lifecycle handlers are already installed");
|
|
919
|
+
_classPrivateFieldSet2(_handlersInstalled, this, true);
|
|
920
|
+
const handlers = {
|
|
921
|
+
fetch: this.fetch.bind(this),
|
|
922
|
+
alarm: this.alarm.bind(this),
|
|
923
|
+
webSocketMessage: this.webSocketMessage.bind(this),
|
|
924
|
+
webSocketClose: this.webSocketClose.bind(this),
|
|
925
|
+
webSocketError: this.webSocketError.bind(this)
|
|
926
|
+
};
|
|
927
|
+
for (const [name, handler] of Object.entries(handlers)) {
|
|
928
|
+
if (name in _classPrivateFieldGet2(_host, this)) continue;
|
|
929
|
+
Object.defineProperty(_classPrivateFieldGet2(_host, this), name, {
|
|
930
|
+
value: handler,
|
|
931
|
+
configurable: true
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Add a reusable capability before this lifecycle starts.
|
|
937
|
+
*
|
|
938
|
+
* Capabilities dispatch in registration order, except that fallbacks
|
|
939
|
+
* always come after non-fallbacks.
|
|
940
|
+
*
|
|
941
|
+
* @param capability - The capability to add.
|
|
942
|
+
* @param options - Dispatch placement.
|
|
943
|
+
* @returns This lifecycle.
|
|
944
|
+
*/
|
|
945
|
+
use(capability, options) {
|
|
946
|
+
if (_classPrivateFieldGet2(_capabilitiesLocked, this)) throw new Error("Lifecycle capabilities must be added before startup");
|
|
947
|
+
const capabilityId = lifecycleCapabilityId(capability);
|
|
948
|
+
if (capabilityId && _classPrivateFieldGet2(_capabilities, this).some((candidate) => lifecycleCapabilityId(candidate) === capabilityId)) throw new Error(`Lifecycle capability ${JSON.stringify(capabilityId)} is already installed`);
|
|
949
|
+
const firstFallback = _classPrivateFieldGet2(_capabilities, this).findIndex((candidate) => _classPrivateFieldGet2(_fallbacks, this).has(candidate));
|
|
950
|
+
if (options?.fallback) _classPrivateFieldGet2(_fallbacks, this).add(capability);
|
|
951
|
+
_classPrivateFieldGet2(_capabilities, this).splice(options?.fallback || firstFallback === -1 ? _classPrivateFieldGet2(_capabilities, this).length : firstFallback, 0, capability);
|
|
952
|
+
if (capability instanceof LifecycleCapability) bindLifecycleCapability(capability, _assertClassBrand(_Lifecycle_brand, this, _servicesForCapability).call(this, capability.capabilityId));
|
|
953
|
+
return this;
|
|
954
|
+
}
|
|
955
|
+
/** @internal Deliver a generic capability envelope to this Lifecycle. */
|
|
956
|
+
route(envelope) {
|
|
957
|
+
return _assertClassBrand(_Lifecycle_brand, this, _dispatchRoute).call(this, envelope);
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Execute SQL queries against the Durable Object's database
|
|
961
|
+
* @template T Type of the returned rows
|
|
962
|
+
* @param strings SQL query template strings
|
|
963
|
+
* @param values Values to be inserted into the query
|
|
964
|
+
* @returns Array of query results
|
|
965
|
+
*/
|
|
966
|
+
sql(strings, ...values) {
|
|
967
|
+
let query = "";
|
|
968
|
+
try {
|
|
969
|
+
query = strings.reduce((acc, str, i) => acc + str + (i < values.length ? "?" : ""), "");
|
|
970
|
+
return [..._classPrivateFieldGet2(_ctx, this).storage.sql.exec(query, ...values)];
|
|
971
|
+
} catch (error) {
|
|
972
|
+
console.error(`failed to execute sql query: ${query}`, error);
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Handle an incoming request for the owning Durable Object.
|
|
978
|
+
*
|
|
979
|
+
* Non-upgrade requests run through the capability middleware chain first,
|
|
980
|
+
* then fall through to the host's `onRequest`.
|
|
981
|
+
*/
|
|
982
|
+
async fetch(request) {
|
|
983
|
+
try {
|
|
984
|
+
const encodedProps = request.headers.get("x-agents-lifecycle-props");
|
|
985
|
+
if (encodedProps) {
|
|
986
|
+
_classPrivateFieldSet2(_props, this, decodeProps(encodedProps));
|
|
987
|
+
request = mutableRequest(request);
|
|
988
|
+
request.headers.delete("x-agents-lifecycle-props");
|
|
989
|
+
}
|
|
990
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
991
|
+
if (request.headers.get("Upgrade")?.toLowerCase() !== "websocket") {
|
|
992
|
+
const capabilityResponse = await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).request({ request }));
|
|
993
|
+
if (capabilityResponse !== void 0) return capabilityResponse;
|
|
994
|
+
if (_classPrivateFieldGet2(_host, this).onRequest) return await runInLifecycleHostContext({
|
|
995
|
+
host: _classPrivateFieldGet2(_host, this),
|
|
996
|
+
request
|
|
997
|
+
}, () => _classPrivateFieldGet2(_host, this).onRequest(request));
|
|
998
|
+
return new Response("Not implemented", { status: 404 });
|
|
999
|
+
} else {
|
|
1000
|
+
const upgradeResponse = await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).webSocketUpgrade({ request }));
|
|
1001
|
+
if (upgradeResponse !== void 0) return upgradeResponse;
|
|
1002
|
+
return new Response("WebSocket upgrades are not enabled on this Durable Object. Install a capability that claims them (e.g. WebSockets).", { status: 404 });
|
|
1003
|
+
}
|
|
1004
|
+
} catch (err) {
|
|
1005
|
+
console.error(`Error in ${_classPrivateFieldGet2(_parentClassName, this)}:${_classPrivateFieldGet2(_ctx, this).id.name ?? "<unnamed>"} fetch:`, err);
|
|
1006
|
+
if (!(err instanceof Error)) throw err;
|
|
1007
|
+
if (request.headers.get("Upgrade") === "websocket") {
|
|
1008
|
+
const pair = new WebSocketPair();
|
|
1009
|
+
pair[1].accept();
|
|
1010
|
+
pair[1].send(JSON.stringify({ error: err.stack }));
|
|
1011
|
+
pair[1].close(1011, "Uncaught exception during session setup");
|
|
1012
|
+
return new Response(null, {
|
|
1013
|
+
status: 101,
|
|
1014
|
+
webSocket: pair[0]
|
|
1015
|
+
});
|
|
1016
|
+
} else return new Response(err.stack, { status: 500 });
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
/** @internal Dispatch a hibernating WebSocket message. */
|
|
1020
|
+
async webSocketMessage(ws, message) {
|
|
1021
|
+
try {
|
|
1022
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
1023
|
+
await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).webSocketMessage(ws, message));
|
|
1024
|
+
} catch (e) {
|
|
1025
|
+
console.error(`Error in ${_classPrivateFieldGet2(_parentClassName, this)}:${_classPrivateFieldGet2(_ctx, this).id.name ?? "<unnamed>"} webSocketMessage:`, e);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
/** @internal Dispatch a hibernating WebSocket close. */
|
|
1029
|
+
async webSocketClose(ws, code, reason, wasClean) {
|
|
1030
|
+
try {
|
|
1031
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
1032
|
+
await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).webSocketClose(ws, code, reason, wasClean));
|
|
1033
|
+
} catch (e) {
|
|
1034
|
+
console.error(`Error in ${_classPrivateFieldGet2(_parentClassName, this)}:${_classPrivateFieldGet2(_ctx, this).id.name ?? "<unnamed>"} webSocketClose:`, e);
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
/** @internal Dispatch a hibernating WebSocket error. */
|
|
1038
|
+
async webSocketError(ws, error) {
|
|
1039
|
+
if (isBenignTeardownError(ws, error)) return;
|
|
1040
|
+
try {
|
|
1041
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
1042
|
+
await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).webSocketError(ws, error));
|
|
1043
|
+
} catch (e) {
|
|
1044
|
+
console.error(`Error in ${_classPrivateFieldGet2(_parentClassName, this)}:${_classPrivateFieldGet2(_ctx, this).id.name ?? "<unnamed>"} webSocketError:`, e);
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Start lifecycle capabilities and the owning Durable Object.
|
|
1049
|
+
*
|
|
1050
|
+
* Runtime fetch, alarm, and WebSocket entry points call this automatically.
|
|
1051
|
+
* RPC methods may call it explicitly because native RPC bypasses fetch.
|
|
1052
|
+
*
|
|
1053
|
+
* @param props - Optional properties supplied to capability and host startup.
|
|
1054
|
+
*/
|
|
1055
|
+
async start(props) {
|
|
1056
|
+
if (props !== void 0) _classPrivateFieldSet2(_props, this, props);
|
|
1057
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* The name used to address this Durable Object.
|
|
1061
|
+
*
|
|
1062
|
+
* Native `ctx.id.name` is authoritative. A read-only legacy storage fallback
|
|
1063
|
+
* lets objects created by older PartyServer releases migrate without new
|
|
1064
|
+
* name writes.
|
|
1065
|
+
*/
|
|
1066
|
+
get name() {
|
|
1067
|
+
const name = _classPrivateFieldGet2(_ctx, this).id.name ?? _classPrivateFieldGet2(_legacyName, this);
|
|
1068
|
+
if (name !== void 0) return name;
|
|
1069
|
+
throw new Error(`${_classPrivateFieldGet2(_parentClassName, this)} could not determine its Durable Object name. Address it with idFromName() or getByName(). In local development, update Wrangler/workerd and use a current compatibility_date. newUniqueId(), idFromString(), and names over 1,024 bytes do not expose ctx.id.name. Alarms created before 2026-03-15 must be rescheduled from a named fetch or RPC handler.`);
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* The host's scoped access to the Lifecycle work queue. Items pushed here
|
|
1073
|
+
* are dispatched to the host's `onJob` inside the host invocation
|
|
1074
|
+
* boundary.
|
|
1075
|
+
*/
|
|
1076
|
+
get jobs() {
|
|
1077
|
+
return _assertClassBrand(_Lifecycle_brand, this, _jobsForOwner).call(this, HOST_JOB_CAPABILITY);
|
|
1078
|
+
}
|
|
1079
|
+
/**
|
|
1080
|
+
* Recompute the physical Durable Object alarm from job-queue state.
|
|
1081
|
+
*
|
|
1082
|
+
* Concurrent requests are serialized so a later durable-state change cannot
|
|
1083
|
+
* be overwritten by an earlier alarm calculation. Queue mutations call this
|
|
1084
|
+
* automatically; it stays public for composition roots and tests.
|
|
1085
|
+
*/
|
|
1086
|
+
async rearmAlarm() {
|
|
1087
|
+
if (_classPrivateFieldGet2(_alarmsDisabled, this)) return;
|
|
1088
|
+
if (_classPrivateFieldGet2(_status, this) === "starting") {
|
|
1089
|
+
_classPrivateFieldSet2(_rearmRequestedDuringStart, this, true);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
const next = _classPrivateFieldGet2(_alarmRearmQueue, this).catch(() => {}).then(async () => {
|
|
1093
|
+
if (_classPrivateFieldGet2(_alarmsDisabled, this)) return;
|
|
1094
|
+
const alarm = _classPrivateFieldGet2(_jobQueue, this).nextAlarmTime(Date.now());
|
|
1095
|
+
if (alarm === null) await _classPrivateFieldGet2(_ctx, this).storage.deleteAlarm();
|
|
1096
|
+
else await _classPrivateFieldGet2(_ctx, this).storage.setAlarm(alarm);
|
|
1097
|
+
});
|
|
1098
|
+
_classPrivateFieldSet2(_alarmRearmQueue, this, next);
|
|
1099
|
+
await next;
|
|
1100
|
+
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Keep work a job handed off at a bounded return inside the current
|
|
1103
|
+
* alarm's memory-limit breaker domain (#1825). Hosts call this where a
|
|
1104
|
+
* queue-driven callback detaches long work and returns.
|
|
1105
|
+
*
|
|
1106
|
+
* @returns True when called during an alarm invocation; false otherwise.
|
|
1107
|
+
*/
|
|
1108
|
+
trackAlarmWork(work) {
|
|
1109
|
+
return _classPrivateFieldGet2(_jobDriver, this).trackAlarmWork(work);
|
|
1110
|
+
}
|
|
1111
|
+
/** Dispose installed capabilities in reverse registration order. */
|
|
1112
|
+
async dispose() {
|
|
1113
|
+
await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).dispose());
|
|
1114
|
+
}
|
|
1115
|
+
/** Permanently disable and clear alarms during explicit object teardown. */
|
|
1116
|
+
async disableAlarms() {
|
|
1117
|
+
_classPrivateFieldSet2(_alarmsDisabled, this, true);
|
|
1118
|
+
await _classPrivateFieldGet2(_alarmRearmQueue, this).catch(() => {});
|
|
1119
|
+
await _classPrivateFieldGet2(_ctx, this).storage.deleteAlarm();
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Run one alarm invocation. The job driver owns the event loop — deadman
|
|
1123
|
+
* pre-arm, due-job dispatch with retry and deferral policy, the alarm
|
|
1124
|
+
* memory-limit circuit breaker (#1825) — and re-arms the physical alarm
|
|
1125
|
+
* from queue state. The host's `onAlarm()` runs after due jobs, inside
|
|
1126
|
+
* the host invocation boundary.
|
|
1127
|
+
*/
|
|
1128
|
+
async alarm() {
|
|
1129
|
+
await _classPrivateFieldGet2(_jobDriver, this).runAlarm(() => _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this), () => runInLifecycleHostContext({ host: _classPrivateFieldGet2(_host, this) }, async () => {
|
|
1130
|
+
await _classPrivateFieldGet2(_host, this).onAlarm?.();
|
|
1131
|
+
}));
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
function _servicesForCapability(capabilityId) {
|
|
1135
|
+
const lifecycle = this;
|
|
1136
|
+
const envelope = (payload) => ({
|
|
1137
|
+
capability: capabilityId,
|
|
1138
|
+
source: lifecycleRouteTransports.get(lifecycle)?.source,
|
|
1139
|
+
payload
|
|
1140
|
+
});
|
|
1141
|
+
return Object.freeze({
|
|
1142
|
+
storage: _classPrivateFieldGet2(_ctx, this).storage,
|
|
1143
|
+
sockets: Object.freeze({
|
|
1144
|
+
accept: (ws, tags) => _classPrivateFieldGet2(_ctx, this).acceptWebSocket(ws, tags),
|
|
1145
|
+
get: (tag) => _classPrivateFieldGet2(_ctx, this).getWebSockets(tag)
|
|
1146
|
+
}),
|
|
1147
|
+
ready: () => _assertClassBrand(_Lifecycle_brand, this, _readyForCapabilityOperation).call(this),
|
|
1148
|
+
starting: () => _classPrivateFieldGet2(_status, this) === "starting",
|
|
1149
|
+
jobs: _assertClassBrand(_Lifecycle_brand, this, _jobsForOwner).call(this, capabilityId),
|
|
1150
|
+
trackAlarmWork: (work) => _classPrivateFieldGet2(_jobDriver, this).trackAlarmWork(work),
|
|
1151
|
+
runInHostContext: async (fn, scope) => _assertClassBrand(_Lifecycle_brand, this, _runInHostBoundary).call(this, fn, scope),
|
|
1152
|
+
events: Object.freeze({ emit: (type, payload) => _assertClassBrand(_Lifecycle_brand, this, _emitCapabilityEvent).call(this, {
|
|
1153
|
+
source: capabilityId,
|
|
1154
|
+
type,
|
|
1155
|
+
payload
|
|
1156
|
+
}) }),
|
|
1157
|
+
routes: Object.freeze({
|
|
1158
|
+
get source() {
|
|
1159
|
+
return lifecycleRouteTransports.get(lifecycle)?.source;
|
|
1160
|
+
},
|
|
1161
|
+
toRoot: (payload) => {
|
|
1162
|
+
const transport = lifecycleRouteTransports.get(lifecycle);
|
|
1163
|
+
return transport ? transport.toRoot(envelope(payload)) : _assertClassBrand(_Lifecycle_brand, this, _dispatchRoute).call(this, envelope(payload));
|
|
1164
|
+
},
|
|
1165
|
+
to: (target, payload) => {
|
|
1166
|
+
const transport = lifecycleRouteTransports.get(lifecycle);
|
|
1167
|
+
if (!transport) throw new Error("Lifecycle has no transport for routed capabilities");
|
|
1168
|
+
return transport.to(target, envelope(payload));
|
|
1169
|
+
}
|
|
1170
|
+
})
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Run a user callback inside the host invocation boundary — plain host
|
|
1175
|
+
* context by default, or the composition root's substitute (Agent installs
|
|
1176
|
+
* its tracing invocation scope).
|
|
1177
|
+
*/
|
|
1178
|
+
function _runInHostBoundary(fn, scope) {
|
|
1179
|
+
const boundary = lifecycleHostInvokers.get(this);
|
|
1180
|
+
return Promise.resolve(boundary ? boundary(fn, scope) : runInLifecycleHostContext({
|
|
1181
|
+
host: _classPrivateFieldGet2(_host, this),
|
|
1182
|
+
connection: scope?.connection,
|
|
1183
|
+
request: scope?.request
|
|
1184
|
+
}, fn));
|
|
1185
|
+
}
|
|
1186
|
+
async function _readyForCapabilityOperation() {
|
|
1187
|
+
if (_classPrivateFieldGet2(_status, this) === "starting" || _classPrivateFieldGet2(_status, this) === "started") return;
|
|
1188
|
+
await this.start();
|
|
1189
|
+
}
|
|
1190
|
+
async function _dispatchRoute(envelope) {
|
|
1191
|
+
await _assertClassBrand(_Lifecycle_brand, this, _ensureInitialized).call(this);
|
|
1192
|
+
return runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).route(envelope.capability, {
|
|
1193
|
+
source: envelope.source,
|
|
1194
|
+
payload: envelope.payload
|
|
1195
|
+
}));
|
|
1196
|
+
}
|
|
1197
|
+
function _emitCapabilityEvent(event) {
|
|
1198
|
+
if (event.source.trim() === "" || event.type.trim() === "") throw new Error("Lifecycle events require non-empty source and type");
|
|
1199
|
+
if (_classPrivateFieldGet2(_status, this) !== "started") {
|
|
1200
|
+
_classPrivateFieldGet2(_pendingEvents, this).push(event);
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
_assertClassBrand(_Lifecycle_brand, this, _publishCapabilityEvent).call(this, event);
|
|
1204
|
+
}
|
|
1205
|
+
function _publishCapabilityEvent(event) {
|
|
1206
|
+
runWithoutCurrentAgent(() => {
|
|
1207
|
+
const sink = lifecycleEventSinks.get(this);
|
|
1208
|
+
try {
|
|
1209
|
+
if (!sink) {
|
|
1210
|
+
publishDiagnosticsEvent({
|
|
1211
|
+
source: event.source,
|
|
1212
|
+
type: event.type,
|
|
1213
|
+
agent: _classPrivateFieldGet2(_parentClassName, this),
|
|
1214
|
+
name: this.name,
|
|
1215
|
+
payload: event.payload,
|
|
1216
|
+
timestamp: Date.now()
|
|
1217
|
+
});
|
|
1218
|
+
return;
|
|
1219
|
+
}
|
|
1220
|
+
const pending = sink(event);
|
|
1221
|
+
if (pending !== void 0) _classPrivateFieldGet2(_ctx, this).waitUntil(Promise.resolve(pending).catch((error) => {
|
|
1222
|
+
_assertClassBrand(_Lifecycle_brand, this, _reportEventSinkFailure).call(this, event, error);
|
|
1223
|
+
}));
|
|
1224
|
+
} catch (error) {
|
|
1225
|
+
_assertClassBrand(_Lifecycle_brand, this, _reportEventSinkFailure).call(this, event, error);
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
function _reportEventSinkFailure(event, error) {
|
|
1230
|
+
console.error(`Lifecycle event sink failed for ${event.source}:${event.type}`, error);
|
|
1231
|
+
}
|
|
1232
|
+
function _deliverPendingEvents() {
|
|
1233
|
+
for (const event of _classPrivateFieldGet2(_pendingEvents, this).splice(0)) _assertClassBrand(_Lifecycle_brand, this, _publishCapabilityEvent).call(this, event);
|
|
1234
|
+
}
|
|
1235
|
+
async function _ensureInitialized() {
|
|
1236
|
+
if (_classPrivateFieldGet2(_status, this) === "started") return;
|
|
1237
|
+
if (_classPrivateFieldGet2(_ctx, this).id.name === void 0 && _classPrivateFieldGet2(_legacyName, this) === void 0) _classPrivateFieldSet2(_legacyName, this, await _classPrivateFieldGet2(_ctx, this).storage.get(LEGACY_NAME_STORAGE_KEY));
|
|
1238
|
+
this.name;
|
|
1239
|
+
_classPrivateFieldSet2(_capabilitiesLocked, this, true);
|
|
1240
|
+
let error;
|
|
1241
|
+
await _classPrivateFieldGet2(_ctx, this).blockConcurrencyWhile(async () => {
|
|
1242
|
+
_classPrivateFieldSet2(_status, this, "starting");
|
|
1243
|
+
try {
|
|
1244
|
+
await runWithoutCurrentAgent(() => _classPrivateFieldGet2(_capabilityRunner, this).start({ props: _classPrivateFieldGet2(_props, this) }));
|
|
1245
|
+
await runInLifecycleHostContext({ host: _classPrivateFieldGet2(_host, this) }, () => _classPrivateFieldGet2(_host, this).onStart?.(_classPrivateFieldGet2(_props, this)));
|
|
1246
|
+
_classPrivateFieldSet2(_status, this, "started");
|
|
1247
|
+
} catch (cause) {
|
|
1248
|
+
_classPrivateFieldSet2(_status, this, "zero");
|
|
1249
|
+
error = cause;
|
|
1250
|
+
}
|
|
1251
|
+
});
|
|
1252
|
+
if (error) {
|
|
1253
|
+
_classPrivateFieldSet2(_rearmRequestedDuringStart, this, false);
|
|
1254
|
+
_classPrivateFieldGet2(_pendingEvents, this).length = 0;
|
|
1255
|
+
throw error;
|
|
1256
|
+
}
|
|
1257
|
+
_assertClassBrand(_Lifecycle_brand, this, _deliverPendingEvents).call(this);
|
|
1258
|
+
if (_classPrivateFieldGet2(_rearmRequestedDuringStart, this)) {
|
|
1259
|
+
_classPrivateFieldSet2(_rearmRequestedDuringStart, this, false);
|
|
1260
|
+
await this.rearmAlarm();
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function _jobsForOwner(owner) {
|
|
1264
|
+
const rearmAfter = async (mutate) => {
|
|
1265
|
+
const result = mutate();
|
|
1266
|
+
await this.rearmAlarm();
|
|
1267
|
+
return result;
|
|
1268
|
+
};
|
|
1269
|
+
return Object.freeze({
|
|
1270
|
+
push: (options) => rearmAfter(() => _classPrivateFieldGet2(_jobQueue, this).push(owner, options)),
|
|
1271
|
+
cancel: (id) => rearmAfter(() => _classPrivateFieldGet2(_jobQueue, this).cancel(owner, id)),
|
|
1272
|
+
reschedule: (id, time) => rearmAfter(() => _classPrivateFieldGet2(_jobQueue, this).reschedule(owner, id, time)),
|
|
1273
|
+
get: (id) => _classPrivateFieldGet2(_jobQueue, this).get(owner, id),
|
|
1274
|
+
list: () => _classPrivateFieldGet2(_jobQueue, this).list(owner),
|
|
1275
|
+
rearm: () => this.rearmAlarm()
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
/**
|
|
1279
|
+
* Resolve a job owner to its dispatch hooks. Host jobs run inside the
|
|
1280
|
+
* host invocation boundary; capability jobs run outside ambient host
|
|
1281
|
+
* context, like every other capability hook.
|
|
1282
|
+
*/
|
|
1283
|
+
async function _resolveJobDispatch(owner) {
|
|
1284
|
+
if (owner === "host") {
|
|
1285
|
+
const host = _classPrivateFieldGet2(_host, this);
|
|
1286
|
+
if (!host.onJob) return void 0;
|
|
1287
|
+
return { onJob: async (context) => runInLifecycleHostContext({ host }, () => host.onJob(context)) };
|
|
1288
|
+
}
|
|
1289
|
+
const capability = await _classPrivateFieldGet2(_capabilityRunner, this).findById(owner);
|
|
1290
|
+
if (!capability?.onJob) return void 0;
|
|
1291
|
+
return {
|
|
1292
|
+
onJob: async (context) => runWithoutCurrentAgent(() => capability.onJob(context)),
|
|
1293
|
+
onJobError: capability.onJobError ? async (context, error) => runWithoutCurrentAgent(() => capability.onJobError(context, error)) : void 0
|
|
1294
|
+
};
|
|
1295
|
+
}
|
|
1296
|
+
//#endregion
|
|
1297
|
+
export { abortWithoutAlarmRetry as a, setLifecycleRouteTransport as i, setLifecycleEventSink as n, setLifecycleHostInvoker as r, Lifecycle as t };
|
|
1298
|
+
|
|
1299
|
+
//# sourceMappingURL=lifecycle-CMRGjZdw.js.map
|