@mcp-b/do-runtime 0.1.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/LICENSE +110 -0
  3. package/LICENSE.workerd +176 -0
  4. package/NOTICE +7 -0
  5. package/README.md +282 -0
  6. package/dist/backends/node-sqlite.d.ts +38 -0
  7. package/dist/backends/node-sqlite.js +335 -0
  8. package/dist/backends/node-sqlite.js.map +1 -0
  9. package/dist/backends/sqlite-wasm.d.ts +130 -0
  10. package/dist/backends/sqlite-wasm.js +259 -0
  11. package/dist/backends/sqlite-wasm.js.map +1 -0
  12. package/dist/chunks/sqlite-DFg92Tgt.js +498 -0
  13. package/dist/chunks/sqlite-DFg92Tgt.js.map +1 -0
  14. package/dist/cloudflare-workers.js +351 -0
  15. package/dist/cloudflare-workers.js.map +1 -0
  16. package/dist/conformance/host.d.ts +58 -0
  17. package/dist/conformance.js +18 -0
  18. package/dist/conformance.js.map +1 -0
  19. package/dist/index.js +7184 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/server/alarm-scheduler.js +513 -0
  22. package/dist/server/alarm-scheduler.js.map +1 -0
  23. package/dist/src/api/actor-state.d.ts +396 -0
  24. package/dist/src/api/actor.d.ts +306 -0
  25. package/dist/src/api/cloudflare-workers.d.ts +259 -0
  26. package/dist/src/api/export-loopback.d.ts +264 -0
  27. package/dist/src/api/global-scope.d.ts +262 -0
  28. package/dist/src/api/http.d.ts +52 -0
  29. package/dist/src/api/sql.d.ts +188 -0
  30. package/dist/src/api/sync-kv.d.ts +51 -0
  31. package/dist/src/api/web-socket.d.ts +93 -0
  32. package/dist/src/api/worker-loader.d.ts +354 -0
  33. package/dist/src/index.d.ts +130 -0
  34. package/dist/src/io/actor-cache.d.ts +203 -0
  35. package/dist/src/io/actor-id.d.ts +74 -0
  36. package/dist/src/io/actor-sqlite.d.ts +298 -0
  37. package/dist/src/io/io-channels.d.ts +191 -0
  38. package/dist/src/io/io-context.d.ts +451 -0
  39. package/dist/src/io/io-gate.d.ts +298 -0
  40. package/dist/src/io/worker-source.d.ts +108 -0
  41. package/dist/src/io/worker.d.ts +88 -0
  42. package/dist/src/server/actor-container.d.ts +525 -0
  43. package/dist/src/server/actor-id-impl.d.ts +118 -0
  44. package/dist/src/server/alarm-scheduler.d.ts +201 -0
  45. package/dist/src/server/facet-deletion.d.ts +156 -0
  46. package/dist/src/server/facet-tree-index.d.ts +94 -0
  47. package/dist/src/server/sha256.d.ts +39 -0
  48. package/dist/src/transport/rpc-session.d.ts +34 -0
  49. package/dist/src/util/sqlite-kv.d.ts +98 -0
  50. package/dist/src/util/sqlite-metadata.d.ts +46 -0
  51. package/dist/src/util/sqlite.d.ts +291 -0
  52. package/package.json +111 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * ← workerd `src/workerd/io/io-channels.h` — `IoChannelFactory::ActorClassChannel`,
