@mcp-b/do-runtime 0.5.0 → 0.6.1
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 +18 -0
- package/README.md +30 -3
- package/dist/browser.js +55 -0
- package/dist/browser.js.map +1 -0
- package/dist/chunks/{io-context-RmmjNtwm.js → io-context-BBgKEsdR.js} +12 -3
- package/dist/chunks/{io-context-RmmjNtwm.js.map → io-context-BBgKEsdR.js.map} +1 -1
- package/dist/chunks/web-socket-W63BdFn5.js +2574 -0
- package/dist/chunks/web-socket-W63BdFn5.js.map +1 -0
- package/dist/conformance/host.d.ts +16 -2
- package/dist/conformance.js.map +1 -1
- package/dist/gate.js +1 -1
- package/dist/index.js +183 -2310
- package/dist/index.js.map +1 -1
- package/dist/src/api/actor-state.d.ts +30 -21
- package/dist/src/api/global-scope.d.ts +12 -3
- package/dist/src/api/web-socket.d.ts +88 -73
- package/dist/src/browser.d.ts +15 -0
- package/dist/src/index.d.ts +6 -4
- package/dist/src/io/io-context.d.ts +1 -0
- package/dist/src/server/actor-container.d.ts +16 -2
- package/dist/src/server/hibernation-mirror.d.ts +16 -0
- package/package.json +5 -1
package/dist/index.js
CHANGED
|
@@ -1,536 +1,9 @@
|
|
|
1
|
-
import { a as hasUserErrorDetail,
|
|
1
|
+
import { a as hasUserErrorDetail, d as tryCurrentSlice, f as CanceledError, i as captureGateStack, m as OutputGate, n as IoContext, o as isExceptionFromInputGateBroken, p as InputGate, r as atCheckpointEnd, s as requireInputLock, t as BrokenActorError, u as tryCurrentIoContext } from "./chunks/io-context-BBgKEsdR.js";
|
|
2
|
+
import { _ as DurableObjectClass, a as installWebSocketGlobals, c as DurableObjectStorage, d as LoopbackColoLocalActorNamespace, f as LoopbackDurableObjectClass, g as ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE, h as asLoopbackDurableObjectClass, i as acceptWebSocket, l as FACET_NAME_MAX_LENGTH, m as LoopbackServiceStub, n as HibernatableWebSocketRegistry, o as markWebSocketUsed, p as LoopbackDurableObjectNamespace, r as RuntimeWebSocketRequestResponsePair, s as DurableObjectState, t as ALREADY_ACCEPTED_MESSAGE, u as FACET_TREE_MAX_DEPTH, v as DurableObjectId, y as DurableObjectNamespace } from "./chunks/web-socket-W63BdFn5.js";
|
|
2
3
|
import { RpcTarget as RpcTarget$1 } from "./cloudflare-workers.js";
|
|
3
4
|
import { a as SqliteDatabase, c as getText, l as hasCurrentSqliteTable, o as getBlob, s as getInt64, t as ensureRuntimeStorageVersion, u as isNull } from "./chunks/sqlite-migrations-DsWmLP_B.js";
|
|
4
5
|
import { ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, AlarmScheduler, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, alarmRetryDelayMs } from "./server/alarm-scheduler.js";
|
|
5
|
-
import { deserialize, serialize } from "@ungap/structured-clone";
|
|
6
6
|
import { RpcTarget, newMessagePortRpcSession } from "capnweb";
|
|
7
|
-
//#region src/api/actor.ts
|
|
8
|
-
/**
|
|
9
|
-
* ← the `[1, 2048]` bound in `ColoLocalActorNamespace::get`.
|
|
10
|
-
*
|
|
11
|
-
* Upstream compares `actorId.size()`, which for a `kj::String` is **bytes**, so
|
|
12
|
-
* this is measured in UTF-8 bytes rather than in UTF-16 code units. That costs
|
|
13
|
-
* one `TextEncoder` pass and buys an exact match on a bound a caller can hit.
|
|
14
|
-
*/
|
|
15
|
-
var MAX_COLO_LOCAL_ACTOR_ID_BYTES = 2048;
|
|
16
|
-
/**
|
|
17
|
-
* Upstream never faces this: JSG unwraps a `jsg::Ref<DurableObjectId>` parameter
|
|
18
|
-
* and throws a `TypeError` before the method body runs, so `getInner()` cannot be
|
|
19
|
-
* reached on something that is not one. Here the parameter type is
|
|
20
|
-
* workers-types' structural `DurableObjectId` interface, which any object with a
|
|
21
|
-
* `toString` and an `equals` satisfies — so the unwrap has to be written, and it
|
|
22
|
-
* fails closed rather than guessing at the string form.
|
|
23
|
-
*/
|
|
24
|
-
var FOREIGN_ACTOR_ID_MESSAGE$1 = "This DurableObjectId was not created by this runtime, so its underlying actor id cannot be read. Ids must come from newUniqueId(), idFromName() or idFromString() on a DurableObjectNamespace.";
|
|
25
|
-
/** Substrate boundary: `jsg::Serializer`, `Frankenvalue` and channel tokens have no port. */
|
|
26
|
-
var ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE = "DurableObjectClass cannot be serialized in this runtime: upstream writes a channel token through jsg::Serializer and Frankenvalue, and neither the token nor the serializer has an equivalent here.";
|
|
27
|
-
/**
|
|
28
|
-
* Replication is a named substrate boundary (`io/actor-cache.ts`), so no request
|
|
29
|
-
* this file builds asks for replica routing. Upstream reads
|
|
30
|
-
* `FeatureFlags::get(js).getReplicaRouting()` here.
|
|
31
|
-
*/
|
|
32
|
-
var ENABLE_REPLICA_ROUTING = false;
|
|
33
|
-
/**
|
|
34
|
-
* ← `ColoLocalActorNamespace` (`actor.h:25-37`). "A capability to an ephemeral
|
|
35
|
-
* Actor namespace."
|
|
36
|
-
*/
|
|
37
|
-
var ColoLocalActorNamespace = class {
|
|
38
|
-
#channel;
|
|
39
|
-
constructor(channel) {
|
|
40
|
-
this.#channel = channel;
|
|
41
|
-
}
|
|
42
|
-
/** ← `ColoLocalActorNamespace::get` (`actor.c++:116-129`). */
|
|
43
|
-
get(actorId) {
|
|
44
|
-
const bytes = utf8Length(actorId);
|
|
45
|
-
if (!(bytes > 0 && bytes <= 2048)) throw new TypeError(`Actor ID length must be in the range [1, ${MAX_COLO_LOCAL_ACTOR_ID_BYTES}].`);
|
|
46
|
-
return this.#channel.getColoLocalActor({ actorId });
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
var textEncoder$1 = new TextEncoder();
|
|
50
|
-
/** `kj::String::size()` is bytes; `String.prototype.length` is UTF-16 code units. */
|
|
51
|
-
function utf8Length(value) {
|
|
52
|
-
return textEncoder$1.encode(value).length;
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* ← `DurableObjectId` (`actor.h:42-84`). "DurableObjectId type seen by
|
|
56
|
-
* JavaScript."
|
|
57
|
-
*
|
|
58
|
-
* `name` and `jurisdiction` are read from the inner id **once, at construction**,
|
|
59
|
-
* where upstream's are `JSG_READONLY_INSTANCE_PROPERTY`s that re-read it on every
|
|
60
|
-
* access. That is not a preference: `@cloudflare/workers-types` declares both
|
|
61
|
-
* `readonly name?: string`, and under `exactOptionalPropertyTypes` a getter
|
|
62
|
-
* returning `string | undefined` does not satisfy an optional `string`. An own
|
|
63
|
-
* property assigned only when the value exists does, and it is what keeps this
|
|
64
|
-
* class assignable to the interface with no cast (§2.4). The one behaviour lost
|
|
65
|
-
* is `ActorIdImpl::clearName()` (`server/actor-id-impl.h`) taking effect on an
|
|
66
|
-
* already-wrapped id — a `server/`-internal that runs before the id reaches JS.
|
|
67
|
-
*/
|
|
68
|
-
var DurableObjectId = class {
|
|
69
|
-
#id;
|
|
70
|
-
name;
|
|
71
|
-
jurisdiction;
|
|
72
|
-
constructor(id) {
|
|
73
|
-
this.#id = id;
|
|
74
|
-
const name = id.getName();
|
|
75
|
-
if (name !== void 0) this.name = name;
|
|
76
|
-
const jurisdiction = id.getJurisdiction();
|
|
77
|
-
if (jurisdiction !== void 0) this.jurisdiction = jurisdiction;
|
|
78
|
-
}
|
|
79
|
-
/** ← `getInner()`. Not JS-visible upstream either; the outgoing factories take it. */
|
|
80
|
-
getInner() {
|
|
81
|
-
return this.#id;
|
|
82
|
-
}
|
|
83
|
-
/** "Converts to a string which can be passed back to the constructor to reproduce the same ID." */
|
|
84
|
-
toString() {
|
|
85
|
-
return this.#id.toString();
|
|
86
|
-
}
|
|
87
|
-
equals(other) {
|
|
88
|
-
return this.#id.equals(innerIdOf(other));
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
/** The unwrap JSG performs for a `jsg::Ref<DurableObjectId>` parameter. */
|
|
92
|
-
function requireDurableObjectId(id) {
|
|
93
|
-
if (id instanceof DurableObjectId) return id;
|
|
94
|
-
throw new TypeError(FOREIGN_ACTOR_ID_MESSAGE$1);
|
|
95
|
-
}
|
|
96
|
-
function innerIdOf(id) {
|
|
97
|
-
return requireDurableObjectId(id).getInner();
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* ← `DurableObject` (`actor.h:87-139`). "Stub object used to send messages to a
|
|
101
|
-
* remote durable object."
|
|
102
|
-
*
|
|
103
|
-
* Upstream's carries its whole behaviour by `JSG_INHERIT(Fetcher)` and adds
|
|
104
|
-
* exactly two readonly properties. So does this: the `Fetcher` is the transport's
|
|
105
|
-
* and everything except `id` and `name` belongs to it. `asDurableObjectStub`
|
|
106
|
-
* below is where the inheritance goes.
|
|
107
|
-
*/
|
|
108
|
-
var DurableObject = class {
|
|
109
|
-
#id;
|
|
110
|
-
#fetcher;
|
|
111
|
-
constructor(id, fetcher) {
|
|
112
|
-
this.#id = id;
|
|
113
|
-
this.#fetcher = fetcher;
|
|
114
|
-
}
|
|
115
|
-
/** ← `JSG_READONLY_INSTANCE_PROPERTY(id, getId)`. */
|
|
116
|
-
getId() {
|
|
117
|
-
return this.#id;
|
|
118
|
-
}
|
|
119
|
-
/** ← `JSG_READONLY_INSTANCE_PROPERTY(name, getName)`. */
|
|
120
|
-
getName() {
|
|
121
|
-
return this.#id.name;
|
|
122
|
-
}
|
|
123
|
-
/** The `Fetcher` upstream inherits from rather than holds. */
|
|
124
|
-
getFetcher() {
|
|
125
|
-
return this.#fetcher;
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
/**
|
|
129
|
-
* ← `js.alloc<DurableObject>(...)` plus `JSG_INHERIT(Fetcher)` plus the
|
|
130
|
-
* `JSG_TS_OVERRIDE` that renames the resource type to `DurableObjectStub`.
|
|
131
|
-
*
|
|
132
|
-
* The named assertion is the same one `io/worker.ts`'s `asFacetStub` makes and
|
|
133
|
-
* for the same reason: `DurableObjectStub<T>` is `Fetcher<T, …> & { id, name }`,
|
|
134
|
-
* and `Fetcher<T>` for an unresolved `T` is `Rpc.Provider<T, …>`, a conditional
|
|
135
|
-
* type TypeScript defers until `T` is known — where `T` is the caller's claim
|
|
136
|
-
* about a class it named, which no value can confirm. Upstream is in the same
|
|
137
|
-
* position and answers it the same way, with the parameter living only inside a
|
|
138
|
-
* `JSG_TS_OVERRIDE`.
|
|
139
|
-
*
|
|
140
|
-
* The `Proxy` is what JSG inheritance costs in JS. Two properties have to answer
|
|
141
|
-
* from the id and every other property — `fetch`, `connect`, and every RPC method
|
|
142
|
-
* name, which are the whole point of a stub — has to reach the transport with
|
|
143
|
-
* `this` still bound to it. Bound methods are memoised so `stub.foo === stub.foo`,
|
|
144
|
-
* which upstream gets for free by there being one object rather than two.
|
|
145
|
-
*/
|
|
146
|
-
function asDurableObjectStub(object) {
|
|
147
|
-
const fetcher = object.getFetcher();
|
|
148
|
-
const bound = /* @__PURE__ */ new Map();
|
|
149
|
-
return new Proxy(fetcher, {
|
|
150
|
-
get(target, property) {
|
|
151
|
-
if (property === "id") return object.getId();
|
|
152
|
-
if (property === "name") return object.getName();
|
|
153
|
-
const cached = bound.get(property);
|
|
154
|
-
if (cached !== void 0) return cached;
|
|
155
|
-
const value = Reflect.get(target, property, target);
|
|
156
|
-
if (typeof value !== "function") return value;
|
|
157
|
-
const method = value.bind(target);
|
|
158
|
-
bound.set(property, method);
|
|
159
|
-
return method;
|
|
160
|
-
},
|
|
161
|
-
has(target, property) {
|
|
162
|
-
if (property === "id" || property === "name") return true;
|
|
163
|
-
return Reflect.has(target, property);
|
|
164
|
-
},
|
|
165
|
-
ownKeys(target) {
|
|
166
|
-
return [
|
|
167
|
-
"id",
|
|
168
|
-
"name",
|
|
169
|
-
...Reflect.ownKeys(target).filter((key) => key !== "id" && key !== "name")
|
|
170
|
-
];
|
|
171
|
-
},
|
|
172
|
-
getOwnPropertyDescriptor(target, property) {
|
|
173
|
-
if (property === "id" || property === "name") return {
|
|
174
|
-
value: property === "id" ? object.getId() : object.getName(),
|
|
175
|
-
writable: false,
|
|
176
|
-
enumerable: true,
|
|
177
|
-
configurable: true
|
|
178
|
-
};
|
|
179
|
-
return Reflect.getOwnPropertyDescriptor(target, property);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* ← `DurableObjectNamespace` (`actor.h:142-291`). "Global durable object class
|
|
185
|
-
* binding type."
|
|
186
|
-
*/
|
|
187
|
-
var DurableObjectNamespace = class DurableObjectNamespace {
|
|
188
|
-
#channel;
|
|
189
|
-
#idFactory;
|
|
190
|
-
constructor(channel, idFactory) {
|
|
191
|
-
this.#channel = channel;
|
|
192
|
-
this.#idFactory = idFactory;
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* "Create a new unique ID for a durable object that will be allocated nearby
|
|
196
|
-
* the calling colo."
|
|
197
|
-
*/
|
|
198
|
-
newUniqueId(options) {
|
|
199
|
-
return new DurableObjectId(this.#idFactory.newUniqueId(options?.jurisdiction ?? void 0));
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* "Create a name-derived ID. Passing in the same `name` (to the same class)
|
|
203
|
-
* will always produce the same ID."
|
|
204
|
-
*/
|
|
205
|
-
idFromName(name) {
|
|
206
|
-
return new DurableObjectId(this.#idFactory.idFromName(name));
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* "Create a DurableObjectId from the stringified form of the ID (as produced by
|
|
210
|
-
* calling `toString()` on a durable object ID). Throws if the ID is not a
|
|
211
|
-
* 64-digit hex number, or if the ID was not originally created for this class."
|
|
212
|
-
*/
|
|
213
|
-
idFromString(id) {
|
|
214
|
-
return new DurableObjectId(this.#idFactory.idFromString(id));
|
|
215
|
-
}
|
|
216
|
-
/** "Gets a durable object by ID or creates it if it doesn't already exist." */
|
|
217
|
-
get(id, options) {
|
|
218
|
-
return this.#getImpl("GET_OR_CREATE", id, options);
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* "Gets a durable object by name or creates it if it doesn't already exist.
|
|
222
|
-
* Short for `idFromName()` followed by `get()`."
|
|
223
|
-
*/
|
|
224
|
-
getByName(name, options) {
|
|
225
|
-
return this.#getImpl("GET_OR_CREATE", this.idFromName(name), options);
|
|
226
|
-
}
|
|
227
|
-
/**
|
|
228
|
-
* "Experimental. Gets a durable object by ID if it already exists. Currently,
|
|
229
|
-
* gated for use by cloudflare only."
|
|
230
|
-
*
|
|
231
|
-
* Upstream exposes it only when the `durableObjectGetExisting` compat flag is
|
|
232
|
-
* on, and `@cloudflare/workers-types` 4.20260702.1 does not declare it. It is
|
|
233
|
-
* exposed unconditionally here, which is the current-behaviour reading every
|
|
234
|
-
* other compat flag in this file gets.
|
|
235
|
-
*/
|
|
236
|
-
getExisting(id, options) {
|
|
237
|
-
return this.#getImpl("GET_EXISTING", id, options);
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* "Creates a subnamespace with the jurisdiction hardcoded."
|
|
241
|
-
*
|
|
242
|
-
* The argument is optional because upstream's is a
|
|
243
|
-
* `jsg::Optional<kj::Maybe<kj::String>>`, so both "omitted" and "null" mean the
|
|
244
|
-
* same thing — `cloneWithJurisdiction(kj::none)`, a subnamespace with none.
|
|
245
|
-
*/
|
|
246
|
-
jurisdiction(jurisdiction) {
|
|
247
|
-
return new DurableObjectNamespace(this.#channel, this.#idFactory.cloneWithJurisdiction(jurisdiction ?? void 0));
|
|
248
|
-
}
|
|
249
|
-
/** ← `DurableObjectNamespace::getImpl` (`actor.c++:167-213`). */
|
|
250
|
-
#getImpl(mode, id, options) {
|
|
251
|
-
const durableObjectId = requireDurableObjectId(id);
|
|
252
|
-
const inner = durableObjectId.getInner();
|
|
253
|
-
if (!this.#idFactory.matchesJurisdiction(inner)) throw new TypeError("get called on jurisdictional subnamespace with an ID from a different jurisdiction");
|
|
254
|
-
let routingMode = "DEFAULT";
|
|
255
|
-
const requestedRoutingMode = options?.routingMode;
|
|
256
|
-
if (requestedRoutingMode !== void 0) {
|
|
257
|
-
if (requestedRoutingMode !== "primary-only") throw new RangeError(`unknown routingMode: ${requestedRoutingMode}`);
|
|
258
|
-
routingMode = "PRIMARY_ONLY";
|
|
259
|
-
}
|
|
260
|
-
return asDurableObjectStub(new DurableObject(durableObjectId, this.#channel.getGlobalActor({
|
|
261
|
-
id: inner,
|
|
262
|
-
locationHint: options?.locationHint,
|
|
263
|
-
mode,
|
|
264
|
-
enableReplicaRouting: ENABLE_REPLICA_ROUTING,
|
|
265
|
-
routingMode,
|
|
266
|
-
version: actorVersionOf(options?.version)
|
|
267
|
-
})));
|
|
268
|
-
}
|
|
269
|
-
};
|
|
270
|
-
/**
|
|
271
|
-
* ← `version = ActorVersion{.cohort = kj::mv(v.cohort)}` (`actor.c++:186-190`),
|
|
272
|
-
* behind `FeatureFlags::get(js).getEnableVersionApi()` which this file reads as
|
|
273
|
-
* on. A version with no cohort is still a version, which is why the empty object
|
|
274
|
-
* is not collapsed to `undefined`.
|
|
275
|
-
*/
|
|
276
|
-
function actorVersionOf(version) {
|
|
277
|
-
if (version === void 0) return void 0;
|
|
278
|
-
return version.cohort === void 0 ? {} : { cohort: version.cohort };
|
|
279
|
-
}
|
|
280
|
-
/**
|
|
281
|
-
* ← `DurableObjectClass` (`actor.h:367-393`). "DurableObjectClass represents a
|
|
282
|
-
* binding to a Durable Object class that can be used as a facet. The only use of
|
|
283
|
-
* this type is to pass to `ctx.facets.get()`."
|
|
284
|
-
*
|
|
285
|
-
* `getChannel()` takes no `IoContext` because the parameter existed to resolve the
|
|
286
|
-
* numbered-channel arm, and there is no numbered-channel arm here.
|
|
287
|
-
*/
|
|
288
|
-
var DurableObjectClass = class {
|
|
289
|
-
#channel;
|
|
290
|
-
constructor(channel) {
|
|
291
|
-
this.#channel = channel;
|
|
292
|
-
}
|
|
293
|
-
/** ← `DurableObjectClass::getChannel` (`actor.c++:232-242`). */
|
|
294
|
-
getChannel() {
|
|
295
|
-
return this.#channel;
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* ← `DurableObjectClass::serialize` (`actor.c++:244-306`). Substrate boundary.
|
|
299
|
-
*
|
|
300
|
-
* `requireAllowsTransfer()` runs first, exactly as upstream's does, so a class
|
|
301
|
-
* that refuses transfer reports that rather than the boundary — the refusal is
|
|
302
|
-
* the more specific answer and it is the one upstream would give too.
|
|
303
|
-
*/
|
|
304
|
-
serialize() {
|
|
305
|
-
this.#channel.requireAllowsTransfer();
|
|
306
|
-
throw new Error(ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE);
|
|
307
|
-
}
|
|
308
|
-
/** ← `DurableObjectClass::deserialize` (`actor.c++:308-359`). Substrate boundary. */
|
|
309
|
-
static deserialize() {
|
|
310
|
-
throw new Error(ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE);
|
|
311
|
-
}
|
|
312
|
-
};
|
|
313
|
-
//#endregion
|
|
314
|
-
//#region src/api/export-loopback.ts
|
|
315
|
-
/**
|
|
316
|
-
* ← what JSG's struct unwrapper does with a value that is not an object
|
|
317
|
-
* (`jsg/struct.h:246`). Undefined and null are **not** in that set: a struct
|
|
318
|
-
* whose every field is optional — which both option structs here are — unwraps
|
|
319
|
-
* from either as an empty struct (`jsg/struct.h:236-243`), so `ctx.exports.Foo()`
|
|
320
|
-
* is upstream's own empty-options call and not an error.
|
|
321
|
-
*/
|
|
322
|
-
var LOOPBACK_OPTIONS_NOT_AN_OBJECT_MESSAGE = "A ctx.exports binding is invoked with an options object: pass { props }, or nothing at all.";
|
|
323
|
-
/** ← what JSG does unwrapping a `jsg::JsRef<jsg::JsObject>` from a non-object. */
|
|
324
|
-
var LOOPBACK_PROPS_NOT_AN_OBJECT_MESSAGE = "`props` must be an object. Upstream unwraps it as a jsg::JsObject, which refuses anything else.";
|
|
325
|
-
/**
|
|
326
|
-
* ← `LoopbackServiceStub` (`export-loopback.h:18-109`).
|
|
327
|
-
*
|
|
328
|
-
* Upstream is a `Fetcher` on the loopback channel and holds the channel number a
|
|
329
|
-
* second time so `callImpl` can re-specialize it. Here the `Fetcher` is the
|
|
330
|
-
* transport's — `api/http.{h,c++}` is not ported — so the unspecialized stub is
|
|
331
|
-
* what the factory returns for a request with no props and no version, and the
|
|
332
|
-
* factory is the thing held twice over.
|
|
333
|
-
*/
|
|
334
|
-
var LoopbackServiceStub = class {
|
|
335
|
-
#channel;
|
|
336
|
-
#fetcher;
|
|
337
|
-
constructor(channel) {
|
|
338
|
-
this.#channel = channel;
|
|
339
|
-
this.#fetcher = channel.getSubrequestChannel({
|
|
340
|
-
props: void 0,
|
|
341
|
-
version: void 0
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
/** The `Fetcher` upstream inherits from rather than holds, as `DurableObject`'s is. */
|
|
345
|
-
getFetcher() {
|
|
346
|
-
return this.#fetcher;
|
|
347
|
-
}
|
|
348
|
-
/**
|
|
349
|
-
* ← `LoopbackServiceStub::callImpl` (`export-loopback.c++:11-29`) reached
|
|
350
|
-
* through `callWithVersion` (`export-loopback.h:53-55`), which is the callable
|
|
351
|
-
* when `enableVersionApi` is on. "Create a specialized Fetcher which can be
|
|
352
|
-
* passed over RPC."
|
|
353
|
-
*/
|
|
354
|
-
callWithVersion(options) {
|
|
355
|
-
return this.#channel.getSubrequestChannel({
|
|
356
|
-
props: requireProps(options.props),
|
|
357
|
-
version: versionRequestOf(options.version)
|
|
358
|
-
});
|
|
359
|
-
}
|
|
360
|
-
};
|
|
361
|
-
/**
|
|
362
|
-
* ← `LoopbackDurableObjectClass` (`export-loopback.h:116-148`). "Similar to
|
|
363
|
-
* LoopbackServiceStub, but for actor classes … this is used for actor classes
|
|
364
|
-
* that do *not* have any storage configured. If you simply export a class
|
|
365
|
-
* extending `DurableObject` but you don't configure storage for it, it shows up
|
|
366
|
-
* in `ctx.exports` as this type. This can be used to create a Durable Object
|
|
367
|
-
* facet."
|
|
368
|
-
*
|
|
369
|
-
* Upstream's base `DurableObjectClass` holds the channel *number*, and
|
|
370
|
-
* `getChannel(ioctx)` resolves it lazily. There is no numbered arm here, so the
|
|
371
|
-
* unspecialized channel is requested once, in the constructor — which is the same
|
|
372
|
-
* value `getActorClass(channel)` with default props would have produced.
|
|
373
|
-
*/
|
|
374
|
-
var LoopbackDurableObjectClass = class extends DurableObjectClass {
|
|
375
|
-
#channel;
|
|
376
|
-
constructor(channel) {
|
|
377
|
-
super(channel.getActorClass({ props: void 0 }));
|
|
378
|
-
this.#channel = channel;
|
|
379
|
-
}
|
|
380
|
-
/**
|
|
381
|
-
* ← `LoopbackDurableObjectClass::call` (`export-loopback.c++:31-40`). "Create a
|
|
382
|
-
* specialized DurableObjectClass which can be passed over RPC."
|
|
383
|
-
*
|
|
384
|
-
* The result is a plain `DurableObjectClass`, as `js.alloc<DurableObjectClass>`
|
|
385
|
-
* is: specializing a loopback class does not produce another loopback class.
|
|
386
|
-
*/
|
|
387
|
-
call(options) {
|
|
388
|
-
return new DurableObjectClass(this.#channel.getActorClass({ props: requireProps(options.props) }));
|
|
389
|
-
}
|
|
390
|
-
};
|
|
391
|
-
function asLoopbackDurableObjectClass(actorClass) {
|
|
392
|
-
return asCallable({
|
|
393
|
-
properties: actorClass,
|
|
394
|
-
prototype: Object.getPrototypeOf(actorClass),
|
|
395
|
-
call: (options) => actorClass.call(requireOptions(options))
|
|
396
|
-
});
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* ← `LoopbackDurableObjectNamespace` (`export-loopback.h:155-189`).
|
|
400
|
-
*
|
|
401
|
-
* Upstream: "used when the class has storage configured. In this case, we want a
|
|
402
|
-
* binding that behaves *both* like a LoopbackDurableObjectClass *and* like a
|
|
403
|
-
* DurableObjectNamespace binding. Easy enough, we'll inherit
|
|
404
|
-
* DurableObjectNamespace, but also make the binding invokable as a function like
|
|
405
|
-
* LoopbackDurableObjectClass."
|
|
406
|
-
*/
|
|
407
|
-
var LoopbackDurableObjectNamespace = class extends DurableObjectNamespace {
|
|
408
|
-
#loopbackClass;
|
|
409
|
-
constructor(channel, idFactory, loopbackClass) {
|
|
410
|
-
super(channel, idFactory);
|
|
411
|
-
this.#loopbackClass = loopbackClass;
|
|
412
|
-
}
|
|
413
|
-
/** ← `getClass()`. "getClass() accessor for use from C++ only." */
|
|
414
|
-
getClass() {
|
|
415
|
-
return this.#loopbackClass;
|
|
416
|
-
}
|
|
417
|
-
/** ← `call`. "Invoking the binding creates a specialization of the class -- not the namespace." */
|
|
418
|
-
call(options) {
|
|
419
|
-
return this.#loopbackClass.call(options);
|
|
420
|
-
}
|
|
421
|
-
};
|
|
422
|
-
/**
|
|
423
|
-
* ← `LoopbackColoLocalActorNamespace` (`export-loopback.h:192-220`). "Like
|
|
424
|
-
* LoopbackDurableObjectNamespace, but for colo-local (ephemeral) actor
|
|
425
|
-
* namespaces."
|
|
426
|
-
*/
|
|
427
|
-
var LoopbackColoLocalActorNamespace = class extends ColoLocalActorNamespace {
|
|
428
|
-
#loopbackClass;
|
|
429
|
-
constructor(channel, loopbackClass) {
|
|
430
|
-
super(channel);
|
|
431
|
-
this.#loopbackClass = loopbackClass;
|
|
432
|
-
}
|
|
433
|
-
/** ← `getClass()`. "getClass() accessor for use from C++ only." */
|
|
434
|
-
getClass() {
|
|
435
|
-
return this.#loopbackClass;
|
|
436
|
-
}
|
|
437
|
-
/** ← `call`. "Invoking the binding creates a specialization of the class -- not the namespace." */
|
|
438
|
-
call(options) {
|
|
439
|
-
return this.#loopbackClass.call(options);
|
|
440
|
-
}
|
|
441
|
-
};
|
|
442
|
-
/**
|
|
443
|
-
* ← `JSG_CALLABLE`, which makes a JSG resource object invocable while leaving
|
|
444
|
-
* every other property answering from the resource type.
|
|
445
|
-
*
|
|
446
|
-
* `properties` is where reads land, with `this` bound to it so a method reaching
|
|
447
|
-
* a private field still finds one — the same binding, memoised the same way,
|
|
448
|
-
* that `asDurableObjectStub` performs for `JSG_INHERIT`. `prototype` is what
|
|
449
|
-
* `instanceof` sees, and it is separate from `properties` because
|
|
450
|
-
* `LoopbackServiceStub` is one object with two halves: upstream's identity is the
|
|
451
|
-
* resource type while its behaviour is the inherited `Fetcher`'s.
|
|
452
|
-
*
|
|
453
|
-
* The target is an arrow function rather than a plain one because a plain
|
|
454
|
-
* function has a non-configurable own `prototype` property, which a Proxy may not
|
|
455
|
-
* hide from `ownKeys`.
|
|
456
|
-
*
|
|
457
|
-
* This is the one assertion the four producers above need, made once here. It is
|
|
458
|
-
* `asDurableObjectStub`'s, for `asDurableObjectStub`'s reason: the declared value
|
|
459
|
-
* is a `Fetcher<T>` or a `DurableObjectClass<T>` intersected with a call
|
|
460
|
-
* signature, and `T` is the caller's claim about a class it named, which no value
|
|
461
|
-
* can confirm. Upstream states the same shapes the same way, in a
|
|
462
|
-
* `JSG_TS_OVERRIDE` that no C++ value is checked against either.
|
|
463
|
-
*/
|
|
464
|
-
var INVOCATION_METHODS = /* @__PURE__ */ new Set([
|
|
465
|
-
"call",
|
|
466
|
-
"apply",
|
|
467
|
-
"bind"
|
|
468
|
-
]);
|
|
469
|
-
function asCallable(facade) {
|
|
470
|
-
const bound = /* @__PURE__ */ new Map();
|
|
471
|
-
const target = () => {
|
|
472
|
-
throw new Error("unreachable: the apply trap answers every invocation");
|
|
473
|
-
};
|
|
474
|
-
return new Proxy(target, {
|
|
475
|
-
apply(_target, _thisArg, args) {
|
|
476
|
-
return facade.call(args[0]);
|
|
477
|
-
},
|
|
478
|
-
get(target, property, receiver) {
|
|
479
|
-
if (INVOCATION_METHODS.has(property)) return Reflect.get(target, property, receiver);
|
|
480
|
-
const cached = bound.get(property);
|
|
481
|
-
if (cached !== void 0) return cached;
|
|
482
|
-
const value = Reflect.get(facade.properties, property, facade.properties);
|
|
483
|
-
if (typeof value !== "function") return value;
|
|
484
|
-
const method = value.bind(facade.properties);
|
|
485
|
-
bound.set(property, method);
|
|
486
|
-
return method;
|
|
487
|
-
},
|
|
488
|
-
has(_target, property) {
|
|
489
|
-
return Reflect.has(facade.properties, property);
|
|
490
|
-
},
|
|
491
|
-
ownKeys() {
|
|
492
|
-
return Reflect.ownKeys(facade.properties);
|
|
493
|
-
},
|
|
494
|
-
getOwnPropertyDescriptor(_target, property) {
|
|
495
|
-
const descriptor = Reflect.getOwnPropertyDescriptor(facade.properties, property);
|
|
496
|
-
if (descriptor === void 0) return void 0;
|
|
497
|
-
return {
|
|
498
|
-
...descriptor,
|
|
499
|
-
configurable: true
|
|
500
|
-
};
|
|
501
|
-
},
|
|
502
|
-
getPrototypeOf() {
|
|
503
|
-
return facade.prototype;
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
/**
|
|
508
|
-
* ← JSG's struct unwrapper (`jsg/struct.h:236-246`), for the two option structs
|
|
509
|
-
* here: every field of both is optional, so undefined and null yield an empty
|
|
510
|
-
* struct and anything that is not an object is a `TypeError`. V8's `IsObject()`
|
|
511
|
-
* is true for functions, which is why one is not refused here either.
|
|
512
|
-
*
|
|
513
|
-
* `cohort` is not checked, because upstream does not check it: JSG's `kj::String`
|
|
514
|
-
* unwrapper calls `ToString` on whatever it is given (`jsg/value.h:501-506`), so
|
|
515
|
-
* refusing a non-string here would refuse what workerd coerces. That is the same
|
|
516
|
-
* reading `api/actor.ts`'s `actorVersionOf` already takes of the same field.
|
|
517
|
-
*/
|
|
518
|
-
function requireOptions(options) {
|
|
519
|
-
if (options !== void 0 && options !== null && typeof options !== "object" && typeof options !== "function") throw new TypeError(LOOPBACK_OPTIONS_NOT_AN_OBJECT_MESSAGE);
|
|
520
|
-
return options ?? {};
|
|
521
|
-
}
|
|
522
|
-
/** ← `jsg::Optional<jsg::JsRef<jsg::JsObject>> props` — present means an object. */
|
|
523
|
-
function requireProps(props) {
|
|
524
|
-
if (props === void 0) return void 0;
|
|
525
|
-
if (props === null || typeof props !== "object" && typeof props !== "function") throw new TypeError(LOOPBACK_PROPS_NOT_AN_OBJECT_MESSAGE);
|
|
526
|
-
return props;
|
|
527
|
-
}
|
|
528
|
-
/** ← `.cohort = kj::mv(version.cohort).orDefault(kj::none)` (`export-loopback.c++:19-23`). */
|
|
529
|
-
function versionRequestOf(version) {
|
|
530
|
-
if (version === void 0) return void 0;
|
|
531
|
-
return { cohort: version.cohort ?? void 0 };
|
|
532
|
-
}
|
|
533
|
-
//#endregion
|
|
534
7
|
//#region src/api/worker-loader.ts
|
|
535
8
|
/** ← `JSG_REQUIRE(code.modules.fields.size() > 0, …)` (`worker-loader.c++:175-176`). */
|
|
536
9
|
var NO_MODULES_MESSAGE = "Dynamic Worker code must contain at least one module.";
|
|
@@ -851,7 +324,7 @@ function objectModuleContentOf(name, module) {
|
|
|
851
324
|
};
|
|
852
325
|
if (module.data !== void 0) return {
|
|
853
326
|
type: "dataModule",
|
|
854
|
-
body: copyBytes
|
|
327
|
+
body: copyBytes(module.data)
|
|
855
328
|
};
|
|
856
329
|
if (module.json !== void 0) return {
|
|
857
330
|
type: "jsonModule",
|
|
@@ -863,1642 +336,112 @@ function objectModuleContentOf(name, module) {
|
|
|
863
336
|
};
|
|
864
337
|
if (module.wasm !== void 0) return {
|
|
865
338
|
type: "wasmModule",
|
|
866
|
-
body: copyBytes
|
|
867
|
-
};
|
|
868
|
-
throw new Error("unreachable: exactly one module field is set");
|
|
869
|
-
}
|
|
870
|
-
/**
|
|
871
|
-
* ← `jsg::asBytes()` followed by `kj::heapArray<const kj::byte>(data.asPtr())`
|
|
872
|
-
* (`worker-loader.c++:231`, `:244`). The copy is the whole point — see the caller.
|
|
873
|
-
*/
|
|
874
|
-
function copyBytes$1(value) {
|
|
875
|
-
if (value instanceof ArrayBuffer) return new Uint8Array(value.slice(0));
|
|
876
|
-
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
|
|
877
|
-
throw new TypeError(NOT_BYTES_MESSAGE);
|
|
878
|
-
}
|
|
879
|
-
/**
|
|
880
|
-
* ← `Fetcher::getSubrequestChannel(ioctx)` followed by
|
|
881
|
-
* `channel->requireAllowsTransfer()` (`worker-loader.c++:125-126`, `:144-145`,
|
|
882
|
-
* `:157-158`).
|
|
883
|
-
*
|
|
884
|
-
* **The check has nothing to call, and that is a recorded divergence rather than
|
|
885
|
-
* an omission.** Upstream's `SubrequestChannel` carries `requireAllowsTransfer()`;
|
|
886
|
-
* here a `SubrequestChannel` and the `Fetcher` built over it are one object
|
|
887
|
-
* (`io/io-channels.ts`'s header, and the same collapse `api/actor.ts` and
|
|
888
|
-
* `api/export-loopback.ts` already made), and a `Fetcher` is
|
|
889
|
-
* `@cloudflare/workers-types`' interface with no such member. The one refusal it
|
|
890
|
-
* produces in the open-source runtime is `throwDynamicEntrypointTransferError`
|
|
891
|
-
* (`server.c++:167-173`), raised by `WorkerService::requireAllowsTransfer` when
|
|
892
|
-
* `isDynamic` — an isolate-host fact here, exactly as it is a `server/` fact there.
|
|
893
|
-
*
|
|
894
|
-
* Kept as a named function rather than inlined so the three call sites read as
|
|
895
|
-
* upstream's do, and so there is one place to put the check if a `Fetcher` seam
|
|
896
|
-
* ever grows one.
|
|
897
|
-
*/
|
|
898
|
-
function requireTransferableChannel(fetcher) {
|
|
899
|
-
return fetcher;
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* ← `Frankenvalue::fromJs(js, …)`, whose serializer refuses any object whose JSG
|
|
903
|
-
* resource type declares no `JSG_SERIALIZABLE` (`jsg/ser.c++:175-183`).
|
|
904
|
-
*
|
|
905
|
-
* Four types reachable from this package are in that set, and all four for one
|
|
906
|
-
* reason: `JSG_INHERIT` does not carry serializability. Upstream says so twice — in
|
|
907
|
-
* `export-loopback.h:57-58` ("Note that `LoopbackServiceStub` is intentionally NOT
|
|
908
|
-
* serializable, unlike its parent class Fetcher") and in the test that pins it,
|
|
909
|
-
* whose comment reads "it's more testing LoopbackServiceStub and that
|
|
910
|
-
* serializability is not inherited" (`worker-loader-test.js:91-92`). None of
|
|
911
|
-
* `LoopbackServiceStub`, `LoopbackDurableObjectClass`,
|
|
912
|
-
* `LoopbackDurableObjectNamespace` or `LoopbackColoLocalActorNamespace` declares
|
|
913
|
-
* `JSG_SERIALIZABLE`; every one of them *invoked* produces something that does — a
|
|
914
|
-
* `Fetcher` or a plain `DurableObjectClass` — which is exactly the distinction
|
|
915
|
-
* `worker-loader-test.js:62-107` measures, accepting the invoked form in `props`
|
|
916
|
-
* and refusing the bare binding.
|
|
917
|
-
*
|
|
918
|
-
* **This discharges the obligation the README left open on Section 7.** That row
|
|
919
|
-
* says the refusal would have to come from `src/transport/` if a `Fetcher` ever
|
|
920
|
-
* became serializable; the layer upstream refuses at is this one, because
|
|
921
|
-
* `Frankenvalue::fromJs` runs inside `getEntrypoint`, `getDurableObjectClass` and
|
|
922
|
-
* `toDynamicWorkerSource`. Putting it here makes it reachable today rather than
|
|
923
|
-
* conditional on a transport change that has not happened, and it depends on no
|
|
924
|
-
* transport fact — so `api/` still imports no transport library.
|
|
925
|
-
*
|
|
926
|
-
* The walk descends into plain objects and arrays only. Everything else is a host
|
|
927
|
-
* object: either one the substrate knows how to carry (a `Fetcher`, a
|
|
928
|
-
* `DurableObjectClass`, an `RpcTarget`) or one of the four above. Walking a host
|
|
929
|
-
* object's own keys would drive proxy traps — a stub's `get` mints an RPC import —
|
|
930
|
-
* which is a cost upstream's serializer never pays, because it asks the type rather
|
|
931
|
-
* than the value.
|
|
932
|
-
*/
|
|
933
|
-
function requireSerializableProps(root, field) {
|
|
934
|
-
const seen = /* @__PURE__ */ new Set();
|
|
935
|
-
const visit = (value, path) => {
|
|
936
|
-
if (value === null || typeof value !== "object" && typeof value !== "function") return;
|
|
937
|
-
const subject = value;
|
|
938
|
-
const refused = notSerializableType(subject);
|
|
939
|
-
if (refused !== void 0) throw new DOMException(`${notSerializableMessage(refused)} At ${path}.`, "DataCloneError");
|
|
940
|
-
if (seen.has(subject)) return;
|
|
941
|
-
seen.add(subject);
|
|
942
|
-
if (Array.isArray(subject)) {
|
|
943
|
-
subject.forEach((entry, index) => {
|
|
944
|
-
visit(entry, `${path}[${index}]`);
|
|
945
|
-
});
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
const prototype = Object.getPrototypeOf(subject);
|
|
949
|
-
if (prototype !== Object.prototype && prototype !== null) return;
|
|
950
|
-
for (const [name, entry] of Object.entries(subject)) visit(entry, `${path}.${name}`);
|
|
339
|
+
body: copyBytes(module.wasm)
|
|
951
340
|
};
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
*
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
* `ColoLocalActorNamespace` and `Fetcher` are all serializable upstream
|
|
963
|
-
* (`actor.h:389`), and it is exactly `JSG_INHERIT`'s failure to carry
|
|
964
|
-
* serializability that makes the four subclasses refuse where their bases accept.
|
|
965
|
-
*/
|
|
966
|
-
function notSerializableType(value) {
|
|
967
|
-
if (value instanceof LoopbackServiceStub) return "LoopbackServiceStub";
|
|
968
|
-
if (value instanceof LoopbackDurableObjectNamespace) return "LoopbackDurableObjectNamespace";
|
|
969
|
-
if (value instanceof LoopbackColoLocalActorNamespace) return "LoopbackColoLocalActorNamespace";
|
|
970
|
-
if (value instanceof LoopbackDurableObjectClass) return "LoopbackDurableObjectClass";
|
|
971
|
-
}
|
|
972
|
-
//#endregion
|
|
973
|
-
//#region src/api/sql.ts
|
|
974
|
-
/**
|
|
975
|
-
* ← workerd `src/workerd/api/sql.{h,c++}`
|
|
976
|
-
*
|
|
977
|
-
* `SqlStorage` and its two nested types. ~330 call sites depend on this — it is
|
|
978
|
-
* the real storage layer, not KV.
|
|
979
|
-
*
|
|
980
|
-
* Four things about the translation, in descending order of how much they cost:
|
|
981
|
-
*
|
|
982
|
-
* 1. **The cursor is materialised, not live.** Upstream's `Cursor` owns a
|
|
983
|
-
* running `SqliteDatabase::Query` and pulls one row at a time; the backend
|
|
984
|
-
* seam this package chose (`SqlDatabase.exec` → `SqlResult`) has already
|
|
985
|
-
* collected every row before a cursor exists. Everything downstream of that
|
|
986
|
-
* follows: there is no statement cache, so `CachedStatement`, the 1 MiB LRU
|
|
987
|
-
* and `reusedCachedQueryForTest` are absent with it; there is no live
|
|
988
|
-
* statement to cancel, so `Cursor::canceled` and `selfRef` — both already
|
|
989
|
-
* dead upstream, written but never assigned — have nothing to guard; and
|
|
990
|
-
* `endQuery`'s job of returning a statement to the cache is nothing here, so
|
|
991
|
-
* the counters it saves off are simply the counters. What is kept is every
|
|
992
|
-
* observable: the position is shared across `next`/`toArray`/`one`/`raw`,
|
|
993
|
-
* and a drained cursor keeps yielding done.
|
|
994
|
-
* 2. **`Cursor` and `Statement` must be constructible with no arguments**, or
|
|
995
|
-
* `SqlStorage` cannot satisfy workers-types without a cast: the interface
|
|
996
|
-
* types them `typeof SqlStorageCursor` / `typeof SqlStorageStatement`, and
|
|
997
|
-
* both are `abstract` there, so their construct signatures take none.
|
|
998
|
-
* Upstream's are unconstructible from JS for the same reason they are
|
|
999
|
-
* `abstract` in the types — JSG nested types have no JS constructor — so the
|
|
1000
|
-
* faithful shape is a constructor that refuses. `sql.Cursor` exists for
|
|
1001
|
-
* `instanceof`, which is all upstream exposes it for.
|
|
1002
|
-
* 3. **The regulator is ported whole, and that is not the whole authorizer.**
|
|
1003
|
-
* Four of its five members are here as callbacks, and none needed the
|
|
1004
|
-
* authorizer to compute anything — `isAllowedName` is a prefix test,
|
|
1005
|
-
* `isAllowedTrigger` is `return true`, `allowTransactions` throws,
|
|
1006
|
-
* `shouldAddQueryStats` is a constant. The fifth, `onError`, is not a
|
|
1007
|
-
* callback at all in this port: it is every `throw new Error(message)`
|
|
1008
|
-
* below, which is all its upstream body does with a refusal message.
|
|
1009
|
-
* For those, what the authorizer supplied was the *identifiers*:
|
|
1010
|
-
* with none, the statement text is the only source, so `exec` tokenizes it
|
|
1011
|
-
* and runs `isAllowedName` over every identifier-shaped token. That is
|
|
1012
|
-
* deliberately STRICTER than upstream — see `SQL_RESERVED_PREFIX_MESSAGE`.
|
|
1013
|
-
*
|
|
1014
|
-
* But the authorizer also makes decisions no callback ever sees, and those
|
|
1015
|
-
* do NOT arrive with the regulator: `SQLITE_ATTACH` / `SQLITE_DETACH`, the
|
|
1016
|
-
* `SQLITE_CREATE_TEMP_*` family and the `temp` schema, `SQLITE_PRAGMA`, and
|
|
1017
|
-
* the `SQLITE_CREATE_VTABLE` module list. Each is refused from the text in
|
|
1018
|
-
* `refuseUnauthorizedForms` and `requireAllowedPragmas`. `SQLITE_FUNCTION`
|
|
1019
|
-
* is the one still unported — see the README divergence row.
|
|
1020
|
-
* 4. **`ingest` stays at upstream's SQLite seam.** `SqliteDatabase.ingest()`
|
|
1021
|
-
* executes every complete statement and returns the partial tail, using the
|
|
1022
|
-
* same compiled boundaries and regulator as `exec`.
|
|
1023
|
-
*
|
|
1024
|
-
* Spec: §1.4, §2.4 in docs/decisions.md.
|
|
1025
|
-
*/
|
|
1026
|
-
/**
|
|
1027
|
-
* ← `SqlStorageRegulator::allowTransactions()`, copied verbatim. Users match on
|
|
1028
|
-
* it and it is the one regulator callback our substrate can still answer.
|
|
1029
|
-
*/
|
|
1030
|
-
var SQL_TRANSACTION_REFUSED_MESSAGE = "To execute a transaction, please use the state.storage.transaction() or state.storage.transactionSync() APIs instead of the SQL BEGIN TRANSACTION or SAVEPOINT statements. The JavaScript API is safer because it will automatically roll back on exceptions, and because it interacts correctly with Durable Objects' automatic atomic write coalescing.";
|
|
1031
|
-
/** See translation 2 in the header: the class is exposed for `instanceof` only. */
|
|
1032
|
-
var CURSOR_NOT_CONSTRUCTIBLE_MESSAGE = "Illegal invocation: SqlStorage.Cursor cannot be constructed directly. Use sql.exec().";
|
|
1033
|
-
/** Same, for the prepared-statement compatibility shim. */
|
|
1034
|
-
var STATEMENT_NOT_CONSTRUCTIBLE_MESSAGE = "Illegal invocation: SqlStorage.Statement cannot be constructed directly. Use sql.prepare().";
|
|
1035
|
-
/**
|
|
1036
|
-
* ← SQLite's own denial text, with the reason appended.
|
|
1037
|
-
*
|
|
1038
|
-
* There is no upstream string to copy here: `SqlStorageRegulator::onError` just
|
|
1039
|
-
* rethrows whatever SQLite produced, and SQLite produces `not authorized` for an
|
|
1040
|
-
* authorizer denial (`access to X.Y is prohibited` for the column-read case,
|
|
1041
|
-
* which needs a resolved identifier we do not have). The prefix is kept so that
|
|
1042
|
-
* anything matching upstream still matches; the rest is here because a bare
|
|
1043
|
-
* `not authorized` is not debuggable.
|
|
1044
|
-
*/
|
|
1045
|
-
var SQL_RESERVED_PREFIX_MESSAGE = "not authorized: a SQL statement may not name the reserved _cf_ prefix, which is where this Durable Object keeps its own KV and metadata tables.";
|
|
1046
|
-
/**
|
|
1047
|
-
* ← the five transaction-control forms `sqlite3_stmt_readonly()` reports
|
|
1048
|
-
* read-only and the authorizer reports as `SQLITE_TRANSACTION` /
|
|
1049
|
-
* `SQLITE_SAVEPOINT`. The same set `util/sqlite.ts` classifies, read here from
|
|
1050
|
-
* the leading keyword because the untrusted path has to refuse them before the
|
|
1051
|
-
* trusted one applies them.
|
|
1052
|
-
*
|
|
1053
|
-
* `;` counts as leading trivia here and in every other leading-keyword refusal
|
|
1054
|
-
* below. A statement boundary comes from the backend, and `node:sqlite` reports
|
|
1055
|
-
* an empty leading statement as part of the span it compiled: the `sourceSQL`
|
|
1056
|
-
* for `;ATTACH ...` is the whole string, so an anchor of `^\s*` would read the
|
|
1057
|
-
* keyword as `;` and let the compiled ATTACH through. The browser backend cuts
|
|
1058
|
-
* the same input at the first `;` and refuses the empty statement instead.
|
|
1059
|
-
*/
|
|
1060
|
-
var TRANSACTION_CONTROL = /^[\s;]*(?:BEGIN|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i;
|
|
1061
|
-
/** Cheap pre-test, so the tokenizer below runs only on a statement that could fail it. */
|
|
1062
|
-
var RESERVED_PREFIX_HINT = /_cf_/i;
|
|
1063
|
-
/** A SQL identifier. Double-quoted and bracketed forms are still identifiers, so only the
|
|
1064
|
-
* delimiters are stripped and the word inside is scanned like any other. */
|
|
1065
|
-
var IDENTIFIER = /[A-Za-z_][A-Za-z0-9_$]*/g;
|
|
1066
|
-
/**
|
|
1067
|
-
* Everything in a statement an identifier cannot come from: single-quoted string
|
|
1068
|
-
* literals (SQLite escapes an embedded quote by doubling it), `--` line comments,
|
|
1069
|
-
* and `/* *\/` block comments. Replaced with a space before tokenizing, so what is
|
|
1070
|
-
* left is code.
|
|
1071
|
-
*
|
|
1072
|
-
* Backtick-quoted names are NOT here: MySQL-compatible quoting produces an
|
|
1073
|
-
* identifier, exactly as the double-quoted form does.
|
|
1074
|
-
*/
|
|
1075
|
-
var NOT_CODE = /'(?:[^']|'')*'|--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
1076
|
-
/**
|
|
1077
|
-
* Comments are never code. String literals STAY, for both of this regex's callers: pragma
|
|
1078
|
-
* arguments may be quoted, and SQLite's misquoting feature reads a single-quoted string as an
|
|
1079
|
-
* identifier — `CREATE TABLE 'temp'.t(x)` really creates a temp-schema table — so the checks
|
|
1080
|
-
* that read identifier positions must still see it. `NOT_CODE` blanks literals, which is right
|
|
1081
|
-
* for the token scans and would be a bypass for these callers.
|
|
1082
|
-
*/
|
|
1083
|
-
var NOT_COMMENT = /--[^\n]*|\/\*[\s\S]*?\*\//g;
|
|
1084
|
-
/**
|
|
1085
|
-
* ← `SqlStorageRegulator` (`sql.h:15-22`, `sql.c++:141-165`), whole.
|
|
1086
|
-
*
|
|
1087
|
-
* Upstream reaches these through the SQLite authorizer while a statement is
|
|
1088
|
-
* being compiled. `exec` calls them from the statement text instead, which is
|
|
1089
|
-
* the same translation Section 3 made for the write classifier and for
|
|
1090
|
-
* transaction state.
|
|
1091
|
-
*/
|
|
1092
|
-
var SqlStorageRegulator = {
|
|
1093
|
-
/**
|
|
1094
|
-
* Upstream's body is `return !name.startsWith("_cf_")`, with an autogate that
|
|
1095
|
-
* makes the comparison case-insensitive and logs a warning until it lands. The
|
|
1096
|
-
* case-insensitive form is taken here: it is the direction upstream is moving,
|
|
1097
|
-
* and there is no logger for the warning half.
|
|
1098
|
-
*/
|
|
1099
|
-
isAllowedName(name) {
|
|
1100
|
-
return name.length < 4 || name.slice(0, 4).toLowerCase() !== "_cf_";
|
|
1101
|
-
},
|
|
1102
|
-
/** Upstream's body is `return true`. */
|
|
1103
|
-
isAllowedTrigger(_name) {
|
|
1104
|
-
return true;
|
|
1105
|
-
},
|
|
1106
|
-
/** Upstream's body is a `JSG_FAIL_REQUIRE` with this message. */
|
|
1107
|
-
allowTransactions() {
|
|
1108
|
-
throw new Error(SQL_TRANSACTION_REFUSED_MESSAGE);
|
|
1109
|
-
},
|
|
1110
|
-
/** "Bill for queries executed from JavaScript." Nothing reads it — `SqliteObserver` has no port. */
|
|
1111
|
-
shouldAddQueryStats() {
|
|
1112
|
-
return true;
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
/**
|
|
1116
|
-
* The text-level stand-in for the authorizer's `isAllowedName` calls — see
|
|
1117
|
-
* `SQL_RESERVED_PREFIX_MESSAGE` and the README row.
|
|
1118
|
-
*
|
|
1119
|
-
* Upstream refuses a *resolved identifier* that starts with `_cf_`, because it
|
|
1120
|
-
* reaches `isAllowedName` through the SQLite authorizer while the statement is
|
|
1121
|
-
* being compiled. There is no authorizer here, so this tokenizes the statement
|
|
1122
|
-
* text instead, over everything that is not a string literal or a comment —
|
|
1123
|
-
* which is the same set of characters an identifier can come from.
|
|
1124
|
-
*
|
|
1125
|
-
* **The literals were once refused too, and that was wrong.** The first draft
|
|
1126
|
-
* scanned the whole statement on the reasoning that no legitimate consumer
|
|
1127
|
-
* statement contains the token, so being stricter than upstream was the safe
|
|
1128
|
-
* direction. A retained conformance case uses `_cf_keepAliveHeartbeat` as a
|
|
1129
|
-
* bound value: real workerd accepts it, so this parser must distinguish data
|
|
1130
|
-
* from identifiers. `conformance/suite/sql.spec.ts` pins the rule: refused as a
|
|
1131
|
-
* table name and as a quoted identifier, allowed as data.
|
|
1132
|
-
*
|
|
1133
|
-
* A name that merely CONTAINS the token — `my_cf_thing` — stays allowed, because
|
|
1134
|
-
* `isAllowedName` tests a prefix.
|
|
1135
|
-
*/
|
|
1136
|
-
function requireAllowedNames(query) {
|
|
1137
|
-
if (!RESERVED_PREFIX_HINT.test(query)) return;
|
|
1138
|
-
const code = query.replace(NOT_CODE, " ");
|
|
1139
|
-
for (const [token] of code.matchAll(IDENTIFIER)) if (!SqlStorageRegulator.isAllowedName(token)) throw new Error(SQL_RESERVED_PREFIX_MESSAGE);
|
|
1140
|
-
}
|
|
1141
|
-
/** Refuse transaction control against one SQLite-decided statement boundary. */
|
|
1142
|
-
function refuseTransactionControl(statement) {
|
|
1143
|
-
const code = statement.replace(NOT_CODE, " ");
|
|
1144
|
-
if (TRANSACTION_CONTROL.test(code)) SqlStorageRegulator.allowTransactions();
|
|
1145
|
-
}
|
|
1146
|
-
/**
|
|
1147
|
-
* ← the message a `SQLITE_DENY` from the authorizer surfaces to JavaScript,
|
|
1148
|
-
* byte-identical so a caller matching on it ports unchanged.
|
|
1149
|
-
*/
|
|
1150
|
-
var SQL_NOT_AUTHORIZED_MESSAGE = "not authorized: SQLITE_AUTH";
|
|
1151
|
-
/**
|
|
1152
|
-
* The forms neither the regulator nor any callback sees: the authorizer's own
|
|
1153
|
-
* action codes, plus `VACUUM`, which SQLite itself refuses by precondition.
|
|
1154
|
-
*
|
|
1155
|
-
* Porting `SqlStorageRegulator` whole (point 3 in the file header) carried over its members, but
|
|
1156
|
-
* not the authorizer's own action codes: `SQLITE_ATTACH`, `SQLITE_DETACH`, `SQLITE_CREATE_TEMP_*`
|
|
1157
|
-
* and `SQLITE_CREATE_VTABLE` consult no callback, so nothing here refused them. Every form below
|
|
1158
|
-
* was measured on real workerd through the conformance oracle, not inferred.
|
|
1159
|
-
*
|
|
1160
|
-
* `ATTACH` and `DETACH` are also the isolation boundary rather than a fidelity detail: both
|
|
1161
|
-
* backends open a real file, so on `node:sqlite` an `ATTACH` reads another actor's database and
|
|
1162
|
-
* a `VACUUM INTO` writes anywhere the process can. The reserved-name scan does not cover it,
|
|
1163
|
-
* because that scan tokenizes the SUBMITTED statement — `other._cf_KV` is caught, and every
|
|
1164
|
-
* application table in the same attached database is not.
|
|
1165
|
-
*/
|
|
1166
|
-
var DATABASE_ATTACHMENT = /^[\s;]*(?:ATTACH|DETACH)\b/i;
|
|
1167
|
-
/**
|
|
1168
|
-
* ← the `SQLITE_CREATE_TEMP_*` denials (`sqlite.c++:1323`) and the `dbName == temp` rule
|
|
1169
|
-
* (`sqlite.c++:1073`), which permits a temp-schema database name only `READ` and `UPDATE`.
|
|
1170
|
-
* Upstream's own reason to deny them applies here unchanged: a temporary table makes SQLite
|
|
1171
|
-
* open a separate temporary file that the storage engine knows nothing about.
|
|
1172
|
-
*
|
|
1173
|
-
* Two spellings, one refusal each by its own upstream path: `CREATE TEMP TABLE t(x)` is the
|
|
1174
|
-
* keyword and hits the action codes; `CREATE TABLE temp.t(x)` is the schema qualifier and hits
|
|
1175
|
-
* the `dbName` rule — and the qualifier was the live gap, where the table was created, written
|
|
1176
|
-
* and read back here while workerd refused it outright. The qualifier accepts every quoting
|
|
1177
|
-
* SQLite does, single quotes included, with or without whitespace after the keyword (measured:
|
|
1178
|
-
* workerd refuses `CREATE TABLE 'temp'.t(x)` and `CREATE TABLE"temp".t(x)` the same way), and
|
|
1179
|
-
* is matched only in the object-name position, so an application table merely NAMED `tempest`
|
|
1180
|
-
* or `temporary_log` is untouched. The keyword spelling needs no qualifier arm of its own here:
|
|
1181
|
-
* `TEMP_SCHEMA` already refuses every `CREATE TEMP…` before this pattern is consulted.
|
|
1182
|
-
*
|
|
1183
|
-
* The rest of the `dbName` rule goes unported on purpose: with no way to create a temp-schema
|
|
1184
|
-
* object, `INSERT`/`DELETE`/`DROP` against one die in SQLite as `no such table`, and the one
|
|
1185
|
-
* silent form measures identically — workerd allows `DROP TABLE IF EXISTS temp.ghost` too.
|
|
1186
|
-
*/
|
|
1187
|
-
var TEMP_SCHEMA = /^[\s;]*CREATE\s+(?:TEMP|TEMPORARY)\b/i;
|
|
1188
|
-
var TEMP_QUALIFIED = /^[\s;]*CREATE\s+(?:UNIQUE\s+|VIRTUAL\s+)?(?:TABLE|VIEW|TRIGGER|INDEX)\s*(?:IF\s+NOT\s+EXISTS\s*)?(?:"temp"|'temp'|`temp`|\[temp\]|temp)\s*\./i;
|
|
1189
|
-
/**
|
|
1190
|
-
* ← `SQLITE_CREATE_VTABLE` (`sqlite.c++:1298-1316`): a virtual table is native-code callbacks, so
|
|
1191
|
-
* upstream allows exactly four modules — FTS5 and its `fts5vocab` companion, R*Tree and its
|
|
1192
|
-
* `rtree_i32` variant — and denies every other module SQLite was compiled with.
|
|
1193
|
-
*
|
|
1194
|
-
* `dbstat` is why this is not only fidelity: it reports a row per table with page counts and
|
|
1195
|
-
* byte sizes, so `SELECT name FROM d` enumerates `_cf_KV` and the rest of the runtime's own
|
|
1196
|
-
* tables without the statement ever naming them — around `requireAllowedNames`, which can only
|
|
1197
|
-
* see the text it was given.
|
|
1198
|
-
*
|
|
1199
|
-
* The table name and the module accept every quoting SQLite does — double quotes, backticks,
|
|
1200
|
-
* brackets, and the misquoting feature's single-quoted string, with or without whitespace
|
|
1201
|
-
* before them — because the module has to be read from PAST the name, and a guessed name
|
|
1202
|
-
* boundary is a bypass in both directions: an unparseable name skipped the check, and
|
|
1203
|
-
* `"a USING fts5 b" USING dbstat` read its module out of the quoted name. Measured: workerd
|
|
1204
|
-
* refuses both, refuses `CREATE VIRTUAL TABLE"d"USING dbstat`, resolves `USING "dbstat"` to
|
|
1205
|
-
* the same denial, and allows `USING 'fts5'`. A `CREATE VIRTUAL TABLE` whose module the
|
|
1206
|
-
* pattern cannot read is refused outright — the deliberately stricter direction the
|
|
1207
|
-
* unparseable-PRAGMA fallback below already takes.
|
|
1208
|
-
*/
|
|
1209
|
-
var SQL_IDENTIFIER_SOURCE = /"(?:[^"]|"")*"|'(?:[^']|'')*'|`(?:[^`]|``)*`|\[[^\]]*\]|[A-Za-z_\u0080-\uffff][A-Za-z0-9_$\u0080-\uffff]*/.source;
|
|
1210
|
-
var VIRTUAL_TABLE = /^[\s;]*CREATE\s+VIRTUAL\s+TABLE\b/i;
|
|
1211
|
-
var VIRTUAL_TABLE_MODULE = new RegExp(String.raw`^[\s;]*CREATE\s+VIRTUAL\s+TABLE\s*(?:IF\s+NOT\s+EXISTS\s*)?(?:${SQL_IDENTIFIER_SOURCE})\s*(?:\.\s*(?:${SQL_IDENTIFIER_SOURCE})\s*)?USING\s*(${SQL_IDENTIFIER_SOURCE})`, "i");
|
|
1212
|
-
var ALLOWED_VIRTUAL_TABLE_MODULES = /* @__PURE__ */ new Set([
|
|
1213
|
-
"fts5",
|
|
1214
|
-
"fts5vocab",
|
|
1215
|
-
"rtree",
|
|
1216
|
-
"rtree_i32"
|
|
1217
|
-
]);
|
|
1218
|
-
/**
|
|
1219
|
-
* `VACUUM` has no action code of its own, so the authorizer never sees it. What refuses it
|
|
1220
|
-
* upstream is SQLite's own `cannot VACUUM from within a transaction` precondition, against the
|
|
1221
|
-
* transaction a Durable Object always has open — and that message is what the oracle returned,
|
|
1222
|
-
* for `VACUUM`, `VACUUM main` and `VACUUM INTO` alike. Byte-identical for the same reason
|
|
1223
|
-
* `SQL_NOT_AUTHORIZED_MESSAGE` is: a caller matching on it ports unchanged.
|
|
1224
|
-
*
|
|
1225
|
-
* Refused here unconditionally rather than by transaction state, which is a divergence only in
|
|
1226
|
-
* mechanism: this runtime never runs a statement where upstream would have allowed it.
|
|
1227
|
-
*/
|
|
1228
|
-
var VACUUM_STATEMENT = /^[\s;]*VACUUM\b/i;
|
|
1229
|
-
var SQL_VACUUM_REFUSED_MESSAGE = "cannot VACUUM from within a transaction: SQLITE_ERROR";
|
|
1230
|
-
/** Refuse the authorizer-only forms against one SQLite-decided statement boundary. */
|
|
1231
|
-
function refuseUnauthorizedForms(statement) {
|
|
1232
|
-
const code = statement.replace(NOT_COMMENT, " ");
|
|
1233
|
-
if (VACUUM_STATEMENT.test(code)) throw new Error(SQL_VACUUM_REFUSED_MESSAGE);
|
|
1234
|
-
if (DATABASE_ATTACHMENT.test(code) || TEMP_SCHEMA.test(code) || TEMP_QUALIFIED.test(code)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1235
|
-
if (VIRTUAL_TABLE.test(code)) {
|
|
1236
|
-
const moduleName = VIRTUAL_TABLE_MODULE.exec(code)?.[1];
|
|
1237
|
-
if (moduleName === void 0 || !ALLOWED_VIRTUAL_TABLE_MODULES.has(unquoted(moduleName).toLowerCase())) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
/** Cheap pre-test; `PRAGMA` and the `pragma_` functions both contain it. */
|
|
1241
|
-
var PRAGMA_HINT = /pragma/i;
|
|
1242
|
-
/** `PRAGMA [schema.]name`, then `= value`, `(argument)`, or nothing. */
|
|
1243
|
-
var PRAGMA_STATEMENT = /^[\s;]*PRAGMA\s+(?:[A-Za-z_][A-Za-z0-9_$]*\s*\.\s*)?([A-Za-z_][A-Za-z0-9_$]*)(?:\s*=\s*([\s\S]+?)|\s*\(\s*([\s\S]*?)\s*\))?\s*;?\s*$/i;
|
|
1244
|
-
/**
|
|
1245
|
-
* ← `ALLOWED_PRAGMAS` (`util/sqlite.c++:543-571`) and `PragmaSignature` (`:528-535`),
|
|
1246
|
-
* verbatim. `table_list`, `table_info`, and `table_xinfo` are special-cased
|
|
1247
|
-
* ahead of the table in the authorizer, exactly as upstream's `SQLITE_PRAGMA`
|
|
1248
|
-
* case does (`util/sqlite.c++:1194-1273`).
|
|
1249
|
-
*/
|
|
1250
|
-
var ALLOWED_PRAGMAS = /* @__PURE__ */ new Map([
|
|
1251
|
-
["data_version", "NO_ARG"],
|
|
1252
|
-
["page_size", "NO_ARG"],
|
|
1253
|
-
["case_sensitive_like", "BOOLEAN"],
|
|
1254
|
-
["foreign_keys", "BOOLEAN"],
|
|
1255
|
-
["defer_foreign_keys", "BOOLEAN"],
|
|
1256
|
-
["ignore_check_constraints", "BOOLEAN"],
|
|
1257
|
-
["legacy_alter_table", "BOOLEAN"],
|
|
1258
|
-
["recursive_triggers", "BOOLEAN"],
|
|
1259
|
-
["reverse_unordered_selects", "BOOLEAN"],
|
|
1260
|
-
["foreign_key_check", "OPTIONAL_OBJECT_NAME"],
|
|
1261
|
-
["foreign_key_list", "OBJECT_NAME"],
|
|
1262
|
-
["index_info", "OBJECT_NAME"],
|
|
1263
|
-
["index_list", "OBJECT_NAME"],
|
|
1264
|
-
["index_xinfo", "OBJECT_NAME"],
|
|
1265
|
-
["quick_check", "NULL_NUMBER_OR_OBJECT_NAME"],
|
|
1266
|
-
["optimize", "NULL_OR_NUMBER"]
|
|
1267
|
-
]);
|
|
1268
|
-
/** Upstream compares the eight literal forms as PREFIXES, case-insensitively. */
|
|
1269
|
-
var BOOLEAN_PRAGMA_VALUE = /^(?:true|false|yes|no|on|off|1|0)/i;
|
|
1270
|
-
/**
|
|
1271
|
-
* The pragmas SQLite ships (https://www.sqlite.org/pragma.html), so a
|
|
1272
|
-
* `pragma_X` identifier can be told apart: `X` here means the table-valued
|
|
1273
|
-
* pragma function and follows the allowlist; any other `pragma_`-prefixed
|
|
1274
|
-
* identifier is an ordinary application name, which upstream's authorizer
|
|
1275
|
-
* distinguishes by resolution and the conformance suite pins.
|
|
1276
|
-
*/
|
|
1277
|
-
var SQLITE_PRAGMA_NAMES = /* @__PURE__ */ new Set([
|
|
1278
|
-
"analysis_limit",
|
|
1279
|
-
"application_id",
|
|
1280
|
-
"auto_vacuum",
|
|
1281
|
-
"automatic_index",
|
|
1282
|
-
"busy_timeout",
|
|
1283
|
-
"cache_size",
|
|
1284
|
-
"cache_spill",
|
|
1285
|
-
"case_sensitive_like",
|
|
1286
|
-
"cell_size_check",
|
|
1287
|
-
"checkpoint_fullfsync",
|
|
1288
|
-
"collation_list",
|
|
1289
|
-
"compile_options",
|
|
1290
|
-
"data_version",
|
|
1291
|
-
"database_list",
|
|
1292
|
-
"defer_foreign_keys",
|
|
1293
|
-
"encoding",
|
|
1294
|
-
"foreign_key_check",
|
|
1295
|
-
"foreign_key_list",
|
|
1296
|
-
"foreign_keys",
|
|
1297
|
-
"freelist_count",
|
|
1298
|
-
"full_column_names",
|
|
1299
|
-
"fullfsync",
|
|
1300
|
-
"function_list",
|
|
1301
|
-
"hard_heap_limit",
|
|
1302
|
-
"ignore_check_constraints",
|
|
1303
|
-
"incremental_vacuum",
|
|
1304
|
-
"index_info",
|
|
1305
|
-
"index_list",
|
|
1306
|
-
"index_xinfo",
|
|
1307
|
-
"integrity_check",
|
|
1308
|
-
"journal_mode",
|
|
1309
|
-
"journal_size_limit",
|
|
1310
|
-
"legacy_alter_table",
|
|
1311
|
-
"legacy_file_format",
|
|
1312
|
-
"locking_mode",
|
|
1313
|
-
"max_page_count",
|
|
1314
|
-
"mmap_size",
|
|
1315
|
-
"module_list",
|
|
1316
|
-
"optimize",
|
|
1317
|
-
"page_count",
|
|
1318
|
-
"page_size",
|
|
1319
|
-
"pragma_list",
|
|
1320
|
-
"query_only",
|
|
1321
|
-
"quick_check",
|
|
1322
|
-
"read_uncommitted",
|
|
1323
|
-
"recursive_triggers",
|
|
1324
|
-
"reverse_unordered_selects",
|
|
1325
|
-
"schema_version",
|
|
1326
|
-
"secure_delete",
|
|
1327
|
-
"short_column_names",
|
|
1328
|
-
"shrink_memory",
|
|
1329
|
-
"soft_heap_limit",
|
|
1330
|
-
"synchronous",
|
|
1331
|
-
"table_info",
|
|
1332
|
-
"table_list",
|
|
1333
|
-
"table_xinfo",
|
|
1334
|
-
"temp_store",
|
|
1335
|
-
"threads",
|
|
1336
|
-
"trusted_schema",
|
|
1337
|
-
"user_version",
|
|
1338
|
-
"wal_autocheckpoint",
|
|
1339
|
-
"wal_checkpoint",
|
|
1340
|
-
"writable_schema"
|
|
1341
|
-
]);
|
|
1342
|
-
/** kj's `tryParseAs` is decimal; keep the same acceptance. */
|
|
1343
|
-
var DECIMAL = /^[+-]?\d+$/;
|
|
1344
|
-
/**
|
|
1345
|
-
* One layer of SQL quoting, any of the four forms — a pragma argument, or the module token
|
|
1346
|
-
* `VIRTUAL_TABLE_MODULE` captured. Doubled inner quotes stay doubled, which cannot change a
|
|
1347
|
-
* verdict here: no allowlisted comparison target contains a quote character.
|
|
1348
|
-
*/
|
|
1349
|
-
function unquoted(argument) {
|
|
1350
|
-
const first = argument[0];
|
|
1351
|
-
const last = argument[argument.length - 1];
|
|
1352
|
-
if (argument.length >= 2) {
|
|
1353
|
-
if ((first === "'" || first === "\"" || first === "`") && last === first) return argument.slice(1, -1);
|
|
1354
|
-
if (first === "[" && last === "]") return argument.slice(1, -1);
|
|
1355
|
-
}
|
|
1356
|
-
return argument;
|
|
1357
|
-
}
|
|
1358
|
-
/** ← the `SQLITE_PRAGMA` authorizer case (`util/sqlite.c++:1194-1273`), whole. */
|
|
1359
|
-
function isAllowedPragma(name, argument) {
|
|
1360
|
-
const pragma = name.toLowerCase();
|
|
1361
|
-
if (pragma === "table_list") return true;
|
|
1362
|
-
if (pragma === "table_info" || pragma === "table_xinfo") {
|
|
1363
|
-
if (argument === void 0) return false;
|
|
1364
|
-
return SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1365
|
-
}
|
|
1366
|
-
const signature = ALLOWED_PRAGMAS.get(pragma);
|
|
1367
|
-
if (signature === void 0) return false;
|
|
1368
|
-
switch (signature) {
|
|
1369
|
-
case "NO_ARG": return argument === void 0;
|
|
1370
|
-
case "BOOLEAN": return argument === void 0 || BOOLEAN_PRAGMA_VALUE.test(unquoted(argument));
|
|
1371
|
-
case "OBJECT_NAME": return argument !== void 0 && SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1372
|
-
case "OPTIONAL_OBJECT_NAME": return argument === void 0 || SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1373
|
-
case "NULL_OR_NUMBER": return argument === void 0 || DECIMAL.test(argument);
|
|
1374
|
-
case "NULL_NUMBER_OR_OBJECT_NAME": return argument === void 0 || DECIMAL.test(argument) || SqlStorageRegulator.isAllowedName(unquoted(argument));
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
/**
|
|
1378
|
-
* The text-level stand-in for the authorizer's `SQLITE_PRAGMA` case, against
|
|
1379
|
-
* one SQLite-decided statement boundary. Load-bearing beyond fidelity:
|
|
1380
|
-
* `user_version` is where runtime storage versioning keeps its per-file stamp
|
|
1381
|
-
* (`util/sqlite-migrations.ts`), and `writable_schema` would let application
|
|
1382
|
-
* SQL rewrite `sqlite_master` out from under the `_cf_` reservation.
|
|
1383
|
-
*
|
|
1384
|
-
* The `pragma_` table-valued functions reach the same authorizer path
|
|
1385
|
-
* upstream, so they follow the same allowlist here — by pragma NAME only. An
|
|
1386
|
-
* argument the text cannot see (a string literal or a binding) goes unchecked
|
|
1387
|
-
* where upstream's authorizer sees the resolved value; a `_cf_` name smuggled
|
|
1388
|
-
* that way reads schema whose shape is public source anyway, while identifier
|
|
1389
|
-
* arguments stay covered by `requireAllowedNames`. The README divergence row
|
|
1390
|
-
* records this.
|
|
1391
|
-
*/
|
|
1392
|
-
function requireAllowedPragmas(statement) {
|
|
1393
|
-
if (!PRAGMA_HINT.test(statement)) return;
|
|
1394
|
-
const code = statement.replace(NOT_COMMENT, " ");
|
|
1395
|
-
const direct = code.match(PRAGMA_STATEMENT);
|
|
1396
|
-
if (direct !== null) {
|
|
1397
|
-
const [, name = "", assigned, called] = direct;
|
|
1398
|
-
const argument = (assigned ?? called)?.trim();
|
|
1399
|
-
if (!isAllowedPragma(name, argument === "" ? void 0 : argument)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1400
|
-
return;
|
|
1401
|
-
}
|
|
1402
|
-
if (/^[\s;]*PRAGMA\b/i.test(code)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1403
|
-
const literalFree = code.replace(NOT_CODE, " ");
|
|
1404
|
-
for (const [token] of literalFree.matchAll(IDENTIFIER)) {
|
|
1405
|
-
if (token.length <= 7 || token.slice(0, 7).toLowerCase() !== "pragma_") continue;
|
|
1406
|
-
const name = token.slice(7).toLowerCase();
|
|
1407
|
-
if (!SQLITE_PRAGMA_NAMES.has(name)) continue;
|
|
1408
|
-
if (name !== "table_list" && name !== "table_info" && name !== "table_xinfo" && !ALLOWED_PRAGMAS.has(name)) throw new Error(SQL_NOT_AUTHORIZED_MESSAGE);
|
|
1409
|
-
}
|
|
1410
|
-
}
|
|
1411
|
-
/** Everything the untrusted path refuses at one statement boundary. */
|
|
1412
|
-
function regulateUntrustedStatement(statement) {
|
|
1413
|
-
refuseTransactionControl(statement);
|
|
1414
|
-
refuseUnauthorizedForms(statement);
|
|
1415
|
-
requireAllowedPragmas(statement);
|
|
1416
|
-
}
|
|
1417
|
-
/** ← `JSG_INHERIT_INTRINSIC(v8::kIteratorPrototype)` (`jsg/iterator.h:1044`). */
|
|
1418
|
-
var IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
|
|
1419
|
-
/**
|
|
1420
|
-
* ← the `JSG_ITERATOR` types (`jsg/iterator.h:1036-1050`): `next` and
|
|
1421
|
-
* self-iterability on `%IteratorPrototype%` — which is what carries the ES
|
|
1422
|
-
* iterator helpers; `raw().toArray()` is what Drizzle's durable-sqlite driver
|
|
1423
|
-
* calls — and NOTHING else. No `return`, no `throw` (only the async variant
|
|
1424
|
-
* registers `return_`, `:1069-1085`), so `IteratorClose` after a `break`, a
|
|
1425
|
-
* partial destructuring, or a `take()` is a no-op and a retained iterator
|
|
1426
|
-
* resumes. Results are `JSG_STRUCT(done, value)` in that key order
|
|
1427
|
-
* (`jsg/iterator.h:706-710`).
|
|
1428
|
-
*/
|
|
1429
|
-
var RawIterator = class {
|
|
1430
|
-
#pull;
|
|
1431
|
-
constructor(pull) {
|
|
1432
|
-
this.#pull = pull;
|
|
1433
|
-
}
|
|
1434
|
-
next() {
|
|
1435
|
-
const raw = this.#pull();
|
|
1436
|
-
if (raw === void 0) return {
|
|
1437
|
-
done: true,
|
|
1438
|
-
value: void 0
|
|
1439
|
-
};
|
|
1440
|
-
return {
|
|
1441
|
-
done: false,
|
|
1442
|
-
value: asRawRow([...raw])
|
|
1443
|
-
};
|
|
1444
|
-
}
|
|
1445
|
-
[Symbol.iterator]() {
|
|
1446
|
-
return this;
|
|
1447
|
-
}
|
|
1448
|
-
};
|
|
1449
|
-
Object.setPrototypeOf(RawIterator.prototype, IteratorPrototype);
|
|
1450
|
-
Object.defineProperty(RawIterator.prototype, Symbol.toStringTag, {
|
|
1451
|
-
value: "RawIterator",
|
|
1452
|
-
configurable: true
|
|
1453
|
-
});
|
|
1454
|
-
/** ← `RowIterator`, shaped exactly as `RawIterator` above. */
|
|
1455
|
-
var RowIterator = class {
|
|
1456
|
-
#pull;
|
|
1457
|
-
constructor(pull) {
|
|
1458
|
-
this.#pull = pull;
|
|
1459
|
-
}
|
|
1460
|
-
next() {
|
|
1461
|
-
const row = this.#pull();
|
|
1462
|
-
if (row === void 0) return {
|
|
1463
|
-
done: true,
|
|
1464
|
-
value: void 0
|
|
1465
|
-
};
|
|
1466
|
-
return {
|
|
1467
|
-
done: false,
|
|
1468
|
-
value: row
|
|
1469
|
-
};
|
|
1470
|
-
}
|
|
1471
|
-
[Symbol.iterator]() {
|
|
1472
|
-
return this;
|
|
1473
|
-
}
|
|
1474
|
-
};
|
|
1475
|
-
Object.setPrototypeOf(RowIterator.prototype, IteratorPrototype);
|
|
1476
|
-
Object.defineProperty(RowIterator.prototype, Symbol.toStringTag, {
|
|
1477
|
-
value: "RowIterator",
|
|
1478
|
-
configurable: true
|
|
1479
|
-
});
|
|
1480
|
-
/**
|
|
1481
|
-
* ← `SqlStorage::Cursor`.
|
|
1482
|
-
*
|
|
1483
|
-
* `rowsRead` is the one counter that is not upstream's. Upstream reads
|
|
1484
|
-
* `Query::getRowsRead()`, a billing counter sourced from libsql's
|
|
1485
|
-
* `STMTSTATUS_ROWS_READ` that counts index rows and that neither backend
|
|
1486
|
-
* exposes — the same absence the README already records for `SqlResult`. The
|
|
1487
|
-
* interface requires a number, so this returns the rows the cursor has yielded,
|
|
1488
|
-
* which is what today's browser host returns and what its tests assert. It
|
|
1489
|
-
* undercounts any query that scans more rows than it returns.
|
|
1490
|
-
*/
|
|
1491
|
-
var Cursor = class {
|
|
1492
|
-
#columnNames;
|
|
1493
|
-
#rawRows;
|
|
1494
|
-
#rowsWritten;
|
|
1495
|
-
#position = 0;
|
|
1496
|
-
constructor(state) {
|
|
1497
|
-
if (state === void 0) throw new Error(CURSOR_NOT_CONSTRUCTIBLE_MESSAGE);
|
|
1498
|
-
this.#columnNames = state.columnNames;
|
|
1499
|
-
this.#rawRows = state.rawRows;
|
|
1500
|
-
this.#rowsWritten = state.rowsWritten;
|
|
1501
|
-
}
|
|
1502
|
-
/**
|
|
1503
|
-
* ← `JSG_READONLY_PROTOTYPE_PROPERTY(columnNames)` (`sql.h:210`): a
|
|
1504
|
-
* prototype accessor, not an own field, so a cursor JSON-stringifies to `{}`.
|
|
1505
|
-
*/
|
|
1506
|
-
get columnNames() {
|
|
1507
|
-
return this.#columnNames;
|
|
1508
|
-
}
|
|
1509
|
-
/** ← `Cursor::next`, whose `RowIterator::Next` is this exact shape. */
|
|
1510
|
-
next() {
|
|
1511
|
-
const row = this.#nextRow();
|
|
1512
|
-
if (row === void 0) return {
|
|
1513
|
-
done: true,
|
|
1514
|
-
value: void 0
|
|
1515
|
-
};
|
|
1516
|
-
return {
|
|
1517
|
-
done: false,
|
|
1518
|
-
value: row
|
|
1519
|
-
};
|
|
1520
|
-
}
|
|
1521
|
-
/** ← `Cursor::toArray`, which drains from the current position. */
|
|
1522
|
-
toArray() {
|
|
1523
|
-
const rows = [];
|
|
1524
|
-
for (;;) {
|
|
1525
|
-
const row = this.#nextRow();
|
|
1526
|
-
if (row === void 0) return rows;
|
|
1527
|
-
rows.push(row);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
/** ← `Cursor::one`. Both messages are upstream's, verbatim. */
|
|
1531
|
-
one() {
|
|
1532
|
-
const row = this.#nextRow();
|
|
1533
|
-
if (row === void 0) throw new Error("Expected exactly one result from SQL query, but got no results.");
|
|
1534
|
-
if (this.#position < this.#rawRows.length) {
|
|
1535
|
-
this.#position = this.#rawRows.length;
|
|
1536
|
-
throw new Error("Expected exactly one result from SQL query, but got multiple results.");
|
|
1537
|
-
}
|
|
1538
|
-
return row;
|
|
1539
|
-
}
|
|
1540
|
-
/**
|
|
1541
|
-
* ← `Cursor::raw`, which shares this cursor's position rather than
|
|
1542
|
-
* restarting. The iterator's shape is `RawIterator`'s whole doc comment.
|
|
1543
|
-
*/
|
|
1544
|
-
raw() {
|
|
1545
|
-
return new RawIterator(() => this.#nextRaw());
|
|
1546
|
-
}
|
|
1547
|
-
/** ← `JSG_ITERABLE(rows)`, yielding through the same shared position. */
|
|
1548
|
-
[Symbol.iterator]() {
|
|
1549
|
-
return new RowIterator(() => this.#nextRow());
|
|
1550
|
-
}
|
|
1551
|
-
get rowsRead() {
|
|
1552
|
-
return this.#position;
|
|
1553
|
-
}
|
|
1554
|
-
/** ← `Cursor::getRowsWritten`, which is `SqlResult.rowsWritten` here. */
|
|
1555
|
-
get rowsWritten() {
|
|
1556
|
-
return this.#rowsWritten;
|
|
1557
|
-
}
|
|
1558
|
-
#nextRaw() {
|
|
1559
|
-
const raw = this.#rawRows[this.#position];
|
|
1560
|
-
if (raw === void 0) return void 0;
|
|
1561
|
-
this.#position += 1;
|
|
1562
|
-
return raw;
|
|
1563
|
-
}
|
|
1564
|
-
/** ← `Cursor::rowIteratorNext`: zip the column names onto the row. */
|
|
1565
|
-
#nextRow() {
|
|
1566
|
-
const raw = this.#nextRaw();
|
|
1567
|
-
if (raw === void 0) return void 0;
|
|
1568
|
-
const row = {};
|
|
1569
|
-
this.#columnNames.forEach((name, index) => {
|
|
1570
|
-
row[name] = raw[index] ?? null;
|
|
1571
|
-
});
|
|
1572
|
-
return asRow(row);
|
|
1573
|
-
}
|
|
1574
|
-
};
|
|
1575
|
-
/** ← the jsg resource-type tag every workerd API object carries (`resource.h`). */
|
|
1576
|
-
Object.defineProperty(Cursor.prototype, Symbol.toStringTag, {
|
|
1577
|
-
value: "Cursor",
|
|
1578
|
-
configurable: true
|
|
1579
|
-
});
|
|
1580
|
-
/**
|
|
1581
|
-
* ← `SqlStorage::Statement`, which upstream describes as "supported only for
|
|
1582
|
-
* backwards compatibility ... it is actually just a wrapper around `exec()`".
|
|
1583
|
-
* `JSG_CALLABLE(run)` makes the object itself callable, so `prepare()` returns a
|
|
1584
|
-
* function wearing this prototype rather than an object with a `run` method.
|
|
1585
|
-
*/
|
|
1586
|
-
var Statement = class {
|
|
1587
|
-
constructor() {
|
|
1588
|
-
throw new Error(STATEMENT_NOT_CONSTRUCTIBLE_MESSAGE);
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
var SqlStorage = class {
|
|
1592
|
-
#ctx;
|
|
1593
|
-
#owner;
|
|
1594
|
-
/** ← `kj::Maybe<uint> pageSize`, memoized for the same reason. */
|
|
1595
|
-
#pageSize;
|
|
1596
|
-
constructor(ctx, owner) {
|
|
1597
|
-
this.#ctx = ctx;
|
|
1598
|
-
this.#owner = owner;
|
|
1599
|
-
}
|
|
1600
|
-
/** ← `JSG_NESTED_TYPE(Cursor)`. Exposed so `instanceof` works, as upstream's is. */
|
|
1601
|
-
Cursor = Cursor;
|
|
1602
|
-
/** ← `JSG_NESTED_TYPE(Statement)`. */
|
|
1603
|
-
Statement = Statement;
|
|
1604
|
-
exec(query, ...bindings) {
|
|
1605
|
-
requireInputLock(this.#ctx, "sql.exec()");
|
|
1606
|
-
const db = this.#owner.getSqliteDb();
|
|
1607
|
-
const sqlBindings = bindings.map(toSqlBindingValue);
|
|
1608
|
-
requireAllowedNames(query);
|
|
1609
|
-
const result = db.run({ regulate: regulateUntrustedStatement }, query, ...sqlBindings);
|
|
1610
|
-
return new Cursor({
|
|
1611
|
-
columnNames: [...result.columnNames],
|
|
1612
|
-
rawRows: result.rawRows.map((row) => row.map(toSqlStorageValue)),
|
|
1613
|
-
rowsWritten: result.rowsWritten
|
|
1614
|
-
});
|
|
1615
|
-
}
|
|
1616
|
-
/**
|
|
1617
|
-
* ← `SqlStorage::getDatabaseSize`.
|
|
1618
|
-
*
|
|
1619
|
-
* Upstream's second query is `PRAGMA page_size;`, which `sqlite3_stmt_readonly()`
|
|
1620
|
-
* reports read-only. With no such call the text is the only source and §1.7.1's
|
|
1621
|
-
* rule is write-unless-provably-a-read, so a bare `PRAGMA` would open a
|
|
1622
|
-
* transaction and take an output-gate lock to answer a size question. The
|
|
1623
|
-
* `pragma_page_size` table-valued function is the same value read through the
|
|
1624
|
-
* `SELECT` upstream already uses for the page count.
|
|
1625
|
-
*/
|
|
1626
|
-
get databaseSize() {
|
|
1627
|
-
requireInputLock(this.#ctx, "sql.databaseSize");
|
|
1628
|
-
const db = this.#owner.getSqliteDb();
|
|
1629
|
-
return readNumber(db.run("select (select * from pragma_page_count) - (select * from pragma_freelist_count);"), "page count") * this.#getPageSize(db);
|
|
1630
|
-
}
|
|
1631
|
-
/** ← `SqlStorage::prepare`. Experimental and deprecated upstream; `exec` caches for you. */
|
|
1632
|
-
prepare(query) {
|
|
1633
|
-
requireInputLock(this.#ctx, "sql.prepare()");
|
|
1634
|
-
const run = (...bindings) => this.exec(query, ...bindings);
|
|
1635
|
-
Object.setPrototypeOf(run, Statement.prototype);
|
|
1636
|
-
return run;
|
|
1637
|
-
}
|
|
1638
|
-
/** ← `SqlStorage::ingest`. */
|
|
1639
|
-
ingest(query) {
|
|
1640
|
-
requireInputLock(this.#ctx, "sql.ingest()");
|
|
1641
|
-
requireAllowedNames(query);
|
|
1642
|
-
return this.#owner.getSqliteDb().ingest(query, regulateUntrustedStatement);
|
|
1643
|
-
}
|
|
1644
|
-
/** ← `SqlStorage::setMaxPageCountForTest`, which is what its name says. */
|
|
1645
|
-
setMaxPageCountForTest(count) {
|
|
1646
|
-
requireInputLock(this.#ctx, "sql.setMaxPageCountForTest()");
|
|
1647
|
-
this.#owner.getSqliteDb().run(`PRAGMA max_page_count = ${count}`);
|
|
1648
|
-
}
|
|
1649
|
-
/** ← `SqlStorage::getPageSize`. */
|
|
1650
|
-
#getPageSize(db) {
|
|
1651
|
-
const cached = this.#pageSize;
|
|
1652
|
-
if (cached !== void 0) return cached;
|
|
1653
|
-
const size = readNumber(db.run("select * from pragma_page_size;"), "page size");
|
|
1654
|
-
this.#pageSize = size;
|
|
1655
|
-
return size;
|
|
1656
|
-
}
|
|
1657
|
-
};
|
|
1658
|
-
function readNumber(result, what) {
|
|
1659
|
-
const value = result.rawRows[0]?.[0];
|
|
1660
|
-
if (typeof value === "number") return value;
|
|
1661
|
-
if (typeof value === "bigint") return Number(value);
|
|
1662
|
-
throw new Error(`Expected a number for the database's ${what}.`);
|
|
1663
|
-
}
|
|
1664
|
-
/** ← JSG's conversion from JavaScript arguments to `SqlStorage::BindingValue`. */
|
|
1665
|
-
function toSqlBindingValue(value) {
|
|
1666
|
-
if (value === null || value === void 0) return null;
|
|
1667
|
-
if (typeof value === "string" || typeof value === "number") return value;
|
|
1668
|
-
if (typeof value === "boolean") return String(value);
|
|
1669
|
-
if (typeof value === "bigint") throw new TypeError("Cannot convert a BigInt value to a number");
|
|
1670
|
-
if (value instanceof ArrayBuffer) return copyBytes(new Uint8Array(value));
|
|
1671
|
-
if (ArrayBuffer.isView(value)) return copyBytes(new Uint8Array(value.buffer, value.byteOffset, value.byteLength));
|
|
1672
|
-
throw new TypeError(`Cannot convert ${Object.prototype.toString.call(value)} to a SQL value`);
|
|
1673
|
-
}
|
|
1674
|
-
function copyBytes(bytes) {
|
|
1675
|
-
const copy = new Uint8Array(bytes.byteLength);
|
|
1676
|
-
copy.set(bytes);
|
|
1677
|
-
return copy;
|
|
1678
|
-
}
|
|
1679
|
-
/**
|
|
1680
|
-
* ← `SqlStorage::wrapSqlValue` plus the `Query::getValue` switch above it.
|
|
1681
|
-
*
|
|
1682
|
-
* Upstream's int64 arm carries its own comment: "int64 will become BigInt, but
|
|
1683
|
-
* most applications won't want all their integers to be BigInt. We will coerce
|
|
1684
|
-
* to a double here." That coercion is kept rather than refused, because it is
|
|
1685
|
-
* the documented behaviour of `sql.exec` and a caller storing an id larger than
|
|
1686
|
-
* 2^53 has already lost on workerd.
|
|
1687
|
-
*/
|
|
1688
|
-
function toSqlStorageValue(value) {
|
|
1689
|
-
if (value === null || value === void 0) return null;
|
|
1690
|
-
if (typeof value === "string" || typeof value === "number") return value;
|
|
1691
|
-
if (typeof value === "bigint") return Number(value);
|
|
1692
|
-
if (typeof value === "boolean") return value ? 1 : 0;
|
|
1693
|
-
if (value instanceof Uint8Array) {
|
|
1694
|
-
const copy = new ArrayBuffer(value.byteLength);
|
|
1695
|
-
new Uint8Array(copy).set(value);
|
|
1696
|
-
return copy;
|
|
1697
|
-
}
|
|
1698
|
-
throw new Error(`SQL returned a ${typeof value}, which is not a SqlStorageValue.`);
|
|
1699
|
-
}
|
|
1700
|
-
/**
|
|
1701
|
-
* The two narrowings a generic row type needs. `T` is the caller's claim about
|
|
1702
|
-
* the shape of a row SQLite produced at runtime, so no check can confirm it and
|
|
1703
|
-
* upstream does not try — its `Cursor<T>` is the same claim written in a
|
|
1704
|
-
* `JSG_TS_OVERRIDE`. Confined to these two functions so the claim is one place
|
|
1705
|
-
* rather than sprinkled through the cursor.
|
|
1706
|
-
*/
|
|
1707
|
-
function asRow(row) {
|
|
1708
|
-
return row;
|
|
1709
|
-
}
|
|
1710
|
-
function asRawRow(values) {
|
|
1711
|
-
return values;
|
|
1712
|
-
}
|
|
1713
|
-
//#endregion
|
|
1714
|
-
//#region src/api/actor-state.ts
|
|
1715
|
-
/**
|
|
1716
|
-
* ← workerd `src/workerd/api/actor-state.{h,c++}`
|
|
1717
|
-
*
|
|
1718
|
-
* The JS-facing storage objects: `DurableObjectStorageOperations` and its two
|
|
1719
|
-
* subclasses, `DurableObjectFacets`, and `DurableObjectState`. Everything below
|
|
1720
|
-
* this file is reached through one of them.
|
|
1721
|
-
*
|
|
1722
|
-
* **`DurableObjectStorage` satisfies workers-types with no cast (§2.4).** That
|
|
1723
|
-
* was checked rather than asserted, and two shapes here exist only because it
|
|
1724
|
-
* has to: `sql.Cursor` and `sql.Statement` must be constructible with no
|
|
1725
|
-
* arguments (see `sql.ts`), and `storage.kv` is required. The narrowings that
|
|
1726
|
-
* remain are all one thing —
|
|
1727
|
-
* `get<T>` returns the caller's claim about the shape of a value SQLite handed
|
|
1728
|
-
* back as bytes, which no check can confirm and which upstream states the same
|
|
1729
|
-
* way, as a `jsg::JsRef<jsg::JsValue>` behind a `JSG_TS_OVERRIDE`'d
|
|
1730
|
-
* `Promise<T>`. There is no `as unknown as` anywhere in this layer.
|
|
1731
|
-
*
|
|
1732
|
-
* **Every throw is synchronous, including from the promise-returning methods.**
|
|
1733
|
-
* That is upstream's: a `JSG_REQUIRE` inside a method returning `jsg::Promise`
|
|
1734
|
-
* throws into the isolate before the promise exists, so `put(k, undefined)`
|
|
1735
|
-
* throws rather than rejecting. The same goes for a value that will not decode,
|
|
1736
|
-
* because §1.4 makes the SQLite path run the decoder before `Promise.resolve`.
|
|
1737
|
-
*
|
|
1738
|
-
* **What the input gate does and does not do here.** Every entry point calls
|
|
1739
|
-
* `requireInputLock` — see its comment in `io/io-context.ts`, which is the one
|
|
1740
|
-
* place this package decides what an empty invocation stack means. Nothing else
|
|
1741
|
-
* takes a lock: a read returns a value, a write returns a resolved promise, and
|
|
1742
|
-
* `atCheckpointEnd` is what keeps the whole chain inside one transaction
|
|
1743
|
-
* (§1.7.1). The two exceptions are upstream's own — `sync()` and the bookmark
|
|
1744
|
-
* pair release the gate via `awaitIo`, and `transaction()` takes a critical
|
|
1745
|
-
* section.
|
|
1746
|
-
*
|
|
1747
|
-
* **Decision 2's branch has one reachable site**, and it is not where upstream's
|
|
1748
|
-
* is. §1.4 measures that SQLite cache operations are immediate, so their
|
|
1749
|
-
* `kj::OneOf<T, kj::Promise<T>>` branch has nothing to select between.
|
|
1750
|
-
* `transformMaybeBackpressure` keeps the branch because
|
|
1751
|
-
* `DeleteAllResults.backpressure` is still a promise in `io/actor-cache.ts`.
|
|
1752
|
-
*
|
|
1753
|
-
* Not ported, because the substrate has no equivalent: Hibernatable WebSockets,
|
|
1754
|
-
* which is the whole reason `DurableObjectState`'s eight WebSocket methods are
|
|
1755
|
-
* named throwing stubs; V8's private wire bytes, replaced by a browser-safe
|
|
1756
|
-
* structured-clone encoding with the same public value semantics; the billing
|
|
1757
|
-
* counters
|
|
1758
|
-
* (`billingUnits`, `ActorObserver`, `updateStorageWriteUnit`) and the trace
|
|
1759
|
-
* spans, both already absent throughout; `enableSql`, a workerd namespace option
|
|
1760
|
-
* that exists to simulate a non-SQLite Durable Object; and `ReplicaActorOutgoingFactory`,
|
|
1761
|
-
* whose replication half is a named boundary in `io/actor-cache.ts`.
|
|
1762
|
-
*
|
|
1763
|
-
* Spec: §1.4, §1.5, §1.10, §2.4, §2.5, decisions 2, 4 and 14 in
|
|
1764
|
-
* docs/decisions.md.
|
|
1765
|
-
*/
|
|
1766
|
-
/**
|
|
1767
|
-
* ← `MAX_FACET_NAME_LENGTH` / `MAX_FACET_TREE_DEPTH`
|
|
1768
|
-
* (`actor-state.c++:943,947`), in the anonymous namespace beside the facet code
|
|
1769
|
-
* that enforces them. The scaffolding had them in `server/`, which is neither
|
|
1770
|
-
* where upstream puts them nor where they are checked.
|
|
1771
|
-
*/
|
|
1772
|
-
var FACET_NAME_MAX_LENGTH = 256;
|
|
1773
|
-
/** Root is at depth 0, so the deepest allowed facet is at depth 3. */
|
|
1774
|
-
var FACET_TREE_MAX_DEPTH = 4;
|
|
1775
|
-
/**
|
|
1776
|
-
* The substrate boundary named in the package README: Hibernatable WebSockets
|
|
1777
|
-
* exist so the platform can evict an actor while keeping its sockets open, and
|
|
1778
|
-
* Chrome exposes no equivalent lifecycle. Under this repo's fail-closed tenet
|
|
1779
|
-
* the throw IS the specified behaviour, which is why §2.5 orders the four
|
|
1780
|
-
* silent no-op stubs beside it replaced.
|
|
1781
|
-
*/
|
|
1782
|
-
var HIBERNATION_UNIMPLEMENTED_MESSAGE = "Hibernatable WebSockets are not available in this runtime: they exist so the platform can evict a Durable Object while keeping its sockets open, and there is no equivalent lifecycle to be faithful to.";
|
|
1783
|
-
/**
|
|
1784
|
-
* ← what falls off the end of `DurableObjectFacets::get`'s class switch
|
|
1785
|
-
* (`actor-state.c++:1029-1043`).
|
|
1786
|
-
*
|
|
1787
|
-
* Upstream accepts three things as `FacetStartupOptions.class`: a bare
|
|
1788
|
-
* `DurableObjectClass`, a `LoopbackDurableObjectNamespace`, or a
|
|
1789
|
-
* `LoopbackColoLocalActorNamespace`, unwrapping the last two through
|
|
1790
|
-
* `getClass()`. All three are ported — the loopback pair by
|
|
1791
|
-
* `api/export-loopback.ts` — and `KJ_UNREACHABLE` is the fourth case there
|
|
1792
|
-
* because JSG has already refused anything else while unwrapping the
|
|
1793
|
-
* `kj::OneOf`. The check has to be written here because
|
|
1794
|
-
* `@cloudflare/workers-types` declares `interface DurableObjectClass<_T> {}`,
|
|
1795
|
-
* which every object satisfies, so nothing refuses it before the method body.
|
|
1796
|
-
*/
|
|
1797
|
-
var FACET_CLASS_UNSUPPORTED_MESSAGE = "facets.get() was given a class this runtime cannot resolve. `class` must be a DurableObjectClass, a LoopbackDurableObjectNamespace or a LoopbackColoLocalActorNamespace — which is what a ctx.exports entry for a Durable Object class is.";
|
|
1798
|
-
/** ← `DurableObjectStorageOperations::OpName`. Named only where an error quotes them. */
|
|
1799
|
-
var OP_GET = "get()";
|
|
1800
|
-
var OP_GET_ALARM = "getAlarm()";
|
|
1801
|
-
var OP_LIST = "list()";
|
|
1802
|
-
var OP_PUT = "put()";
|
|
1803
|
-
var OP_PUT_ALARM = "setAlarm()";
|
|
1804
|
-
var OP_DELETE = "delete()";
|
|
1805
|
-
var OP_DELETE_ALARM = "deleteAlarm()";
|
|
1806
|
-
var OP_ROLLBACK = "rollback()";
|
|
1807
|
-
/** ← `actor-state.c++:455`, verbatim: the one message both overloads' misuse produces. */
|
|
1808
|
-
var PUT_OVERLOAD_MESSAGE = "put() may only be called with a single key-value pair and optional options as put(key, value, options) or with multiple key-value pairs and optional options as put(entries, options)";
|
|
1809
|
-
/**
|
|
1810
|
-
* ← the `kj::OneOf<kj::String, jsg::Dict<…>>` unwrap on put()'s first parameter: `jsg::Dict`
|
|
1811
|
-
* takes any JS object except an Array — functions and Maps included — and `kj::String` takes
|
|
1812
|
-
* everything else by coercion. A type predicate, so the overload split narrows without a cast.
|
|
1813
|
-
*/
|
|
1814
|
-
function isEntriesArgument(value) {
|
|
1815
|
-
return (typeof value === "object" || typeof value === "function") && value !== null && !Array.isArray(value);
|
|
1816
|
-
}
|
|
1817
|
-
/**
|
|
1818
|
-
* ← the struct wrapper (`jsg/struct.h:246-258`), which is NOT the Dict wrapper: `PutOptions` is
|
|
1819
|
-
* all-optional fields, so `null` unwraps to default options, and any object does — arrays and
|
|
1820
|
-
* functions included, because the wrapper checks `IsObject()` with no Array exclusion. Only a
|
|
1821
|
-
* non-null primitive fails to unwrap. Measured on real workerd: `put({k: 1}, null)`, `…, [])`
|
|
1822
|
-
* and `…, function () {})` all write, and `put({k: 1}, "v")` alone is the overload error.
|
|
1823
|
-
*/
|
|
1824
|
-
function isPutOptions(value) {
|
|
1825
|
-
return value === null || typeof value === "object" || typeof value === "function";
|
|
1826
|
-
}
|
|
1827
|
-
/** The key immediately after `k` in byte order is `k` plus this. */
|
|
1828
|
-
var NULL_CHARACTER = "\0";
|
|
1829
|
-
/** ← the `0xff` upstream strips from the tail of a prefix, in UTF-16 code units. */
|
|
1830
|
-
var MAX_CODE_UNIT = 65535;
|
|
1831
|
-
var textEncoder = new TextEncoder();
|
|
1832
|
-
var textDecoder = new TextDecoder();
|
|
1833
|
-
/** A byte JSON could never begin with, `DO`, and the local codec version. */
|
|
1834
|
-
var VALUE_CODEC_HEADER = new Uint8Array([
|
|
1835
|
-
0,
|
|
1836
|
-
68,
|
|
1837
|
-
79,
|
|
1838
|
-
1
|
|
1839
|
-
]);
|
|
1840
|
-
/**
|
|
1841
|
-
* ← `serializeV8Value`. The wire bytes differ because V8's serializer is not
|
|
1842
|
-
* available in browsers; the public structured-clone value semantics do not.
|
|
1843
|
-
* The short header keeps the new representation unambiguous while old JSON rows
|
|
1844
|
-
* remain readable.
|
|
1845
|
-
*/
|
|
1846
|
-
function serializeValue(value) {
|
|
1847
|
-
const body = textEncoder.encode(JSON.stringify(serialize(value)));
|
|
1848
|
-
const encoded = new Uint8Array(VALUE_CODEC_HEADER.byteLength + body.byteLength);
|
|
1849
|
-
encoded.set(VALUE_CODEC_HEADER);
|
|
1850
|
-
encoded.set(body, VALUE_CODEC_HEADER.byteLength);
|
|
1851
|
-
return encoded;
|
|
1852
|
-
}
|
|
1853
|
-
/**
|
|
1854
|
-
* ← `deserializeV8Value`.
|
|
1855
|
-
*
|
|
1856
|
-
* Upstream logs "the key (to help find the data in the database if it hasn't
|
|
1857
|
-
* been deleted), the length of the value, and the first three bytes of the value
|
|
1858
|
-
* (which is just the v8-internal version header and the tag that indicates the
|
|
1859
|
-
* type of the value, but not its contents)". Our four-byte header carries only
|
|
1860
|
-
* a marker and version for the same reason.
|
|
1861
|
-
*/
|
|
1862
|
-
function deserializeValue(key, buffer) {
|
|
1863
|
-
if (buffer.byteLength === 0) throw new Error(`unexpectedly empty value buffer; key = ${key}`);
|
|
1864
|
-
try {
|
|
1865
|
-
const structured = VALUE_CODEC_HEADER.every((byte, index) => buffer[index] === byte);
|
|
1866
|
-
const bytes = structured ? buffer.subarray(VALUE_CODEC_HEADER.byteLength) : buffer;
|
|
1867
|
-
const parsed = JSON.parse(textDecoder.decode(bytes));
|
|
1868
|
-
return structured ? deserialize(parsed) : parsed;
|
|
1869
|
-
} catch (exception) {
|
|
1870
|
-
throw new Error(`actor storage deserialization failed: failed to deserialize stored value; key = ${key}; size = ${buffer.byteLength}`, { cause: exception });
|
|
1871
|
-
}
|
|
1872
|
-
}
|
|
1873
|
-
/**
|
|
1874
|
-
* ← `transformMaybeBackpressure` (`actor-state.c++:103-119`). THIS is decision
|
|
1875
|
-
* 2's live site: `DeleteAllResults.backpressure` is still `Promise<void> |
|
|
1876
|
-
* undefined`, so the branch has something to select between.
|
|
1877
|
-
*
|
|
1878
|
-
* Upstream's own note, kept because it is the reason the flag is threaded here
|
|
1879
|
-
* at all: "In practice `allowConcurrency` will have no effect on a backpressure
|
|
1880
|
-
* promise since backpressure blocks everything anyway, but we pass the option
|
|
1881
|
-
* through for consistency in case of future changes."
|
|
1882
|
-
*/
|
|
1883
|
-
function transformMaybeBackpressure(ctx, options, maybeBackpressure) {
|
|
1884
|
-
if (maybeBackpressure === void 0) return Promise.resolve();
|
|
1885
|
-
if (options.allowConcurrency === true) return ctx.awaitIo(maybeBackpressure);
|
|
1886
|
-
return ctx.awaitIoWithInputLock(maybeBackpressure, () => {});
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* ← `DurableObjectStorageOperations::compileListOptions`
|
|
1890
|
-
* (`actor-state.c++:314-417`). Returns undefined if the list operation would
|
|
1891
|
-
* provably return no results. `SyncKvStorage` reuses it, exactly as upstream's
|
|
1892
|
-
* comment says it must.
|
|
1893
|
-
*
|
|
1894
|
-
* Two translations. `startAfter` gains ONE null character where upstream's
|
|
1895
|
-
* `kj::String` gains two, because the second of upstream's is the terminator and
|
|
1896
|
-
* a JS string has none. And every comparison here is on UTF-16 code units where
|
|
1897
|
-
* upstream's is on UTF-8 bytes, while the range the database actually applies is
|
|
1898
|
-
* SQLite's `BINARY` collation over UTF-8 — the two orders agree for every key
|
|
1899
|
-
* outside the astral planes, and a key that mixes astral characters with a
|
|
1900
|
-
* prefix can land on the wrong side of a clamp this function computes.
|
|
1901
|
-
*/
|
|
1902
|
-
function compileListOptions(options) {
|
|
1903
|
-
let start = "";
|
|
1904
|
-
let end;
|
|
1905
|
-
let reverse = false;
|
|
1906
|
-
let limit;
|
|
1907
|
-
if (options !== void 0) {
|
|
1908
|
-
if (options.start !== void 0) {
|
|
1909
|
-
if (options.startAfter !== void 0) throw new TypeError("list() cannot be called with both start and startAfter values.");
|
|
1910
|
-
start = options.start;
|
|
1911
|
-
}
|
|
1912
|
-
if (options.startAfter !== void 0) start = options.startAfter + NULL_CHARACTER;
|
|
1913
|
-
if (options.end !== void 0) end = options.end;
|
|
1914
|
-
if (options.reverse !== void 0) reverse = options.reverse;
|
|
1915
|
-
if (options.limit !== void 0) {
|
|
1916
|
-
if (!(options.limit > 0)) throw new TypeError("List limit must be positive.");
|
|
1917
|
-
limit = options.limit;
|
|
1918
|
-
}
|
|
1919
|
-
const prefix = options.prefix;
|
|
1920
|
-
if (prefix !== void 0 && prefix.length > 0) {
|
|
1921
|
-
if (start < prefix) start = prefix;
|
|
1922
|
-
else if (start.startsWith(prefix)) {} else return;
|
|
1923
|
-
const keyAfterPrefix = firstKeyAfterPrefix(prefix);
|
|
1924
|
-
if (keyAfterPrefix === void 0) {} else if (end === void 0) end = keyAfterPrefix;
|
|
1925
|
-
else if (end <= prefix) return;
|
|
1926
|
-
else if (end.startsWith(prefix)) {} else end = keyAfterPrefix;
|
|
1927
|
-
}
|
|
1928
|
-
}
|
|
1929
|
-
if (end !== void 0 && end <= start) return;
|
|
1930
|
-
return {
|
|
1931
|
-
start,
|
|
1932
|
-
end,
|
|
1933
|
-
reverse,
|
|
1934
|
-
limit
|
|
1935
|
-
};
|
|
1936
|
-
}
|
|
1937
|
-
/**
|
|
1938
|
-
* ← the `keyAfterPrefix` vector: strip maximal trailing units, then increment.
|
|
1939
|
-
*
|
|
1940
|
-
* Returns undefined when the prefix is nothing but maximal units, which is
|
|
1941
|
-
* upstream's "the prefix is a string of some number of 0xff bytes, so includes
|
|
1942
|
-
* the entire key space up through the last possible key".
|
|
1943
|
-
*/
|
|
1944
|
-
function firstKeyAfterPrefix(prefix) {
|
|
1945
|
-
let head = prefix;
|
|
1946
|
-
while (head.length > 0 && head.charCodeAt(head.length - 1) === MAX_CODE_UNIT) head = head.slice(0, -1);
|
|
1947
|
-
if (head.length === 0) return void 0;
|
|
1948
|
-
return head.slice(0, -1) + String.fromCharCode(head.charCodeAt(head.length - 1) + 1);
|
|
1949
|
-
}
|
|
1950
|
-
/**
|
|
1951
|
-
* ← workerd `src/workerd/api/sync-kv.{h,c++}`. The synchronous surface lives
|
|
1952
|
-
* beside the asynchronous storage owner because both share the same codec and
|
|
1953
|
-
* list-option compiler over one `SqliteKv`.
|
|
1954
|
-
*/
|
|
1955
|
-
var SyncKvStorage = class {
|
|
1956
|
-
#ctx;
|
|
1957
|
-
#kv;
|
|
1958
|
-
constructor(ctx, kv) {
|
|
1959
|
-
this.#ctx = ctx;
|
|
1960
|
-
this.#kv = kv;
|
|
1961
|
-
}
|
|
1962
|
-
get(key) {
|
|
1963
|
-
requireInputLock(this.#ctx, "kv.get()");
|
|
1964
|
-
const value = this.#kv.get(key);
|
|
1965
|
-
if (value === void 0) return void 0;
|
|
1966
|
-
return deserializeValue(key, value);
|
|
1967
|
-
}
|
|
1968
|
-
list(options) {
|
|
1969
|
-
requireInputLock(this.#ctx, "kv.list()");
|
|
1970
|
-
const compiled = compileListOptions(options);
|
|
1971
|
-
if (compiled === void 0) return [];
|
|
1972
|
-
return listIterator(this.#kv.list(compiled.start, compiled.end, compiled.limit, compiled.reverse ? "REVERSE" : "FORWARD"));
|
|
1973
|
-
}
|
|
1974
|
-
put(key, value) {
|
|
1975
|
-
requireInputLock(this.#ctx, "kv.put()");
|
|
1976
|
-
this.#kv.put(key, serializeValue(value));
|
|
1977
|
-
}
|
|
1978
|
-
delete(key) {
|
|
1979
|
-
requireInputLock(this.#ctx, "kv.delete()");
|
|
1980
|
-
return this.#kv.delete(key);
|
|
1981
|
-
}
|
|
1982
|
-
};
|
|
1983
|
-
/** ← `SyncKvStorage::listNext`, whose cancellation branch is the reason it is not a plain loop. */
|
|
1984
|
-
function* listIterator(cursor) {
|
|
1985
|
-
for (;;) {
|
|
1986
|
-
const pair = cursor.next();
|
|
1987
|
-
if (pair !== void 0) {
|
|
1988
|
-
yield [pair.key, deserializeValue(pair.key, pair.value)];
|
|
1989
|
-
continue;
|
|
1990
|
-
}
|
|
1991
|
-
if (cursor.wasCanceled()) throw new Error("kv.list() iterator was invalidated because a new call to kv.list() was started. Only one kv.list() iterator can exist at a time.");
|
|
1992
|
-
return;
|
|
1993
|
-
}
|
|
1994
|
-
}
|
|
1995
|
-
/**
|
|
1996
|
-
* ← `DurableObjectStorageOperations`. "Common implementation of
|
|
1997
|
-
* DurableObjectStorage and DurableObjectTransaction. This class is designed to
|
|
1998
|
-
* be used as a mixin."
|
|
1999
|
-
*/
|
|
2000
|
-
var DurableObjectStorageOperations = class {
|
|
2001
|
-
ctx;
|
|
2002
|
-
constructor(ctx) {
|
|
2003
|
-
this.ctx = ctx;
|
|
2004
|
-
}
|
|
2005
|
-
get(keyOrKeys, maybeOptions) {
|
|
2006
|
-
requireInputLock(this.ctx, OP_GET);
|
|
2007
|
-
const options = { ...maybeOptions };
|
|
2008
|
-
if (typeof keyOrKeys === "string") return this.#getOne(keyOrKeys, options);
|
|
2009
|
-
return this.#getMultiple(keyOrKeys, options);
|
|
2010
|
-
}
|
|
2011
|
-
getAlarm(maybeOptions) {
|
|
2012
|
-
requireInputLock(this.ctx, OP_GET_ALARM);
|
|
2013
|
-
const options = {
|
|
2014
|
-
...maybeOptions,
|
|
2015
|
-
noCache: false
|
|
2016
|
-
};
|
|
2017
|
-
return Promise.resolve(this.getCache(OP_GET_ALARM).getAlarm(options));
|
|
2018
|
-
}
|
|
2019
|
-
list(maybeOptions) {
|
|
2020
|
-
requireInputLock(this.ctx, OP_LIST);
|
|
2021
|
-
const compiled = compileListOptions(maybeOptions);
|
|
2022
|
-
if (compiled === void 0) return Promise.resolve(/* @__PURE__ */ new Map());
|
|
2023
|
-
const options = { ...maybeOptions };
|
|
2024
|
-
const cache = this.getCache(OP_LIST);
|
|
2025
|
-
const result = compiled.reverse ? cache.listReverse(compiled.start, compiled.end, compiled.limit, options) : cache.list(compiled.start, compiled.end, compiled.limit, options);
|
|
2026
|
-
return Promise.resolve(listResultsToMap(result));
|
|
2027
|
-
}
|
|
2028
|
-
put(keyOrEntries, valueOrOptions, maybeOptions) {
|
|
2029
|
-
requireInputLock(this.ctx, OP_PUT);
|
|
2030
|
-
if (!isEntriesArgument(keyOrEntries)) {
|
|
2031
|
-
if (valueOrOptions === void 0) throw new TypeError("put() called with undefined value.");
|
|
2032
|
-
return this.#putOne(`${keyOrEntries}`, valueOrOptions, { ...maybeOptions });
|
|
2033
|
-
}
|
|
2034
|
-
if (valueOrOptions !== void 0 && !isPutOptions(valueOrOptions)) throw new TypeError(PUT_OVERLOAD_MESSAGE);
|
|
2035
|
-
return this.#putMultiple(keyOrEntries, { ...valueOrOptions });
|
|
2036
|
-
}
|
|
2037
|
-
delete(keyOrKeys, maybeOptions) {
|
|
2038
|
-
requireInputLock(this.ctx, OP_DELETE);
|
|
2039
|
-
const options = { ...maybeOptions };
|
|
2040
|
-
if (typeof keyOrKeys === "string") return Promise.resolve(this.getCache(OP_DELETE).delete(keyOrKeys, options));
|
|
2041
|
-
return Promise.resolve(this.getCache(OP_DELETE).deleteMultiple(keyOrKeys, options));
|
|
2042
|
-
}
|
|
2043
|
-
setAlarm(scheduledTime, maybeOptions) {
|
|
2044
|
-
requireInputLock(this.ctx, OP_PUT_ALARM);
|
|
2045
|
-
const when = scheduledTime instanceof Date ? scheduledTime.getTime() : scheduledTime;
|
|
2046
|
-
if (!(when > 0)) throw new TypeError("setAlarm() cannot be called with an alarm time <= 0");
|
|
2047
|
-
this.ctx.getActorOrThrow().assertCanSetAlarm();
|
|
2048
|
-
const options = {
|
|
2049
|
-
...maybeOptions,
|
|
2050
|
-
noCache: false
|
|
2051
|
-
};
|
|
2052
|
-
this.getCache(OP_PUT_ALARM).setAlarm(Math.max(when, this.ctx.now()), options);
|
|
2053
|
-
return Promise.resolve();
|
|
2054
|
-
}
|
|
2055
|
-
deleteAlarm(maybeOptions) {
|
|
2056
|
-
requireInputLock(this.ctx, OP_DELETE_ALARM);
|
|
2057
|
-
const options = {
|
|
2058
|
-
...maybeOptions,
|
|
2059
|
-
noCache: false
|
|
2060
|
-
};
|
|
2061
|
-
this.getCache(OP_DELETE_ALARM).setAlarm(null, options);
|
|
2062
|
-
return Promise.resolve();
|
|
2063
|
-
}
|
|
2064
|
-
#getOne(key, options) {
|
|
2065
|
-
const value = this.getCache(OP_GET).get(key, options);
|
|
2066
|
-
return Promise.resolve(value === void 0 ? void 0 : deserializeValue(key, value));
|
|
2067
|
-
}
|
|
2068
|
-
#getMultiple(keys, options) {
|
|
2069
|
-
const result = this.getCache(OP_GET).getMultiple(keys, options);
|
|
2070
|
-
return Promise.resolve(listResultsToMap(result));
|
|
2071
|
-
}
|
|
2072
|
-
#putOne(key, value, options) {
|
|
2073
|
-
this.getCache(OP_PUT).put(key, serializeValue(value), options);
|
|
2074
|
-
return Promise.resolve();
|
|
2075
|
-
}
|
|
2076
|
-
#putMultiple(entries, options) {
|
|
2077
|
-
const pairs = [];
|
|
2078
|
-
for (const [key, value] of Object.entries(entries)) {
|
|
2079
|
-
if (value === void 0) continue;
|
|
2080
|
-
pairs.push({
|
|
2081
|
-
key,
|
|
2082
|
-
value: serializeValue(value)
|
|
2083
|
-
});
|
|
2084
|
-
}
|
|
2085
|
-
this.getCache(OP_PUT).putMultiple(pairs, options);
|
|
2086
|
-
return Promise.resolve();
|
|
2087
|
-
}
|
|
2088
|
-
};
|
|
2089
|
-
/** ← `listResultsToMap` and `getMultipleResultsToMap`, minus the billing halves. */
|
|
2090
|
-
function listResultsToMap(rows) {
|
|
2091
|
-
const map = /* @__PURE__ */ new Map();
|
|
2092
|
-
for (const entry of rows) map.set(entry.key, deserializeValue(entry.key, entry.value));
|
|
2093
|
-
return map;
|
|
341
|
+
throw new Error("unreachable: exactly one module field is set");
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* ← `jsg::asBytes()` followed by `kj::heapArray<const kj::byte>(data.asPtr())`
|
|
345
|
+
* (`worker-loader.c++:231`, `:244`). The copy is the whole point — see the caller.
|
|
346
|
+
*/
|
|
347
|
+
function copyBytes(value) {
|
|
348
|
+
if (value instanceof ArrayBuffer) return new Uint8Array(value.slice(0));
|
|
349
|
+
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer.slice(value.byteOffset, value.byteOffset + value.byteLength));
|
|
350
|
+
throw new TypeError(NOT_BYTES_MESSAGE);
|
|
2094
351
|
}
|
|
2095
|
-
var DurableObjectStorage = class extends DurableObjectStorageOperations {
|
|
2096
|
-
#cache;
|
|
2097
|
-
#sql;
|
|
2098
|
-
#kv;
|
|
2099
|
-
constructor(ctx, cache) {
|
|
2100
|
-
super(ctx);
|
|
2101
|
-
this.#cache = cache;
|
|
2102
|
-
}
|
|
2103
|
-
/** ← `DurableObjectStorage::getActorCacheInterface`, which `DurableObjectState::abort` needs. */
|
|
2104
|
-
getActorCacheInterface() {
|
|
2105
|
-
return this.#cache;
|
|
2106
|
-
}
|
|
2107
|
-
/** ← `DurableObjectStorage::getSqliteDb`. Always SQLite-backed here; see the header. */
|
|
2108
|
-
getSqliteDb() {
|
|
2109
|
-
return this.#cache.getSqliteDatabase();
|
|
2110
|
-
}
|
|
2111
|
-
getCache() {
|
|
2112
|
-
return this.#cache;
|
|
2113
|
-
}
|
|
2114
|
-
/** ← `JSG_LAZY_INSTANCE_PROPERTY(sql, getSql)`. */
|
|
2115
|
-
get sql() {
|
|
2116
|
-
this.#sql ??= new SqlStorage(this.ctx, this);
|
|
2117
|
-
return this.#sql;
|
|
2118
|
-
}
|
|
2119
|
-
/** ← `JSG_LAZY_INSTANCE_PROPERTY(kv, getKv)`. */
|
|
2120
|
-
get kv() {
|
|
2121
|
-
this.#kv ??= new SyncKvStorage(this.ctx, this.#cache.getSqliteKv());
|
|
2122
|
-
return this.#kv;
|
|
2123
|
-
}
|
|
2124
|
-
/**
|
|
2125
|
-
* ← `DurableObjectStorage::deleteAll`.
|
|
2126
|
-
*
|
|
2127
|
-
* `deleteAlarm` is upstream's `FeatureFlags::get(js).getDeleteAllDeletesAlarm()`,
|
|
2128
|
-
* a compatibility flag that exists so Workers published before it keep the old
|
|
2129
|
-
* behaviour. A runtime with no deployed history takes the current behaviour.
|
|
2130
|
-
*/
|
|
2131
|
-
deleteAll(maybeOptions) {
|
|
2132
|
-
requireInputLock(this.ctx, "deleteAll()");
|
|
2133
|
-
const options = { ...maybeOptions };
|
|
2134
|
-
const result = this.#cache.deleteAll(options, { deleteAlarm: true });
|
|
2135
|
-
return transformMaybeBackpressure(this.ctx, options, result.backpressure);
|
|
2136
|
-
}
|
|
2137
|
-
/**
|
|
2138
|
-
* ← `DurableObjectStorage::transaction`.
|
|
2139
|
-
*
|
|
2140
|
-
* The critical section is load bearing and upstream says why: "the call to
|
|
2141
|
-
* `startTransaction()` is when the SQLite-backed implementation will actually
|
|
2142
|
-
* invoke `BEGIN TRANSACTION`, so it's important that we're inside the
|
|
2143
|
-
* blockConcurrencyWhile block before that point so we don't accidentally catch
|
|
2144
|
-
* some other asynchronous event in our transaction."
|
|
2145
|
-
*
|
|
2146
|
-
* The exception is packed into the result rather than thrown out of the
|
|
2147
|
-
* section, and then rethrown outside it. Upstream's reason: "We don't actually
|
|
2148
|
-
* want to reset the object, we only want to roll back the transaction and
|
|
2149
|
-
* propagate the exception." A throw out of a critical section permanently
|
|
2150
|
-
* breaks the input gate (§1.5), so a failing transaction callback would
|
|
2151
|
-
* destroy the actor.
|
|
2152
|
-
*/
|
|
2153
|
-
transaction(closure) {
|
|
2154
|
-
requireInputLock(this.ctx, "transaction()");
|
|
2155
|
-
return this.ctx.blockConcurrencyWhile(async () => {
|
|
2156
|
-
const txn = new DurableObjectTransaction(this.ctx, this.#cache.startTransaction());
|
|
2157
|
-
try {
|
|
2158
|
-
const value = await closure(txn);
|
|
2159
|
-
txn.maybeCommit();
|
|
2160
|
-
return {
|
|
2161
|
-
isError: false,
|
|
2162
|
-
value
|
|
2163
|
-
};
|
|
2164
|
-
} catch (exception) {
|
|
2165
|
-
txn.maybeRollback();
|
|
2166
|
-
return {
|
|
2167
|
-
isError: true,
|
|
2168
|
-
exception
|
|
2169
|
-
};
|
|
2170
|
-
}
|
|
2171
|
-
}).then((result) => {
|
|
2172
|
-
if (result.isError) throw result.exception;
|
|
2173
|
-
return result.value;
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2176
|
-
/** ← `DurableObjectStorage::transactionSync`, a forward for the reason above. */
|
|
2177
|
-
transactionSync(callback) {
|
|
2178
|
-
requireInputLock(this.ctx, "transactionSync()");
|
|
2179
|
-
return this.#cache.transactionSync(callback);
|
|
2180
|
-
}
|
|
2181
|
-
/**
|
|
2182
|
-
* ← `DurableObjectStorage::sync`.
|
|
2183
|
-
*
|
|
2184
|
-
* Upstream's `awaitIo` rather than `awaitIoWithInputLock`, which is the one
|
|
2185
|
-
* storage method that deliberately opens the gate: "we're merely checking if
|
|
2186
|
-
* we have any pending or in-flight operations, and providing a promise that
|
|
2187
|
-
* resolves when they succeed."
|
|
2188
|
-
*/
|
|
2189
|
-
sync() {
|
|
2190
|
-
requireInputLock(this.ctx, "sync()");
|
|
2191
|
-
return this.ctx.awaitIo(this.#cache.onNoPendingFlush());
|
|
2192
|
-
}
|
|
2193
|
-
/**
|
|
2194
|
-
* Real, not a boundary: `ActorSqlite`'s is "an ersatz implementation that's
|
|
2195
|
-
* good enough for local dev with D1's Session API", built on the metadata
|
|
2196
|
-
* table's local-development bookmark. Anything above this package that
|
|
2197
|
-
* surfaces it to an application should know it is a counter and not a
|
|
2198
|
-
* recovery point — as it is on workerd.
|
|
2199
|
-
*/
|
|
2200
|
-
getCurrentBookmark() {
|
|
2201
|
-
requireInputLock(this.ctx, "getCurrentBookmark()");
|
|
2202
|
-
return this.ctx.awaitIo(this.#cache.getCurrentBookmark());
|
|
2203
|
-
}
|
|
2204
|
-
waitForBookmark(bookmark) {
|
|
2205
|
-
requireInputLock(this.ctx, "waitForBookmark()");
|
|
2206
|
-
return this.ctx.awaitIo(this.#cache.waitForBookmark(bookmark));
|
|
2207
|
-
}
|
|
2208
|
-
/** Substrate boundary: point-in-time recovery. Upstream reaches the cache directly, as this does. */
|
|
2209
|
-
getBookmarkForTime(timestamp) {
|
|
2210
|
-
return this.#cache.getBookmarkForTime(timestamp instanceof Date ? timestamp.getTime() : timestamp);
|
|
2211
|
-
}
|
|
2212
|
-
/** Substrate boundary: point-in-time recovery. */
|
|
2213
|
-
onNextSessionRestoreBookmark(bookmark) {
|
|
2214
|
-
return this.#cache.onNextSessionRestoreBookmark(bookmark);
|
|
2215
|
-
}
|
|
2216
|
-
/** Substrate boundary: replication. */
|
|
2217
|
-
ensureReplicas() {
|
|
2218
|
-
this.#cache.ensureReplicas();
|
|
2219
|
-
}
|
|
2220
|
-
/** Substrate boundary: replication. */
|
|
2221
|
-
disableReplicas() {
|
|
2222
|
-
this.#cache.disableReplicas();
|
|
2223
|
-
}
|
|
2224
|
-
/**
|
|
2225
|
-
* ← `DurableObjectStorage::getPrimary` / `isReplica`. `maybePrimary` is set
|
|
2226
|
-
* only by the replica constructor, and nothing constructs a replica here, so
|
|
2227
|
-
* these answer upstream's own non-replica case rather than a stubbed one.
|
|
2228
|
-
*/
|
|
2229
|
-
getPrimary() {}
|
|
2230
|
-
isReplica() {
|
|
2231
|
-
return false;
|
|
2232
|
-
}
|
|
2233
|
-
};
|
|
2234
|
-
var DurableObjectTransaction = class extends DurableObjectStorageOperations {
|
|
2235
|
-
/** Becomes undefined when committed or rolled back. */
|
|
2236
|
-
#cacheTxn;
|
|
2237
|
-
#rolledBack = false;
|
|
2238
|
-
constructor(ctx, cacheTxn) {
|
|
2239
|
-
super(ctx);
|
|
2240
|
-
this.#cacheTxn = cacheTxn;
|
|
2241
|
-
}
|
|
2242
|
-
getCache(op) {
|
|
2243
|
-
if (this.#rolledBack) throw new Error(`Cannot ${op} on rolled back transaction`);
|
|
2244
|
-
const txn = this.#cacheTxn;
|
|
2245
|
-
if (txn === void 0) throw new Error(`Cannot call ${op} on transaction that has already committed: did you move \`txn\` outside of the closure?`);
|
|
2246
|
-
return txn;
|
|
2247
|
-
}
|
|
2248
|
-
/** Called from JS. */
|
|
2249
|
-
rollback() {
|
|
2250
|
-
if (this.#rolledBack) return;
|
|
2251
|
-
this.getCache(OP_ROLLBACK);
|
|
2252
|
-
const txn = this.#cacheTxn;
|
|
2253
|
-
if (txn !== void 0) {
|
|
2254
|
-
txn.rollback();
|
|
2255
|
-
txn.drop();
|
|
2256
|
-
this.#cacheTxn = void 0;
|
|
2257
|
-
}
|
|
2258
|
-
this.#rolledBack = true;
|
|
2259
|
-
}
|
|
2260
|
-
/** Just throws an exception saying this isn't supported. */
|
|
2261
|
-
deleteAll() {
|
|
2262
|
-
throw new Error("Cannot call deleteAll() within a transaction");
|
|
2263
|
-
}
|
|
2264
|
-
/**
|
|
2265
|
-
* Called from the runtime, not JS, after the transaction callback has
|
|
2266
|
-
* completed. Does nothing if the transaction is already committed or rolled
|
|
2267
|
-
* back. Synchronous, because `ActorCacheTransaction::commit` is (§1.4).
|
|
2268
|
-
*/
|
|
2269
|
-
maybeCommit() {
|
|
2270
|
-
const txn = this.#cacheTxn;
|
|
2271
|
-
if (txn === void 0) return;
|
|
2272
|
-
this.#cacheTxn = void 0;
|
|
2273
|
-
txn.commit();
|
|
2274
|
-
txn.drop();
|
|
2275
|
-
}
|
|
2276
|
-
/** Same, for the failure path. Upstream's drops the transaction, whose destructor rolls back. */
|
|
2277
|
-
maybeRollback() {
|
|
2278
|
-
const txn = this.#cacheTxn;
|
|
2279
|
-
this.#cacheTxn = void 0;
|
|
2280
|
-
this.#rolledBack = true;
|
|
2281
|
-
txn?.drop();
|
|
2282
|
-
}
|
|
2283
|
-
};
|
|
2284
352
|
/**
|
|
2285
|
-
* ← `
|
|
353
|
+
* ← `Fetcher::getSubrequestChannel(ioctx)` followed by
|
|
354
|
+
* `channel->requireAllowsTransfer()` (`worker-loader.c++:125-126`, `:144-145`,
|
|
355
|
+
* `:157-158`).
|
|
356
|
+
*
|
|
357
|
+
* **The check has nothing to call, and that is a recorded divergence rather than
|
|
358
|
+
* an omission.** Upstream's `SubrequestChannel` carries `requireAllowsTransfer()`;
|
|
359
|
+
* here a `SubrequestChannel` and the `Fetcher` built over it are one object
|
|
360
|
+
* (`io/io-channels.ts`'s header, and the same collapse `api/actor.ts` and
|
|
361
|
+
* `api/export-loopback.ts` already made), and a `Fetcher` is
|
|
362
|
+
* `@cloudflare/workers-types`' interface with no such member. The one refusal it
|
|
363
|
+
* produces in the open-source runtime is `throwDynamicEntrypointTransferError`
|
|
364
|
+
* (`server.c++:167-173`), raised by `WorkerService::requireAllowsTransfer` when
|
|
365
|
+
* `isDynamic` — an isolate-host fact here, exactly as it is a `server/` fact there.
|
|
2286
366
|
*
|
|
2287
|
-
*
|
|
2288
|
-
* so
|
|
2289
|
-
*
|
|
2290
|
-
* Comparing `name.length` accepts a 256-character non-ASCII name that upstream
|
|
2291
|
-
* refuses, which is a bound a caller can hit.
|
|
367
|
+
* Kept as a named function rather than inlined so the three call sites read as
|
|
368
|
+
* upstream's do, and so there is one place to put the check if a `Fetcher` seam
|
|
369
|
+
* ever grows one.
|
|
2292
370
|
*/
|
|
2293
|
-
function
|
|
2294
|
-
|
|
371
|
+
function requireTransferableChannel(fetcher) {
|
|
372
|
+
return fetcher;
|
|
2295
373
|
}
|
|
2296
374
|
/**
|
|
2297
|
-
* ←
|
|
375
|
+
* ← `Frankenvalue::fromJs(js, …)`, whose serializer refuses any object whose JSG
|
|
376
|
+
* resource type declares no `JSG_SERIALIZABLE` (`jsg/ser.c++:175-183`).
|
|
377
|
+
*
|
|
378
|
+
* Four types reachable from this package are in that set, and all four for one
|
|
379
|
+
* reason: `JSG_INHERIT` does not carry serializability. Upstream says so twice — in
|
|
380
|
+
* `export-loopback.h:57-58` ("Note that `LoopbackServiceStub` is intentionally NOT
|
|
381
|
+
* serializable, unlike its parent class Fetcher") and in the test that pins it,
|
|
382
|
+
* whose comment reads "it's more testing LoopbackServiceStub and that
|
|
383
|
+
* serializability is not inherited" (`worker-loader-test.js:91-92`). None of
|
|
384
|
+
* `LoopbackServiceStub`, `LoopbackDurableObjectClass`,
|
|
385
|
+
* `LoopbackDurableObjectNamespace` or `LoopbackColoLocalActorNamespace` declares
|
|
386
|
+
* `JSG_SERIALIZABLE`; every one of them *invoked* produces something that does — a
|
|
387
|
+
* `Fetcher` or a plain `DurableObjectClass` — which is exactly the distinction
|
|
388
|
+
* `worker-loader-test.js:62-107` measures, accepting the invoked form in `props`
|
|
389
|
+
* and refusing the bare binding.
|
|
2298
390
|
*
|
|
2299
|
-
*
|
|
2300
|
-
*
|
|
2301
|
-
*
|
|
2302
|
-
*
|
|
2303
|
-
*
|
|
391
|
+
* **This discharges the obligation the README left open on Section 7.** That row
|
|
392
|
+
* says the refusal would have to come from `src/transport/` if a `Fetcher` ever
|
|
393
|
+
* became serializable; the layer upstream refuses at is this one, because
|
|
394
|
+
* `Frankenvalue::fromJs` runs inside `getEntrypoint`, `getDurableObjectClass` and
|
|
395
|
+
* `toDynamicWorkerSource`. Putting it here makes it reachable today rather than
|
|
396
|
+
* conditional on a transport change that has not happened, and it depends on no
|
|
397
|
+
* transport fact — so `api/` still imports no transport library.
|
|
2304
398
|
*
|
|
2305
|
-
*
|
|
2306
|
-
*
|
|
2307
|
-
*
|
|
399
|
+
* The walk descends into plain objects and arrays only. Everything else is a host
|
|
400
|
+
* object: either one the substrate knows how to carry (a `Fetcher`, a
|
|
401
|
+
* `DurableObjectClass`, an `RpcTarget`) or one of the four above. Walking a host
|
|
402
|
+
* object's own keys would drive proxy traps — a stub's `get` mints an RPC import —
|
|
403
|
+
* which is a cost upstream's serializer never pays, because it asks the type rather
|
|
404
|
+
* than the value.
|
|
2308
405
|
*/
|
|
2309
|
-
function
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
406
|
+
function requireSerializableProps(root, field) {
|
|
407
|
+
const seen = /* @__PURE__ */ new Set();
|
|
408
|
+
const visit = (value, path) => {
|
|
409
|
+
if (value === null || typeof value !== "object" && typeof value !== "function") return;
|
|
410
|
+
const subject = value;
|
|
411
|
+
const refused = notSerializableType(subject);
|
|
412
|
+
if (refused !== void 0) throw new DOMException(`${notSerializableMessage(refused)} At ${path}.`, "DataCloneError");
|
|
413
|
+
if (seen.has(subject)) return;
|
|
414
|
+
seen.add(subject);
|
|
415
|
+
if (Array.isArray(subject)) {
|
|
416
|
+
subject.forEach((entry, index) => {
|
|
417
|
+
visit(entry, `${path}[${index}]`);
|
|
418
|
+
});
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
const prototype = Object.getPrototypeOf(subject);
|
|
422
|
+
if (prototype !== Object.prototype && prototype !== null) return;
|
|
423
|
+
for (const [name, entry] of Object.entries(subject)) visit(entry, `${path}.${name}`);
|
|
424
|
+
};
|
|
425
|
+
visit(root, `<${field}>`);
|
|
426
|
+
return root;
|
|
2314
427
|
}
|
|
2315
428
|
/**
|
|
2316
|
-
*
|
|
429
|
+
* The four `ctx.exports` binding types, named by the class whose name
|
|
430
|
+
* `GetConstructorName()` would report.
|
|
2317
431
|
*
|
|
2318
|
-
*
|
|
2319
|
-
*
|
|
2320
|
-
*
|
|
2321
|
-
*
|
|
2322
|
-
* `
|
|
2323
|
-
*
|
|
2324
|
-
* 4.20260702.1 — a month newer than the snapshot — declares
|
|
2325
|
-
* `clone(src: string, dst: string): void` on `DurableObjectFacets`. So the
|
|
2326
|
-
* signature comes from the types and the semantics from §1.10 (abort dst, delete
|
|
2327
|
-
* dst storage, recursive copy of the src subtree), and the orchestration is
|
|
2328
|
-
* `server/`'s `cloneFacet`. There is nothing upstream to check the body against,
|
|
2329
|
-
* which makes it the one method here with no reference — worth knowing when it
|
|
2330
|
-
* is wrong.
|
|
432
|
+
* The four are mutually exclusive — each extends a different base
|
|
433
|
+
* (`api/export-loopback.ts`) — so the order is presentational. What the order does
|
|
434
|
+
* NOT do is reach a base class: `DurableObjectClass`, `DurableObjectNamespace`,
|
|
435
|
+
* `ColoLocalActorNamespace` and `Fetcher` are all serializable upstream
|
|
436
|
+
* (`actor.h:389`), and it is exactly `JSG_INHERIT`'s failure to carry
|
|
437
|
+
* serializability that makes the four subclasses refuse where their bases accept.
|
|
2331
438
|
*/
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
this.#facetManager = facetManager;
|
|
2339
|
-
this.#parentId = parentId;
|
|
2340
|
-
}
|
|
2341
|
-
/**
|
|
2342
|
-
* Get a facet by name, starting it if it isn't already running.
|
|
2343
|
-
* `getStartupOptions` is invoked only if the facet wasn't already running.
|
|
2344
|
-
*
|
|
2345
|
-
* Returns a `Fetcher` instead of a `DurableObject` because the returned stub
|
|
2346
|
-
* does not have the `id` or `name` methods that a DO stub normally has.
|
|
2347
|
-
*/
|
|
2348
|
-
get(name, getStartupOptions) {
|
|
2349
|
-
requireValidFacetName(name);
|
|
2350
|
-
const facetManager = this.#getFacetManager();
|
|
2351
|
-
if (facetManager.getDepth() + 1 >= 4) throw new Error(`Facet nesting depth limit exceeded. The maximum depth including the root Durable Object is 4.`);
|
|
2352
|
-
requireInputLock(this.#ctx, "facets.get()");
|
|
2353
|
-
const getStartInfo = this.#ctx.makeReentryCallback(async () => {
|
|
2354
|
-
const options = await getStartupOptions();
|
|
2355
|
-
const id = options.id;
|
|
2356
|
-
return {
|
|
2357
|
-
actorClass: requireFacetClass(options.class).getChannel(),
|
|
2358
|
-
id: id === void 0 ? this.#parentId : typeof id === "string" ? id : id.name ?? id.toString()
|
|
2359
|
-
};
|
|
2360
|
-
});
|
|
2361
|
-
return facetManager.getFacet(name, getStartInfo);
|
|
2362
|
-
}
|
|
2363
|
-
abort(name, reason) {
|
|
2364
|
-
requireValidFacetName(name);
|
|
2365
|
-
this.#getFacetManager().abortFacet(name, reason);
|
|
2366
|
-
}
|
|
2367
|
-
delete(name) {
|
|
2368
|
-
requireValidFacetName(name);
|
|
2369
|
-
this.#getFacetManager().deleteFacet(name);
|
|
2370
|
-
}
|
|
2371
|
-
clone(src, dst) {
|
|
2372
|
-
requireValidFacetName(src);
|
|
2373
|
-
requireValidFacetName(dst);
|
|
2374
|
-
this.#getFacetManager().cloneFacet(src, dst);
|
|
2375
|
-
}
|
|
2376
|
-
#getFacetManager() {
|
|
2377
|
-
const facetManager = this.#facetManager;
|
|
2378
|
-
if (facetManager === void 0) throw new Error("This Durable Object does not support creating facets.");
|
|
2379
|
-
return facetManager;
|
|
2380
|
-
}
|
|
2381
|
-
};
|
|
2382
|
-
/** The type passed as the first parameter to a Durable Object class's constructor. */
|
|
2383
|
-
var DurableObjectState = class {
|
|
2384
|
-
#ctx;
|
|
2385
|
-
#options;
|
|
2386
|
-
#facets;
|
|
2387
|
-
constructor(ctx, options) {
|
|
2388
|
-
this.#ctx = ctx;
|
|
2389
|
-
this.#options = options;
|
|
2390
|
-
}
|
|
2391
|
-
get id() {
|
|
2392
|
-
return this.#options.id;
|
|
2393
|
-
}
|
|
2394
|
-
get props() {
|
|
2395
|
-
return this.#options.props;
|
|
2396
|
-
}
|
|
2397
|
-
/** ← `JSG_LAZY_INSTANCE_PROPERTY(exports, getExports)`, behind `enableCtxExports` upstream. */
|
|
2398
|
-
get exports() {
|
|
2399
|
-
return this.#options.exports;
|
|
2400
|
-
}
|
|
2401
|
-
get version() {
|
|
2402
|
-
return this.#options.version;
|
|
2403
|
-
}
|
|
2404
|
-
/**
|
|
2405
|
-
* NO upstream correspondence, because upstream needs none: a
|
|
2406
|
-
* `ServiceWorkerGlobalScope` IS the isolate's global object there, so an
|
|
2407
|
-
* actor's class reaches its gated `setTimeout` by writing `setTimeout`.
|
|
2408
|
-
*
|
|
2409
|
-
* Here one realm hosts several actors, so the names on `globalThis` can only
|
|
2410
|
-
* be bound to one of them and a continuation cannot be asked which one it
|
|
2411
|
-
* belongs to. `ctx` is the one reference every Durable Object class already
|
|
2412
|
-
* holds and that already means exactly one actor — the constructor was handed
|
|
2413
|
-
* it — so it is where the scope goes. An actor's method writes
|
|
2414
|
-
* `this.ctx.globals.setTimeout(…)`; a free function it calls takes the scope
|
|
2415
|
-
* as a parameter.
|
|
2416
|
-
*
|
|
2417
|
-
* `installActorScope` still exists and is still what a host uses for a
|
|
2418
|
-
* dynamically-loaded Worker source, which has no `ctx` to reach through and
|
|
2419
|
-
* its own module scope to destructure into. The two are the same object.
|
|
2420
|
-
*/
|
|
2421
|
-
get globals() {
|
|
2422
|
-
return this.#options.globals;
|
|
2423
|
-
}
|
|
2424
|
-
get storage() {
|
|
2425
|
-
const storage = this.#options.storage;
|
|
2426
|
-
if (storage === void 0) throw new Error("This Durable Object does not have storage.");
|
|
2427
|
-
return storage;
|
|
2428
|
-
}
|
|
2429
|
-
/** ← `JSG_LAZY_INSTANCE_PROPERTY(facets, getFacets)`. */
|
|
2430
|
-
get facets() {
|
|
2431
|
-
this.#facets ??= new DurableObjectFacets(this.#ctx, this.#options.facets, this.#options.id.toString());
|
|
2432
|
-
return this.#facets;
|
|
2433
|
-
}
|
|
2434
|
-
waitUntil(promise) {
|
|
2435
|
-
this.#ctx.addWaitUntil(promise.then(() => {}));
|
|
2436
|
-
}
|
|
2437
|
-
/**
|
|
2438
|
-
* ← `DurableObjectState::blockConcurrencyWhile` (`actor-state.c++:1128-1131`),
|
|
2439
|
-
* which is a one-line forward and nothing else. The 30-second deadline, the
|
|
2440
|
-
* brokenness annotation and the never-settled promise on failure all live in
|
|
2441
|
-
* `IoContext::blockConcurrencyWhile`, which Section 2 already implements.
|
|
2442
|
-
*
|
|
2443
|
-
* Its precondition comes with it: `IoContext::blockConcurrencyWhile` calls
|
|
2444
|
-
* `getInputLock()`, which asserts, so this is reachable only from inside a
|
|
2445
|
-
* gated slice.
|
|
2446
|
-
*/
|
|
2447
|
-
blockConcurrencyWhile(callback) {
|
|
2448
|
-
return this.#ctx.blockConcurrencyWhile(callback);
|
|
2449
|
-
}
|
|
2450
|
-
/**
|
|
2451
|
-
* ← `DurableObjectState::abort`. Reset the object, including breaking the
|
|
2452
|
-
* output gate and canceling any writes that haven't been committed yet.
|
|
2453
|
-
*
|
|
2454
|
-
* `js.terminateExecutionNow()` has no port — there is no isolate to terminate —
|
|
2455
|
-
* so the caller's own slice keeps running to its next await, where `IoContext`
|
|
2456
|
-
* refuses to re-enter.
|
|
2457
|
-
*/
|
|
2458
|
-
abort(reason) {
|
|
2459
|
-
const description = reason === void 0 ? "broken.outputGateBroken; jsg.Error: Application called abort() to reset Durable Object." : `broken.outputGateBroken; jsg.Error: ${reason}`;
|
|
2460
|
-
const error = new Error(description);
|
|
2461
|
-
setUserErrorDetail(error);
|
|
2462
|
-
this.#options.storage?.getActorCacheInterface().shutdown(error);
|
|
2463
|
-
this.#ctx.abort(error);
|
|
2464
|
-
}
|
|
2465
|
-
/** ← `DurableObjectState::getPrimaryStub`. Non-null only for a replica; see the storage note. */
|
|
2466
|
-
get primaryStub() {
|
|
2467
|
-
return this.#options.storage?.getPrimary();
|
|
2468
|
-
}
|
|
2469
|
-
/** Substrate boundary: replication. */
|
|
2470
|
-
configureReadReplication(options) {
|
|
2471
|
-
const storage = this.#options.storage;
|
|
2472
|
-
if (storage === void 0) throw new TypeError("This actor does not support read replication.");
|
|
2473
|
-
if (storage.isReplica()) throw new Error("Replica Durable Objects cannot call configureReadReplication().");
|
|
2474
|
-
if (options.mode !== "auto" && options.mode !== "disabled") throw new TypeError(`configureReadReplication() called with unknown mode setting: ${options.mode}.`);
|
|
2475
|
-
return this.#ctx.awaitIo(storage.getActorCacheInterface().configureReadReplication(options.mode === "auto"));
|
|
2476
|
-
}
|
|
2477
|
-
acceptWebSocket(_ws, _tags) {
|
|
2478
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2479
|
-
}
|
|
2480
|
-
getWebSockets(_tag) {
|
|
2481
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2482
|
-
}
|
|
2483
|
-
setWebSocketAutoResponse(_maybeReqResp) {
|
|
2484
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2485
|
-
}
|
|
2486
|
-
getWebSocketAutoResponse() {
|
|
2487
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2488
|
-
}
|
|
2489
|
-
getWebSocketAutoResponseTimestamp(_ws) {
|
|
2490
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2491
|
-
}
|
|
2492
|
-
setHibernatableWebSocketEventTimeout(_timeoutMs) {
|
|
2493
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2494
|
-
}
|
|
2495
|
-
getHibernatableWebSocketEventTimeout() {
|
|
2496
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2497
|
-
}
|
|
2498
|
-
getTags(_ws) {
|
|
2499
|
-
throw new Error(HIBERNATION_UNIMPLEMENTED_MESSAGE);
|
|
2500
|
-
}
|
|
2501
|
-
};
|
|
439
|
+
function notSerializableType(value) {
|
|
440
|
+
if (value instanceof LoopbackServiceStub) return "LoopbackServiceStub";
|
|
441
|
+
if (value instanceof LoopbackDurableObjectNamespace) return "LoopbackDurableObjectNamespace";
|
|
442
|
+
if (value instanceof LoopbackColoLocalActorNamespace) return "LoopbackColoLocalActorNamespace";
|
|
443
|
+
if (value instanceof LoopbackDurableObjectClass) return "LoopbackDurableObjectClass";
|
|
444
|
+
}
|
|
2502
445
|
//#endregion
|
|
2503
446
|
//#region src/api/http.ts
|
|
2504
447
|
/**
|
|
@@ -3019,10 +962,11 @@ var ActorGlobalScope = class {
|
|
|
3019
962
|
#readCurrentExternalEntry;
|
|
3020
963
|
scheduler;
|
|
3021
964
|
crypto;
|
|
3022
|
-
constructor(ctx, options
|
|
965
|
+
constructor(ctx, options) {
|
|
3023
966
|
this.#ctx = ctx;
|
|
3024
967
|
this.#fetch = options.fetch;
|
|
3025
968
|
this.#readCurrentExternalEntry = options.currentExternalEntry;
|
|
969
|
+
installWebSocketGlobals(this, options.webSockets.WebSocketPair);
|
|
3026
970
|
this.scheduler = new Scheduler(this);
|
|
3027
971
|
this.crypto = new GatedCrypto((op) => {
|
|
3028
972
|
this.#requireOwnSlice(op);
|
|
@@ -3127,6 +1071,7 @@ var ActorGlobalScope = class {
|
|
|
3127
1071
|
* scope instead. A single-actor host simply writes `() => scope`.
|
|
3128
1072
|
*/
|
|
3129
1073
|
function actorScopeBindings(resolve) {
|
|
1074
|
+
const BoundWebSocketPair = new Proxy(class WebSocketPair {}, { construct: () => new (resolve()).WebSocketPair() });
|
|
3130
1075
|
return {
|
|
3131
1076
|
awaitIo: (promise) => resolve().awaitIo(promise),
|
|
3132
1077
|
scheduler: {
|
|
@@ -3143,6 +1088,9 @@ function actorScopeBindings(resolve) {
|
|
|
3143
1088
|
},
|
|
3144
1089
|
fetch: (input, init) => resolve().fetch(input, init),
|
|
3145
1090
|
crypto: scopeCrypto(resolve),
|
|
1091
|
+
WebSocket: globalThis.WebSocket,
|
|
1092
|
+
WebSocketPair: BoundWebSocketPair,
|
|
1093
|
+
WebSocketRequestResponsePair: RuntimeWebSocketRequestResponsePair,
|
|
3146
1094
|
get currentExternalEntry() {
|
|
3147
1095
|
return resolve().currentExternalEntry;
|
|
3148
1096
|
}
|
|
@@ -3153,7 +1101,7 @@ function actorScopeBindings(resolve) {
|
|
|
3153
1101
|
* an operation actually runs.
|
|
3154
1102
|
*
|
|
3155
1103
|
* That laziness is required rather than tidy, and both lanes proved it. A facet's
|
|
3156
|
-
* module destructures its
|
|
1104
|
+
* module destructures its actor globals at module scope, which is BEFORE its container
|
|
3157
1105
|
* exists — so a `crypto` that resolved on read threw at import. And on the root
|
|
3158
1106
|
* path `globalThis.crypto` is read by things that are not the actor at all: capnweb,
|
|
3159
1107
|
* the sqlite driver, the test runner. So the binding is a pair of plain objects
|
|
@@ -3199,8 +1147,8 @@ var ASYNC_SUBTLE_METHODS = [
|
|
|
3199
1147
|
* one that is not.
|
|
3200
1148
|
*
|
|
3201
1149
|
* **A host should call this rather than assigning the names itself**, and the
|
|
3202
|
-
* reason is the failure it prevents: a host that installs
|
|
3203
|
-
*
|
|
1150
|
+
* reason is the failure it prevents: a host that installs only a subset leaves a
|
|
1151
|
+
* primitive ungated, and an ungated primitive that WORKS is invisible until
|
|
3204
1152
|
* a continuation after it touches storage — possibly never, on the path that
|
|
3205
1153
|
* matters. The set is the package's, so it can grow without every host growing
|
|
3206
1154
|
* with it.
|
|
@@ -3229,153 +1177,6 @@ function installActorScope(target, resolve) {
|
|
|
3229
1177
|
});
|
|
3230
1178
|
}
|
|
3231
1179
|
}
|
|
3232
|
-
//#endregion
|
|
3233
|
-
//#region src/api/web-socket.ts
|
|
3234
|
-
/** ← the `JSG_REQUIRE(!native.state.is<Accepted>(), ...)` at the head of `accept()`. */
|
|
3235
|
-
var ALREADY_ACCEPTED_MESSAGE = "acceptWebSocket(): this socket has already been accepted by an actor. A socket's frames are delivered by exactly one read loop, and a second accept would deliver them under two gates.";
|
|
3236
|
-
/** Sockets this runtime has accepted, so the refusal above is answerable. */
|
|
3237
|
-
var accepted = /* @__PURE__ */ new WeakSet();
|
|
3238
|
-
/** The four events a `WebSocket` dispatches, which `readLoop` and its `.then` cover upstream. */
|
|
3239
|
-
var SOCKET_EVENTS = [
|
|
3240
|
-
"open",
|
|
3241
|
-
"message",
|
|
3242
|
-
"close",
|
|
3243
|
-
"error"
|
|
3244
|
-
];
|
|
3245
|
-
/**
|
|
3246
|
-
* ← `WebSocket::Accepted` (`web-socket.h:~300-360`), reached through
|
|
3247
|
-
* `accept()` → `internalAccept(js, IoContext::current().getCriticalSection())`
|
|
3248
|
-
* → `startReadLoop` (`web-socket.c++:133`, `:426`, `:429-433`, `:507`).
|
|
3249
|
-
*
|
|
3250
|
-
* An `EventTarget`, so a consumer registers listeners the way it would on a real
|
|
3251
|
-
* socket — but on THIS object rather than on the raw one, because this is what
|
|
3252
|
-
* runs them inside a gated slice.
|
|
3253
|
-
*/
|
|
3254
|
-
var AcceptedWebSocket = class extends EventTarget {
|
|
3255
|
-
#ctx;
|
|
3256
|
-
#socket;
|
|
3257
|
-
/**
|
|
3258
|
-
* ← `readLoop`'s `cs` parameter, captured at accept and replayed for every
|
|
3259
|
-
* frame via `mapAddRef(cs)` (`web-socket.c++:1110`). A socket accepted inside
|
|
3260
|
-
* `blockConcurrencyWhile` therefore delivers its messages inside that critical
|
|
3261
|
-
* section — §1.8's second bullet, and the reason this is captured here rather
|
|
3262
|
-
* than read when a frame arrives.
|
|
3263
|
-
*/
|
|
3264
|
-
#criticalSection;
|
|
3265
|
-
/**
|
|
3266
|
-
* ← `OutgoingMessagesMap outgoingMessages` plus `ensurePumping`
|
|
3267
|
-
* (`web-socket.h:582-590`, `web-socket.c++:948-975`), as a chain.
|
|
3268
|
-
*
|
|
3269
|
-
* The table is insertion-ordered and the pump awaits each entry's own
|
|
3270
|
-
* `outputLock` before sending it, so messages leave in order and message N
|
|
3271
|
-
* waits only for the writes outstanding when IT was enqueued. A promise chain
|
|
3272
|
-
* is the same two properties with nothing to schedule.
|
|
3273
|
-
*/
|
|
3274
|
-
#pump = Promise.resolve();
|
|
3275
|
-
onopen = null;
|
|
3276
|
-
onmessage = null;
|
|
3277
|
-
onclose = null;
|
|
3278
|
-
onerror = null;
|
|
3279
|
-
constructor(ctx, socket) {
|
|
3280
|
-
super();
|
|
3281
|
-
this.#ctx = ctx;
|
|
3282
|
-
this.#socket = socket;
|
|
3283
|
-
this.#criticalSection = ctx.getCriticalSection();
|
|
3284
|
-
for (const type of SOCKET_EVENTS) socket.addEventListener(type, (event) => {
|
|
3285
|
-
this.#deliver(type, event);
|
|
3286
|
-
});
|
|
3287
|
-
}
|
|
3288
|
-
/**
|
|
3289
|
-
* ← `co_await context.run([...](auto& wLock) { dispatchEventImpl(...) }, mapAddRef(cs))`
|
|
3290
|
-
* (`web-socket.c++:1065-1110`).
|
|
3291
|
-
*
|
|
3292
|
-
* The run rides `addWaitUntil`, as upstream's read loop does ("We put the read
|
|
3293
|
-
* loop in a `waitUntil`, since there would otherwise be a race condition
|
|
3294
|
-
* between delivering the final close message and the request being canceled",
|
|
3295
|
-
* `web-socket.c++:537-541`). That is also what stops a listener's throw
|
|
3296
|
-
* becoming an unhandled rejection: it lands in `waitUntilStatus()`.
|
|
3297
|
-
*/
|
|
3298
|
-
#deliver(type, event) {
|
|
3299
|
-
this.#ctx.addWaitUntil(this.#ctx.run(() => {
|
|
3300
|
-
const delivered = cloneEventFor(type, event);
|
|
3301
|
-
this.dispatchEvent(delivered);
|
|
3302
|
-
const handler = this[`on${type}`];
|
|
3303
|
-
handler?.(delivered);
|
|
3304
|
-
}, { input: this.#criticalSection }));
|
|
3305
|
-
}
|
|
3306
|
-
/**
|
|
3307
|
-
* ← `WebSocket::send` (`web-socket.c++:~640`), which inserts a
|
|
3308
|
-
* `GatedMessage{IoContext::current().waitForOutputLocksIfNecessary(), …}`.
|
|
3309
|
-
*
|
|
3310
|
-
* Synchronous, as upstream's is: the wait is the pump's, not the caller's. The
|
|
3311
|
-
* output gate is what "blocks all outgoing messages from an actor that would
|
|
3312
|
-
* allow the rest of the world to observe the actor's state" (§1.1), and a
|
|
3313
|
-
* socket frame is exactly such a message.
|
|
3314
|
-
*
|
|
3315
|
-
* `waitForOutputLocksIfNecessary()` collapses to `waitForOutputLocks()` here
|
|
3316
|
-
* for the reason the whole file collapses `kj::Maybe<Worker::Actor&>`: its
|
|
3317
|
-
* body is `actor.map(…)` (`io-context.c++:383-386`) and every context in this
|
|
3318
|
-
* runtime is an actor context.
|
|
3319
|
-
*/
|
|
3320
|
-
send(data) {
|
|
3321
|
-
this.#enqueue(() => {
|
|
3322
|
-
this.#socket.send(data);
|
|
3323
|
-
});
|
|
3324
|
-
}
|
|
3325
|
-
/** ← `WebSocket::close`, which enqueues a `Close` through the same gate. */
|
|
3326
|
-
close(code, reason) {
|
|
3327
|
-
this.#enqueue(() => {
|
|
3328
|
-
this.#socket.close(code, reason);
|
|
3329
|
-
});
|
|
3330
|
-
}
|
|
3331
|
-
#enqueue(write) {
|
|
3332
|
-
const outputLock = this.#ctx.waitForOutputLocks();
|
|
3333
|
-
this.#pump = this.#pump.then(async () => {
|
|
3334
|
-
await outputLock;
|
|
3335
|
-
write();
|
|
3336
|
-
});
|
|
3337
|
-
this.#ctx.addWaitUntil(this.#pump);
|
|
3338
|
-
}
|
|
3339
|
-
};
|
|
3340
|
-
/**
|
|
3341
|
-
* ← `accept()` / `state.acceptWebSocket()`, as the one verb.
|
|
3342
|
-
*
|
|
3343
|
-
* Named for what upstream names it, because the critical-section capture is a
|
|
3344
|
-
* property of accepting rather than of constructing: "a socket accepted inside a
|
|
3345
|
-
* `blockConcurrencyWhile` delivers its messages inside that critical section"
|
|
3346
|
-
* (§1.8).
|
|
3347
|
-
*/
|
|
3348
|
-
function acceptWebSocket(ctx, socket) {
|
|
3349
|
-
if (accepted.has(socket)) throw new Error(ALREADY_ACCEPTED_MESSAGE);
|
|
3350
|
-
accepted.add(socket);
|
|
3351
|
-
return new AcceptedWebSocket(ctx, socket);
|
|
3352
|
-
}
|
|
3353
|
-
/**
|
|
3354
|
-
* An `Event` may be dispatched by exactly one target at a time, so the raw
|
|
3355
|
-
* socket's event object cannot be re-dispatched: `dispatchEvent` on an event
|
|
3356
|
-
* that is already dispatched throws `InvalidStateError`, and one that has
|
|
3357
|
-
* finished carries the raw socket as its `target`. Rebuilding it is what makes
|
|
3358
|
-
* `event.target` the accepted socket, which is what a listener expects.
|
|
3359
|
-
*/
|
|
3360
|
-
function cloneEventFor(type, event) {
|
|
3361
|
-
if (type === "message") {
|
|
3362
|
-
const source = event;
|
|
3363
|
-
return new MessageEvent("message", {
|
|
3364
|
-
data: source.data,
|
|
3365
|
-
origin: source.origin,
|
|
3366
|
-
lastEventId: source.lastEventId
|
|
3367
|
-
});
|
|
3368
|
-
}
|
|
3369
|
-
if (type === "close") {
|
|
3370
|
-
const source = event;
|
|
3371
|
-
return new CloseEvent("close", {
|
|
3372
|
-
code: source.code,
|
|
3373
|
-
reason: source.reason,
|
|
3374
|
-
wasClean: source.wasClean
|
|
3375
|
-
});
|
|
3376
|
-
}
|
|
3377
|
-
return new Event(type);
|
|
3378
|
-
}
|
|
3379
1180
|
/**
|
|
3380
1181
|
* ← the message every unimplemented `ActorCacheInterface` PITR method throws.
|
|
3381
1182
|
* `ActorSqlite` overrides two of the four; the other two keep this.
|
|
@@ -5608,14 +3409,16 @@ var ActorTree = class {
|
|
|
5608
3409
|
* which is the whole mechanism behind §1.10's parent↔child re-entrancy.
|
|
5609
3410
|
*/
|
|
5610
3411
|
var ActorImpl = class {
|
|
5611
|
-
#inputGate
|
|
5612
|
-
#outputGate
|
|
3412
|
+
#inputGate;
|
|
3413
|
+
#outputGate;
|
|
5613
3414
|
#isFacet;
|
|
5614
3415
|
/** Assigned after construction; `storage` is a WXT auto-import in extension bundles. */
|
|
5615
3416
|
actorStorage;
|
|
5616
3417
|
classInstance = { kind: "before-ctor" };
|
|
5617
|
-
constructor(isFacet) {
|
|
3418
|
+
constructor(isFacet, hooks = {}) {
|
|
5618
3419
|
this.#isFacet = isFacet;
|
|
3420
|
+
this.#inputGate = new InputGate(hooks.input);
|
|
3421
|
+
this.#outputGate = new OutputGate(hooks.output);
|
|
5619
3422
|
}
|
|
5620
3423
|
getInputGate() {
|
|
5621
3424
|
return this.#inputGate;
|
|
@@ -5925,6 +3728,7 @@ var ActorContainerImpl = class {
|
|
|
5925
3728
|
#facets;
|
|
5926
3729
|
#tree;
|
|
5927
3730
|
#env;
|
|
3731
|
+
#webSockets;
|
|
5928
3732
|
state;
|
|
5929
3733
|
facetTree;
|
|
5930
3734
|
globals;
|
|
@@ -5932,16 +3736,22 @@ var ActorContainerImpl = class {
|
|
|
5932
3736
|
#alarmTail = Promise.resolve();
|
|
5933
3737
|
constructor(options, db, tree, facetTree) {
|
|
5934
3738
|
const facet = options.facet;
|
|
5935
|
-
this.#actor = new ActorImpl(facet !== void 0);
|
|
3739
|
+
this.#actor = new ActorImpl(facet !== void 0, options.gateHooks);
|
|
5936
3740
|
this.#ctx = new IoContext(this.#actor, options.ports.timer);
|
|
5937
3741
|
this.#env = options.env;
|
|
5938
3742
|
this.#tree = tree;
|
|
5939
3743
|
this.#cache = new ActorSqlite(db, this.#actor.getOutputGate(), async () => {}, facet === void 0 ? options.ports.alarms : DEFAULT_ALARM_OUTLET);
|
|
5940
3744
|
this.#actor.actorStorage = this.#cache;
|
|
5941
3745
|
this.#durableStorage = new DurableObjectStorage(this.#ctx, this.#cache);
|
|
3746
|
+
this.#webSockets = new HibernatableWebSocketRegistry(this.#ctx, {
|
|
3747
|
+
message: (socket, message) => this.#runWebSocketHandler("webSocketMessage", socket, message),
|
|
3748
|
+
close: (socket, code, reason, wasClean) => this.#runWebSocketHandler("webSocketClose", socket, code, reason, wasClean),
|
|
3749
|
+
error: (socket, error) => this.#runWebSocketHandler("webSocketError", socket, error)
|
|
3750
|
+
}, options.ports.hibernation, options.webSockets);
|
|
5942
3751
|
this.globals = new ActorGlobalScope(this.#ctx, {
|
|
5943
3752
|
fetch: options.ports.fetch,
|
|
5944
|
-
currentExternalEntry: () => this.#currentExternalEntry
|
|
3753
|
+
currentExternalEntry: () => this.#currentExternalEntry,
|
|
3754
|
+
webSockets: this.#webSockets
|
|
5945
3755
|
});
|
|
5946
3756
|
this.facetTree = facetTree;
|
|
5947
3757
|
this.#facets = new FacetManagerImpl(this, options.ports.facets, facet?.id ?? 0, facet?.depth ?? 0, this.facetTree);
|
|
@@ -5955,7 +3765,8 @@ var ActorContainerImpl = class {
|
|
|
5955
3765
|
props: void 0,
|
|
5956
3766
|
storage: this.#durableStorage,
|
|
5957
3767
|
facets: this.#facets,
|
|
5958
|
-
globals: actorScopeBindings(() => this.globals)
|
|
3768
|
+
globals: actorScopeBindings(() => this.globals),
|
|
3769
|
+
webSockets: this.#webSockets
|
|
5959
3770
|
});
|
|
5960
3771
|
}
|
|
5961
3772
|
get onBroken() {
|
|
@@ -6089,6 +3900,14 @@ var ActorContainerImpl = class {
|
|
|
6089
3900
|
drainWaitUntil() {
|
|
6090
3901
|
return this.#ctx.drainWaitUntil();
|
|
6091
3902
|
}
|
|
3903
|
+
quiescence() {
|
|
3904
|
+
return {
|
|
3905
|
+
armedTimers: this.#ctx.getTimeoutCount(),
|
|
3906
|
+
pendingWaitUntil: this.#ctx.waitUntilTaskCount(),
|
|
3907
|
+
inputLockHeld: this.#ctx.hasCurrent(),
|
|
3908
|
+
outputGateBroken: this.#ctx.isOutputGateBroken()
|
|
3909
|
+
};
|
|
3910
|
+
}
|
|
6092
3911
|
/** ← `WorkerdApi::compileGlobals`'s `Global::WorkerLoader` arm. */
|
|
6093
3912
|
workerLoader(channel, options) {
|
|
6094
3913
|
return new WorkerLoader(this.#ctx, channel, options);
|
|
@@ -6182,6 +4001,13 @@ var ActorContainerImpl = class {
|
|
|
6182
4001
|
if (!hasAlarmHandler(instance.instance)) throw new TypeError("Your Durable Object class must have an alarm() handler.");
|
|
6183
4002
|
return instance.instance.alarm(new AlarmInvocationInfo(scheduledTime, retryCount));
|
|
6184
4003
|
}
|
|
4004
|
+
#runWebSocketHandler(name, socket, ...args) {
|
|
4005
|
+
const instance = this.#actor.classInstance;
|
|
4006
|
+
if (instance.kind !== "running") return void 0;
|
|
4007
|
+
const handler = Reflect.get(instance.instance, name);
|
|
4008
|
+
if (typeof handler !== "function") return void 0;
|
|
4009
|
+
return Reflect.apply(handler, instance.instance, [socket, ...args]);
|
|
4010
|
+
}
|
|
6185
4011
|
};
|
|
6186
4012
|
/**
|
|
6187
4013
|
* Builds one actor: the two gates, the `IoContext` over them, the storage engine
|
|
@@ -6203,6 +4029,53 @@ async function createActorContainer(options) {
|
|
|
6203
4029
|
return new ActorContainerImpl(options, db, tree, tree);
|
|
6204
4030
|
}
|
|
6205
4031
|
//#endregion
|
|
4032
|
+
//#region src/server/hibernation-mirror.ts
|
|
4033
|
+
/** In-memory socket state shared by embedders that replace live actor containers. */
|
|
4034
|
+
var HibernationMirror = class {
|
|
4035
|
+
#entries = /* @__PURE__ */ new Map();
|
|
4036
|
+
#autoResponsePair;
|
|
4037
|
+
constructor(rehydrated = [], autoResponsePair = null) {
|
|
4038
|
+
for (const value of rehydrated) this.#entries.set(value.socket, cloneEntry(value));
|
|
4039
|
+
this.#autoResponsePair = cloneAutoResponse(autoResponsePair);
|
|
4040
|
+
}
|
|
4041
|
+
get autoResponsePair() {
|
|
4042
|
+
return cloneAutoResponse(this.#autoResponsePair);
|
|
4043
|
+
}
|
|
4044
|
+
accepted(socket, tags) {
|
|
4045
|
+
this.#entries.set(socket, {
|
|
4046
|
+
socket,
|
|
4047
|
+
tags: [...tags]
|
|
4048
|
+
});
|
|
4049
|
+
}
|
|
4050
|
+
attachment(socket, bytes) {
|
|
4051
|
+
const entry = this.#entries.get(socket);
|
|
4052
|
+
if (entry === void 0) throw new Error("Hibernation mirror: attachment preceded socket acceptance.");
|
|
4053
|
+
if (bytes === null) delete entry.attachment;
|
|
4054
|
+
else entry.attachment = bytes.slice();
|
|
4055
|
+
}
|
|
4056
|
+
autoResponse(pair) {
|
|
4057
|
+
this.#autoResponsePair = cloneAutoResponse(pair);
|
|
4058
|
+
}
|
|
4059
|
+
closed(socket) {
|
|
4060
|
+
this.#entries.delete(socket);
|
|
4061
|
+
}
|
|
4062
|
+
snapshot() {
|
|
4063
|
+
return [...this.#entries.values()].map(cloneEntry);
|
|
4064
|
+
}
|
|
4065
|
+
};
|
|
4066
|
+
function cloneEntry(value) {
|
|
4067
|
+
const entry = {
|
|
4068
|
+
socket: value.socket,
|
|
4069
|
+
tags: [...value.tags ?? []]
|
|
4070
|
+
};
|
|
4071
|
+
if (value.attachment !== void 0) entry.attachment = value.attachment.slice();
|
|
4072
|
+
if (value.autoResponseTimestamp !== void 0) entry.autoResponseTimestamp = value.autoResponseTimestamp;
|
|
4073
|
+
return entry;
|
|
4074
|
+
}
|
|
4075
|
+
function cloneAutoResponse(pair) {
|
|
4076
|
+
return pair === null ? null : { ...pair };
|
|
4077
|
+
}
|
|
4078
|
+
//#endregion
|
|
6206
4079
|
//#region src/server/actor-namespace.ts
|
|
6207
4080
|
/** Assemble the configured namespace binding a host places in `env`. */
|
|
6208
4081
|
function createDurableObjectNamespace(uniqueKey, channel) {
|
|
@@ -6252,6 +4125,6 @@ function newRpcSession(port, localMain) {
|
|
|
6252
4125
|
return newMessagePortRpcSession(port, localMain);
|
|
6253
4126
|
}
|
|
6254
4127
|
//#endregion
|
|
6255
|
-
export { ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE, ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, ALLOW_EXPERIMENTAL_MESSAGE, ALREADY_ACCEPTED_MESSAGE, AlarmInvocationInfo, AlarmScheduler, BYOB_READER_UNGATABLE_MESSAGE, BrokenActorError, CanceledError, DEAD_LOAD_CONTEXT_MESSAGE, DEFAULT_ALARM_OUTLET, FACET_ALARM_UNIMPLEMENTED_MESSAGE, FACET_NAME_MAX_LENGTH, FACET_TREE_MAX_DEPTH, FOREIGN_SLICE_MESSAGE,
|
|
4128
|
+
export { ACTOR_CLASS_SERIALIZATION_UNIMPLEMENTED_MESSAGE, ALARM_RETRY_MAX_TRIES, ALARM_RETRY_START_SECONDS, ALLOW_EXPERIMENTAL_MESSAGE, ALREADY_ACCEPTED_MESSAGE, AlarmInvocationInfo, AlarmScheduler, BYOB_READER_UNGATABLE_MESSAGE, BrokenActorError, CanceledError, DEAD_LOAD_CONTEXT_MESSAGE, DEFAULT_ALARM_OUTLET, FACET_ALARM_UNIMPLEMENTED_MESSAGE, FACET_NAME_MAX_LENGTH, FACET_TREE_MAX_DEPTH, FOREIGN_SLICE_MESSAGE, HibernationMirror, LoopbackDurableObjectClass, NOT_BYTES_MESSAGE, NO_GLOBAL_OUTBOUND_MESSAGE, NO_MODULES_MESSAGE, PITR_UNIMPLEMENTED_MESSAGE, REPLICATION_UNIMPLEMENTED_MESSAGE, RETRY_BACKOFF_MAX, RETRY_JITTER_FACTOR, STREAMING_TAILS_EXPERIMENTAL_MESSAGE, RuntimeWebSocketRequestResponsePair as WebSocketRequestResponsePair, WorkerLoader, WorkerStub, actorScopeBindings, alarmRetryDelayMs, asLoopbackDurableObjectClass, createActorContainer, createDurableObjectNamespace, gateRequestBody, installActorScope, installWebSocketGlobals, jsModuleInPythonWorkerMessage, markWebSocketUsed, moduleFieldCountMessage, moduleNameMessage, newRpcSession, noFacets, notSerializableMessage, pythonModuleInJsWorkerMessage, typeScriptModuleNameMessage };
|
|
6256
4129
|
|
|
6257
4130
|
//# sourceMappingURL=index.js.map
|