@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,201 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/server/alarm-scheduler.{h,c++}`
|
|
3
|
+
*
|
|
4
|
+
* Delivery and retry: the `_cf_ALARM` table, the watchdog arming, the queued
|
|
5
|
+
* alarm, and the retry ladder. Measured on real workerd: an alarm re-armed for
|
|
6
|
+
* `Date.now()` from inside a running handler does NOT re-enter — delivery is
|
|
7
|
+
* serialised (`enter:1, exit:1, enter:2, exit:2`). That property is load
|
|
8
|
+
* bearing; `_cf_executingScheduleRowId` upstream is safe only because of it
|
|
9
|
+
* (§2.3). Here it falls out of the queued alarm: a `setAlarm` that arrives
|
|
10
|
+
* while a handler runs is stored on the entry and started only after the run
|
|
11
|
+
* finishes (`alarm-scheduler.c++:116-124`, `:220-227`).
|
|
12
|
+
*
|
|
13
|
+
* **This is runtime-internal, and it is what a host puts behind
|
|
14
|
+
* `ActorPorts.alarms`.** Upstream wires it the same way: `ActorSqliteHooks`
|
|
15
|
+
* (`server.c++:3199-3219`) is a three-line adapter whose `scheduleRun` is
|
|
16
|
+
* `setAlarm`/`deleteAlarm` on the scheduler, and the scheduler is built once per
|
|
17
|
+
* namespace (`server.c++:2325-2350`) rather than once per actor. `hooks(actorId)`
|
|
18
|
+
* below is that adapter, so a host composes the two instead of writing its own
|
|
19
|
+
* ladder.
|
|
20
|
+
*
|
|
21
|
+
* **One deliberate divergence, and it is the table's shape.** Upstream keeps the
|
|
22
|
+
* retry ladder in memory and reloads every alarm with its counters at zero,
|
|
23
|
+
* which is right for a process that lives for hours and is a regression on a
|
|
24
|
+
* service worker Chrome evicts after seconds — see `_cf_ALARM` below and the
|
|
25
|
+
* README's divergence table. Everything else here is upstream's, line for line.
|
|
26
|
+
*
|
|
27
|
+
* Spec: §1.8, §2.6, decisions 6, 11 and 16 in
|
|
28
|
+
* docs/decisions.md.
|
|
29
|
+
*/
|
|
30
|
+
import type { AlarmOutlet } from "../io/actor-sqlite.js";
|
|
31
|
+
import type { Timer } from "../io/io-context.js";
|
|
32
|
+
import { type SqlDatabase } from "../util/sqlite.js";
|
|
33
|
+
/**
|
|
34
|
+
* ← `WorkerInterface::ALARM_RETRY_START_SECONDS` (`io/worker-interface.h:130`),
|
|
35
|
+
* re-declared as `AlarmScheduler::RETRY_START_SECONDS` (`alarm-scheduler.h:42`).
|
|
36
|
+
*
|
|
37
|
+
* "not a duration so we can left shift it" — upstream's own comment, and the
|
|
38
|
+
* reason the ladder below is a shift rather than a table.
|
|
39
|
+
*/
|
|
40
|
+
export declare const ALARM_RETRY_START_SECONDS = 2;
|
|
41
|
+
/**
|
|
42
|
+
* ← `WorkerInterface::ALARM_RETRY_MAX_TRIES` (`io/worker-interface.h:131`) /
|
|
43
|
+
* `AlarmScheduler::RETRY_MAX_TRIES` (`alarm-scheduler.h:45`).
|
|
44
|
+
*
|
|
45
|
+
* "Max number of 'valid' retry attempts, i.e the worker returned an error."
|
|
46
|
+
* It bounds `countedRetry`, NOT `backoff`: a run of failures that do not count
|
|
47
|
+
* against the limit is retried forever, and its delay is bounded by
|
|
48
|
+
* `RETRY_BACKOFF_MAX` instead.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ALARM_RETRY_MAX_TRIES = 6;
|
|
51
|
+
/**
|
|
52
|
+
* ← `AlarmScheduler::RETRY_BACKOFF_MAX` (`alarm-scheduler.h:50`).
|
|
53
|
+
*
|
|
54
|
+
* "Bound for exponential backoff when RETRY_MAX_TRIES is exceeded due to
|
|
55
|
+
* internal errors. 2 << 9 is 1024 seconds, about 17 minutes. Total time spent in
|
|
56
|
+
* retries once the backoff limit is reached is over 30 minutes."
|
|
57
|
+
*/
|
|
58
|
+
export declare const RETRY_BACKOFF_MAX = 9;
|
|
59
|
+
/**
|
|
60
|
+
* ← `AlarmScheduler::RETRY_JITTER_FACTOR` (`alarm-scheduler.h:54`).
|
|
61
|
+
*
|
|
62
|
+
* "How much jitter should be applied to retry times to avoid bundled retries
|
|
63
|
+
* overloading some common dependency between a set of failed alarms."
|
|
64
|
+
*/
|
|
65
|
+
export declare const RETRY_JITTER_FACTOR = 0.25;
|
|
66
|
+
/**
|
|
67
|
+
* ← `(AlarmScheduler::RETRY_START_SECONDS << backoff) * kj::SECONDS`
|
|
68
|
+
* (`alarm-scheduler.c++:270`), before jitter.
|
|
69
|
+
*
|
|
70
|
+
* It refuses a backoff outside `[0, RETRY_BACKOFF_MAX]` rather than shifting it,
|
|
71
|
+
* because JS's `<<` is a 32-bit operator: an unclamped counter would wrap to a
|
|
72
|
+
* zero or negative delay — a hot retry loop — instead of saturating. The ladder
|
|
73
|
+
* clamps immediately above its own call, exactly where upstream does; this is
|
|
74
|
+
* what makes that clamp load bearing rather than decorative.
|
|
75
|
+
*/
|
|
76
|
+
export declare function alarmRetryDelayMs(backoff: number): number;
|
|
77
|
+
/**
|
|
78
|
+
* ← `EventOutcome` (`io/outcome.capnp`), restricted to the values the alarm path
|
|
79
|
+
* can produce.
|
|
80
|
+
*
|
|
81
|
+
* The whole enum is a metrics type with no port — divergence 154 records that
|
|
82
|
+
* for `waitUntilStatus()` — but `runAlarm` reads one bit of it
|
|
83
|
+
* (`alarm-scheduler.c++:162`, `result.outcome != EventOutcome::OK`), so the
|
|
84
|
+
* values `ServiceWorkerGlobalScope::runAlarm` actually returns are named here
|
|
85
|
+
* and the rest are not.
|
|
86
|
+
*/
|
|
87
|
+
export type EventOutcome = "ok" | "canceled" | "script-not-found" | "exception" | "exceeded-cpu" | "unknown";
|
|
88
|
+
/**
|
|
89
|
+
* ← `WorkerInterface::AlarmResult` (`io/worker-interface.h:71-81`).
|
|
90
|
+
*
|
|
91
|
+
* Upstream defaults all three fields; here every one is required, because the
|
|
92
|
+
* producer is `server/actor-container.ts` rather than a capnp wire default and a
|
|
93
|
+
* silently-defaulted `retryCountsAgainstLimit` is the difference between an
|
|
94
|
+
* alarm that survives a broken actor and one that is abandoned.
|
|
95
|
+
*/
|
|
96
|
+
export type AlarmResult = {
|
|
97
|
+
readonly retry: boolean;
|
|
98
|
+
readonly retryCountsAgainstLimit: boolean;
|
|
99
|
+
readonly outcome: EventOutcome;
|
|
100
|
+
readonly errorDescription?: string;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* ← the `WorkerInterface` `GetActorFn` hands back
|
|
104
|
+
* (`alarm-scheduler.c++:160`, `:244`), restricted to its two alarm members.
|
|
105
|
+
*
|
|
106
|
+
* `WorkerInterface` itself has no port — it is capnp dispatch, and divergence
|
|
107
|
+
* 176 records it collapsing into the stub the transport returns — so the seam is
|
|
108
|
+
* the two methods the scheduler calls. `ActorContainer` satisfies it
|
|
109
|
+
* structurally; `deliverAlarm` is upstream's `runAlarm` under the name Section
|
|
110
|
+
* 6b already gave it.
|
|
111
|
+
*/
|
|
112
|
+
export interface AlarmTarget {
|
|
113
|
+
deliverAlarm(scheduledTime: number, retryCount: number): Promise<AlarmResult>;
|
|
114
|
+
/**
|
|
115
|
+
* ← `WorkerInterface::abandonAlarm` (`io/worker-interface.h:114`): "Returns the
|
|
116
|
+
* actor's stored alarm time if it differs from scheduledTime (i.e. the user
|
|
117
|
+
* set a new alarm), or null if the alarm was cleared or no alarm was stored."
|
|
118
|
+
*/
|
|
119
|
+
abandonAlarm(scheduledTime: number): Promise<number | null>;
|
|
120
|
+
}
|
|
121
|
+
/** ← `AlarmScheduler::GetActorFn` (`alarm-scheduler.h:56`). */
|
|
122
|
+
export type GetActorFn = (actorId: string) => AlarmTarget;
|
|
123
|
+
export type AlarmSchedulerOptions = {
|
|
124
|
+
/**
|
|
125
|
+
* ← the `const kj::Clock&` and the `kj::Timer&` upstream takes separately
|
|
126
|
+
* (`alarm-scheduler.h:58-59`). One object here because `Timer.now()` is
|
|
127
|
+
* already wall-clock milliseconds — `IoContext::now()` reads the same one —
|
|
128
|
+
* so nothing distinguishes the two. `checkTimestamp`'s re-check loop stays,
|
|
129
|
+
* because a JS timer really can fire a fraction of a millisecond early
|
|
130
|
+
* relative to the clock it is compared against.
|
|
131
|
+
*/
|
|
132
|
+
timer: Timer;
|
|
133
|
+
/**
|
|
134
|
+
* The database `_cf_ALARM` lives in — upstream's `metadata.sqlite`, one per
|
|
135
|
+
* namespace beside the per-actor files (`server.c++:2336-2346`).
|
|
136
|
+
*
|
|
137
|
+
* Already open, where upstream's constructor opens it from a vfs and a path:
|
|
138
|
+
* `SqlDatabaseProvider.open` is asynchronous and a constructor cannot await,
|
|
139
|
+
* which is the same reason `createActorContainer` is a promise.
|
|
140
|
+
*/
|
|
141
|
+
db: SqlDatabase;
|
|
142
|
+
getActor: GetActorFn;
|
|
143
|
+
/**
|
|
144
|
+
* Browser hosts can mirror the earliest durable wake onto a platform watchdog
|
|
145
|
+
* such as `chrome.alarms`. Workerd needs no such seam because its process owns
|
|
146
|
+
* the scheduler timer.
|
|
147
|
+
*/
|
|
148
|
+
projectWake?: (scheduledTime: number | null) => Promise<void> | void;
|
|
149
|
+
/**
|
|
150
|
+
* ← `std::default_random_engine`, seeded from the monotonic clock
|
|
151
|
+
* (`alarm-scheduler.c++:20-27`). A test seam on a runtime-internal class, not
|
|
152
|
+
* a substrate port: the jitter is the one part of the ladder that is
|
|
153
|
+
* deliberately not a function of its inputs.
|
|
154
|
+
*/
|
|
155
|
+
random?: () => number;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Allows scheduling alarm executions at specific times, returning a promise
|
|
159
|
+
* representing the completion of the alarm event.
|
|
160
|
+
*/
|
|
161
|
+
export declare class AlarmScheduler {
|
|
162
|
+
#private;
|
|
163
|
+
constructor(options: AlarmSchedulerOptions);
|
|
164
|
+
/**
|
|
165
|
+
* ← `getAlarm` (`alarm-scheduler.c++:84-99`), including its TODO: "Might be
|
|
166
|
+
* able to simplify AlarmScheduler somewhat, now that ActorSqlite no longer
|
|
167
|
+
* relies on it for getAlarm()?"
|
|
168
|
+
*/
|
|
169
|
+
getAlarm(actorId: string): number | null;
|
|
170
|
+
/**
|
|
171
|
+
* ← `setAlarm` (`alarm-scheduler.c++:101-127`).
|
|
172
|
+
*
|
|
173
|
+
* The `boolean` is upstream's `query.changeCount() > 0`, and it is constant
|
|
174
|
+
* true against SQLite's semantics: an `INSERT … ON CONFLICT DO UPDATE` always
|
|
175
|
+
* reports one changed row, even when the value is unchanged. No caller reads it
|
|
176
|
+
* — `ActorSqliteHooks::scheduleRun` discards it — so it is kept as upstream's
|
|
177
|
+
* shape rather than as a signal.
|
|
178
|
+
*/
|
|
179
|
+
setAlarm(actorId: string, scheduledTime: number): boolean;
|
|
180
|
+
/** ← `deleteAll` (`alarm-scheduler.c++:129-134`). */
|
|
181
|
+
deleteAll(): void;
|
|
182
|
+
/** ← `deleteAlarm` (`alarm-scheduler.c++:136-156`). */
|
|
183
|
+
deleteAlarm(actorId: string): boolean;
|
|
184
|
+
/**
|
|
185
|
+
* ← `ActorSqliteHooks` (`server.c++:3199-3219`), which is the whole of how an
|
|
186
|
+
* actor's storage engine reaches a scheduler: one adapter per actor, holding
|
|
187
|
+
* that actor's key, turning a time into `setAlarm` or `deleteAlarm`.
|
|
188
|
+
*/
|
|
189
|
+
hooks(actorId: string): AlarmOutlet;
|
|
190
|
+
/**
|
|
191
|
+
* ← `taskFailed`'s `KJ_LOG(WARNING, e)` (`alarm-scheduler.c++:289-291`), and
|
|
192
|
+
* the two other log sites in `makeAlarmTask` that report a failure and carry
|
|
193
|
+
* on (`:202`, `:285`).
|
|
194
|
+
*
|
|
195
|
+
* This package has no logger, so the exception is kept instead of written —
|
|
196
|
+
* the same treatment divergence 154 records for `waitUntilStatus()`, and for
|
|
197
|
+
* the same reason: a background failure that is neither logged nor readable is
|
|
198
|
+
* one nothing can notice.
|
|
199
|
+
*/
|
|
200
|
+
taskFailure(): unknown;
|
|
201
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `NO upstream correspondence`
|
|
3
|
+
*
|
|
4
|
+
* Generation-fenced deletion receipts, serialized subtree deletion, reference
|
|
5
|
+
* epochs. Keyed by facet id, never by an app address.
|
|
6
|
+
*
|
|
7
|
+
* **Why there is no upstream twin.** `ActorContainer::deleteFacet`
|
|
8
|
+
* (`server.c++:2642-2655`) aborts the child and then calls
|
|
9
|
+
* `directory->remove(...)` — synchronous, in-process, on a real filesystem. It
|
|
10
|
+
* cannot be interrupted between "the app asked" and "the bytes are gone", so it
|
|
11
|
+
* needs no record that it was asked. Here the same call is
|
|
12
|
+
* `FacetHost.deleteStorage`, which is asynchronous because it is the HOST's and a
|
|
13
|
+
* host's storage removal is not required to be prompt: OPFS removal is
|
|
14
|
+
* asynchronous in general, and the extension's supervisor crosses a worker to do
|
|
15
|
+
* it. `ctx.facets.delete()` is synchronous and `void` regardless — so the app's
|
|
16
|
+
* request outlives the process that has to carry it out. §2.7 records the browser
|
|
17
|
+
* host's answer to that, generation-fenced receipts, as "a genuine improvement
|
|
18
|
+
* over workerd, which has no equivalent because its facets are in-process". This
|
|
19
|
+
* is that answer, moved down into the runtime and re-keyed.
|
|
20
|
+
*
|
|
21
|
+
* In-process facets do not remove this gap. What upstream gets from them is that
|
|
22
|
+
* `kj::Directory::remove` is a synchronous call it can finish before returning.
|
|
23
|
+
* The seam here is `Promise<void>` whoever implements it, so the gap between "the
|
|
24
|
+
* app asked" and "the bytes are gone" is still real and still spans a possible
|
|
25
|
+
* teardown. The two conformance lanes happen to close that gap promptly — one
|
|
26
|
+
* through `rmSync`, one through the SAH pool's `unlink` — and the receipt is what
|
|
27
|
+
* makes a host that cannot, such as the extension's supervisor, survivable.
|
|
28
|
+
*
|
|
29
|
+
* **What changed in the move, and it is the whole of the change.** The
|
|
30
|
+
* extension's version (`offscreen/worker/host/facet-deletion.ts`) is keyed by an
|
|
31
|
+
* `AgentWorkerAddress` — a root name plus a path of `{className, name}` steps —
|
|
32
|
+
* and derives "is this address inside that subtree" by comparing path prefixes.
|
|
33
|
+
* Here the key is a `FacetId`, the small integer `server/facet-tree-index.ts`
|
|
34
|
+
* assigns, and ancestry is not derivable from it: id 7 says nothing about who its
|
|
35
|
+
* parent is. So every operation that needs a subtree is *given* one, computed
|
|
36
|
+
* from the index by the caller that already holds it, and this file has no
|
|
37
|
+
* opinion about tree shape at all. That is a smaller module, not a larger one:
|
|
38
|
+
* three of the extension's helpers — `isAgentAddressInTree`,
|
|
39
|
+
* `isAgentStorageKeyInTree`, `isAgentStorageEntryInTree` — exist only to answer
|
|
40
|
+
* the ancestry question from a string, and none of them has anything to do here.
|
|
41
|
+
*
|
|
42
|
+
* Storage is the facet-tree database rather than the actor's own, and that is
|
|
43
|
+
* load bearing: `SqliteKv::deleteAll()` calls `db.reset()`
|
|
44
|
+
* (`util/sqlite-kv.ts`), which replaces the actor's database file wholesale. A
|
|
45
|
+
* receipt recorded there would be destroyed by the very `deleteAll()` whose
|
|
46
|
+
* cascade it exists to make recoverable. It sits beside the tree index for the
|
|
47
|
+
* same reason the index does — both are facts about the tree rather than about
|
|
48
|
+
* any one actor's contents.
|
|
49
|
+
*
|
|
50
|
+
* Spec: §2.7, decision 14 in docs/decisions.md.
|
|
51
|
+
*/
|
|
52
|
+
import { type SqlDatabase } from "../util/sqlite.js";
|
|
53
|
+
import type { FacetId } from "./actor-container.js";
|
|
54
|
+
/**
|
|
55
|
+
* One recorded intent to delete a facet's storage.
|
|
56
|
+
*
|
|
57
|
+
* `generation` is the fence. A receipt is cleared only if the row still carries
|
|
58
|
+
* the generation this receipt was issued with, so a delete that was requested
|
|
59
|
+
* again while the first deletion was in flight cannot have its second request
|
|
60
|
+
* erased by the first request's completion.
|
|
61
|
+
*/
|
|
62
|
+
export type FacetDeletionReceipt = {
|
|
63
|
+
readonly id: FacetId;
|
|
64
|
+
readonly generation: number;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Parent-owned durable receipts for the synchronous `ctx.facets.delete()`
|
|
68
|
+
* boundary. The doomed child never owns its own deletion decision — it may not
|
|
69
|
+
* be running, and if it is, it is the thing being destroyed.
|
|
70
|
+
*/
|
|
71
|
+
export declare class FacetDeletionReceiptStore {
|
|
72
|
+
#private;
|
|
73
|
+
constructor(db: SqlDatabase);
|
|
74
|
+
/** Bumps the generation for `id` and returns the receipt naming it. */
|
|
75
|
+
record(id: FacetId): FacetDeletionReceipt;
|
|
76
|
+
read(id: FacetId): FacetDeletionReceipt | undefined;
|
|
77
|
+
/**
|
|
78
|
+
* Every outstanding receipt, oldest facet first, for boot-time replay.
|
|
79
|
+
*
|
|
80
|
+
* The `ORDER BY` cannot be shown to matter and is kept anyway: `facet_id` is
|
|
81
|
+
* an `INTEGER PRIMARY KEY`, which is the rowid, so both backends scan the
|
|
82
|
+
* table in that order with or without it. Removing it survives the whole
|
|
83
|
+
* suite — a mutant that no test can kill, because killing it needs a SQLite
|
|
84
|
+
* that returns rows out of rowid order, and nothing this package can reach
|
|
85
|
+
* does. Relying on the scan order rather than saying so is the kind of thing
|
|
86
|
+
* that is right until a schema change makes it silently wrong.
|
|
87
|
+
*/
|
|
88
|
+
list(): FacetDeletionReceipt[];
|
|
89
|
+
/**
|
|
90
|
+
* Clears the receipt if and only if it is still the one that was issued.
|
|
91
|
+
* Returns false when a newer request has superseded it, which is the whole
|
|
92
|
+
* point of the generation.
|
|
93
|
+
*/
|
|
94
|
+
clear(receipt: FacetDeletionReceipt): boolean;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Replays and generation-fences one parent's durable child-deletion receipts.
|
|
98
|
+
*
|
|
99
|
+
* Recording is synchronous — that is the boundary `ctx.facets.delete()` has to
|
|
100
|
+
* hold — and only the physical deletion crosses an async boundary. A second
|
|
101
|
+
* delete of the same facet while the first is in flight records a newer
|
|
102
|
+
* generation and queues behind it rather than racing it, so a facet cannot be
|
|
103
|
+
* half-deleted by two overlapping attempts.
|
|
104
|
+
*/
|
|
105
|
+
export declare class FacetDeletionController {
|
|
106
|
+
#private;
|
|
107
|
+
constructor(receipts: FacetDeletionReceiptStore, deleteSubtree: (receipt: FacetDeletionReceipt) => Promise<void>);
|
|
108
|
+
/**
|
|
109
|
+
* Record the intent durably now, then carry it out after the caller's actor
|
|
110
|
+
* ordering has settled. The record is synchronous even when the barrier is
|
|
111
|
+
* still pending.
|
|
112
|
+
*/
|
|
113
|
+
delete(id: FacetId, waitBeforeDelete?: Promise<unknown>): Promise<void>;
|
|
114
|
+
/** Carry out whatever is still recorded for `id`, until nothing is. */
|
|
115
|
+
flush(id: FacetId): Promise<void>;
|
|
116
|
+
/** ← boot. Every receipt a previous session left behind is carried out before the actor runs. */
|
|
117
|
+
recoverAll(): Promise<void>;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Serializes physical subtree deletion while retaining subtree-aware waits.
|
|
121
|
+
*
|
|
122
|
+
* Two deletions that overlap in the tree must not run at once: the inner one
|
|
123
|
+
* would be removing files the outer one is walking. Serializing every deletion
|
|
124
|
+
* is the simplest thing that is correct, and deletion is not on any hot path.
|
|
125
|
+
* `waitFor` is the other half — anything about to *use* a facet has to wait for
|
|
126
|
+
* a pending deletion that covers it, and the ids each pending operation covers
|
|
127
|
+
* are recorded rather than derived, because a facet id does not encode its
|
|
128
|
+
* ancestry.
|
|
129
|
+
*/
|
|
130
|
+
export declare class SerializedSubtreeDeletionQueue {
|
|
131
|
+
#private;
|
|
132
|
+
/** Runs `operation` after every operation already queued, covering `ids`. */
|
|
133
|
+
run(ids: Iterable<FacetId>, operation: () => Promise<void>): Promise<void>;
|
|
134
|
+
/** Resolves once no queued deletion covers `id`. Failures are not the waiter's to report. */
|
|
135
|
+
waitFor(id: FacetId): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Epochs make every capability captured before an ancestor abort or delete
|
|
139
|
+
* stale.
|
|
140
|
+
*
|
|
141
|
+
* The hazard this closes has no workerd equivalent for the same reason the
|
|
142
|
+
* receipts do not: upstream's `abortFacet` erases the map entry and the stub
|
|
143
|
+
* that was handed out is refcounted against a container that is now broken, so
|
|
144
|
+
* a later call on it fails by itself. Here the stub is a value that outlives the
|
|
145
|
+
* placement, so something has to be able to say "the thing you are holding was
|
|
146
|
+
* torn down". Invalidation is by explicit subtree because a `FacetId` does not
|
|
147
|
+
* encode ancestry.
|
|
148
|
+
*/
|
|
149
|
+
export declare class FacetReferenceEpochs {
|
|
150
|
+
#private;
|
|
151
|
+
/** The epoch to remember alongside a capability. */
|
|
152
|
+
capture(id: FacetId): number;
|
|
153
|
+
/** Bumps `root` and every id in `subtree`, so captures older than this call stop matching. */
|
|
154
|
+
invalidate(root: FacetId, subtree?: Iterable<FacetId>): void;
|
|
155
|
+
isCurrent(id: FacetId, epoch: number): boolean;
|
|
156
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/server/facet-tree-index.{h,c++}`
|
|
3
|
+
*
|
|
4
|
+
* Upstream's own summary: "Implements an index, stored on disk, which maps
|
|
5
|
+
* leaves of a tree to small integers in a stable way." One facet — id zero — is
|
|
6
|
+
* the root; every other facet has a parent and a name, names are unique among
|
|
7
|
+
* siblings but not globally, and each (parent, name) pair is assigned the next
|
|
8
|
+
* sequential id the first time it is seen. Deleting a facet does not release its
|
|
9
|
+
* id: recreating the same name under the same parent gets the same id back,
|
|
10
|
+
* which is what decision 14 means by "stable ids across delete-and-recreate."
|
|
11
|
+
*
|
|
12
|
+
* The whole index is held in memory, loaded at construction, because upstream
|
|
13
|
+
* assumes "the total number of facets created for a single Durable Object over
|
|
14
|
+
* its entire lifetime will never be very large" (`facet-tree-index.h:19-22`).
|
|
15
|
+
* That is what makes the file append-only, and the append-only format is what
|
|
16
|
+
* makes a torn tail safe to discard: an entry written but not synced cannot have
|
|
17
|
+
* been relied on, so a nonsensical entry ends the read and the remainder is
|
|
18
|
+
* truncated away.
|
|
19
|
+
*
|
|
20
|
+
* **The one seam: `kj::File` becomes `IndexFile`.** Upstream takes a
|
|
21
|
+
* `kj::Own<const kj::File>` and calls exactly four members on it —
|
|
22
|
+
* `readAllBytes`, `write`, `truncate` and `datasync`. There is no `kj/filesystem`
|
|
23
|
+
* port and no reason to build one for four methods, so those four become an
|
|
24
|
+
* interface and `server/` supplies it. Every method is synchronous because every
|
|
25
|
+
* method upstream is, and because `facets.get` is synchronous all the way down;
|
|
26
|
+
* both substrates can answer that (`FileSystemSyncAccessHandle` in a worker,
|
|
27
|
+
* `node:fs`'s sync family), which is the same shape the storage backends already
|
|
28
|
+
* take.
|
|
29
|
+
*
|
|
30
|
+
* **A name is its UTF-8 bytes, not its JS string.** Upstream's names are
|
|
31
|
+
* `kj::String`s, so the on-disk bytes *are* the identity and the ordering.
|
|
32
|
+
* `TextEncoder` is not injective on JS strings — every lone surrogate encodes to
|
|
33
|
+
* U+FFFD — so keying this index by the JS string would let two distinct names
|
|
34
|
+
* collide on disk and share one facet's storage file after a reload. Entries are
|
|
35
|
+
* therefore identified and ordered by their encoded bytes, and `forEachChild`
|
|
36
|
+
* reports the round-tripped name, which is what a reload would report. That also
|
|
37
|
+
* makes the ordering exact: upstream's `kj::TreeSet` orders by `kj::String`'s
|
|
38
|
+
* byte comparison, where JS `<` would order by UTF-16 code unit and disagree for
|
|
39
|
+
* any name mixing astral characters with U+E000..U+FFFF.
|
|
40
|
+
*
|
|
41
|
+
* **The scaffolding recorded a divergence here that is wrong, and it is not
|
|
42
|
+
* kept.** That header said workerd keeps one index per root actor while "our tree
|
|
43
|
+
* spans workers, so each parent indexes its direct children." Upstream's index
|
|
44
|
+
* already *is* keyed by (parent, name) — `getId(parent, name)`,
|
|
45
|
+
* `forEachChild(parentId, …)` — so a per-parent index changes nothing about what
|
|
46
|
+
* is indexed and only changes what an id *means*: upstream's ids are sequential
|
|
47
|
+
* across the whole tree, and they name storage files in one flat namespace,
|
|
48
|
+
* `<actor-id>.<facetId>.sqlite` (`server.c++:2737-2743`). Per-parent counters
|
|
49
|
+
* would mint id 1 under every parent and collide those files. Nor does the
|
|
50
|
+
* premise hold: the index is owned by the root *container*, not by an actor's
|
|
51
|
+
* worker (`server.c++:2680-2681`, `:2697`), and `FacetHost` already speaks a flat
|
|
52
|
+
* `FacetId = number` with a precomputed subtree — which only a whole-tree index
|
|
53
|
+
* can produce. Ported as upstream has it.
|
|
54
|
+
*
|
|
55
|
+
* Spec: §1.10, decision 14 in docs/decisions.md.
|
|
56
|
+
*/
|
|
57
|
+
/**
|
|
58
|
+
* ← the `kj::File` members `FacetTreeIndex` calls, and nothing else.
|
|
59
|
+
*
|
|
60
|
+
* `datasync()` is not decoration: the format's recovery story is that an entry
|
|
61
|
+
* which was written but never synced was never relied upon, so a substrate that
|
|
62
|
+
* drops it turns a torn tail from "discard and reassign" into "two facets, one
|
|
63
|
+
* id".
|
|
64
|
+
*/
|
|
65
|
+
export interface IndexFile {
|
|
66
|
+
/** ← `kj::File::readAllBytes()`. Called once, at construction. */
|
|
67
|
+
readAllBytes(): Uint8Array;
|
|
68
|
+
/** ← `kj::File::write(offset, data)`. Extends the file when it writes past the end. */
|
|
69
|
+
write(offset: number, data: Uint8Array): void;
|
|
70
|
+
/** ← `kj::File::truncate(size)`. Only ever shrinks, to drop a corrupted tail. */
|
|
71
|
+
truncate(size: number): void;
|
|
72
|
+
/** ← `kj::File::datasync()`. */
|
|
73
|
+
datasync(): void;
|
|
74
|
+
}
|
|
75
|
+
/** ← `FacetTreeIndex` (`facet-tree-index.h:50-123`). */
|
|
76
|
+
export declare class FacetTreeIndex {
|
|
77
|
+
#private;
|
|
78
|
+
/**
|
|
79
|
+
* ← the constructor (`facet-tree-index.c++:11-86`). "Construct the index,
|
|
80
|
+
* reading the given file to populate the initial index, and then arranging to
|
|
81
|
+
* append new entries to the file as needed."
|
|
82
|
+
*/
|
|
83
|
+
constructor(file: IndexFile);
|
|
84
|
+
/** ← `FacetTreeIndex::getId`. "Gets the ID for the given facet, assigning it if needed." */
|
|
85
|
+
getId(parent: number, name: string): number;
|
|
86
|
+
/**
|
|
87
|
+
* ← `FacetTreeIndex::forEachChild`. "For each child of the given parent ID,
|
|
88
|
+
* call the callback."
|
|
89
|
+
*
|
|
90
|
+
* Upstream walks a `kj::TreeSet` range, so children arrive ordered by name
|
|
91
|
+
* rather than by id; the sort here is that ordering, over the same bytes.
|
|
92
|
+
*/
|
|
93
|
+
forEachChild(parentId: number, callback: (childId: number, name: string) => void): void;
|
|
94
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `NO upstream correspondence`
|
|
3
|
+
*
|
|
4
|
+
* The substrate replacement for the two includes `server/actor-id-impl.{h,c++}`
|
|
5
|
+
* makes — `<openssl/sha.h>` and `<openssl/hmac.h>` — and nothing else. Only
|
|
6
|
+
* `actor-id-impl.ts` consumes it, which is why it sits here rather than in
|
|
7
|
+
* `util/`: `src/util/` corresponds 1:1 to `src/workerd/util/`, and workerd has no
|
|
8
|
+
* digest there. `server/facet-deletion.ts` sets the precedent for a file in this
|
|
9
|
+
* directory with no upstream twin.
|
|
10
|
+
*
|
|
11
|
+
* **Why this exists at all.** Every method on `ActorIdFactory` is synchronous
|
|
12
|
+
* (`io/actor-id.h:66-71`), and the browser exposes no synchronous digest:
|
|
13
|
+
* `crypto.subtle.digest` returns a promise, and `node:crypto` is one lane only.
|
|
14
|
+
* So the algorithm is written out. It is a **substrate** divergence in decision
|
|
15
|
+
* 16's sense and not a semantic one — the bytes are the bytes FIPS 180-4 and RFC
|
|
16
|
+
* 2104 specify, which is exactly what BoringSSL computes, so an id minted here is
|
|
17
|
+
* the same 64 hex digits workerd mints from the same unique key. That equality is
|
|
18
|
+
* the whole reason for writing the real digest rather than a cheaper keyed
|
|
19
|
+
* function the reduced threat model would have tolerated: it keeps workerd
|
|
20
|
+
* available as an oracle for ids, where an invented construction would have made
|
|
21
|
+
* every future id question original research.
|
|
22
|
+
*
|
|
23
|
+
* No dependency was added. Nothing in the workspace ships a synchronous SHA-256,
|
|
24
|
+
* and the catalog's `crypto-browserify` is a CommonJS bundler shim for the
|
|
25
|
+
* extension that would not typecheck under this package's `WebWorker`-only lib.
|
|
26
|
+
*/
|
|
27
|
+
/** ← `SHA256_DIGEST_LENGTH`. */
|
|
28
|
+
export declare const SHA256_DIGEST_LENGTH = 32;
|
|
29
|
+
/** ← `SHA256(data, length, out)`. FIPS 180-4 §6.2. */
|
|
30
|
+
export declare function sha256(message: Uint8Array): Uint8Array;
|
|
31
|
+
/**
|
|
32
|
+
* ← `HMAC(EVP_sha256(), key, keyLength, data, dataLength, out, &outLength)`.
|
|
33
|
+
* RFC 2104.
|
|
34
|
+
*
|
|
35
|
+
* Upstream's comment on why a MAC is used for something that is not
|
|
36
|
+
* authentication: "We're using HMAC as a keyed hash here, not actually for
|
|
37
|
+
* authentication, but it works" (`actor-id-impl.c++:74-75`).
|
|
38
|
+
*/
|
|
39
|
+
export declare function hmacSha256(key: Uint8Array, message: Uint8Array): Uint8Array;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `NO upstream correspondence (capnweb adaptation)`
|
|
3
|
+
*
|
|
4
|
+
* The one door onto a capnweb session, so that decision 18's identity graft
|
|
5
|
+
* cannot be skipped by establishing one some other way.
|
|
6
|
+
*
|
|
7
|
+
* The `RpcTarget` identity graft lives here because the guarantee belongs to
|
|
8
|
+
* the call that establishes a session, not to whichever sibling module happened
|
|
9
|
+
* to run a side effect first. It is re-applied for every session and is
|
|
10
|
+
* idempotent.
|
|
11
|
+
*
|
|
12
|
+
* **What this deliberately is not.** It is not a transport abstraction and takes
|
|
13
|
+
* no options capnweb does not: a lane or a host that needs
|
|
14
|
+
* `newWebSocketRpcSession` instead should call `reconcileRpcTargetIdentity()`
|
|
15
|
+
* itself and say so, rather than growing this into a second capnweb API. The
|
|
16
|
+
* `MessagePort` form is the only one this substrate uses — the extension's
|
|
17
|
+
* offscreen↔worker hop and the browser lane's page↔actor and page↔alarms hops are
|
|
18
|
+
* all `newMessagePortRpcSession` — and a port carries structured clone, which is
|
|
19
|
+
* what makes capnweb's `structuredClonable` encoding level available.
|
|
20
|
+
*
|
|
21
|
+
* Spec: decision 18 in docs/decisions.md.
|
|
22
|
+
*/
|
|
23
|
+
import { type RpcStub } from "capnweb";
|
|
24
|
+
/** Make the declared Workers RpcTarget recognizable to capnweb by reference. */
|
|
25
|
+
export declare function reconcileRpcTargetIdentity(): void;
|
|
26
|
+
/**
|
|
27
|
+
* Establish a capnweb session over a `MessagePort`, with the `RpcTarget`
|
|
28
|
+
* identity reconciled first.
|
|
29
|
+
*
|
|
30
|
+
* `localMain` is what the peer reaches; the returned stub is what the peer
|
|
31
|
+
* exposed. Both ends call this — a session is symmetric — and either side may
|
|
32
|
+
* omit its main when it exports nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function newRpcSession<T = unknown>(port: MessagePort, localMain?: unknown): RpcStub<T>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/util/sqlite-kv.{h,c++}`
|
|
3
|
+
*
|
|
4
|
+
* KV storage on top of SQLite, for Durable Object storage.
|
|
5
|
+
*
|
|
6
|
+
* The table is named `_cf_KV`. The naming is designed so that if the
|
|
7
|
+
* application is allowed to perform direct SQL queries, we can block it from
|
|
8
|
+
* accessing any table prefixed with `_cf_`.
|
|
9
|
+
*
|
|
10
|
+
* This layer is bytes in, bytes out, exactly as upstream is. The structured
|
|
11
|
+
* value encoding happens above it, in `api/actor-state.ts`, which is where
|
|
12
|
+
* upstream V8-serializes.
|
|
13
|
+
*
|
|
14
|
+
* Three translations, each of them forced:
|
|
15
|
+
*
|
|
16
|
+
* - `get`'s callback exists upstream to avoid copying bytes out of a live
|
|
17
|
+
* sqlite row. Our backends have already materialised the row by the time we
|
|
18
|
+
* see it, so there is no copy to avoid and the value is returned.
|
|
19
|
+
* - `delete_` is spelled `delete` — the C++ name carries a trailing underscore
|
|
20
|
+
* only because `delete` is a keyword there.
|
|
21
|
+
* - Upstream's `Uninitialized` / `Initialized` pair exists solely to hold
|
|
22
|
+
* thirteen `SqliteDatabase::Statement`s. Prepared statements are not part of
|
|
23
|
+
* our backend seam (each `exec` prepares), so the pair collapses into the
|
|
24
|
+
* `tableCreated` flag it already sits beside. The statements survive as
|
|
25
|
+
* `STMT` below: same names, same order, SQL copied verbatim, so the
|
|
26
|
+
* correspondence a reader needs is intact.
|
|
27
|
+
*
|
|
28
|
+
* Not ported: `SqliteKvRegulator`. Its remaining job is `shouldAddQueryStats`,
|
|
29
|
+
* which is row-count billing; neither backend exposes those counters.
|
|
30
|
+
*
|
|
31
|
+
* Spec: §2.4 in docs/decisions.md.
|
|
32
|
+
*/
|
|
33
|
+
import { type ResetListener, type SqliteDatabase } from "./sqlite.js";
|
|
34
|
+
export type KeyPtr = string;
|
|
35
|
+
export type ValuePtr = Uint8Array;
|
|
36
|
+
/** ← `SqliteKv::Order`. */
|
|
37
|
+
export type Order = "FORWARD" | "REVERSE";
|
|
38
|
+
/** ← `SqliteKv::WriteOptions`. */
|
|
39
|
+
export type WriteOptions = {
|
|
40
|
+
allowUnconfirmed?: boolean;
|
|
41
|
+
};
|
|
42
|
+
/** ← `SqliteKv::ListCursor::KeyValuePair`, and the shape `put(pairs)` iterates. */
|
|
43
|
+
export type KeyValuePair = {
|
|
44
|
+
key: KeyPtr;
|
|
45
|
+
value: ValuePtr;
|
|
46
|
+
};
|
|
47
|
+
export declare class SqliteKv implements ResetListener {
|
|
48
|
+
#private;
|
|
49
|
+
constructor(db: SqliteDatabase);
|
|
50
|
+
/**
|
|
51
|
+
* Search for a match for the given key. Returns the value if found, undefined
|
|
52
|
+
* if not.
|
|
53
|
+
*/
|
|
54
|
+
get(key: KeyPtr): ValuePtr | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Search for all known keys and values in a range. `end` and `limit` can be
|
|
57
|
+
* undefined to request no constraint be enforced.
|
|
58
|
+
*
|
|
59
|
+
* With a callback, calls it for each row seen and returns the count. Without
|
|
60
|
+
* one, returns a cursor which can be iterated one at a time.
|
|
61
|
+
*/
|
|
62
|
+
list(begin: KeyPtr, end: KeyPtr | undefined, limit: number | undefined, order: Order): SqliteKvListCursor;
|
|
63
|
+
list(begin: KeyPtr, end: KeyPtr | undefined, limit: number | undefined, order: Order, callback: (key: KeyPtr, value: ValuePtr) => void): number;
|
|
64
|
+
/** Store a value into the table, or atomically store multiple values. */
|
|
65
|
+
put(key: KeyPtr, value: ValuePtr, options?: WriteOptions): void;
|
|
66
|
+
put(pairs: Iterable<KeyValuePair>, options: WriteOptions): void;
|
|
67
|
+
/** Delete the key and return whether it was matched. */
|
|
68
|
+
delete(key: KeyPtr, options?: WriteOptions): boolean;
|
|
69
|
+
deleteAll(): number;
|
|
70
|
+
/** ResetListener interface: we'll need to recreate the table on the next operation. */
|
|
71
|
+
beforeSqliteReset(): void;
|
|
72
|
+
/** Called by a cursor that has run out of rows, mirroring `~ListCursor::State`. */
|
|
73
|
+
releaseCursor(cursor: SqliteKvListCursor): void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* ← `SqliteKv::ListCursor`.
|
|
77
|
+
*
|
|
78
|
+
* Upstream's iterates a live sqlite statement, which is why only one may be
|
|
79
|
+
* open at a time and why a new `list()` cancels the previous cursor. Our rows
|
|
80
|
+
* arrive materialised, so nothing forces that constraint — it is kept because
|
|
81
|
+
* `wasCanceled()` is part of the contract above this layer, and a cursor whose
|
|
82
|
+
* cancellation depended on the substrate would make the browser and Node lanes
|
|
83
|
+
* disagree. What we do lose is streaming: an unbounded `list()` reads the whole
|
|
84
|
+
* range into memory, where upstream reads a row at a time.
|
|
85
|
+
*/
|
|
86
|
+
export declare class SqliteKvListCursor {
|
|
87
|
+
#private;
|
|
88
|
+
constructor(parent: SqliteKv | null, rows: readonly (readonly unknown[])[] | null);
|
|
89
|
+
next(): KeyValuePair | undefined;
|
|
90
|
+
forEach(callback: (key: KeyPtr, value: ValuePtr) => void): number;
|
|
91
|
+
/**
|
|
92
|
+
* If true, the cursor was canceled due to a new list() operation starting.
|
|
93
|
+
* Only one list() is allowed at a time.
|
|
94
|
+
*/
|
|
95
|
+
wasCanceled(): boolean;
|
|
96
|
+
/** Called by `SqliteKv` only. */
|
|
97
|
+
cancel(): void;
|
|
98
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ← workerd `src/workerd/util/sqlite-metadata.{h,c++}`
|
|
3
|
+
*
|
|
4
|
+
* A simple metadata kv storage and cache on top of SQLite. Currently used to
|
|
5
|
+
* store:
|
|
6
|
+
*
|
|
7
|
+
* - Durable Object alarm times (hardcoded as key = 1);
|
|
8
|
+
* - a local development bookmark used to simulate the getCurrentBookmark API
|
|
9
|
+
* used by D1 (hardcoded as key = 2), not used in production.
|
|
10
|
+
*
|
|
11
|
+
* The table is named `_cf_METADATA`. The naming is designed so that if the
|
|
12
|
+
* application is allowed to perform direct SQL queries, we can block it from
|
|
13
|
+
* accessing any table prefixed with `_cf_`.
|
|
14
|
+
*
|
|
15
|
+
* Times are milliseconds, not nanoseconds. Upstream stores
|
|
16
|
+
* `(t - UNIX_EPOCH) / kj::NANOSECONDS` as an int64. A JS number runs out of
|
|
17
|
+
* integer precision 104 days into the epoch at nanosecond scale, so storing
|
|
18
|
+
* what upstream stores would silently round every alarm. Milliseconds are also
|
|
19
|
+
* what every caller above this already uses.
|
|
20
|
+
*
|
|
21
|
+
* The local-development bookmark IS ported, which the package's bookmark
|
|
22
|
+
* substrate boundary might seem to rule out. It does not: that boundary is
|
|
23
|
+
* `getCurrentBookmark` / `getBookmarkForTime` / `onNextSessionRestoreBookmark`,
|
|
24
|
+
* which need point-in-time recovery from the storage engine. Key 2 is an
|
|
25
|
+
* integer in a row, and D1 uses it locally precisely because it needs nothing.
|
|
26
|
+
*
|
|
27
|
+
* Spec: §1.8, §2.6 in docs/decisions.md.
|
|
28
|
+
*/
|
|
29
|
+
import { type ResetListener, type SqliteDatabase } from "./sqlite.js";
|
|
30
|
+
export declare class SqliteMetadata implements ResetListener {
|
|
31
|
+
#private;
|
|
32
|
+
constructor(db: SqliteDatabase);
|
|
33
|
+
/** Return currently set alarm time, or null. */
|
|
34
|
+
getAlarm(): number | null;
|
|
35
|
+
/**
|
|
36
|
+
* Sets current alarm time, or null. Returns true if the value changed, false
|
|
37
|
+
* if it was already set to the same value.
|
|
38
|
+
*/
|
|
39
|
+
setAlarm(currentTime: number | null, allowUnconfirmed: boolean): boolean;
|
|
40
|
+
/** Return the current local development bookmark, or null if none has been set. */
|
|
41
|
+
getLocalDevelopmentBookmark(): number | null;
|
|
42
|
+
/** Set the current ersatz bookmark. */
|
|
43
|
+
setLocalDevelopmentBookmark(bookmark: number): void;
|
|
44
|
+
/** ResetListener interface: we'll need to recreate the table on the next operation. */
|
|
45
|
+
beforeSqliteReset(): void;
|
|
46
|
+
}
|