3
+ * `ResourceLimits`, `WorkerStubChannel` and `DynamicWorkerSource`.
4
+ *
5
+ * **A partial port, and deliberately so.** `io-channels.h` is 492 lines and
6
+ * almost all of it is outgoing I/O: `SubrequestChannel`, `CacheClient`,
7
+ * `TimerChannel`, logfwdr and channel tokens. Every one of those is either a
8
+ * substrate boundary or a transport concern, and none of them is reachable from
9
+ * the files this module exists to serve. What IS reachable is four types. One is
10
+ * the token `DurableObjectClass` holds and
11
+ * `Worker::Actor::FacetManager::StartInfo` carries, which has to live in `io/`
12
+ * because `io/worker.ts` names it and `io/` may not see `api/`. The other three
13
+ * are the dynamic-worker half `api/worker-loader.ts` consumes; an earlier
14
+ * revision of this header deferred them to Section 7, which is where they landed.
15
+ *
16
+ * Two things upstream has here that this package does not, both for the same
17
+ * reason. `IoChannelFactory` itself is absent because **there is no numbered
18
+ * channel table**: upstream resolves a binding to a `uint` at configuration time
19
+ * and every channel method takes that number, while here a binding is a property
20
+ * of the `env` object the consumer supplies. So every `kj::OneOf<uint,
21
+ * IoOwn<...>>` in `api/actor.ts` keeps only its object arm — which is upstream's
22
+ * own alternative, offered on `DurableObjectNamespace` for exactly the case
23
+ * where a namespace "is constructed dynamically within an execution context,
24
+ * rather than being a long-lived binding". And `ActorChannel` is absent because
25
+ * its one method returns a `WorkerInterface`, capnp dispatch with no port; the
26
+ * JS-visible product of an actor channel is a `Fetcher`, so the two collapse —
27
+ * the same collapse `io/worker.ts`'s `FacetManager.getFacet` and
28
+ * `server/actor-container.ts`'s `FacetHandle.stub` already make. That collapse
29
+ * reaches `SubrequestChannel` too, which is why `DynamicWorkerSource`'s outbound
30
+ * and tail fields below are `Fetcher`s.
31
+ *
32
+ * Spec: §1.10, §1.11, decisions 14 and 15 in
33
+ * docs/decisions.md.
34
+ */
35
+ import type { WorkerSource } from "./worker-source.js";
36
+ /**
37
+ * ← `IoChannelFactory::ActorClassChannel`. Upstream: "a reference to an actor
38
+ * class in another worker. This class acts as a token which can be passed into
39
+ * other interfaces that might use the actor class, particularly
40
+ * `Worker::Actor::FacetManager`" and "This class has no functional methods, since
41
+ * it serves as a token to be passed to other interfaces (namely the facets API)."
42
+ *
43
+ * Ours is the same token with the contents workerd keeps opaque made visible,
44
+ * because they have to be: upstream's token is opaque precisely because the class
45
+ * is in another isolate and only the supervisor can resolve it, whereas here the
46
+ * container resolves it against the `ctx.exports` record the consumer handed in.
47
+ * The class name is the host-visible value the container needs to resolve the
48
+ * otherwise opaque token against `ctx.exports`.
49
+ *
50
+ * `requireAllowsTransfer()` is upstream's and is kept, because it is the one
51
+ * behaviour on the token rather than data: it decides whether a stub over this
52
+ * channel may be serialized, and `DurableObjectClass.serialize` calls it before
53
+ * anything else.
54
+ */
55
+ export interface ActorClassChannel {
56
+ /** The `ctx.exports` key the container resolves this class under. */
57
+ readonly className: string;
58
+ /**
59
+ * ← `requireAllowsTransfer()`. "Throws a JSG error if a Fetcher backed by this
60
+ * channel should not be serialized and passed to other workers."
61
+ */
62
+ requireAllowsTransfer(): void;
63
+ }
64
+ /**
65
+ * ← `ResourceLimits` (`io-channels.h:375-386`). "provides a means to control the
66
+ * resource allocation for a worker stage via a set of optionally overridden
67
+ * parameters."
68
+ *
69
+ * **It is a no-op in workerd and it is a no-op here.** The bag is accepted by
70
+ * `WorkerLoader.load`, by `WorkerStub.getEntrypoint`/`getDurableObjectClass`, and
71
+ * by both `WorkerStubChannel` methods; the one implementation that receives it —
72
+ * `Server::WorkerLoaderNamespace::WorkerStubImpl::getEntrypointResolved` and
73
+ * `::getActorClassResolved` (`server.c++:4359`, `:4364`) — names the parameter
74
+ * and never reads it. `DynamicWorkerSource.limits` is likewise written by
75
+ * `toDynamicWorkerSource` (`worker-loader.c++:165`) and read by nothing in the
76
+ * open-source tree. So local development never reproduces limit enforcement on
77
+ * either runtime, and a reader who assumes otherwise is reading a field that is
78
+ * carried to production and dropped in workerd. `clone()` is absent for the
79
+ * reason `io/worker-source.ts`'s header gives.
80
+ */
81
+ export type ResourceLimits = {
82
+ readonly cpuMs?: number;
83
+ readonly subRequests?: number;
84
+ };
85
+ /**
86
+ * ← `CompatibilityDateValidation` (`io/compatibility-date.h:12-32`). How far into
87
+ * the future a dynamic Worker's `compatibilityDate` may be, which upstream says
88
+ * "will differ between workerd vs. production".
89
+ */
90
+ export type CompatibilityDateValidation =
91
+ /** "Allow dates up through the date specified by `supportedCompatibilityDate`." */
92
+ "codeVersion"
93
+ /** "Allow dates up to through the current date. This should ONLY be used by Cloudflare." */
94
+ | "currentDateForCloudflare"
95
+ /** "Allow any future date. This should only be used to test `compileCompatibilityFlags` itself." */
96
+ | "futureForTest";
97
+ /**
98
+ * ← the arguments `compileCompatibilityFlags` takes
99
+ * (`io/compatibility-date.h:38-44`), where upstream's `DynamicWorkerSource` holds
100
+ * the compiled `CompatibilityFlags::Reader` it returns.
101
+ *
102
+ * **The compilation is a substrate boundary and this is the request that replaces
103
+ * it.** `compileCompatibilityFlags` walks the capnp *schema* of
104
+ * `CompatibilityFlags` reflectively — every flag's enable/disable names, its
105
+ * default-on date and its experimental annotation are schema annotations
106
+ * (`io/compatibility-date.c++:102-260`) — and this package has no
107
+ * `compatibility-date.capnp`, no schema reflection and no `FeatureFlags`. It is
108
+ * the same absence the four flags `api/actor.ts` meets already records. So the
109
+ * inputs travel to whatever is on the far side of `loadIsolate`, which is the
110
+ * layer that has an isolate to configure and therefore the only layer that could
111
+ * validate them.
112
+ */
113
+ export type CompatibilityFlagsRequest = {
114
+ readonly compatibilityDate: string;
115
+ readonly compatibilityFlags: readonly string[];
116
+ readonly allowExperimental: boolean;
117
+ readonly dateValidation: CompatibilityDateValidation;
118
+ };
119
+ /**
120
+ * ← the two arguments `WorkerStubChannel`'s methods take besides the entrypoint
121
+ * name (`io-channels.h:397-406`).
122
+ *
123
+ * `props` is upstream's `Frankenvalue`, which is a JS value plus a cap table; here
124
+ * it is the JS value, because there is no numbered channel table for a cap table
125
+ * to index.
126
+ */
127
+ export type EntrypointRequest = {
128
+ /** `kj::none` selects the default entrypoint, as `"default"` does. */
129
+ readonly name: string | undefined;
130
+ readonly props: unknown;
131
+ readonly limits: ResourceLimits | undefined;
132
+ };
133
+ /**
134
+ * ← `WorkerStubChannel` (`io-channels.h:390-408`). "Represents a dynamically-loaded
135
+ * Worker to which requests can be sent. This object is returned before the Worker
136
+ * actually loads, so if any errors occur while loading, any requests sent to the
137
+ * Worker will fail, propagating the exception."
138
+ *
139
+ * That last sentence is the contract `api/worker-loader.ts` depends on twice over:
140
+ * it is why `WorkerLoader.get` can return a stub synchronously while its code
141
+ * callback has not run, and it is why upstream's own tests assert a bad module
142
+ * list from `worker.getEntrypoint().greet(...)` rather than from `get()`
143
+ * (`api/tests/worker-loader-test.js:812`).
144
+ *
145
+ * Upstream splits each method in two — a non-virtual half that "waits for `props`
146
+ * to resolve first" and a virtual `…Resolved` half. The waiting half resolves
147
+ * `Frankenvalue` cap-table entries that are still promises; a JS value has no
148
+ * unresolved arm, so the pair collapses to one method.
149
+ */
150
+ export interface WorkerStubChannel {
151
+ /** ← `getEntrypoint` / `getEntrypointResolved`, with `SubrequestChannel` collapsed. */
152
+ getEntrypoint(request: EntrypointRequest): Fetcher;
153
+ /** ← `getActorClass` / `getActorClassResolved`. */
154
+ getActorClass(request: EntrypointRequest): ActorClassChannel;
155
+ }
156
+ /**
157
+ * ← `DynamicWorkerSource` (`io-channels.h:411-455`). "Source code needed to
158
+ * dynamically load a Worker."
159
+ *
160
+ * Field for field, with three departures, each recorded on the type it touches:
161
+ * `compatibilityFlags` carries the request rather than the compiled reader
162
+ * (`CompatibilityFlagsRequest` above), the three `SubrequestChannel` fields are
163
+ * `Fetcher`s (this file's header), and `ownContent` / `ownContentIsRpcResponse` /
164
+ * `clone()` / `ensureAllResolved()` are kj lifetime bookkeeping with nothing to
165
+ * bookkeep (`io/worker-source.ts`'s header).
166
+ */
167
+ export type DynamicWorkerSource = {
168
+ readonly source: WorkerSource;
169
+ readonly compatibilityFlags: CompatibilityFlagsRequest;
170
+ readonly limits: ResourceLimits | undefined;
171
+ /**
172
+ * "`env` object to pass to the loaded worker. Can contain anything that can be
173
+ * serialized to a `Frankenvalue`."
174
+ */
175
+ readonly env: unknown;
176
+ /**
177
+ * "Where should global fetch() (and connect()) be sent?"
178
+ *
179
+ * **`undefined` means blocked, and that is upstream's encoding rather than a
180
+ * shorthand.** The JS-facing `globalOutbound` has three states — omitted, `null`,
181
+ * a `Fetcher` — and `toDynamicWorkerSource` collapses them to two here:
182
+ * `kj::none` is written only for the explicit `null`, while the omitted case is
183
+ * resolved at that layer into the calling worker's own outbound
184
+ * (`worker-loader.c++:122-139`). By the time a source exists, "inherit" is no
185
+ * longer a state.
186
+ */
187
+ readonly globalOutbound: Fetcher | undefined;
188
+ /** "Tail workers that should receive tail events for invocations of the dynamic worker." */
189
+ readonly tails: readonly Fetcher[];
190
+ readonly streamingTails: readonly Fetcher[];
191
+ };
@@ -0,0 +1,451 @@
1
+ /**
2
+ * ← workerd `src/workerd/io/io-context.{h,c++}`
3
+ *
4
+ * IoContext: the one door, plus the two await forms.
5
+ *
6
+ * This is the file with no true upstream correspondence for its *enforcement*.
7
+ * Workerd acquires the input gate at isolate entry, so acquisition is
8
+ * structural. We have no isolate hook, so a lock is taken at our own dispatch
9
+ * boundary instead. See "The enforcement point is the one thing we cannot port"
10
+ * in the design record.
11
+ *
12
+ * What this file does, stated before anything about how it got here:
13
+ *
14
+ * 1. `#currentInputLocks` is a STACK of the locks held by the slices that are
15
+ * running. It is upstream's single `kj::Maybe<InputGate::Lock>
16
+ * currentInputLock` member (`io-context.h:993`) with the `Maybe` widened,
17
+ * and a "frame" is an entry in it. A frame carries nothing else: the only
18
+ * two things ever read from one are the lock (`getInputLock`) and the
19
+ * critical section that lock belongs to (`getCriticalSection`), and the
20
+ * lock answers both.
21
+ * 2. A lock is released, and leaves the stack, at the END OF THE MICROTASK
22
+ * CHECKPOINT of the slice holding it — not when that slice's synchronous
23
+ * body returns. That is upstream's own boundary, not a relaxation of it:
24
+ * `runImpl`'s inner `KJ_DEFER` calls `js.runMicrotasks()`
25
+ * (`io-context.c++:1262`) and `runInContextScope`'s outer one then clears
26
+ * `currentInputLock` (`:1214`), inner scope first, so the whole checkpoint
27
+ * drains under the lock. `currentInputLock` holds the `Lock` by value, so
28
+ * clearing it is the release. `atCheckpointEnd` below is that point.
29
+ * 3. NEITHER await form releases anything at the moment of the await.
30
+ * `awaitIo` reads `getCriticalSection()` and `awaitIoWithInputLock` takes
31
+ * an `addRef`; both then return a promise and let the slice end normally.
32
+ * So an invocation that calls `awaitIo` keeps the gate until its own
33
+ * checkpoint-end exit and the gate opens there — which is still before any
34
+ * real I/O can complete, so "a bare timer releases the input gate" holds.
35
+ * Upstream is the same: `getCriticalSection()` (`io-context.c++:362`) does
36
+ * not touch `currentInputLock`, and `:1214` is the only place that clears
37
+ * it. The difference between the two forms is entirely on the far side —
38
+ * `awaitIo` re-enters through `run(func, criticalSection)` and queues for a
39
+ * fresh lock, `awaitIoWithInputLock` re-enters holding the ref it took.
40
+ * 4. Removal from the stack is by identity, not by popping, because entries do
41
+ * overlap — three deep in the unit tests. One invocation can have several
42
+ * held awaits outstanding (`Promise.all` over two storage reads), and a
43
+ * section's last body slice overlaps the slice that resolves it.
44
+ *
45
+ * Two mechanics were validated against real workerd before this scaffolding
46
+ * landed, and both are easy to get wrong:
47
+ *
48
+ * 1. The ambient "which lock am I under" must be a STACK of invocation frames,
49
+ * not a single slot, so that `current` always names the running invocation.
50
+ * The prototype that found this stated it as "`awaitIo` splices its frame
51
+ * out and re-pushes the SAME frame on resume". That is not what happens
52
+ * here and the difference is worth knowing: nothing splices at the point of
53
+ * the await, because a lock already leaves the stack when its slice ends,
54
+ * which is the same event that releases it (`#exit` does both). A
55
+ * resumption then pushes a fresh lock. The prototype needed the splice
56
+ * because its door held one lock for a whole invocation; the identity that
57
+ * has to survive an await here is the critical section, and that is
58
+ * captured at the call rather than looked up later.
59
+ * 2. That ambient is safe here for a reason that does NOT generalise to the
60
+ * §2.3 ambient-field hazard: the gate guarantees pushes and pops nest
61
+ * properly in time, because only one holder chain is inside at once.
62
+ * `_cf_currentSubAgentBridge` has no such guarantee, which is why it is a
63
+ * live bug and this is not.
64
+ *
65
+ * Consequence: no async context is required. Do not add a dependency on
66
+ * decision 8 here without re-running the conformance gate suite first.
67
+ *
68
+ * The invariant a future simplifier has to re-check: a single slot would pass
69
+ * every test in `io-context.test.ts`, and that was established by trying it,
70
+ * not by argument. `current()` is only read from inside a slice, every slice
71
+ * pushes its own lock last, and no second slice can begin while an earlier
72
+ * frame is still waiting for its checkpoint-end exit — that frame is holding
73
+ * the gate. So the top of a stack and the last write to a slot always agree.
74
+ * The stack is still what is here, because a slot would be holding a stale
75
+ * value at every one of the overlaps in (4), and that is unobservable for
76
+ * exactly as long as the invariant holds. Tightening (2) back to the end of the
77
+ * synchronous body breaks it immediately, which is the regime the mechanic was
78
+ * found in.
79
+ *
80
+ * Spec: §1.2, §1.3, §1.5, §1.6, §1.7.1, §1.9, decisions 1, 2, 4 and 13 in
81
+ * docs/decisions.md.
82
+ *
83
+ * `TimeoutManager` is the only part of this file a consumer's own code reaches
84
+ * directly: every host-provided async primitive a
85
+ * Durable Object can await has to route through here, or the continuation after
86
+ * it resumes with an empty invocation stack. `TimeoutManager` below is the
87
+ * timer half; `api/global-scope.ts` and `api/web-socket.ts` are the rest.
88
+ *
89
+ * Not ported, because the substrate has no equivalent to port onto: isolate and
90
+ * async locks (`Worker::Lock`, `jsg::Lock`, `takeAsyncLock`) and everything
91
+ * that exists to enter or leave an isolate — which is precisely the thing this
92
+ * file substitutes for; the limit enforcer and `afterLimitTimeout` (the
93
+ * deadline takes the `Timer` port instead); trace spans, already a recorded
94
+ * divergence documented in §1.12; subrequest channels and HTTP, which are
95
+ * `api/http.ts`'s gating over the substrate's own `fetch`;
96
+ * `IoOwn`/`IoPtr`/`DeleteQueue`, which guard cross-context dereferences that GC
97
+ * makes impossible; hang detection and `registerPendingEvent`, which need the
98
+ * isolate's own idea of pending work; and the thread-local
99
+ * `IoContext::current()` static, whose lock-resolving half the invocation stack
100
+ * replaces — its *identity* half is `currentSlice` below, narrowed to the
101
+ * synchronous slice, with one consumer and no resolver. `EventOutcome` and
102
+ * `RequestObserver` are metrics types with no port, so `waitUntilStatus()`
103
+ * returns the first exception instead.
104
+ */
105
+ import { CriticalSection, type InputGate, Lock, type OutputGate } from "./io-gate.js";
106
+ /**
107
+ * ← `kj::Timer`, threaded into IoContext upstream.
108
+ *
109
+ * A port with one production implementation would be an invented seam; this
110
+ * one has two (real clock in browser and workerd) plus a fake the conformance
111
+ * suite cannot exist without — the 30-second critical-section deadline and the
112
+ * alarm retry ladder are not assertable on wall-clock time in CI.
113
+ */
114
+ export interface Timer {
115
+ now(): number;
116
+ /** `kj::Timer::afterDelay`. The signal replaces kj's cancel-by-drop. */
117
+ afterDelay(ms: number, signal?: AbortSignal): Promise<void>;
118
+ }
119
+ /**
120
+ * ← the `Worker::Actor` surface reached through an `IoContext`:
121
+ * `a.getInputGate()`, `a.getOutputGate()` and `a.shutdownActorCache()` from
122
+ * `io-context.{h,c++}` itself, plus `assertCanSetAlarm()`, which
123
+ * `api/actor-state.c++:486` reaches through
124
+ * `IoContext::current().getActorOrThrow()`.
125
+ *
126
+ * `Worker::Actor` lives in `io/worker.h`, the same Bazel target as this file, so
127
+ * naming the members its consumers use is upstream's own layering rather than a
128
+ * new seam. Every context here is an actor context — there is no non-actor
129
+ * request in a Durable Object runtime — so upstream's
130
+ * `kj::Maybe<Worker::Actor&>` and every branch that tests it collapse to this
131
+ * being required, and `getActorOrThrow()` cannot actually throw.
132
+ */
133
+ export interface Actor {
134
+ getInputGate(): InputGate;
135
+ getOutputGate(): OutputGate;
136
+ /** Abort abandons scheduled writes rather than flushing them (§1.6). */
137
+ shutdownActorCache(reason: unknown): void;
138
+ /**
139
+ * ← `Worker::Actor::assertCanSetAlarm()` (`io/worker.c++:4090`). Every branch
140
+ * of it reads the actor's class-instance lifecycle, which `server/` owns, so
141
+ * `api/actor-state.ts`'s obligation is to call it and the container's is to
142
+ * answer it.
143
+ */
144
+ assertCanSetAlarm(): void;
145
+ }
146
+ /** ← `afterLimitTimeout(30 * kj::SECONDS)` in `IoContext::blockConcurrencyWhile`. */
147
+ export declare const BLOCK_CONCURRENCY_WHILE_TIMEOUT_MS = 30000;
148
+ /** Copied verbatim: users and upstream tests match on it. */
149
+ export declare const BLOCK_CONCURRENCY_WHILE_TIMEOUT_MESSAGE: string;
150
+ /**
151
+ * THE check every storage entry point in `api/` makes before touching the
152
+ * database, and the only place this package throws for a missing input lock.
153
+ *
154
+ * Upstream never faces the question. `IoContext::current()` is a thread-local
155
+ * read with a `KJ_REQUIRE` behind it, and it cannot fail for a storage call
156
+ * because isolate entry is the only way into an actor and it always took a
157
+ * lock. We have no isolate hook (see this file's header), so a continuation
158
+ * that resumed from a raw `setTimeout`, a raw `fetch`, or any other promise the
159
+ * runtime does not own comes back with an empty invocation stack, and its next
160
+ * storage call reaches `ActorSqlite` — which is synchronous, touches no gate,
161
+ * and would happily serve it outside any transaction boundary.
162
+ *
163
+ * So this throws, matching the assert. It is deliberately ONE function called
164
+ * from every entry point rather than a check written at each of them: the
165
+ * policy is a design decision that belongs to the design record, and when it
166
+ * changes it has to change in one place rather than across a surface the
167
+ * vendored consumer reaches from hundreds of call sites. There is no lenient
168
+ * mode, no flag, and no implicit acquire — a lost invocation is loud, and a
169
+ * lost transaction boundary is not.
170
+ */
171
+ export declare function requireInputLock(ctx: IoContext, op: string): void;
172
+ /**
173
+ * The end of the microtask checkpoint — the moment `runImpl`'s `KJ_DEFER` fires.
174
+ *
175
+ * Upstream drains the whole checkpoint INSIDE the isolate run: the defer calls
176
+ * `js.runMicrotasks()` and only the outer defer in `runInContextScope` then
177
+ * clears `currentInputLock`. So a continuation that awaits nothing but
178
+ * already-resolved promises stays under the same lock, and a continuation that
179
+ * waits on real I/O does not. In JS the only observable end of a microtask drain
180
+ * is the next macrotask, so that is where a lock leaves the invocation stack.
181
+ *
182
+ * Measured, because the obvious alternatives are wrong in ways nothing catches:
183
+ * releasing synchronously when the invoked function returns, or on the next
184
+ * microtask, both hand the lock back BEFORE the code awaiting the I/O resumes as
185
+ * soon as one promise sits between the two — one `async` wrapper, a `.then`, a
186
+ * `Promise.all` — and `actor-state.ts` is exactly such a wrapper. The gate would
187
+ * then open in the middle of a storage await with no test failing, which is the
188
+ * silent loss of atomicity §1.7.1 names.
189
+ *
190
+ * `MessageChannel` and not `setTimeout`, decided by benchmark rather than by
191
+ * argument, because the two are three orders of magnitude apart on the shapes
192
+ * that wait for a release. A chain of these schedules each hand-off from inside
193
+ * the previous one's callback, so a `setTimeout` chain's nesting level climbs
194
+ * past five and stays there, where browsers clamp it to 4ms. Median per hand-off
195
+ * over 50 chained hops:
196
+ *
197
+ * | | setTimeout | MessageChannel |
198
+ * | node | 1.273 ms | 0.018 ms |
199
+ * | chromium page | 4.96 ms | 0.024 ms |
200
+ * | chromium Worker | 5.542 ms | 0.022 ms |
201
+ *
202
+ * Only two shapes pay it: an `awaitIo` chain, which has to re-acquire the lock
203
+ * its slice gave up, and a queue of events waiting on the holder. A chain of
204
+ * held storage awaits does not — the lock passes from `addRef` to `addRef` and
205
+ * the release is off the critical path. So 50 sequential facet RPCs cost 277ms
206
+ * of pure clamp in a Worker under `setTimeout` and 1.1ms under this.
207
+ *
208
+ * One channel per BATCH, and a batch is an explicit array: `atCheckpointEnd`
209
+ * pushes onto `pendingCheckpointEnds` and only the push that finds it empty opens
210
+ * a channel. Ordering then comes from the array, which the language guarantees,
211
+ * rather than from delivery order across separate channels, which no
212
+ * specification does. That mattered because the storage engine below depends on
213
+ * a commit scheduled inside a slice running before the release scheduled at the
214
+ * end of that slice; separate channels do deliver in post order in Node,
215
+ * measured across 200 of them including one scheduled from inside another's
216
+ * callback, but a browser that chose otherwise would open the gate onto a
217
+ * transaction a previous event left open, and nothing would say so.
218
+ *
219
+ * A callback scheduled DURING a drain lands in the next batch, which is the
220
+ * semantics to want: one hand-off is one checkpoint end, and a slice that begins
221
+ * inside this drain gets its own. The drain runs every callback even if one
222
+ * throws, and rethrows the first exception afterwards, because abandoning the
223
+ * rest of a batch is how a gate wedges with nothing to see.
224
+ *
225
+ * A long-lived shared port was rejected for the reason it always is: it has to be
226
+ * closed on abort and `unref`'d so it cannot hold a test runner's event loop
227
+ * open, and an `unref`'d port can drop a release at exit — a wedged gate. A
228
+ * channel that lives for exactly one message cannot. Batching gets most of what
229
+ * a shared port was worth anyway: a slice that schedules a commit and a release
230
+ * now allocates one channel where it used to allocate two.
231
+ *
232
+ * **Exported because `kj::evalLater()` is this same point.** `ActorSqlite` opens
233
+ * its implicit transaction on the first write and commits it "on the next turn of
234
+ * the event loop" (`actor-sqlite.c++:352-357`); upstream's next turn is after the
235
+ * isolate run, which is after `js.runMicrotasks()`, which is after
236
+ * `currentInputLock` is cleared. Upstream's two boundaries are one boundary, and
237
+ * they stay one here only if the commit rides the same primitive as the release.
238
+ * Two consequences the storage engine depends on, both properties of this
239
+ * function rather than of `ActorSqlite`:
240
+ *
241
+ * 1. **Everything that holds the input lock across an await is a microtask
242
+ * chain.** `awaitIoWithInputLock` resumes through `#awaitIoImpl`'s `.then`
243
+ * into `run(func, lock)`, which never waits on the gate. So a whole run of
244
+ * held storage awaits finishes before the next hand-off and its writes are
245
+ * one transaction (§1.7.1 row 1).
246
+ * 2. **Everything that releases it needs at least one hand-off.** `awaitIo`
247
+ * resumes through `gate.wait()`, which cannot resolve until `#exit` runs
248
+ * here. So a timer or outbound await puts the commit between the two writes
249
+ * (§1.7.1 row 2).
250
+ *
251
+ */
252
+ export declare function atCheckpointEnd(run: () => void): void;
253
+ /** ← `IoContext::tryCurrent()` (`io-context.c++:1416-1422`), over the narrowed scope above. */
254
+ export declare function tryCurrentSlice(): IoContext | undefined;
255
+ /**
256
+ * ← `jsg::isExceptionFromInputGateBroken` (`jsg/exception.c++:168-172`):
257
+ * "annotateBroken() produces 'broken.inputGateBroken; {message}', optionally
258
+ * prefixed with 'remote.' when crossing RPC boundaries. Strip the remote prefix
259
+ * first, then check the tag."
260
+ *
261
+ * Its writer is `annotateInputGateBroken` directly above, which is why the two
262
+ * live together rather than the reader moving to its consumer: `jsg/` has no
263
+ * module here, and a prefix known in two places is a prefix that drifts.
264
+ */
265
+ export declare function isExceptionFromInputGateBroken(exception: unknown): boolean;
266
+ /**
267
+ * ← `jsg::EXCEPTION_IS_USER_ERROR` (`jsg/exception.h:160`), a
268
+ * `kj::Exception::DetailTypeId` attached to an arbitrary exception rather than a
269
+ * type of exception.
270
+ *
271
+ * A symbol-keyed property is the closest JS has: it rides any thrown object,
272
+ * survives a rethrow, and cannot collide with anything an application writes.
273
+ * `Symbol.for` rather than `Symbol()` so the detail is still legible after the
274
+ * exception crosses a realm — the mistake decision 18 records capnweb making.
275
+ */
276
+ export declare const EXCEPTION_IS_USER_ERROR: unique symbol;
277
+ /** ← `error.setDetail(jsg::EXCEPTION_IS_USER_ERROR, kj::heapArray<byte>(0))`. */
278
+ export declare function setUserErrorDetail(exception: unknown): void;
279
+ /** ← `e.getDetail(jsg::EXCEPTION_IS_USER_ERROR) != kj::none`. */
280
+ export declare function hasUserErrorDetail(exception: unknown): boolean;
281
+ export declare class IoContext {
282
+ #private;
283
+ constructor(actor: Actor, timer: Timer);
284
+ /**
285
+ * Get the current input lock. Throws an exception if no input lock is held (e.g. because
286
+ * this is not an actor request).
287
+ *
288
+ * ← `KJ_ASSERT_NONNULL(currentInputLock, ...).addRef()`. The `addRef` IS the §1.2
289
+ * distinction: it is the only way to hold the gate past the end of this slice.
290
+ */
291
+ getInputLock(): Lock;
292
+ /** Get the current CriticalSection, if there is one, or returns null if not. */
293
+ getCriticalSection(): CriticalSection | undefined;
294
+ /** Is a gated slice running? The question `IoContext::hasCurrent()` answers upstream. */
295
+ hasCurrent(): boolean;
296
+ /**
297
+ * ← `IoContext::isCurrent()` (`io-context.c++:1428-1430`), over the narrowed
298
+ * scope `currentSlice` documents: true only while a synchronous body of THIS
299
+ * context is on the JS stack.
300
+ *
301
+ * Distinct from `hasCurrent()` above, which asks whether this context holds a
302
+ * lock at all — true throughout an outstanding held await, and true for a
303
+ * parent whose slice is awaiting a facet while the facet's body runs. This one
304
+ * is the question a shared global has to answer: is the code calling me this
305
+ * actor's?
306
+ */
307
+ isCurrentSlice(): boolean;
308
+ /**
309
+ * ← `IoContext::getActorOrThrow()`. Upstream's throws when the request is not
310
+ * an actor request; there is no such request here, so it is a plain accessor.
311
+ */
312
+ getActorOrThrow(): Actor;
313
+ /** ← `IoContext::now()` (`io-context.h:703`), which reads the same timer. */
314
+ now(): number;
315
+ /**
316
+ * ← `IoContext::setTimeoutImpl` (`io-context.c++:885-899`), clamp included.
317
+ *
318
+ * The generator parameter is gone with `TimeoutId::Generator` — see
319
+ * `TimeoutManager`'s header — so the signature is upstream's minus that one
320
+ * argument.
321
+ */
322
+ setTimeoutImpl(repeat: boolean, callback: () => void, msDelay: number): number;
323
+ /** ← `IoContext::clearTimeoutImpl` (`io-context.c++:901-903`). */
324
+ clearTimeoutImpl(id: number): void;
325
+ /** ← `IoContext::getTimeoutCount` (`io-context.c++:905-907`). */
326
+ getTimeoutCount(): number;
327
+ /**
328
+ * Wait until all outstanding output locks have been unlocked. Does not wait for future
329
+ * output locks, even if they are created before past locks are unlocked.
330
+ */
331
+ waitForOutputLocks(): Promise<void>;
332
+ /**
333
+ * Check if the output gate is currently broken. This indicates that there was a problem
334
+ * with committing storage writes.
335
+ */
336
+ isOutputGateBroken(): boolean;
337
+ /** Lock output until the given promise completes. */
338
+ lockOutputWhile<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T>;
339
+ /**
340
+ * Rejects if and when the context should be aborted, e.g. because a gate broke. This
341
+ * promise never resolves, only rejects.
342
+ */
343
+ onAbort(): Promise<never>;
344
+ /** Force context abort now. */
345
+ abort(exception: unknown): void;
346
+ /**
347
+ * Await the given promise and, if it throws, call `abort()` with the exception. The promise
348
+ * given here should just be a monitoring promise, it should not represent any sort of
349
+ * background work beyond monitoring.
350
+ */
351
+ abortWhen(promise: Promise<unknown>): void;
352
+ /**
353
+ * Arrange for the given promise to execute as part of this request.
354
+ *
355
+ * "In Actors, we treat all tasks as wait-until tasks, because it's perfectly legit to start
356
+ * a task under one request and then expect some other request to handle it later." Every
357
+ * context here is an actor context, so that branch is the only branch.
358
+ */
359
+ addTask(promise: Promise<void>): void;
360
+ /**
361
+ * Indicates that the script has requested that it stay active until the given promise
362
+ * resolves. `drainWaitUntil()` waits until all such promises have completed. Touches
363
+ * neither gate (§1.9).
364
+ */
365
+ addWaitUntil(promise: Promise<void>): void;
366
+ /** Returns the number of times addTask() has been called (even if the tasks have completed). */
367
+ taskCount(): number;
368
+ /**
369
+ * The first exception a background task failed with, if any.
370
+ *
371
+ * ← `waitUntilStatus()`, which returns an `EventOutcome` derived from the exception by
372
+ * `RequestObserver`. There is no observer here, so the exception itself is the status —
373
+ * and keeping it is what stops a failed background task from being swallowed, since
374
+ * upstream's other half of `taskFailed()` is a log this package has no port for.
375
+ */
376
+ waitUntilStatus(): unknown;
377
+ /**
378
+ * ← `IncomingRequest::drain()`, actor branch. "For actors, all promises are canceled on
379
+ * actor shutdown, not on a fixed timeout, because work doesn't necessarily happen on a
380
+ * per-request basis in actors."
381
+ */
382
+ drainWaitUntil(): Promise<void>;
383
+ /**
384
+ * Run the given callback within this context, holding an input lock.
385
+ *
386
+ * ← the two `IoContext::run()` overloads: given a CriticalSection it waits on that, given
387
+ * an already-held Lock it runs under it, and given neither it takes a fresh lock from the
388
+ * gate. The third case is what a new external event does, and it is the reason inheritance
389
+ * cannot be read from gate state — see `makeReentryCallback`.
390
+ */
391
+ run<T>(func: (lock: Lock) => T | PromiseLike<T>, ilOrCs?: Lock | CriticalSection): Promise<T>;
392
+ /**
393
+ * Make a function which, when called, re-enters this IoContext to run some code.
394
+ *
395
+ * Upstream, on why the critical section travels with the callback at all:
396
+ *
397
+ * > "What if the call was made within blockConcurrencyWhile()? The callback will be blocked
398
+ * > until the critical section ends, which could lead to deadlock if the critical section
399
+ * > code is waiting on it? ... The callback is allowed to run within the critical section
400
+ * > (blockConcurrencyWhile()) from which it was called."
401
+ *
402
+ * The section is read here, at the point of capture, and never on invocation: a new
403
+ * external event that inherited the running section would skip the queue and
404
+ * `blockConcurrencyWhile` would silently block nothing (Part 4, mechanic 2).
405
+ *
406
+ * The returned function can be called multiple times.
407
+ *
408
+ * It does not route through `io-gate.ts`'s `makeReentryCallback`, which is the same idea
409
+ * expressed at the gate. Upstream's `IoContext::makeReentryCallback` is literally
410
+ * `ctx.run(func, cs)`, and going through the gate helper instead would take a lock this
411
+ * file then has to make current a second time. The gate copy stays: it is the shape a
412
+ * consumer holding only a gate needs, and Section 1's tests cover it.
413
+ */
414
+ makeReentryCallback<Args extends unknown[], Result>(func: (lock: Lock, ...args: Args) => Result | PromiseLike<Result>): (...args: Args) => Promise<Result>;
415
+ /**
416
+ * Waits for some background I/O to complete, then executes `func` on the result.
417
+ *
418
+ * The input lock is NOT held across the wait: the resumption re-enters through
419
+ * `run(func, criticalSection)` and takes a fresh lock, so it queues behind whatever
420
+ * arrived in the meantime. This is what makes a Durable Object awaiting another Durable
421
+ * Object fully re-entrant (§1.3).
422
+ *
423
+ * `func` is a parameter rather than something the caller chains, for upstream's reason:
424
+ * chaining "required returning to the KJ event loop between running func() and running
425
+ * whatever JavaScript code was waiting on it". Here the equivalent cost is a promise hop
426
+ * outside the lock.
427
+ */
428
+ awaitIo<T>(promise: Promise<T>): Promise<T>;
429
+ awaitIo<T, R>(promise: Promise<T>, func: (value: T) => R | PromiseLike<R>): Promise<R>;
430
+ /**
431
+ * Waits for the given I/O while holding the input lock, so that all other I/O is blocked
432
+ * from completing in the meantime (unless it is also holding the same input lock).
433
+ *
434
+ * This is the whole of §1.2's asymmetry, and per §1.7.1 it is also the implicit-transaction
435
+ * boundary: the four async storage calls take this form, everything else takes `awaitIo`.
436
+ * Calling it outside a gated slice throws rather than inventing a lock — a lost invocation
437
+ * is loud, a lost transaction boundary is not.
438
+ */
439
+ awaitIoWithInputLock<T>(promise: Promise<T>): Promise<T>;
440
+ awaitIoWithInputLock<T, R>(promise: Promise<T>, func: (value: T) => R | PromiseLike<R>): Promise<R>;
441
+ /**
442
+ * Runs `callback` within its own critical section, returning its final result. If
443
+ * `callback` throws, the input lock will break, resetting the actor.
444
+ *
445
+ * Three behaviours live here rather than in `io-gate.ts`, which has no timer, and rather
446
+ * than in `api/actor-state.ts`, whose own `blockConcurrencyWhile` is a one-line forward:
447
+ * the 30-second deadline, the brokenness annotation, and the fact that on failure the
448
+ * returned promise is never settled at all.
449
+ */
450
+ blockConcurrencyWhile<T>(callback: (lock: Lock) => T | PromiseLike<T>): Promise<T>;
451
+ }