@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.
- package/CHANGELOG.md +14 -0
- package/LICENSE +110 -0
- package/LICENSE.workerd +176 -0
- package/NOTICE +7 -0
- package/README.md +282 -0
- package/dist/backends/node-sqlite.d.ts +38 -0
- package/dist/backends/node-sqlite.js +335 -0
- package/dist/backends/node-sqlite.js.map +1 -0
- package/dist/backends/sqlite-wasm.d.ts +130 -0
- package/dist/backends/sqlite-wasm.js +259 -0
- package/dist/backends/sqlite-wasm.js.map +1 -0
- package/dist/chunks/sqlite-DFg92Tgt.js +498 -0
- package/dist/chunks/sqlite-DFg92Tgt.js.map +1 -0
- package/dist/cloudflare-workers.js +351 -0
- package/dist/cloudflare-workers.js.map +1 -0
- package/dist/conformance/host.d.ts +58 -0
- package/dist/conformance.js +18 -0
- package/dist/conformance.js.map +1 -0
- package/dist/index.js +7184 -0
- package/dist/index.js.map +1 -0
- package/dist/server/alarm-scheduler.js +513 -0
- package/dist/server/alarm-scheduler.js.map +1 -0
- package/dist/src/api/actor-state.d.ts +396 -0
- package/dist/src/api/actor.d.ts +306 -0
- package/dist/src/api/cloudflare-workers.d.ts +259 -0
- package/dist/src/api/export-loopback.d.ts +264 -0
- package/dist/src/api/global-scope.d.ts +262 -0
- package/dist/src/api/http.d.ts +52 -0
- package/dist/src/api/sql.d.ts +188 -0
- package/dist/src/api/sync-kv.d.ts +51 -0
- package/dist/src/api/web-socket.d.ts +93 -0
- package/dist/src/api/worker-loader.d.ts +354 -0
- package/dist/src/index.d.ts +130 -0
- package/dist/src/io/actor-cache.d.ts +203 -0
- package/dist/src/io/actor-id.d.ts +74 -0
- package/dist/src/io/actor-sqlite.d.ts +298 -0
- package/dist/src/io/io-channels.d.ts +191 -0
- package/dist/src/io/io-context.d.ts +451 -0
- package/dist/src/io/io-gate.d.ts +298 -0
- package/dist/src/io/worker-source.d.ts +108 -0
- package/dist/src/io/worker.d.ts +88 -0
- package/dist/src/server/actor-container.d.ts +525 -0
- package/dist/src/server/actor-id-impl.d.ts +118 -0
- package/dist/src/server/alarm-scheduler.d.ts +201 -0
- package/dist/src/server/facet-deletion.d.ts +156 -0
- package/dist/src/server/facet-tree-index.d.ts +94 -0
- package/dist/src/server/sha256.d.ts +39 -0
- package/dist/src/transport/rpc-session.d.ts +34 -0
- package/dist/src/util/sqlite-kv.d.ts +98 -0
- package/dist/src/util/sqlite-metadata.d.ts +46 -0
- package/dist/src/util/sqlite.d.ts +291 -0
- package/package.json +111 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/io/actor-cache.h` — INTERFACE ONLY.
|
|
3
|
+
*
|
|
4
|
+
* The `actor-cache.c++` LRU implementation is ABSENT rather than skipped: it
|
|
5
|
+
* caches a remote storage service that none of our substrates have. Upstream
|
|
6
|
+
* does not use it in SQLite mode either — `ActorSqlite` is the sole
|
|
7
|
+
* `ActorCacheInterface` implementation there, and the same is true here.
|
|
8
|
+
*
|
|
9
|
+
* The two behaviours decision 5 names live in `io/actor-sqlite.ts`, which is
|
|
10
|
+
* where workerd's SQLite path exhibits them: `allowUnconfirmed` skips the
|
|
11
|
+
* output-gate lock but STILL breaks the gate on error, and a batch started as
|
|
12
|
+
* unconfirmed is retroactively upgraded when a must-confirm write joins it.
|
|
13
|
+
*
|
|
14
|
+
* Ordering guarantee, upstream's words (`actor-cache.h:334-340`): writes are
|
|
15
|
+
* never committed out-of-order, by brute force — one transaction commits all
|
|
16
|
+
* dirty keys at once.
|
|
17
|
+
*
|
|
18
|
+
* This file carries only what `ActorSqlite` genuinely implements. Everything in
|
|
19
|
+
* `ActorCacheInterface` that exists solely for the LRU — `evictStale`'s
|
|
20
|
+
* backpressure, the RPC storage client, the shared LRU and its hooks — is
|
|
21
|
+
* absent with it.
|
|
22
|
+
*
|
|
23
|
+
* The one shape that is ours rather than upstream's: **every read and write is
|
|
24
|
+
* synchronous.** Upstream returns `kj::OneOf<T, kj::Promise<T>>` so a cache miss
|
|
25
|
+
* can go to the network; §1.4 measures that a SQLite-backed actor never does,
|
|
26
|
+
* and the SQLite arm of every one of those `OneOf`s is the immediate value. A
|
|
27
|
+
* `OneOf` with one reachable arm is a promise nobody can observe, and keeping it
|
|
28
|
+
* would make `api/actor-state.ts` unwrap something that is never a promise.
|
|
29
|
+
* `onNoPendingFlush` and `abandonAlarm` stay asynchronous because upstream's
|
|
30
|
+
* SQLite arm is genuinely asynchronous there.
|
|
31
|
+
*
|
|
32
|
+
* Spec: §1.7, decisions 2 and 5.
|
|
33
|
+
*/
|
|
34
|
+
/** ← `ActorCacheOps::Key`. "Keys are text for now." */
|
|
35
|
+
export type Key = string;
|
|
36
|
+
/** ← `ActorCacheOps::Value`. Values are raw bytes; the value encoding is `api/`'s. */
|
|
37
|
+
export type Value = Uint8Array;
|
|
38
|
+
/** ← `ActorCacheOps::KeyValuePair`. */
|
|
39
|
+
export type KeyValuePair = {
|
|
40
|
+
readonly key: Key;
|
|
41
|
+
readonly value: Value;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* ← `ActorCacheOps::GetResultList`, which upstream makes a class so it can
|
|
45
|
+
* iterate pointers into the cache's own storage. Ours has already copied.
|
|
46
|
+
*/
|
|
47
|
+
export type GetResultList = readonly KeyValuePair[];
|
|
48
|
+
/**
|
|
49
|
+
* The option bags. Per §1.2 `allowConcurrency` is precisely the input-gate
|
|
50
|
+
* opt-out: it selects `awaitIo` over `awaitIoWithInputLock`, which per §1.7.1
|
|
51
|
+
* also ends the implicit transaction.
|
|
52
|
+
*
|
|
53
|
+
* Nothing passes any of these today — not upstream, not this repo, not the
|
|
54
|
+
* vendored tests. They are built anyway; building half of the
|
|
55
|
+
* `awaitIoWithInputLock` branch is the feature-subset failure the porting
|
|
56
|
+
* philosophy rejects. The conformance suite exercises them deliberately.
|
|
57
|
+
*
|
|
58
|
+
* Placement note: upstream's `ActorCacheReadOptions` holds only `noCache`, and
|
|
59
|
+
* `allowConcurrency` is read one layer up, in `DurableObjectStorageOperations`
|
|
60
|
+
* (`actor-state.c++:68-79`) — `ActorCacheOps` never sees it. Part 4's table puts
|
|
61
|
+
* decision 2 in this file, so it is declared here and consumed by `api/`;
|
|
62
|
+
* `ActorSqlite` itself reads neither `allowConcurrency` nor `noCache`, exactly
|
|
63
|
+
* as upstream's does not.
|
|
64
|
+
*/
|
|
65
|
+
export type ReadOptions = {
|
|
66
|
+
/** Release the input gate across the await. Ends the implicit transaction. */
|
|
67
|
+
allowConcurrency?: boolean;
|
|
68
|
+
/** Do not retain the value in cache. */
|
|
69
|
+
noCache?: boolean;
|
|
70
|
+
};
|
|
71
|
+
export type WriteOptions = ReadOptions & {
|
|
72
|
+
/** Skip the output-gate lock. Never skips break-on-error. */
|
|
73
|
+
allowUnconfirmed?: boolean;
|
|
74
|
+
};
|
|
75
|
+
/** ← `DeleteAllOptions`. */
|
|
76
|
+
export type DeleteAllOptions = {
|
|
77
|
+
/**
|
|
78
|
+
* When true, deleteAll() will also delete any scheduled alarm. The alarm
|
|
79
|
+
* deletion is guaranteed to take effect only after the deleteAll() itself
|
|
80
|
+
* succeeds, so that we never end up in a state where the alarm is deleted but
|
|
81
|
+
* KV data remains.
|
|
82
|
+
*/
|
|
83
|
+
deleteAlarm?: boolean;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* ← `ActorCacheInterface::DeleteAllResults`.
|
|
87
|
+
*
|
|
88
|
+
* Upstream splits these "so client code that doesn't need the count doesn't have
|
|
89
|
+
* to wait for it just to account for backpressure". Both arms are immediate for
|
|
90
|
+
* SQLite: `backpressure` is always `kj::none` and `count` is a ready promise.
|
|
91
|
+
*/
|
|
92
|
+
export type DeleteAllResults = {
|
|
93
|
+
readonly backpressure: Promise<void> | undefined;
|
|
94
|
+
readonly count: number;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* ← `ActorCacheInterface::CancelAlarmHandler`. Alarm should be canceled without
|
|
98
|
+
* retry, because alarm state has changed such that the requested alarm time is
|
|
99
|
+
* no longer valid.
|
|
100
|
+
*/
|
|
101
|
+
export type CancelAlarmHandler = {
|
|
102
|
+
/** Caller should wait for this promise to complete before canceling. */
|
|
103
|
+
readonly waitBeforeCancel: Promise<void>;
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* ← the `kj::Own<void>` that `RunAlarmHandler` carries, whose disposer runs
|
|
107
|
+
* `maybeDeleteDeferredAlarm()`. Section 1's rule applies: a kj destructor
|
|
108
|
+
* becomes an explicit call, so the caller attaches `drop()` to the promise
|
|
109
|
+
* representing the handler's execution rather than a scope exit.
|
|
110
|
+
*/
|
|
111
|
+
export interface DeferredAlarmDeleter {
|
|
112
|
+
drop(): void;
|
|
113
|
+
}
|
|
114
|
+
/** ← `ActorCacheInterface::RunAlarmHandler`. Alarm should be run. */
|
|
115
|
+
export type RunAlarmHandler = {
|
|
116
|
+
readonly deferredDelete: DeferredAlarmDeleter;
|
|
117
|
+
};
|
|
118
|
+
/** ← `kj::OneOf<CancelAlarmHandler, RunAlarmHandler>`. */
|
|
119
|
+
export type ArmAlarmResult = {
|
|
120
|
+
readonly kind: "cancel";
|
|
121
|
+
readonly cancel: CancelAlarmHandler;
|
|
122
|
+
} | {
|
|
123
|
+
readonly kind: "run";
|
|
124
|
+
readonly run: RunAlarmHandler;
|
|
125
|
+
};
|
|
126
|
+
/** ← `ActorCache::SHUTDOWN_ERROR_MESSAGE`, which `ActorSqlite::shutdown` reuses. */
|
|
127
|
+
export declare const SHUTDOWN_ERROR_MESSAGE = "broken.ignored; jsg.Error: Durable Object storage is no longer accessible.";
|
|
128
|
+
/**
|
|
129
|
+
* ← the message every unimplemented `ActorCacheInterface` PITR method throws.
|
|
130
|
+
* `ActorSqlite` overrides two of the four; the other two keep this.
|
|
131
|
+
*/
|
|
132
|
+
export declare const PITR_UNIMPLEMENTED_MESSAGE = "This Durable Object's storage back-end does not implement point-in-time recovery.";
|
|
133
|
+
/** ← the message the three replication methods throw. */
|
|
134
|
+
export declare const REPLICATION_UNIMPLEMENTED_MESSAGE = "This Durable Object's storage back-end does not support replication.";
|
|
135
|
+
/**
|
|
136
|
+
* Common interface between the storage engine and a transaction on it.
|
|
137
|
+
*
|
|
138
|
+
* ← `ActorCacheOps`. Upstream's `list`/`listReverse` split exists because the
|
|
139
|
+
* two directions "require a subtly different implementation of pretty much the
|
|
140
|
+
* entire algorithm" in the cache; both are kept, because both are separate
|
|
141
|
+
* entry points a caller reaches.
|
|
142
|
+
*/
|
|
143
|
+
export interface ActorCacheOps {
|
|
144
|
+
get(key: Key, options: ReadOptions): Value | undefined;
|
|
145
|
+
getMultiple(keys: readonly Key[], options: ReadOptions): GetResultList;
|
|
146
|
+
getAlarm(options: ReadOptions): number | null;
|
|
147
|
+
list(begin: Key, end: Key | undefined, limit: number | undefined, options: ReadOptions): GetResultList;
|
|
148
|
+
listReverse(begin: Key, end: Key | undefined, limit: number | undefined, options: ReadOptions): GetResultList;
|
|
149
|
+
put(key: Key, value: Value, options: WriteOptions): void;
|
|
150
|
+
putMultiple(pairs: readonly KeyValuePair[], options: WriteOptions): void;
|
|
151
|
+
/** Returns whether the key was present. */
|
|
152
|
+
delete(key: Key, options: WriteOptions): boolean;
|
|
153
|
+
/** Returns how many of the keys were present. */
|
|
154
|
+
deleteMultiple(keys: readonly Key[], options: WriteOptions): number;
|
|
155
|
+
setAlarm(newAlarmTime: number | null, options: WriteOptions): void;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* ← `ActorCacheInterface::Transaction`.
|
|
159
|
+
*
|
|
160
|
+
* "If commit() is not called before the Transaction is destroyed, nothing is
|
|
161
|
+
* written." JS has no destruction, so `drop()` is that moment and exactly one of
|
|
162
|
+
* `commit()`/`rollback()`+`drop()` has to run — the same contract Section 1
|
|
163
|
+
* established for `Lock` and `CriticalSection`.
|
|
164
|
+
*/
|
|
165
|
+
export interface ActorCacheTransaction extends ActorCacheOps {
|
|
166
|
+
/**
|
|
167
|
+
* Write all changes to the underlying storage.
|
|
168
|
+
*
|
|
169
|
+
* "This will NOT detect conflicts, it will always just write blindly, because
|
|
170
|
+
* conflicts inherently cannot happen."
|
|
171
|
+
*/
|
|
172
|
+
commit(): void;
|
|
173
|
+
rollback(): void;
|
|
174
|
+
/** ← `~ExplicitTxn`: roll back if not committed, then leave the txn stack. */
|
|
175
|
+
drop(): void;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Abstract interface that upstream implements twice, and that this package
|
|
179
|
+
* implements once — `ActorSqlite` is the sole implementation, exactly as on
|
|
180
|
+
* workerd-with-SQLite.
|
|
181
|
+
*/
|
|
182
|
+
export interface ActorCacheInterface extends ActorCacheOps {
|
|
183
|
+
startTransaction(): ActorCacheTransaction;
|
|
184
|
+
deleteAll(options: WriteOptions, deleteAllOptions?: DeleteAllOptions): DeleteAllResults;
|
|
185
|
+
/**
|
|
186
|
+
* "Call each time the isolate lock is taken to evict stale entries." There is
|
|
187
|
+
* no cache to evict from and never any backpressure to apply.
|
|
188
|
+
*/
|
|
189
|
+
evictStale(now: number): undefined;
|
|
190
|
+
shutdown(exception?: unknown): void;
|
|
191
|
+
armAlarmHandler(scheduledTime: number, currentTime: number): ArmAlarmResult;
|
|
192
|
+
cancelDeferredAlarmDeletion(): void;
|
|
193
|
+
abandonAlarm(scheduledTime: number): Promise<number | null>;
|
|
194
|
+
/** Implements `sync()`. */
|
|
195
|
+
onNoPendingFlush(): Promise<void>;
|
|
196
|
+
getCurrentBookmark(): Promise<string>;
|
|
197
|
+
getBookmarkForTime(timestamp: number): Promise<string>;
|
|
198
|
+
onNextSessionRestoreBookmark(bookmark: string): Promise<string>;
|
|
199
|
+
waitForBookmark(bookmark: string): Promise<void>;
|
|
200
|
+
ensureReplicas(): void;
|
|
201
|
+
disableReplicas(): void;
|
|
202
|
+
configureReadReplication(enabled: boolean): Promise<void>;
|
|
203
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/io/actor-id.h` (74 lines, header-only — there is no
|
|
3
|
+
* `actor-id.c++`).
|
|
4
|
+
*
|
|
5
|
+
* `api/actor.h` includes this file directly, and every type in it is named by
|
|
6
|
+
* `api/actor.{h,c++}`: `ActorIdFactory` is `DurableObjectNamespace`'s member,
|
|
7
|
+
* `ActorIdFactory::ActorId` is what a `DurableObjectId` wraps, and
|
|
8
|
+
* `ActorGetMode` / `ActorRoutingMode` / `ActorVersion` are the three parameters
|
|
9
|
+
* `getImpl` computes before handing them to an outgoing factory. Per the
|
|
10
|
+
* package README's rule for a legitimate upstream include crossing a wall, the
|
|
11
|
+
* reference widens to match upstream rather than the declarations being
|
|
12
|
+
* relocated into `api/`.
|
|
13
|
+
*
|
|
14
|
+
* The whole file is ported. What is NOT here is the **implementation** —
|
|
15
|
+
* upstream's is `server/actor-id-impl.{h,c++}`, a keyed SHA-256 construction —
|
|
16
|
+
* because it is `server/`'s and because a faithful port of it needs a
|
|
17
|
+
* synchronous digest, which the browser does not expose (`crypto.subtle` is
|
|
18
|
+
* asynchronous and every method below is synchronous). That is Section 6's
|
|
19
|
+
* problem to solve, and this interface is the seam it fills.
|
|
20
|
+
*
|
|
21
|
+
* Spec: §1.10 in docs/decisions.md.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* ← `ActorGetMode`. "Behavior mode for getting an actor."
|
|
25
|
+
*
|
|
26
|
+
* A union of the upstream enumerator names rather than a TypeScript `enum`,
|
|
27
|
+
* which is the shape every other discriminant in this package already takes.
|
|
28
|
+
*/
|
|
29
|
+
export type ActorGetMode = "GET_OR_CREATE" | "GET_EXISTING";
|
|
30
|
+
/** ← `ActorRoutingMode`. "Routing mode for actor requests when replicas are available." */
|
|
31
|
+
export type ActorRoutingMode = "DEFAULT" | "PRIMARY_ONLY";
|
|
32
|
+
/** ← `ActorVersion`. "Version information for an actor. Used to specify cohort." */
|
|
33
|
+
export type ActorVersion = {
|
|
34
|
+
readonly cohort?: string;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* ← `ActorIdFactory::ActorId`. "Abstract actor ID."
|
|
38
|
+
*
|
|
39
|
+
* Upstream's comment, kept because it is the reason this is an interface rather
|
|
40
|
+
* than a class: "This is NOT an I/O type. An ActorId created in one IoContext
|
|
41
|
+
* can be used in other IoContexts."
|
|
42
|
+
*
|
|
43
|
+
* `clone()` is dropped: it exists so a `kj::Own<ActorId>` can be copied out of a
|
|
44
|
+
* borrowed reference, and a JS value needs no such thing. Every upstream call
|
|
45
|
+
* site (`DurableObjectId`'s constructor, `cloneId()`, the facet start info) is
|
|
46
|
+
* satisfied by passing the same object.
|
|
47
|
+
*/
|
|
48
|
+
export interface ActorId {
|
|
49
|
+
/** "Get the string that could be passed to `idFromString()` to recreate this ID." */
|
|
50
|
+
toString(): string;
|
|
51
|
+
/**
|
|
52
|
+
* "If the ActorId was created using `idFromName()`, return a copy of the name
|
|
53
|
+
* that was passed to it. Otherwise, returns null."
|
|
54
|
+
*/
|
|
55
|
+
getName(): string | undefined;
|
|
56
|
+
/** "Get the jurisdiction that was used when creating this ID." */
|
|
57
|
+
getJurisdiction(): string | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* "Compare with another ID. This is allowed to assume the other ID was created
|
|
60
|
+
* by some other ActorIdFactory passed to one of the worker's other bindings."
|
|
61
|
+
*/
|
|
62
|
+
equals(other: ActorId): boolean;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* ← `ActorIdFactory`. "An abstract class that implements generation of global
|
|
66
|
+
* actor IDs in a particular namespace."
|
|
67
|
+
*/
|
|
68
|
+
export interface ActorIdFactory {
|
|
69
|
+
newUniqueId(jurisdiction: string | undefined): ActorId;
|
|
70
|
+
idFromName(name: string): ActorId;
|
|
71
|
+
idFromString(str: string): ActorId;
|
|
72
|
+
matchesJurisdiction(id: ActorId): boolean;
|
|
73
|
+
cloneWithJurisdiction(maybeJurisdiction: string | undefined): ActorIdFactory;
|
|
74
|
+
}
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/io/actor-sqlite.{h,c++}`
|
|
3
|
+
*
|
|
4
|
+
* The storage engine. Owns:
|
|
5
|
+
* - implicit transactions, bounded by GATE RELEASE rather than by an
|
|
6
|
+
* event-loop turn (§1.7.1 — measured; a storage await does not end the
|
|
7
|
+
* transaction, a timer or outbound await does);
|
|
8
|
+
* - `onWrite` taking the output-gate lock at the first must-confirm write,
|
|
9
|
+
* one lock per flush batch;
|
|
10
|
+
* - `transactionSync` as SAVEPOINT/RELEASE/ROLLBACK TO with a depth counter,
|
|
11
|
+
* plus the async-callback guard today's version lacks;
|
|
12
|
+
* - alarm arm/consume/deferred-deletion, and `deleteAll`.
|
|
13
|
+
*
|
|
14
|
+
* Sole `ActorCacheInterface` implementation, exactly as on workerd-with-SQLite.
|
|
15
|
+
*
|
|
16
|
+
* The single most important constraint in the whole port lives here: the
|
|
17
|
+
* transaction boundary and the gate boundary are the same line. Implement them
|
|
18
|
+
* as one mechanism. Release at every await — the naive reading of §1.2 — and
|
|
19
|
+
* every multi-statement write silently loses atomicity, with nothing failing
|
|
20
|
+
* until a crash lands between two statements that were meant to be one.
|
|
21
|
+
*
|
|
22
|
+
* **How that one line is drawn here, since it is the question the design record
|
|
23
|
+
* left open.** Upstream needs no gate hook: `startImplicitTxn` wraps the commit
|
|
24
|
+
* in `kj::evalLater`, which runs it on the next turn of the KJ event loop, and
|
|
25
|
+
* the next KJ turn is by construction after the isolate run — which is after
|
|
26
|
+
* `js.runMicrotasks()`, which is after `KJ_DEFER` clears `currentInputLock`.
|
|
27
|
+
* "Next turn" and "gate release" are one boundary upstream, so `ActorSqlite`
|
|
28
|
+
* hangs the commit on the cheaper of the two. They are one boundary here for the
|
|
29
|
+
* same reason, provided the commit rides `atCheckpointEnd` — the primitive
|
|
30
|
+
* `io-context.ts` releases on. Its comment carries the proof; the short form is
|
|
31
|
+
* that holding the lock across an await is a pure microtask chain and releasing
|
|
32
|
+
* it always costs a hand-off, so no write can cross a hand-off inside one
|
|
33
|
+
* transaction and no two events can share one. `IoContext` therefore grows no
|
|
34
|
+
* per-invocation exit notification, and the root gate's `inputGateReleased` hook
|
|
35
|
+
* is *not* the right edge: it fires when `lockCount` hits zero, which never
|
|
36
|
+
* happens inside a critical section, so decision 4's fifteen boot phases would
|
|
37
|
+
* become a single transaction nobody chose.
|
|
38
|
+
*
|
|
39
|
+
* `onWrite` and `onCriticalError` are `SqliteDatabase`'s, in `util/sqlite.ts`,
|
|
40
|
+
* exactly as upstream has them, and the constructor binds itself to both the way
|
|
41
|
+
* `ActorSqlite`'s does. What is ours is only the substitute for the question
|
|
42
|
+
* upstream answers with `sqlite3_stmt_readonly()` — see `isWrite` there.
|
|
43
|
+
*
|
|
44
|
+
* Not ported, because the substrate has no equivalent to port onto: `SpanParent`
|
|
45
|
+
* tracing, already a Section 1 divergence, so every `traceSpan` parameter and
|
|
46
|
+
* `currentCommitSpan` with it; `debugAlarmSync` and every `LOG_*`, which are a
|
|
47
|
+
* logger this package does not have; and `TxnCommitRegulator::onError`, which
|
|
48
|
+
* re-reports `SQLITE_CONSTRAINT` during commit as a user-visible error — error
|
|
49
|
+
* codes do not cross the backend seam, the same reason Section 3 dropped
|
|
50
|
+
* `SqliteKvRegulator::onError`.
|
|
51
|
+
*
|
|
52
|
+
* Spec: §1.4, §1.7, §1.7.1, §1.8, §2.4, §2.6, decisions 2, 5, 6 and 7 in
|
|
53
|
+
* docs/decisions.md.
|
|
54
|
+
*/
|
|
55
|
+
import type { ActorCacheInterface, ActorCacheTransaction, ArmAlarmResult, DeleteAllOptions, DeleteAllResults, GetResultList, Key, KeyValuePair, ReadOptions, Value, WriteOptions } from "./actor-cache.js";
|
|
56
|
+
import type { OutputGate } from "./io-gate.js";
|
|
57
|
+
import { SqliteKv } from "../util/sqlite-kv.js";
|
|
58
|
+
import { SqliteDatabase } from "../util/sqlite.js";
|
|
59
|
+
/**
|
|
60
|
+
* The alarm port — one outbound method, matching upstream's seam exactly.
|
|
61
|
+
*
|
|
62
|
+
* Everything else about alarms is runtime-internal: arm/consume semantics here,
|
|
63
|
+
* retry ladder and serialised delivery in `server/alarm-scheduler.ts`. Delivery
|
|
64
|
+
* comes back IN through `ActorContainer.deliverAlarm`, not through this port.
|
|
65
|
+
*/
|
|
66
|
+
export interface AlarmOutlet {
|
|
67
|
+
/**
|
|
68
|
+
* Must be durable before the returned promise resolves.
|
|
69
|
+
*
|
|
70
|
+
* `priorTask` is upstream's second parameter and is load bearing rather than
|
|
71
|
+
* decorative: "any work we must wait on prior to scheduling the new request,
|
|
72
|
+
* as of this writing, this would be the alarmLaterInFlight promise, which
|
|
73
|
+
* tracks any in-flight request to move the alarm 'later' than is currently
|
|
74
|
+
* set." An implementation that ignores it can send a move-earlier request
|
|
75
|
+
* concurrently with a move-later one and lose the ordering invariant that the
|
|
76
|
+
* scheduled alarm is always at or before the persisted one.
|
|
77
|
+
*
|
|
78
|
+
* May throw synchronously; `ActorSqlite` relies on it, because a scheduling
|
|
79
|
+
* failure has to reach the caller before the local database commits.
|
|
80
|
+
*/
|
|
81
|
+
scheduleRun(newAlarmTime: number | null, priorTask: Promise<void>): Promise<void>;
|
|
82
|
+
}
|
|
83
|
+
/** ← `ActorSqlite::Hooks::DEFAULT`, whose `scheduleRun` refuses. */
|
|
84
|
+
export declare const DEFAULT_ALARM_OUTLET: AlarmOutlet;
|
|
85
|
+
/**
|
|
86
|
+
* ← `kj::TaskSet` plus its `ErrorHandler`. `ActorSqlite` owns one of its own,
|
|
87
|
+
* separate from `IoContext`'s, exactly as upstream does.
|
|
88
|
+
*/
|
|
89
|
+
declare class TaskSet {
|
|
90
|
+
#private;
|
|
91
|
+
constructor(taskFailed: (exception: unknown) => void);
|
|
92
|
+
add(promise: Promise<void>): void;
|
|
93
|
+
}
|
|
94
|
+
/** ← `kj::OneOf<NoTxn, ImplicitTxn*, ExplicitTxn*>`. */
|
|
95
|
+
type CurrentTxn = {
|
|
96
|
+
readonly kind: "none";
|
|
97
|
+
} | {
|
|
98
|
+
readonly kind: "implicit";
|
|
99
|
+
readonly txn: ImplicitTxn;
|
|
100
|
+
} | {
|
|
101
|
+
readonly kind: "explicit";
|
|
102
|
+
readonly txn: ExplicitTxn;
|
|
103
|
+
};
|
|
104
|
+
/** ← `ActorSqlite::PrecommitAlarmState`. */
|
|
105
|
+
type PrecommitAlarmState = {
|
|
106
|
+
/** Promise for the completion of precommit alarm scheduling */
|
|
107
|
+
schedulingPromise?: Promise<void>;
|
|
108
|
+
};
|
|
109
|
+
/**
|
|
110
|
+
* An implementation of ActorCacheOps that is backed by SqliteKv.
|
|
111
|
+
*
|
|
112
|
+
* Constructing one arranges to honor the output gate, that is, any writes to the
|
|
113
|
+
* database which occur without any `await`s in between will automatically be
|
|
114
|
+
* combined into a single atomic write. This is accomplished using transactions.
|
|
115
|
+
* In addition to ensuring atomicity, this tends to improve performance, as
|
|
116
|
+
* SQLite is able to coalesce writes across statements that modify the same page.
|
|
117
|
+
*
|
|
118
|
+
* `commitCallback` will be invoked after committing a transaction. The output
|
|
119
|
+
* gate will block on the returned promise. This can be used e.g. when the
|
|
120
|
+
* database needs to be replicated to other machines before being considered
|
|
121
|
+
* durable.
|
|
122
|
+
*
|
|
123
|
+
* Members upstream marks `private` and reaches through `ImplicitTxn` and
|
|
124
|
+
* `ExplicitTxn`, which are nested classes with implicit friendship, are ordinary
|
|
125
|
+
* members here for the reason `io-gate.ts` gives: TypeScript has no friendship,
|
|
126
|
+
* and the boundary that actually holds is the package facade in `src/index.ts`.
|
|
127
|
+
*/
|
|
128
|
+
export declare class ActorSqlite implements ActorCacheInterface {
|
|
129
|
+
#private;
|
|
130
|
+
/** Upstream-private; reached by the two transaction classes. */
|
|
131
|
+
readonly db: SqliteDatabase;
|
|
132
|
+
/** Upstream-private; reached by the two transaction classes. */
|
|
133
|
+
readonly outputGate: OutputGate;
|
|
134
|
+
/** Upstream-private; reached by the two transaction classes. */
|
|
135
|
+
readonly commitTasks: TaskSet;
|
|
136
|
+
/** Upstream-private; the transaction classes read it to skip their rollback. */
|
|
137
|
+
broken: unknown | undefined;
|
|
138
|
+
/**
|
|
139
|
+
* When set to `none`, there is no transaction outstanding.
|
|
140
|
+
*
|
|
141
|
+
* When set to an `ImplicitTxn`, an implicit transaction is currently open,
|
|
142
|
+
* owned by `commitTasks`. If there is a need to commit this early, e.g. to
|
|
143
|
+
* start an explicit transaction, that can be done through this reference.
|
|
144
|
+
*
|
|
145
|
+
* When set to an `ExplicitTxn`, an explicit transaction is currently open, so
|
|
146
|
+
* no implicit transactions should be used in the meantime.
|
|
147
|
+
*/
|
|
148
|
+
currentTxn: CurrentTxn;
|
|
149
|
+
/**
|
|
150
|
+
* State for tracking completion of all commits (both confirmed and
|
|
151
|
+
* unconfirmed) for implementing sync() in onNoPendingFlush.
|
|
152
|
+
*
|
|
153
|
+
* Upstream-private; `ExplicitTxn::commit` replaces it.
|
|
154
|
+
*/
|
|
155
|
+
lastCommit: Promise<void>;
|
|
156
|
+
/**
|
|
157
|
+
* We need to track some additional alarm state to guarantee at-least-once
|
|
158
|
+
* alarm delivery: within an alarm handler, we want the observable alarm state
|
|
159
|
+
* to look like the running alarm was deleted at the start of the handler (when
|
|
160
|
+
* armAlarmHandler() is called), but we don't actually want to persist that
|
|
161
|
+
* deletion until after the handler has successfully completed.
|
|
162
|
+
*
|
|
163
|
+
* Upstream-private; `ExplicitTxn::commit` clears it when the txn was alarm-dirty.
|
|
164
|
+
*/
|
|
165
|
+
haveDeferredDelete: boolean;
|
|
166
|
+
constructor(db: SqliteDatabase, outputGate: OutputGate, commitCallback: () => Promise<void>, hooks?: AlarmOutlet);
|
|
167
|
+
isCommitScheduled(): boolean;
|
|
168
|
+
getSqliteDatabase(): SqliteDatabase;
|
|
169
|
+
getSqliteKv(): SqliteKv;
|
|
170
|
+
/**
|
|
171
|
+
* To be called just before committing the local sqlite db, to synchronously
|
|
172
|
+
* start any necessary alarm scheduling.
|
|
173
|
+
*
|
|
174
|
+
* Upstream-private; `ExplicitTxn::commit` calls it for the root transaction.
|
|
175
|
+
*/
|
|
176
|
+
startPrecommitAlarmScheduling(): PrecommitAlarmState;
|
|
177
|
+
/**
|
|
178
|
+
* Performs the rest of the asynchronous commit, to be waited on after
|
|
179
|
+
* committing the local sqlite db. Should be called in the same turn of the
|
|
180
|
+
* event loop as startPrecommitAlarmScheduling() and passed the state that it
|
|
181
|
+
* returned.
|
|
182
|
+
*
|
|
183
|
+
* Upstream-private; `ExplicitTxn::commit` calls it for the root transaction.
|
|
184
|
+
*/
|
|
185
|
+
commitImpl(precommitAlarmState: PrecommitAlarmState): Promise<void>;
|
|
186
|
+
/** Upstream-private; the transaction classes call it before touching the db. */
|
|
187
|
+
requireNotBroken(): void;
|
|
188
|
+
get(key: Key, _options?: ReadOptions): Value | undefined;
|
|
189
|
+
getMultiple(keys: readonly Key[], _options?: ReadOptions): GetResultList;
|
|
190
|
+
getAlarm(_options?: ReadOptions): number | null;
|
|
191
|
+
list(begin: Key, end: Key | undefined, limit: number | undefined, _options?: ReadOptions): GetResultList;
|
|
192
|
+
listReverse(begin: Key, end: Key | undefined, limit: number | undefined, _options?: ReadOptions): GetResultList;
|
|
193
|
+
put(key: Key, value: Value, options?: WriteOptions): void;
|
|
194
|
+
putMultiple(pairs: readonly KeyValuePair[], options?: WriteOptions): void;
|
|
195
|
+
delete(key: Key, options?: WriteOptions): boolean;
|
|
196
|
+
deleteMultiple(keys: readonly Key[], options?: WriteOptions): number;
|
|
197
|
+
setAlarm(newAlarmTime: number | null, options?: WriteOptions): void;
|
|
198
|
+
startTransaction(): ActorCacheTransaction;
|
|
199
|
+
deleteAll(options?: WriteOptions, deleteAllOptions?: DeleteAllOptions): DeleteAllResults;
|
|
200
|
+
evictStale(_now: number): undefined;
|
|
201
|
+
shutdown(exception?: unknown): void;
|
|
202
|
+
armAlarmHandler(scheduledTime: number, currentTime: number): ArmAlarmResult;
|
|
203
|
+
cancelDeferredAlarmDeletion(): void;
|
|
204
|
+
abandonAlarm(scheduledTime: number): Promise<number | null>;
|
|
205
|
+
/**
|
|
206
|
+
* This implements sync().
|
|
207
|
+
*
|
|
208
|
+
* sync() should wait for ALL writes (both confirmed and unconfirmed) that are
|
|
209
|
+
* outstanding at the time sync() is called. We use lastCommit which keeps track
|
|
210
|
+
* of the most recent commit to be formed. We join with the outputGate because
|
|
211
|
+
* there are a lot of edge cases where we break the output gate and it's easiest
|
|
212
|
+
* to catch all of those instances here rather than updating everything to also
|
|
213
|
+
* break lastCommit.
|
|
214
|
+
*/
|
|
215
|
+
onNoPendingFlush(): Promise<void>;
|
|
216
|
+
/**
|
|
217
|
+
* This is an ersatz implementation that's good enough for local dev with D1's
|
|
218
|
+
* Session API.
|
|
219
|
+
*
|
|
220
|
+
* The returned bookmark satisfies the properties that D1 cares about:
|
|
221
|
+
*
|
|
222
|
+
* * Later bookmarks sort after earlier bookmarks. We implement this by
|
|
223
|
+
* incrementing the bookmark whenever getCurrentBookmark() is called.
|
|
224
|
+
*
|
|
225
|
+
* * Bookmarks from the current session sort after bookmarks from previous
|
|
226
|
+
* sessions. We implement this by saving an ersatz bookmark in the metadata
|
|
227
|
+
* table.
|
|
228
|
+
*
|
|
229
|
+
* This is NOT the point-in-time-recovery bookmark API, which is a substrate
|
|
230
|
+
* boundary: it needs nothing the substrate lacks, which is exactly why Section
|
|
231
|
+
* 3 ported `getLocalDevelopmentBookmark`/`setLocalDevelopmentBookmark`.
|
|
232
|
+
*/
|
|
233
|
+
getCurrentBookmark(): Promise<string>;
|
|
234
|
+
waitForBookmark(_bookmark: string): Promise<void>;
|
|
235
|
+
getBookmarkForTime(_timestamp: number): Promise<string>;
|
|
236
|
+
onNextSessionRestoreBookmark(_bookmark: string): Promise<string>;
|
|
237
|
+
ensureReplicas(): void;
|
|
238
|
+
disableReplicas(): void;
|
|
239
|
+
configureReadReplication(_enabled: boolean): Promise<void>;
|
|
240
|
+
/**
|
|
241
|
+
* ← `DurableObjectStorage::transactionSync` (`actor-state.c++:713-753`).
|
|
242
|
+
*
|
|
243
|
+
* One layer lower than upstream, which is where `util/sqlite.ts` already
|
|
244
|
+
* records it belongs: the savepoint depth counter and `notifyWrite` both live
|
|
245
|
+
* here, and `api/actor-state.ts`'s `transactionSync` becomes a one-line forward
|
|
246
|
+
* the way `blockConcurrencyWhile` already is.
|
|
247
|
+
*
|
|
248
|
+
* The nesting guard §2.4 asks for is upstream's own and is the depth-named
|
|
249
|
+
* savepoint: a second `BEGIN IMMEDIATE` is a SQLite error, a nested SAVEPOINT
|
|
250
|
+
* is not, which is why this issues savepoints and lets the implicit transaction
|
|
251
|
+
* underneath be the only `BEGIN`.
|
|
252
|
+
*
|
|
253
|
+
* The async-callback guard is ours and has no upstream twin, because upstream's
|
|
254
|
+
* `jsg::Function<jsg::JsRef<jsg::JsValue>()>` callback cannot be awaited at
|
|
255
|
+
* all: it returns a value, and a JS function that returns a promise simply has
|
|
256
|
+
* its promise ignored. Here the same mistake is silent and corrupting — the
|
|
257
|
+
* RELEASE fires at the first await and everything after it lands outside the
|
|
258
|
+
* transaction — so a thenable result is refused and the savepoint rolled back.
|
|
259
|
+
* Work the callback already started is not cancellable and keeps running; the
|
|
260
|
+
* throw is what stops it being mistaken for transactional.
|
|
261
|
+
*/
|
|
262
|
+
transactionSync<T>(callback: () => T): T;
|
|
263
|
+
}
|
|
264
|
+
/** ← `ActorSqlite::ImplicitTxn`. */
|
|
265
|
+
declare class ImplicitTxn {
|
|
266
|
+
#private;
|
|
267
|
+
constructor(parent: ActorSqlite);
|
|
268
|
+
commit(): void;
|
|
269
|
+
rollback(): void;
|
|
270
|
+
setSomeWriteConfirmed(someWriteConfirmed: boolean): void;
|
|
271
|
+
isSomeWriteConfirmed(): boolean;
|
|
272
|
+
/** ← `~ImplicitTxn`. Idempotent, because the commit path drops before and after the callback. */
|
|
273
|
+
drop(): void;
|
|
274
|
+
}
|
|
275
|
+
/** ← `ActorSqlite::ExplicitTxn`. */
|
|
276
|
+
declare class ExplicitTxn implements ActorCacheTransaction {
|
|
277
|
+
#private;
|
|
278
|
+
constructor(actorSqlite: ActorSqlite);
|
|
279
|
+
getAlarmDirty(): boolean;
|
|
280
|
+
setAlarmDirty(): void;
|
|
281
|
+
setSomeWriteConfirmed(someWriteConfirmed: boolean): void;
|
|
282
|
+
isSomeWriteConfirmed(): boolean;
|
|
283
|
+
commit(): void;
|
|
284
|
+
rollback(): void;
|
|
285
|
+
/** ← `~ExplicitTxn`. */
|
|
286
|
+
drop(): void;
|
|
287
|
+
get(key: Key, options?: ReadOptions): Value | undefined;
|
|
288
|
+
getMultiple(keys: readonly Key[], options?: ReadOptions): GetResultList;
|
|
289
|
+
getAlarm(options?: ReadOptions): number | null;
|
|
290
|
+
list(begin: Key, end: Key | undefined, limit: number | undefined, options?: ReadOptions): GetResultList;
|
|
291
|
+
listReverse(begin: Key, end: Key | undefined, limit: number | undefined, options?: ReadOptions): GetResultList;
|
|
292
|
+
put(key: Key, value: Value, options?: WriteOptions): void;
|
|
293
|
+
putMultiple(pairs: readonly KeyValuePair[], options?: WriteOptions): void;
|
|
294
|
+
delete(key: Key, options?: WriteOptions): boolean;
|
|
295
|
+
deleteMultiple(keys: readonly Key[], options?: WriteOptions): number;
|
|
296
|
+
setAlarm(newAlarmTime: number | null, options?: WriteOptions): void;
|
|
297
|
+
}
|
|
298
|
+
export {};
|