@sproutboat/runtime 0.44.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3043,6 +3043,12 @@ if (globalThis.setTimeout == null) globalThis.setTimeout = setTimeout;
3043
3043
  if (globalThis.clearTimeout == null) globalThis.clearTimeout = clearTimeout;
3044
3044
  if (globalThis.setInterval == null) globalThis.setInterval = setInterval;
3045
3045
  if (globalThis.clearInterval == null) globalThis.clearInterval = clearInterval;
3046
+ // The runtime's timers, for the prelude's own use. Once a Durable Object is built at the eviction date, the four globals become
3047
+ // wrappers that pin the object that armed a timer (__sbDOWrapTimers); a parked timer or a poll of ours must never pin anything.
3048
+ const __sbSetTimeout = globalThis.setTimeout;
3049
+ const __sbClearTimeout = globalThis.clearTimeout;
3050
+ const __sbSetInterval = globalThis.setInterval;
3051
+ const __sbClearInterval = globalThis.clearInterval;
3046
3052
 
3047
3053
  // Prepended to every handler by tools/compile.ts, before Porffor's native-fetch
3048
3054
  // bundled handler. Porffor's runtime/fetch-globals.js (checked through alpha-10)
@@ -3895,7 +3901,7 @@ if (typeof globalThis.EventTarget !== "function") {
3895
3901
  }
3896
3902
  static timeout(ms) {
3897
3903
  const signal = new AbortSignal();
3898
- setTimeout(() => signal.__sbAbort(namedError("TimeoutError", "The operation timed out")), ms);
3904
+ __sbSetTimeout(() => signal.__sbAbort(namedError("TimeoutError", "The operation timed out")), ms);
3899
3905
  return signal;
3900
3906
  }
3901
3907
  static any(signals) {
@@ -11024,28 +11030,63 @@ function __sbR2StrictObject(meta, bytes, range, include) {
11024
11030
  };
11025
11031
  if (bytes == null) return obj;
11026
11032
  let used = false;
11033
+ // A lazy body (bytes.lazy: the object's file, read as asked) is never read whole unless text(), json(),
11034
+ // arrayBuffer() or blob() ask for all of it; its stream reads one chunk at a time.
11035
+ const lazy = bytes.lazy === true;
11027
11036
  const take = () => {
11028
11037
  if (used) throw new TypeError("Body has already been used. It can only be used once. Use tee() first if you need to read it twice.");
11029
11038
  used = true;
11030
- return bytes;
11039
+ return lazy ? __sbR2ReadAll(bytes, range) : bytes;
11031
11040
  };
11032
11041
  // The stream is made when first asked for: text() and friends read the bytes directly,
11033
11042
  // so an object read as text never holds a second copy (sproutd #22).
11034
11043
  let stream = null;
11035
11044
  Object.defineProperty(obj, "body", {
11036
11045
  get: () => {
11037
- if (stream === null) stream = new Response(__sbBufFrom(bytes)).body;
11046
+ if (stream === null) stream = lazy ? __sbR2LazyStream(bytes, range) : new Response(__sbBufFrom(bytes)).body;
11038
11047
  return stream;
11039
11048
  },
11040
11049
  enumerable: true,
11041
11050
  });
11042
11051
  Object.defineProperty(obj, "bodyUsed", { get: () => used, enumerable: true });
11043
- obj.text = async () => __sbFromUtf8(take());
11044
- obj.json = async () => JSON.parse(__sbFromUtf8(take()));
11045
- obj.arrayBuffer = async () => __sbBufFrom(take());
11046
- obj.blob = async () => new Blob([__sbBufFrom(take())], { type: obj.httpMetadata.contentType || "" });
11052
+ // A lazy body reads into a Uint8Array (never a bytestring), so each of these takes bytes, not text.
11053
+ obj.text = async () => (lazy ? new TextDecoder().decode(take()) : __sbFromUtf8(take()));
11054
+ obj.json = async () => JSON.parse(lazy ? new TextDecoder().decode(take()) : __sbFromUtf8(take()));
11055
+ obj.arrayBuffer = async () => (lazy ? take().buffer : __sbBufFrom(take()));
11056
+ obj.blob = async () => new Blob([lazy ? take().buffer : __sbBufFrom(take())], { type: obj.httpMetadata.contentType || "" });
11047
11057
  return obj;
11048
11058
  }
11059
+ // The chunk a streamed R2 body reads at a time (a Uint8Array of this size, then collected): a chunk is
11060
+ // what the worker's heap holds per read, and what the response's flow control hands to the host.
11061
+ const __SB_R2_CHUNK = 256 * 1024;
11062
+ // The bytes of a lazy body's window, all of them: text(), json(), arrayBuffer() and blob() need the whole
11063
+ // object in memory whatever they are called on.
11064
+ function __sbR2ReadAll(src, range) {
11065
+ const out = new Uint8Array(range.length);
11066
+ if (range.length > 0 && __sbR2ReadRange(src.bucket, src.key, src.blobId, range.offset, out) !== range.length)
11067
+ throw new Error("sproutboat r2.get: the object could not be read");
11068
+ return out;
11069
+ }
11070
+ // A lazy body's window as a ReadableStream of __SB_R2_CHUNK-byte reads.
11071
+ function __sbR2LazyStream(src, range) {
11072
+ let at = 0;
11073
+ return new ReadableStream({
11074
+ pull(controller) {
11075
+ if (at >= range.length) {
11076
+ controller.close();
11077
+ return;
11078
+ }
11079
+ const n = Math.min(__SB_R2_CHUNK, range.length - at);
11080
+ const chunk = new Uint8Array(n);
11081
+ if (__sbR2ReadRange(src.bucket, src.key, src.blobId, range.offset + at, chunk) !== n) {
11082
+ controller.error(new Error("sproutboat r2.get: the object could not be read"));
11083
+ return;
11084
+ }
11085
+ at += n;
11086
+ controller.enqueue(chunk);
11087
+ },
11088
+ });
11089
+ }
11049
11090
  // A hex string's bytes, one char per byte.
11050
11091
  function __sbR2Bytes(hex) {
11051
11092
  let out = "";
@@ -11092,7 +11133,19 @@ function __sbMakeR2(name) {
11092
11133
  get(key, options) {
11093
11134
  return attempt(() => {
11094
11135
  const o = options || {};
11095
- const r = __sbR2Get(name, __sbR2Key(key, "get"));
11136
+ const objectKey = __sbR2Key(key, "get");
11137
+ // Where the transport can read an object's file as asked (embedded: sproutd and standalone), get() takes its
11138
+ // metadata and no bytes: a range reads only its own window, a body streams. The broker answers null here
11139
+ // and returns the object whole, as before.
11140
+ const opened = __sbR2Open(name, objectKey);
11141
+ if (opened !== null) {
11142
+ if (!opened.found) return null;
11143
+ const meta = opened.object;
11144
+ if (!__sbR2OnlyIf(o.onlyIf, meta.md5 || meta.etag, meta.uploaded)) return __sbR2StrictObject(meta, null, { offset: 0, length: meta.size });
11145
+ const window = o.range == null ? { offset: 0, length: meta.size } : __sbR2Range(o.range, meta.size);
11146
+ return __sbR2StrictObject(meta, { lazy: true, bucket: name, key: objectKey, blobId: opened.blobId }, window);
11147
+ }
11148
+ const r = __sbR2Get(name, objectKey);
11096
11149
  if (!r.found) return null;
11097
11150
  const meta = r.object;
11098
11151
  if (!__sbR2OnlyIf(o.onlyIf, meta.md5 || meta.etag, meta.uploaded)) return __sbR2StrictObject(meta, null, { offset: 0, length: meta.size });
@@ -11202,27 +11255,104 @@ function __sbMakeR2(name) {
11202
11255
  };
11203
11256
  }
11204
11257
 
11205
- // Asset rules use the same parsed manifest in both transports. Matching mirrors
11206
- // @sproutboat/assets; parity tests cover the portable native implementation.
11207
- function __sbAssetRedirect(rule, path) {
11208
- const star = rule.from.indexOf("*");
11209
- if (star !== -1) {
11210
- const prefix = rule.from.slice(0, star);
11211
- if (!path.startsWith(prefix)) return null;
11212
- return rule.to.split(":splat").join(path.slice(prefix.length));
11258
+ // Asset rules use the same parsed manifest in both transports. Redirect matching mirrors
11259
+ // @sproutboat/assets (Cloudflare's `_redirects` rules: the whole path must match, so a trailing
11260
+ // slash is significant; `:name` is one or more characters up to the next "/"; one `*` is
11261
+ // `:splat`; a static rule beats a dynamic one); parity tests cover the portable implementation.
11262
+ function __sbRuleAlpha(code) {
11263
+ return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
11264
+ }
11265
+ function __sbRuleWord(code) {
11266
+ return __sbRuleAlpha(code) || (code >= 48 && code <= 57) || code === 95;
11267
+ }
11268
+ function __sbRuleIsStatic(from) {
11269
+ for (let i = 0; i < from.length; i++) {
11270
+ const code = from.charCodeAt(i);
11271
+ if (code === 42) return false;
11272
+ if (code === 58 && i + 1 < from.length && __sbRuleAlpha(from.charCodeAt(i + 1))) return false;
11213
11273
  }
11214
- const from = rule.from.split("/").filter(Boolean);
11215
- const parts = path.split("/").filter(Boolean);
11216
- if (from.length !== parts.length) return null;
11217
- const params = {};
11274
+ return true;
11275
+ }
11276
+ // Tokens are [kind, text-or-name] pairs: 0 literal text, 1 :name, 2 splat. Null: the rule can never match.
11277
+ function __sbRuleTokens(from) {
11278
+ const tokens = [];
11279
+ const names = [];
11280
+ let text = "";
11218
11281
  for (let i = 0; i < from.length; i++) {
11219
- if (from[i].startsWith(":")) params[from[i].slice(1)] = parts[i];
11220
- else if (from[i] !== parts[i]) return null;
11282
+ const code = from.charCodeAt(i);
11283
+ if (code === 42) {
11284
+ if (text !== "") tokens.push([0, text]);
11285
+ text = "";
11286
+ if (names.indexOf("splat") !== -1) return null;
11287
+ names.push("splat");
11288
+ tokens.push([2, "splat"]);
11289
+ } else if (code === 58 && i + 1 < from.length && __sbRuleAlpha(from.charCodeAt(i + 1))) {
11290
+ let end = i + 2;
11291
+ while (end < from.length && __sbRuleWord(from.charCodeAt(end))) end++;
11292
+ const name = from.slice(i + 1, end);
11293
+ if (names.indexOf(name) !== -1) return null;
11294
+ names.push(name);
11295
+ if (text !== "") tokens.push([0, text]);
11296
+ text = "";
11297
+ tokens.push([1, name]);
11298
+ i = end - 1;
11299
+ } else {
11300
+ text += from.charAt(i);
11301
+ }
11221
11302
  }
11303
+ if (text !== "") tokens.push([0, text]);
11304
+ return tokens;
11305
+ }
11306
+ function __sbRuleMatch(tokens, t, path, p, names, values) {
11307
+ if (t === tokens.length) return p === path.length;
11308
+ const kind = tokens[t][0];
11309
+ const word = tokens[t][1];
11310
+ if (kind === 0) {
11311
+ if (path.slice(p, p + word.length) !== word) return false;
11312
+ return __sbRuleMatch(tokens, t + 1, path, p + word.length, names, values);
11313
+ }
11314
+ let limit = path.length;
11315
+ if (kind === 1) {
11316
+ for (let i = p; i < path.length; i++) {
11317
+ if (path.charCodeAt(i) === 47) {
11318
+ limit = i;
11319
+ break;
11320
+ }
11321
+ }
11322
+ }
11323
+ const least = kind === 1 ? p + 1 : p;
11324
+ for (let q = limit; q >= least; q--) {
11325
+ names.push(word);
11326
+ values.push(path.slice(p, q));
11327
+ if (__sbRuleMatch(tokens, t + 1, path, q, names, values)) return true;
11328
+ names.pop();
11329
+ values.pop();
11330
+ }
11331
+ return false;
11332
+ }
11333
+ function __sbAssetRedirect(rule, path) {
11334
+ if (__sbRuleIsStatic(rule.from)) return rule.from === path ? rule.to : null;
11335
+ const tokens = __sbRuleTokens(rule.from);
11336
+ if (tokens === null) return null;
11337
+ const names = [];
11338
+ const values = [];
11339
+ if (!__sbRuleMatch(tokens, 0, path, 0, names, values)) return null;
11222
11340
  let to = rule.to;
11223
- for (const name in params) to = to.split(":" + name).join(params[name]);
11341
+ for (let i = 0; i < names.length; i++) to = to.split(":" + names[i]).join(values[i]);
11224
11342
  return to;
11225
11343
  }
11344
+ // The first static rule that matches, else the first dynamic one: { status, location } or null.
11345
+ function __sbFindAssetRedirect(rules, path) {
11346
+ for (let i = 0; i < rules.length; i++) {
11347
+ if (__sbRuleIsStatic(rules[i].from) && rules[i].from === path) return { status: rules[i].status, location: rules[i].to };
11348
+ }
11349
+ for (let i = 0; i < rules.length; i++) {
11350
+ if (__sbRuleIsStatic(rules[i].from)) continue;
11351
+ const location = __sbAssetRedirect(rules[i], path);
11352
+ if (location !== null) return { status: rules[i].status, location };
11353
+ }
11354
+ return null;
11355
+ }
11226
11356
 
11227
11357
  function __sbAssetHeaderMatches(pattern, path) {
11228
11358
  const from = pattern.split("/").filter(Boolean);
@@ -11237,11 +11367,8 @@ function __sbAssetHeaderMatches(pattern, path) {
11237
11367
  function __sbAssetResponse(path, r) {
11238
11368
  if (r.location != null) return new Response(null, { status: r.status, headers: { location: r.location } });
11239
11369
  const rules = r.rules || {};
11240
- const redirects = rules.redirects || [];
11241
- for (let i = 0; i < redirects.length; i++) {
11242
- const to = __sbAssetRedirect(redirects[i], path);
11243
- if (to !== null) return new Response(null, { status: redirects[i].status, headers: { location: to } });
11244
- }
11370
+ const redirect = __sbFindAssetRedirect(rules.redirects || [], path);
11371
+ if (redirect !== null) return new Response(null, { status: redirect.status, headers: { location: redirect.location } });
11245
11372
  const headers = new Headers();
11246
11373
  if (r.type) headers.set("content-type", r.type);
11247
11374
  if (r.found) headers.set("etag", '"' + r.hash + '"');
@@ -12155,7 +12282,7 @@ async function __sbPumpRequestBody(id, source, ended) {
12155
12282
  if (bytes.length === 0) continue;
12156
12283
  let backlog = Number(globalThis.__sbAsyncBody(id, "c", __sbToBytes(bytes)));
12157
12284
  while (backlog > 4 * 1024 * 1024 && !ended()) {
12158
- await new Promise((resolve) => setTimeout(resolve, 2));
12285
+ await new Promise((resolve) => __sbSetTimeout(resolve, 2));
12159
12286
  backlog = Number(globalThis.__sbAsyncBody(id, "q", ""));
12160
12287
  }
12161
12288
  }
@@ -12175,10 +12302,10 @@ function __sbUpstreamStreamed(op, msg, label, signal) {
12175
12302
  let done = false;
12176
12303
  const finish = () => {
12177
12304
  done = true;
12178
- clearInterval(id);
12305
+ __sbClearInterval(id);
12179
12306
  if (onAbort) signal.removeEventListener("abort", onAbort);
12180
12307
  };
12181
- const id = setInterval(() => {
12308
+ const id = __sbSetInterval(() => {
12182
12309
  if (done) return;
12183
12310
  const raw = __sbHttpAsyncResult(id);
12184
12311
  if (controller === null) {
@@ -12314,7 +12441,7 @@ function __sbUpstream(op, msg, label, signal, always) {
12314
12441
  const settled = () => {
12315
12442
  if (onAbort) signal.removeEventListener("abort", onAbort);
12316
12443
  };
12317
- const id = setTimeout(() => {
12444
+ const id = __sbSetTimeout(() => {
12318
12445
  settled();
12319
12446
  try {
12320
12447
  resolve(__sbUpstreamResponse(__sbFetchReply(__sbHttpAsyncResult(id))));
@@ -12325,7 +12452,7 @@ function __sbUpstream(op, msg, label, signal, always) {
12325
12452
  if (signal) {
12326
12453
  onAbort = () => {
12327
12454
  settled();
12328
- clearTimeout(id);
12455
+ __sbClearTimeout(id);
12329
12456
  __sbCancelAsync(id);
12330
12457
  reject(signal.reason);
12331
12458
  };
@@ -12336,7 +12463,7 @@ function __sbUpstream(op, msg, label, signal, always) {
12336
12463
  globalThis.__sbFetchUpstream(op, msg);
12337
12464
  } catch (e) {
12338
12465
  settled();
12339
- clearTimeout(id);
12466
+ __sbClearTimeout(id);
12340
12467
  reject(e);
12341
12468
  }
12342
12469
  });
@@ -12349,7 +12476,7 @@ function __sbUpstream(op, msg, label, signal, always) {
12349
12476
  globalThis.__sbInstallFetch = function () {
12350
12477
  __sbIngressStreamingSetup();
12351
12478
  __sbInstallWebSockets();
12352
- globalThis.fetch = function (input, init) {
12479
+ const plainFetch = function (input, init) {
12353
12480
  let parts;
12354
12481
  try {
12355
12482
  parts = __sbFetchParts(input, init);
@@ -12371,6 +12498,29 @@ globalThis.__sbInstallFetch = function () {
12371
12498
  if (stream instanceof ReadableStream) return __sbCollectBody(stream).then((bytes) => send(__sbRequestBody(bytes)));
12372
12499
  return send(__sbPartsBody(parts));
12373
12500
  };
12501
+ // Inside a Durable Object's event an unawaited fetch keeps its object in memory until it settles.
12502
+ globalThis.fetch = function (input, init) {
12503
+ const rec = __sbDOL !== null ? __sbDOFrameRec() : undefined;
12504
+ if (rec === undefined || rec.dead) return plainFetch(input, init);
12505
+ __sbDOPin(rec);
12506
+ let p;
12507
+ try {
12508
+ p = plainFetch(input, init);
12509
+ } catch (error) {
12510
+ __sbDOUnpin(rec);
12511
+ throw error;
12512
+ }
12513
+ return p.then(
12514
+ (r) => {
12515
+ __sbDOUnpin(rec);
12516
+ return r;
12517
+ },
12518
+ (error) => {
12519
+ __sbDOUnpin(rec);
12520
+ throw error;
12521
+ },
12522
+ );
12523
+ };
12374
12524
  };
12375
12525
 
12376
12526
  // ---------------------------------------------------------------------------
@@ -12618,6 +12768,7 @@ function __sbDOCallback(fn, args) {
12618
12768
 
12619
12769
  // One RPC call: method `method` with `args`, or the getter `method` when args is null.
12620
12770
  function __sbDOInvoke(cls, id, method, args) {
12771
+ if (__sbDOEvictOn()) return __sbDOInvokeLive(cls, id, method, args);
12621
12772
  let copied = null;
12622
12773
  let inst;
12623
12774
  try {
@@ -12644,7 +12795,38 @@ function __sbDOInvoke(cls, id, method, args) {
12644
12795
  );
12645
12796
  }
12646
12797
 
12798
+ // RPC into an object that may be evicted (compatibility date 2026-11-21): the event enters through __sbDORun.
12799
+ function __sbDOInvokeLive(cls, id, method, args) {
12800
+ let copied = null;
12801
+ try {
12802
+ if (args !== null) copied = __sbDOCopy(args);
12803
+ } catch (error) {
12804
+ return Promise.reject(error);
12805
+ }
12806
+ return __sbDORun(
12807
+ cls,
12808
+ id.toString(),
12809
+ id,
12810
+ (inst) => {
12811
+ const d = __sbDOFind(__sbDOClasses[cls], method);
12812
+ if (args === null) return d.get.call(inst);
12813
+ if (!d || !__sbIsFn(d.value)) throw new TypeError('The RPC receiver does not implement the method "' + method + '".');
12814
+ return d.value.apply(inst, copied);
12815
+ },
12816
+ false,
12817
+ ).then(__sbDOCopy);
12818
+ }
12819
+
12647
12820
  function __sbDOFetchV2(className, id, input, init) {
12821
+ if (__sbDOEvictOn()) {
12822
+ let request;
12823
+ try {
12824
+ request = new Request(input, init);
12825
+ } catch (error) {
12826
+ return Promise.reject(error);
12827
+ }
12828
+ return __sbDORun(className, id.toString(), id, (inst) => __sbDOFetchEvent(inst, request), false);
12829
+ }
12648
12830
  let req;
12649
12831
  let inst;
12650
12832
  try {
@@ -12769,6 +12951,691 @@ function __sbGetDOInstance(cls, idText, idObj) {
12769
12951
  return inst;
12770
12952
  }
12771
12953
 
12954
+ // ---------------------------------------------------------------------------
12955
+ // Durable Object lifecycle (design 07 stage 1c, plan 018), from compatibility date 2026-11-21: an idle object
12956
+ // leaves memory and is constructed again by its next event, so one app can hold one object per customer.
12957
+ //
12958
+ // A record per object in a worker (not the bare instance): { cls, id, key, inst, state, gate, holder, tasks, busy,
12959
+ // pins, last, dead, prev, next, ... }. `busy` counts the events that entered through __sbDORun and have not
12960
+ // finished, their waitUntil drain included; `pins` counts things that outlive an event and cannot be rebuilt (a pending
12961
+ // timer, an unawaited fetch, a streamed response); open plain sockets are checked when idleness is. The records are one
12962
+ // least-recently-idle list (`head` the stalest). An object is evicted only when __sbDOIsIdle says so, in one synchronous
12963
+ // turn (__sbDOEvict): nothing in it awaits. Before the date nothing here runs and every object stays resident.
12964
+ //
12965
+ // Settings (environment; sproutd sets them from `limits ID do-idle-ms|objects|do-wait-ms`):
12966
+ // SB_DO_IDLE_MS how long an object sits idle before it leaves, 100 to 600000 (default 10000; 0 turns idle eviction off)
12967
+ // SB_DO_MAX most objects in memory in this worker (default 16 per MiB of SB_MEMORY_LIMIT, 64 to 4096; 4096 with no limit)
12968
+ // SB_DO_WAIT_MS how long an object's first event waits for a slot at the cap before it fails alone (default 5000)
12969
+ // SB_DO_QUEUE_MAX most events queued behind one object's input gate (default 1024)
12970
+ const __SB_DO_EVICT_DATE = "2026-11-21";
12971
+ const __sbDOCtxKey = {};
12972
+ let __sbDOL = null;
12973
+
12974
+ function __sbDOEvictOn() {
12975
+ return __sbCompatDate() >= __SB_DO_EVICT_DATE && __sbDOGated() && __sbDOv3();
12976
+ }
12977
+
12978
+ function __sbDOEnvNum(name, fallback, min, max) {
12979
+ const text = __sbEnv(name);
12980
+ if (text === "") return fallback;
12981
+ const n = Number(text);
12982
+ return n >= min && n <= max ? Math.trunc(n) : fallback;
12983
+ }
12984
+
12985
+ function __sbDOLife() {
12986
+ if (__sbDOL !== null) return __sbDOL;
12987
+ let idle = __sbDOEnvNum("SB_DO_IDLE_MS", 10000, 100, 600000);
12988
+ if (__sbEnv("SB_DO_IDLE_MS") === "0") idle = 0;
12989
+ const limit = Number(__sbEnv("SB_MEMORY_LIMIT"));
12990
+ const byLimit = limit > 0 ? Math.max(64, Math.min(4096, Math.floor(limit / 1048576) * 16)) : 4096;
12991
+ __sbDOL = {
12992
+ idle,
12993
+ cap: __sbDOEnvNum("SB_DO_MAX", byLimit, 1, 100000),
12994
+ waitMs: __sbDOEnvNum("SB_DO_WAIT_MS", 5000, 10, 600000),
12995
+ queueMax: __sbDOEnvNum("SB_DO_QUEUE_MAX", 1024, 1, 1000000),
12996
+ recs: {},
12997
+ head: null,
12998
+ tail: null,
12999
+ count: 0,
13000
+ waiters: [],
13001
+ sweepArmed: false,
13002
+ hostU: -1,
13003
+ restoreParked: false,
13004
+ restoreTries: {},
13005
+ noteAt: 0,
13006
+ noteText: "",
13007
+ pinned: 0,
13008
+ evictedIdle: 0,
13009
+ evictedCap: 0,
13010
+ evictedAbort: 0,
13011
+ waits: 0,
13012
+ waitMsMax: 0,
13013
+ overloaded: 0,
13014
+ zombie: 0,
13015
+ wakeUsLast: 0,
13016
+ wakeUsMax: 0,
13017
+ };
13018
+ __sbDOWrapTimers();
13019
+ return __sbDOL;
13020
+ }
13021
+
13022
+ function __sbDOEvictedError() {
13023
+ return new Error("This Durable Object instance was evicted from memory.");
13024
+ }
13025
+
13026
+ function __sbDOOverloaded(why) {
13027
+ const error = new Error("Durable Object is overloaded: " + why);
13028
+ error.overloaded = true;
13029
+ return error;
13030
+ }
13031
+
13032
+ // The record of the object whose event is running now (the frame the event entered carries it), or undefined.
13033
+ function __sbDOFrameRec() {
13034
+ const frame = __sbCtxGet();
13035
+ if (!frame) return undefined;
13036
+ const at = __sbFrameIndex(frame, __sbDOCtxKey);
13037
+ return at === -1 ? undefined : frame.vals[at];
13038
+ }
13039
+
13040
+ // ---- the list: head is the stalest record, tail the freshest ----
13041
+ function __sbDOLink(L, rec) {
13042
+ rec.prev = L.tail;
13043
+ rec.next = null;
13044
+ if (L.tail !== null) L.tail.next = rec;
13045
+ else L.head = rec;
13046
+ L.tail = rec;
13047
+ L.count++;
13048
+ }
13049
+ function __sbDOUnlink(L, rec) {
13050
+ if (rec.prev !== null) rec.prev.next = rec.next;
13051
+ else L.head = rec.next;
13052
+ if (rec.next !== null) rec.next.prev = rec.prev;
13053
+ else L.tail = rec.prev;
13054
+ rec.prev = null;
13055
+ rec.next = null;
13056
+ L.count--;
13057
+ }
13058
+ // Becomes idle now: the freshest record.
13059
+ function __sbDOFresh(L, rec, now) {
13060
+ rec.last = now === undefined ? Date.now() : now;
13061
+ if (L.tail === rec) return;
13062
+ __sbDOUnlink(L, rec);
13063
+ __sbDOLink(L, rec);
13064
+ }
13065
+
13066
+ // ---- idleness ----
13067
+ function __sbDOSocketsFree(L, rec) {
13068
+ const plain = rec.plain;
13069
+ if (plain.length > 0) {
13070
+ for (let i = plain.length - 1; i >= 0; i--) if (plain[i]._state === __SB_WS_CLOSED) plain.splice(i, 1);
13071
+ if (plain.length > 0) return false;
13072
+ }
13073
+ const tcp = rec.tcp;
13074
+ if (tcp.length > 0) {
13075
+ for (let i = tcp.length - 1; i >= 0; i--) if (tcp[i].id === 0) tcp.splice(i, 1);
13076
+ if (tcp.length > 0) return false;
13077
+ }
13078
+ const sockets = rec.holder.sockets;
13079
+ for (let i = 0; i < sockets.length; i++) {
13080
+ const server = sockets[i];
13081
+ if (server._state === __SB_WS_CLOSED) continue;
13082
+ const client = server._peer;
13083
+ // A pair whose two ends are both in the worker, or no host that takes a socket back: nothing outside holds it.
13084
+ if (client === null || client._host === 0) return false;
13085
+ if (L.hostU < 0) L.hostU = __sbWsOp("U", 0, "", 0) === "1" ? 1 : 0;
13086
+ if (L.hostU !== 1) return false;
13087
+ // An event that left the bridge and exists only in this worker (the bridge keeps the rest of the queue).
13088
+ if (client._inbox.length > 0 || server._inbox.length > 0 || client._flushing || server._flushing) return false;
13089
+ }
13090
+ return true;
13091
+ }
13092
+ function __sbDOIsIdle(L, rec) {
13093
+ const gate = rec.gate;
13094
+ return (
13095
+ rec.busy === 0 &&
13096
+ rec.pins === 0 &&
13097
+ gate.locks === 0 &&
13098
+ gate.waiting.length === 0 &&
13099
+ !gate.txnBusy &&
13100
+ gate.txnQueue.length === 0 &&
13101
+ __sbDOSocketsFree(L, rec)
13102
+ );
13103
+ }
13104
+
13105
+ function __sbDOPin(rec) {
13106
+ rec.pins++;
13107
+ if (rec.pins === 1) {
13108
+ rec.pinSince = Date.now();
13109
+ __sbDOL.pinned++;
13110
+ }
13111
+ }
13112
+ function __sbDOUnpin(rec) {
13113
+ if (rec.pins === 0) return;
13114
+ rec.pins--;
13115
+ if (rec.pins !== 0) return;
13116
+ const L = __sbDOL;
13117
+ if (rec.dead) return;
13118
+ L.pinned--;
13119
+ rec.pinLogged = false;
13120
+ if (rec.busy === 0) {
13121
+ __sbDOFresh(L, rec);
13122
+ __sbDOServe(L);
13123
+ __sbDOArmSweep(L);
13124
+ }
13125
+ }
13126
+
13127
+ // ---- eviction: one synchronous turn, nothing awaits ----
13128
+ // The reason says why: "idle", "cap" (a slot was needed), "abort" (state.abort()), "bcw" (blockConcurrencyWhile failed).
13129
+ // `force` skips the idleness check and closes the object's sockets (abort and reset); otherwise sockets go back to the host.
13130
+ function __sbDOEvict(rec, reason, force) {
13131
+ if (rec.dead) return false;
13132
+ const L = __sbDOL;
13133
+ if (!force && !__sbDOIsIdle(L, rec)) return false;
13134
+ if (rec.registered) {
13135
+ delete L.recs[rec.key];
13136
+ __sbDOUnlink(L, rec);
13137
+ rec.registered = false;
13138
+ if (rec.pins > 0) L.pinned--;
13139
+ }
13140
+ // Revoke: every state method of this instance now throws the evicted error, so a leftover closure cannot write as a second instance.
13141
+ rec.dead = true;
13142
+ __sbDOReleaseSockets(L, rec, force);
13143
+ if (rec.usedSql) {
13144
+ try {
13145
+ __sbRpc("do.sql.close", { cls: rec.cls, file: rec.sqlFile });
13146
+ } catch (error) {
13147
+ __sbLogUncaught("do.sql.close", error);
13148
+ }
13149
+ }
13150
+ const holder = rec.holder;
13151
+ if (holder !== null) {
13152
+ holder.inst = null;
13153
+ holder.rec = null;
13154
+ holder.sockets = [];
13155
+ }
13156
+ rec.inst = null;
13157
+ rec.state = null;
13158
+ rec.plain = [];
13159
+ rec.tcp = [];
13160
+ if (reason === "idle") L.evictedIdle++;
13161
+ else if (reason === "cap") L.evictedCap++;
13162
+ else L.evictedAbort++;
13163
+ return true;
13164
+ }
13165
+
13166
+ // The object's hibernatable sockets go back to the host (op g: unclaimed, their queued events kept), or are closed for a reset.
13167
+ function __sbDOReleaseSockets(L, rec, force) {
13168
+ const holder = rec.holder;
13169
+ if (holder === null) return;
13170
+ const sockets = holder.sockets;
13171
+ for (let i = 0; i < sockets.length; i++) {
13172
+ const server = sockets[i];
13173
+ const client = server._peer;
13174
+ if (client !== null && client._pumpTimer !== undefined) {
13175
+ __sbClearTimeout(client._pumpTimer);
13176
+ client._pumpTimer = undefined;
13177
+ }
13178
+ if (server._state === __SB_WS_CLOSED) continue;
13179
+ if (force) {
13180
+ try {
13181
+ server.close(1011, "Durable Object reset");
13182
+ } catch {
13183
+ /* already closing */
13184
+ }
13185
+ continue;
13186
+ }
13187
+ if (client !== null && client._host > 0) {
13188
+ const hostId = client._host;
13189
+ client._host = 0;
13190
+ server._state = __SB_WS_CLOSED;
13191
+ if (!L.restoreParked) {
13192
+ L.restoreParked = true;
13193
+ __sbWsRestorePark();
13194
+ }
13195
+ __sbWsOp("g", hostId, "", 0);
13196
+ }
13197
+ }
13198
+ }
13199
+
13200
+ // state.abort() and a failed blockConcurrencyWhile(): the object is forgotten (storage kept) and this instance is revoked.
13201
+ function __sbDOReset(rec, reason, error) {
13202
+ if (rec.dead) return;
13203
+ const L = __sbDOL;
13204
+ rec.deadError = error === undefined ? null : error;
13205
+ __sbDOEvict(rec, reason, true);
13206
+ // Events waiting at its gate now fail (each finds the record dead).
13207
+ rec.gate.locks = 0;
13208
+ __sbDOPump(rec.gate);
13209
+ __sbDOServe(L);
13210
+ }
13211
+
13212
+ function __sbDOStalest(L) {
13213
+ for (let rec = L.head; rec !== null; rec = rec.next) if (__sbDOIsIdle(L, rec)) return rec;
13214
+ return null;
13215
+ }
13216
+
13217
+ // The sweep: idle objects older than the timeout leave, from the stale end of the list. Bounded twice: `limit` records are looked at and at
13218
+ // most `most` leave, because an eviction closes a SQLite handle (a checkpoint and its fsyncs, about a millisecond) and the whole sweep is one
13219
+ // synchronous turn: 500 objects going at once would stop every request for half a second or more (measured: p99 1.9 s in an alarm storm).
13220
+ // What is left goes on the next tick; the cap evicts one object at a time when a slot is needed in the meantime.
13221
+ function __sbDOSweep(L, limit, most) {
13222
+ if (L.idle === 0) return;
13223
+ const now = Date.now();
13224
+ let seen = 0;
13225
+ let left = most;
13226
+ for (let rec = L.head; rec !== null && seen < limit && left > 0; ) {
13227
+ const next = rec.next;
13228
+ seen++;
13229
+ if (rec.busy === 0) {
13230
+ if (now - rec.last < L.idle) break;
13231
+ if (rec.pins > 0) {
13232
+ if (!rec.pinLogged && now - rec.pinSince > 60000) {
13233
+ rec.pinLogged = true;
13234
+ console.error("Durable Object " + rec.cls + " has been held in memory for over 60 s by a timer, a connection or a streamed response; it cannot be evicted while that lives");
13235
+ }
13236
+ } else if (__sbDOEvict(rec, "idle", false)) left--;
13237
+ }
13238
+ rec = next;
13239
+ }
13240
+ }
13241
+
13242
+ // The sweep timer is parked at the host (op D): the host fires it when its deadline passes, and it is not background work, so
13243
+ // an app whose objects are all evicted is still reaped on time. A host without the op (an older sproutd) gets no timer: objects
13244
+ // then leave when the next event arrives. A standalone binary has no reaper, so a plain timer does it.
13245
+ function __sbDOArmSweep(L) {
13246
+ if (L.sweepArmed || L.idle === 0 || L.count === 0) return;
13247
+ let delay = L.idle;
13248
+ if (L.head !== null) delay = Math.max(50, Math.min(L.idle, L.head.last + L.idle - Date.now()));
13249
+ const timer = __sbSetTimeout(__sbDOSweepFire, 2147483647);
13250
+ const parked = __sbWsOp("D", Math.trunc(delay), "", timer) === "0";
13251
+ if (parked) {
13252
+ L.sweepArmed = true;
13253
+ return;
13254
+ }
13255
+ __sbClearTimeout(timer);
13256
+ if (!__sbHosted()) {
13257
+ L.sweepArmed = true;
13258
+ __sbSetTimeout(__sbDOSweepFire, delay);
13259
+ }
13260
+ }
13261
+ function __sbDOSweepFire() {
13262
+ const L = __sbDOL;
13263
+ if (L === null) return;
13264
+ L.sweepArmed = false;
13265
+ const before = L.evictedIdle;
13266
+ __sbDOSweep(L, 100000, 24);
13267
+ // Objects left: SQLite hands the pages of the databases still open back too (the quiet collection follows this timer).
13268
+ if (L.evictedIdle !== before) {
13269
+ try {
13270
+ __sbRpc("do.release", {});
13271
+ } catch (error) {
13272
+ __sbLogUncaught("do.release", error);
13273
+ }
13274
+ }
13275
+ __sbDOServe(L);
13276
+ __sbDOArmSweep(L);
13277
+ __sbDONote(L, true);
13278
+ }
13279
+
13280
+ // ---- acquire: the record of an object, constructing it when it has none ----
13281
+ function __sbDOResolveId(cls, idText, idObj) {
13282
+ const stateId = idObj || (idText.indexOf("name:") === 0 ? __sbDONamedId(cls, idText.slice(5)) : __sbDOMakeId(idText, undefined));
13283
+ return stateId;
13284
+ }
13285
+
13286
+ // Returns the record, or a promise of one when the cap is full and nothing is idle.
13287
+ function __sbDOAcquire(cls, idText, idObj) {
13288
+ const L = __sbDOLife();
13289
+ // The common case: the caller's id text is the object's canonical id, so the record is one lookup away.
13290
+ const quick = L.recs[cls + " " + idText];
13291
+ if (quick !== undefined) return quick;
13292
+ if (!__sbDOClasses[cls]) throw new Error("no such Durable Object class: " + cls);
13293
+ const stateId = __sbDOResolveId(cls, idText, idObj);
13294
+ const id = stateId.toString();
13295
+ const key = cls + " " + id;
13296
+ const rec = L.recs[key];
13297
+ if (rec !== undefined) return rec;
13298
+ __sbDOSweep(L, 8, 4);
13299
+ if (L.count >= L.cap) {
13300
+ const victim = __sbDOStalest(L);
13301
+ if (victim === null) return __sbDOWait(L, cls, id, stateId, key);
13302
+ __sbDOEvict(victim, "cap", false);
13303
+ }
13304
+ return __sbDOBuild(L, cls, id, stateId, key);
13305
+ }
13306
+
13307
+ function __sbDOWait(L, cls, id, stateId, key) {
13308
+ L.waits++;
13309
+ return new Promise((resolve, reject) => {
13310
+ const w = { cls, id, stateId, key, resolve, reject, timer: 0, done: false, t0: Date.now() };
13311
+ w.timer = __sbSetTimeout(() => {
13312
+ if (w.done) return;
13313
+ w.done = true;
13314
+ const at = L.waiters.indexOf(w);
13315
+ if (at >= 0) L.waiters.splice(at, 1);
13316
+ L.overloaded++;
13317
+ reject(__sbDOOverloaded("too many objects are awake in this app"));
13318
+ }, L.waitMs);
13319
+ L.waiters.push(w);
13320
+ });
13321
+ }
13322
+
13323
+ // A slot or an idle object freed: serve the waiting first events in order.
13324
+ function __sbDOServe(L) {
13325
+ while (L.waiters.length > 0) {
13326
+ const w = L.waiters[0];
13327
+ let rec = L.recs[w.key];
13328
+ if (rec === undefined) {
13329
+ if (L.count >= L.cap) {
13330
+ const victim = __sbDOStalest(L);
13331
+ if (victim === null) return;
13332
+ __sbDOEvict(victim, "cap", false);
13333
+ }
13334
+ }
13335
+ L.waiters.shift();
13336
+ w.done = true;
13337
+ __sbClearTimeout(w.timer);
13338
+ const waited = Date.now() - w.t0;
13339
+ if (waited > L.waitMsMax) L.waitMsMax = waited;
13340
+ try {
13341
+ if (rec === undefined) rec = __sbDOBuild(L, w.cls, w.id, w.stateId, w.key);
13342
+ w.resolve(rec);
13343
+ } catch (error) {
13344
+ w.reject(error);
13345
+ }
13346
+ }
13347
+ }
13348
+
13349
+ // A clock finer than Date.now() for the wake counter (microseconds); the performance object is built on its first read.
13350
+ function __sbDONow() {
13351
+ const p = globalThis.performance;
13352
+ return p && __sbIsFn(p.now) ? p.now() : Date.now();
13353
+ }
13354
+
13355
+ function __sbDOBuild(L, cls, id, stateId, key) {
13356
+ const Ctor = __sbDOClasses[cls];
13357
+ const t0 = __sbDONow();
13358
+ if (stateId.name !== undefined) __sbDOAdopt(cls, "name:" + stateId.name, id);
13359
+ const tasks = [];
13360
+ const gate = __sbDOMakeGate();
13361
+ const rec = {
13362
+ cls,
13363
+ id,
13364
+ key,
13365
+ inst: null,
13366
+ state: null,
13367
+ gate,
13368
+ holder: null,
13369
+ tasks,
13370
+ busy: 0,
13371
+ pins: 0,
13372
+ pinSince: 0,
13373
+ pinLogged: false,
13374
+ last: Date.now(),
13375
+ usedSql: false,
13376
+ sqlFile: "",
13377
+ dead: false,
13378
+ registered: false,
13379
+ prev: null,
13380
+ next: null,
13381
+ frame: null,
13382
+ plain: [],
13383
+ tcp: [],
13384
+ deadError: null,
13385
+ };
13386
+ rec.frame = __sbFrameWith(undefined, __sbDOCtxKey, rec);
13387
+ const state = {
13388
+ id: stateId,
13389
+ storage: __sbDOAsyncStorage(cls, id, gate, rec),
13390
+ blockConcurrencyWhile(fn) {
13391
+ if (!__sbIsFn(fn))
13392
+ throw new TypeError("Failed to execute 'blockConcurrencyWhile' on 'DurableObjectState': parameter 1 is not of type 'function'.");
13393
+ if (rec.dead) throw __sbDOEvictedError();
13394
+ // No event enters until fn settles; a throw or 30 s without settling resets the object, as in workerd.
13395
+ gate.locks++;
13396
+ let settled = false;
13397
+ const done = () => {
13398
+ settled = true;
13399
+ __sbClearTimeout(timer);
13400
+ if (!rec.dead) {
13401
+ gate.locks--;
13402
+ __sbDOPump(gate);
13403
+ }
13404
+ };
13405
+ const timer = __sbSetTimeout(() => {
13406
+ if (settled) return;
13407
+ settled = true;
13408
+ console.error("blockConcurrencyWhile() did not settle in 30 seconds: the Durable Object " + cls + " was reset");
13409
+ __sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() did not settle in 30 seconds."));
13410
+ }, 30000);
13411
+ let r;
13412
+ try {
13413
+ r = fn();
13414
+ } catch (error) {
13415
+ done();
13416
+ __sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() threw: " + String((error && error.message) || error)));
13417
+ return Promise.reject(error);
13418
+ }
13419
+ return Promise.resolve(r).then(
13420
+ (v) => {
13421
+ done();
13422
+ return v;
13423
+ },
13424
+ (error) => {
13425
+ done();
13426
+ __sbDOReset(rec, "bcw", new Error("Durable Object reset because blockConcurrencyWhile() failed: " + String((error && error.message) || error)));
13427
+ throw error;
13428
+ },
13429
+ );
13430
+ },
13431
+ waitUntil(p) {
13432
+ if (rec.dead) throw __sbDOEvictedError();
13433
+ if (p && __sbIsFn(p.then)) tasks.push(p);
13434
+ },
13435
+ };
13436
+ state.props = {};
13437
+ state.abort = (reason) => {
13438
+ __sbDOReset(rec, "abort");
13439
+ throw new Error(reason == null ? "Durable Object was aborted." : String(reason));
13440
+ };
13441
+ const holder = { inst: null, rec, auto: null, cls, id, restored: [], sockets: [] };
13442
+ rec.holder = holder;
13443
+ rec.state = state;
13444
+ if (__sbWsEnabled()) __sbDOWebSocketState(state, holder);
13445
+ let inst;
13446
+ try {
13447
+ inst = new Ctor(state, __sbEnvValue());
13448
+ } catch (error) {
13449
+ rec.dead = true;
13450
+ throw error;
13451
+ }
13452
+ // blockConcurrencyWhile() reset the object while its constructor was still running (a synchronous throw).
13453
+ if (rec.dead) throw new Error("The Durable Object " + cls + " was reset while it was starting.");
13454
+ holder.inst = inst;
13455
+ inst.__sbTasks = tasks;
13456
+ inst.__sbGate = gate;
13457
+ rec.inst = inst;
13458
+ inst.__sbRec = rec;
13459
+ L.recs[key] = rec;
13460
+ rec.registered = true;
13461
+ __sbDOLink(L, rec);
13462
+ const wake = Math.round((__sbDONow() - t0) * 1000);
13463
+ L.wakeUsLast = wake;
13464
+ if (wake > L.wakeUsMax) L.wakeUsMax = wake;
13465
+ // Sockets restored from hibernation start delivering now that the object exists.
13466
+ const restored = holder.restored;
13467
+ holder.restored = [];
13468
+ for (let i = 0; i < restored.length; i++) __sbWsPump(restored[i]);
13469
+ __sbDOArmSweep(L);
13470
+ return rec;
13471
+ }
13472
+
13473
+ // ---- run an event: the one entry for RPC, stub.fetch, alarms and socket events ----
13474
+ // `run(inst)` is the event; it enters the object's gate and runs in a frame that names the object. The returned promise settles with
13475
+ // the event's value. The object counts as busy until the event and the drain of its waitUntil tasks have settled (`wait`: the
13476
+ // returned promise waits for that drain too).
13477
+ function __sbDORun(cls, idText, idObj, run, wait) {
13478
+ let acquired;
13479
+ try {
13480
+ acquired = __sbDOAcquire(cls, idText, idObj);
13481
+ } catch (error) {
13482
+ return Promise.reject(error);
13483
+ }
13484
+ if (__sbIsFn(acquired.then)) return acquired.then((rec) => __sbDOStart(rec, run, wait));
13485
+ return __sbDOStart(acquired, run, wait);
13486
+ }
13487
+
13488
+ function __sbDOStart(rec, run, wait) {
13489
+ const L = __sbDOL;
13490
+ if (rec.dead) return Promise.reject(rec.deadError !== null ? rec.deadError : __sbDOEvictedError());
13491
+ const gate = rec.gate;
13492
+ if (gate.waiting.length >= L.queueMax) {
13493
+ L.overloaded++;
13494
+ return Promise.reject(__sbDOOverloaded("too many events are queued for this object"));
13495
+ }
13496
+ rec.busy++;
13497
+ // The input gate, as __sbDOEnter has it: an open gate runs the event now, a shut one queues it behind the others.
13498
+ if (gate.locks === 0 && gate.waiting.length === 0) return __sbDOEvent(L, rec, run, wait);
13499
+ return new Promise((resolve) => {
13500
+ gate.waiting.push(() => {
13501
+ resolve(__sbDOEvent(L, rec, run, wait));
13502
+ });
13503
+ });
13504
+ }
13505
+
13506
+ // One event, in the object's frame; busy until it and the drain of its waitUntil tasks have settled. One coroutine and nothing else
13507
+ // per event: this path is paid by every call into every object.
13508
+ async function __sbDOEvent(L, rec, run, wait) {
13509
+ try {
13510
+ if (rec.dead) throw rec.deadError !== null ? rec.deadError : __sbDOEvictedError();
13511
+ return await __sbRunInFrame(rec.frame, run, [rec.inst]);
13512
+ } finally {
13513
+ const drained = __sbDOSettle(L, rec, wait);
13514
+ if (drained !== undefined) await drained;
13515
+ }
13516
+ }
13517
+
13518
+ // The event is over: drain its waitUntil tasks (it stays busy until they settle), then it may become idle.
13519
+ function __sbDOSettle(L, rec, wait) {
13520
+ if (rec.tasks.length > 0) {
13521
+ const finish = () => __sbDOIdle(L, rec);
13522
+ const drained = __sbDrainWaitUntil(rec.tasks);
13523
+ drained.then(finish, finish);
13524
+ return wait ? drained.then(undefined, () => {}) : undefined;
13525
+ }
13526
+ __sbDOIdle(L, rec);
13527
+ return undefined;
13528
+ }
13529
+ function __sbDOIdle(L, rec) {
13530
+ rec.busy--;
13531
+ if (rec.busy !== 0 || rec.dead) return;
13532
+ const now = Date.now();
13533
+ if (rec.pins === 0) __sbDOFresh(L, rec, now);
13534
+ if (L.waiters.length > 0) __sbDOServe(L);
13535
+ if (!L.sweepArmed) __sbDOArmSweep(L);
13536
+ if (now - L.noteAt >= 1000) __sbDONote(L, true);
13537
+ }
13538
+
13539
+ // ---- pins: a timer, an unawaited fetch and a streamed response keep their object in memory ----
13540
+ // The timer globals become wrappers once the first object is built. The prelude's own timers use __sbSetTimeout and the rest,
13541
+ // which are the runtime's, so a parked timer of ours never pins anything.
13542
+ function __sbDOWrapTimers() {
13543
+ const armed = new Map();
13544
+ const arm = (original, repeat) =>
13545
+ function (callback, delay, ...args) {
13546
+ const rec = __sbDOFrameRec();
13547
+ if (rec === undefined || rec.dead || !__sbIsFn(callback)) return original(callback, delay, ...args);
13548
+ const slot = { id: 0, rec };
13549
+ __sbDOPin(rec);
13550
+ const fired = repeat
13551
+ ? callback
13552
+ : function (...fireArgs) {
13553
+ armed.delete(slot.id);
13554
+ __sbDOUnpin(slot.rec);
13555
+ return callback.apply(undefined, fireArgs);
13556
+ };
13557
+ slot.id = Number(original(fired, delay, ...args));
13558
+ armed.set(slot.id, slot);
13559
+ return slot.id;
13560
+ };
13561
+ const clear = (original) =>
13562
+ function (id) {
13563
+ const key = Number(id);
13564
+ const slot = armed.get(key);
13565
+ if (slot !== undefined) {
13566
+ armed.delete(key);
13567
+ __sbDOUnpin(slot.rec);
13568
+ }
13569
+ return original(id);
13570
+ };
13571
+ globalThis.setTimeout = arm(__sbSetTimeout, false);
13572
+ globalThis.setInterval = arm(__sbSetInterval, true);
13573
+ globalThis.clearTimeout = clear(__sbClearTimeout);
13574
+ globalThis.clearInterval = clear(__sbClearInterval);
13575
+ }
13576
+
13577
+ // ---- counters: cheap, one host note a second at most, only when changed ----
13578
+ function __sbDOStats(L) {
13579
+ let sqlOpen = 0;
13580
+ try {
13581
+ sqlOpen = Number(__sbRpc("do.stats", {}).stats.doOpen) || 0;
13582
+ } catch {
13583
+ sqlOpen = 0;
13584
+ }
13585
+ return (
13586
+ "objects_awake=" + L.count +
13587
+ " objects_pinned=" + L.pinned +
13588
+ " do_sql_open=" + sqlOpen +
13589
+ " evicted_idle=" + L.evictedIdle +
13590
+ " evicted_cap=" + L.evictedCap +
13591
+ " evicted_abort=" + L.evictedAbort +
13592
+ " waits=" + L.waits +
13593
+ " wait_ms_max=" + L.waitMsMax +
13594
+ " overloaded=" + L.overloaded +
13595
+ " zombie_calls=" + L.zombie +
13596
+ " wake_us=" + L.wakeUsLast + "," + L.wakeUsMax
13597
+ );
13598
+ }
13599
+ function __sbDONote(L, force) {
13600
+ const now = Date.now();
13601
+ if (!force && now - L.noteAt < 1000) return;
13602
+ L.noteAt = now;
13603
+ const text = __sbDOStats(L);
13604
+ if (text === L.noteText) return;
13605
+ L.noteText = text;
13606
+ if (__sbHosted()) __sbHostNote("do " + text);
13607
+ if (__sbEnv("SB_DO_STATS") === "1") console.error("do " + text);
13608
+ }
13609
+
13610
+ // A revoked state method: the evicted (or reset) object's instance cannot touch storage any more.
13611
+ function __sbDOZombie(rec) {
13612
+ __sbDOL.zombie++;
13613
+ return rec.deadError !== null ? rec.deadError : __sbDOEvictedError();
13614
+ }
13615
+ function __sbDOFetchEvent(inst, req) {
13616
+ return __sbFetchWithDrain(inst.fetch(req), [], false, true);
13617
+ }
13618
+ function __sbWsRestorePark() {
13619
+ const timer = __sbSetTimeout(__sbWsRestoreLoop, 2147483647);
13620
+ __sbWsOp("R", 0, "", timer);
13621
+ }
13622
+ function __sbWsRestoreFailed(id, error) {
13623
+ __sbLogUncaught("websocket", error);
13624
+ if (!(error && error.overloaded === true)) return;
13625
+ const L = __sbDOL;
13626
+ const tries = (L.restoreTries[id] || 0) + 1;
13627
+ L.restoreTries[id] = tries;
13628
+ if (tries >= 3) {
13629
+ delete L.restoreTries[id];
13630
+ // Three waits for a slot and none came: try again later, said to the client.
13631
+ __sbWsOp("c", id, "", 1013);
13632
+ __sbWsOp("r", id, "", 0);
13633
+ return;
13634
+ }
13635
+ // Back to the host's restore list with its queued events: the next slot serves it.
13636
+ __sbWsOp("g", id, "", 0);
13637
+ }
13638
+
12772
13639
  function __sbDOStorage(cls, id) {
12773
13640
  return {
12774
13641
  get(key) {
@@ -12866,7 +13733,7 @@ function __sbDOPump(gate) {
12866
13733
  // Shut the gate until the next timer turn (see above).
12867
13734
  function __sbDOHold(gate) {
12868
13735
  gate.locks++;
12869
- setTimeout(function () {
13736
+ __sbSetTimeout(function () {
12870
13737
  gate.locks--;
12871
13738
  __sbDOPump(gate);
12872
13739
  }, 0);
@@ -12902,6 +13769,12 @@ function __sbDOEnter(gate, run) {
12902
13769
  // (kept on the instance, as __sbAlarmAt and __sbAlarmFails)
12903
13770
  function __sbDOAlarmCall(inst, cls, id, at, attempts) {
12904
13771
  if (__sbCompatDate() < "2026-11-04") return inst.alarm();
13772
+ // From the eviction date the count is the delivery row's (or the host's), never the instance's: the instance can be gone
13773
+ // between two runs of the same alarm.
13774
+ if (__sbDOEvictOn()) {
13775
+ const retries = attempts != null && Number(attempts) > 0 ? Number(attempts) : 0;
13776
+ return __sbDOAlarmRun(inst, { retryCount: retries, isRetry: retries > 0 });
13777
+ }
12905
13778
  const when = Number(at) || 0;
12906
13779
  if (inst.__sbAlarmAt !== when) {
12907
13780
  inst.__sbAlarmAt = when;
@@ -13212,7 +14085,8 @@ function __sbDOStrictStorage(cls, id) {
13212
14085
  setAlarm(when) {
13213
14086
  const at = when instanceof Date ? when.getTime() : when;
13214
14087
  if (__sbDOv3()) {
13215
- const inst = __sbDOInstances[cls + " " + id];
14088
+ const live = __sbDOL !== null ? __sbDOL.recs[cls + " " + id] : undefined;
14089
+ const inst = live !== undefined ? live.inst : __sbDOInstances[cls + " " + id];
13216
14090
  if (inst && !__sbIsFn(inst.alarm))
13217
14091
  throw new TypeError("Your Durable Object class must have an alarm() handler in order to call setAlarm()");
13218
14092
  if (when instanceof Date || Object(when) instanceof Number) {
@@ -13254,9 +14128,11 @@ function __sbDOStrictStorage(cls, id) {
13254
14128
  }
13255
14129
 
13256
14130
  // The 2026-10-28 storage: every method returns a Promise; reads shut the input gate.
13257
- function __sbDOAsyncStorage(cls, id, gate) {
14131
+ function __sbDOAsyncStorage(cls, id, gate, rec) {
13258
14132
  const s = __sbDOStrictStorage(cls, id);
14133
+ // rec is the record of an object that may be evicted (2026-11-21): once it is dead every storage call fails with the evicted error.
13259
14134
  const settle = (f) => {
14135
+ if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
13260
14136
  try {
13261
14137
  return Promise.resolve(f());
13262
14138
  } catch (error) {
@@ -13264,9 +14140,13 @@ function __sbDOAsyncStorage(cls, id, gate) {
13264
14140
  }
13265
14141
  };
13266
14142
  const read = (f) => {
14143
+ if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
13267
14144
  __sbDOHold(gate);
13268
14145
  return settle(f);
13269
14146
  };
14147
+ const alive = () => {
14148
+ if (rec !== undefined && rec.dead) throw __sbDOZombie(rec);
14149
+ };
13270
14150
  const storage = {
13271
14151
  get: (key) => read(() => s.get(key)),
13272
14152
  list: (options) => read(() => s.list(options)),
@@ -13277,10 +14157,13 @@ function __sbDOAsyncStorage(cls, id, gate) {
13277
14157
  setAlarm: (when) => settle(() => s.setAlarm(when)),
13278
14158
  deleteAlarm: () => settle(() => s.deleteAlarm()),
13279
14159
  sync: () => Promise.resolve(),
13280
- transaction: (fn) => __sbDOTransaction(cls, id, s, gate, fn),
14160
+ transaction: (fn) => {
14161
+ if (rec !== undefined && rec.dead) return Promise.reject(__sbDOZombie(rec));
14162
+ return __sbDOTransaction(cls, id, s, gate, fn);
14163
+ },
13281
14164
  };
13282
14165
  if (__sbDOv2()) {
13283
- const sql = __sbDOSql(cls, id);
14166
+ const sql = __sbDOSql(cls, id, rec);
13284
14167
  storage.sql = sql;
13285
14168
  storage.transactionSync = (fn) => sql.__sbTransaction(fn);
13286
14169
  if (__sbDOv3()) {
@@ -13292,12 +14175,22 @@ function __sbDOAsyncStorage(cls, id, gate) {
13292
14175
  });
13293
14176
  // The synchronous key-value API, over the same keys as get() and put().
13294
14177
  storage.kv = {
13295
- get: (key) => s.get(key),
14178
+ get: (key) => {
14179
+ alive();
14180
+ return s.get(key);
14181
+ },
13296
14182
  put: (key, value) => {
14183
+ alive();
13297
14184
  s.put(key, value);
13298
14185
  },
13299
- delete: (key) => s.delete(key),
13300
- list: (options) => s.list(options).entries(),
14186
+ delete: (key) => {
14187
+ alive();
14188
+ return s.delete(key);
14189
+ },
14190
+ list: (options) => {
14191
+ alive();
14192
+ return s.list(options).entries();
14193
+ },
13301
14194
  };
13302
14195
  // Point-in-time recovery needs the storage engine's log, which this one does not keep.
13303
14196
  const pitr = () => Promise.reject(new Error("Point-in-time recovery is not supported by this Durable Object storage."));
@@ -13472,9 +14365,14 @@ function __sbDOSqlPolicy(text) {
13472
14365
  }
13473
14366
  }
13474
14367
 
13475
- function __sbDOSql(cls, id) {
14368
+ function __sbDOSql(cls, id, rec) {
13476
14369
  const file = __sbDOIsHex64(id) ? id.toLowerCase() : __sbDOSha("sbdo-file:" + id);
14370
+ if (rec !== undefined) rec.sqlFile = file;
13477
14371
  const run = (sql, params) => {
14372
+ if (rec !== undefined) {
14373
+ if (rec.dead) throw __sbDOZombie(rec);
14374
+ rec.usedSql = true;
14375
+ }
13478
14376
  try {
13479
14377
  return __sbRpc("do.sql", { cls, file, sql, params });
13480
14378
  } catch (err) {
@@ -14027,10 +14925,10 @@ async function __sbDrainWaitUntil(tasks, logRec) {
14027
14925
  // busy for the full 25 s (sproutd will not reap a worker with timers left, B22).
14028
14926
  let timer;
14029
14927
  const cap = new Promise((resolve) => {
14030
- timer = setTimeout(resolve, __SB_WAITUNTIL_TIMEOUT_MS);
14928
+ timer = __sbSetTimeout(resolve, __SB_WAITUNTIL_TIMEOUT_MS);
14031
14929
  });
14032
14930
  await Promise.race([__sbDrainAll(pending, logRec), cap]);
14033
- clearTimeout(timer);
14931
+ __sbClearTimeout(timer);
14034
14932
  }
14035
14933
 
14036
14934
  async function __sbDrainAll(pending, logRec) {
@@ -14090,7 +14988,8 @@ function __sbHasStreamBody(res) {
14090
14988
  // x-sb-stream header, the native writer sends the status and headers and holds the
14091
14989
  // connection open, and each chunk is written as it arrives. Hosts that do not stream
14092
14990
  // (sproutd's workers, for now) link the weak fallbacks, report unsupported and keep buffering.
14093
- // op: "s" supported, "o" open, "w" write data, "e" end. Returns a short string.
14991
+ // op: "s" supported, "o" open, "w" write data (a bytestring), "W" write the Uint8Array in data, "p" the bytes written
14992
+ // the client's connection has not taken yet, "k" park the timer in data until that falls, "e" end. Returns a short string.
14094
14993
  // oxlint-disable-next-line no-unused-vars -- read inside the RawC block, not by JS.
14095
14994
  function __sbStreamOp(op, id, data) {
14096
14995
  let out = "";
@@ -14100,6 +14999,11 @@ function __sbStreamOp(op, id, data) {
14100
14999
  extern unsigned int sb_stream_open(void);
14101
15000
  extern int sb_stream_write(unsigned int, const char*, size_t);
14102
15001
  extern void sb_stream_end(unsigned int);
15002
+ // Flow control (sproutd): the bytes written and not yet taken by the client's connection, and a timer the
15003
+ // host fires when that falls. A host without them (a standalone executable, whose server buffers by itself)
15004
+ // reports none, so a stream is never held back there.
15005
+ extern size_t sb_stream_pending(unsigned int) __attribute__((weak));
15006
+ extern void sb_stream_wait(unsigned int, unsigned int) __attribute__((weak));
14103
15007
  const char* __o; size_t __ol; char* __oo = 0;
14104
15008
  porf_native_fetch_read_value(op, &__o, &__ol, &__oo);
14105
15009
  char __op = __ol ? __o[0] : 0;
@@ -14119,7 +15023,17 @@ function __sbStreamOp(op, id, data) {
14119
15023
  int __w = -1;
14120
15024
  if (porf_native_fetch_read_raw_bytes(data, &__d, &__dl) == 0) __w = sb_stream_write(__id, __d, __dl);
14121
15025
  snprintf(__r, sizeof(__r), "%d", __w);
14122
- } else if (__op == 'e') { sb_stream_end(__id); snprintf(__r, sizeof(__r), "0"); }
15026
+ } else if (__op == 'W' && data.type == 81) {
15027
+ // A Uint8Array written as it is, with no bytestring copy of it.
15028
+ const u32 __ta = (u32)data.val;
15029
+ const int32_t __len = *(int32_t*)(MEM + __ta);
15030
+ const u32 __at = *(u32*)(MEM + __ta + 4) + 4;
15031
+ int __w = -1;
15032
+ if (__len >= 0) __w = sb_stream_write(__id, (const char*)(MEM + __at), (size_t)__len);
15033
+ snprintf(__r, sizeof(__r), "%d", __w);
15034
+ } else if (__op == 'p') snprintf(__r, sizeof(__r), "%zu", sb_stream_pending ? sb_stream_pending(__id) : (size_t)0);
15035
+ else if (__op == 'k') { if (sb_stream_wait && data.type == 1 && data.val > 0) sb_stream_wait(__id, (unsigned int)data.val); }
15036
+ else if (__op == 'e') { sb_stream_end(__id); snprintf(__r, sizeof(__r), "0"); }
14123
15037
  }
14124
15038
  out = porf_box((f64)porf_native_fetch_alloc_bytestring(__r, strlen(__r)), 195);
14125
15039
  `;
@@ -14130,6 +15044,16 @@ function __sbStreamingSupported() {
14130
15044
  if (__sbStreaming < 0) __sbStreaming = __sbStreamOp("s", "0", "") === "1" ? 1 : 0;
14131
15045
  return __sbStreaming === 1;
14132
15046
  }
15047
+ // What the client's connection may hold of a stream before the pump waits for it (the host credits bytes as
15048
+ // they leave): a client slower than the app is waited for, not buffered for or cut off.
15049
+ const __SB_STREAM_WINDOW = 1024 * 1024;
15050
+ // Resolves when the host says the stream's unsent bytes fell (or the client went away).
15051
+ function __sbStreamDrained(id) {
15052
+ return new Promise((resolve) => {
15053
+ const timer = __sbSetTimeout(resolve, 2147483647);
15054
+ __sbStreamOp("k", id, timer);
15055
+ });
15056
+ }
14133
15057
  function __sbStreamResponse(response) {
14134
15058
  const stream = __sbRawBody(response);
14135
15059
  const id = __sbStreamOp("o", "0", "");
@@ -14137,6 +15061,14 @@ function __sbStreamResponse(response) {
14137
15061
  response.headers._immutable = false;
14138
15062
  response.headers.set("x-sb-stream", id);
14139
15063
  response.body = "";
15064
+ // A streamed response of a Durable Object keeps its object busy until the stream ends (this runs in the object's frame).
15065
+ if (__sbDOL !== null) {
15066
+ const owner = __sbDOFrameRec();
15067
+ if (owner !== undefined && !owner.dead) {
15068
+ __sbDOPin(owner);
15069
+ response._sbDOPin = owner;
15070
+ }
15071
+ }
14140
15072
  (async () => {
14141
15073
  const reader = stream.getReader();
14142
15074
  try {
@@ -14148,10 +15080,22 @@ function __sbStreamResponse(response) {
14148
15080
  else if (value instanceof ArrayBuffer) bytes = new Uint8Array(value);
14149
15081
  else if (ArrayBuffer.isView(value)) bytes = new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
14150
15082
  else throw new TypeError("This ReadableStream did not return bytes.");
14151
- if (bytes.length > 0 && __sbStreamOp("w", id, __sbToBytes(bytes)) !== "0") {
14152
- // The client went away: stop the source.
14153
- reader.cancel();
14154
- break;
15083
+ if (bytes.length > 0) {
15084
+ // A whole Uint8Array goes to the host without a copy; a view of part of a buffer is copied.
15085
+ const wrote =
15086
+ bytes.byteOffset === 0 && bytes.byteLength === bytes.buffer.byteLength
15087
+ ? __sbStreamOp("W", id, bytes)
15088
+ : __sbStreamOp("w", id, __sbToBytes(bytes));
15089
+ if (wrote !== "0") {
15090
+ // The client went away: stop the source.
15091
+ reader.cancel();
15092
+ break;
15093
+ }
15094
+ // Flow control: while the connection holds more than the window, read no more of the source.
15095
+ while (Number(__sbStreamOp("p", id, "")) > __SB_STREAM_WINDOW) {
15096
+ await __sbStreamDrained(id);
15097
+ // Woken by the client going away: the next write says so (-1).
15098
+ }
14155
15099
  }
14156
15100
  }
14157
15101
  } catch (error) {
@@ -14159,6 +15103,7 @@ function __sbStreamResponse(response) {
14159
15103
  __sbLogUncaught("fetch", error);
14160
15104
  }
14161
15105
  __sbStreamOp("e", id, "");
15106
+ if (response._sbDOPin !== undefined) __sbDOUnpin(response._sbDOPin);
14162
15107
  })();
14163
15108
  return response;
14164
15109
  }
@@ -14301,6 +15246,10 @@ class __SbWebSocket {
14301
15246
  if (this._hib !== null) throw new TypeError("Can't accept() a WebSocket that was accepted with acceptWebSocket().");
14302
15247
  if (this._accepted) return;
14303
15248
  this._accepted = true;
15249
+ if (__sbDOL !== null) {
15250
+ const rec = __sbDOFrameRec();
15251
+ if (rec !== undefined && !rec.dead) rec.plain.push(this);
15252
+ }
14304
15253
  __sbWsSchedule(this);
14305
15254
  }
14306
15255
  send(message) {
@@ -14472,6 +15421,16 @@ function __sbWsDispatch(ws, event) {
14472
15421
 
14473
15422
  // A hibernatable socket's event runs the object's handler through its input gate.
14474
15423
  function __sbWsHibernated(ws, name, args) {
15424
+ const rec = ws._hib.holder.rec;
15425
+ if (rec !== undefined && rec !== null) {
15426
+ if (rec.dead || rec.inst === null || !__sbIsFn(rec.inst[name])) return;
15427
+ __sbDOStart(
15428
+ rec,
15429
+ (inst) => (name === "webSocketClose" ? inst.webSocketClose(args[0], args[1], args[2], args[3]) : inst.webSocketMessage(args[0], args[1])),
15430
+ false,
15431
+ ).then(undefined, (error) => __sbLogUncaught("websocket", error));
15432
+ return;
15433
+ }
14475
15434
  const inst = ws._hib.holder.inst;
14476
15435
  if (inst == null || !__sbIsFn(inst[name])) return;
14477
15436
  const run = () => {
@@ -14492,12 +15451,14 @@ function __sbWsHibernated(ws, name, args) {
14492
15451
  // state from 2026-10-30. `holder.inst` is the object once it exists.
14493
15452
  function __sbDOWebSocketState(state, holder) {
14494
15453
  const sockets = [];
15454
+ holder.sockets = sockets;
14495
15455
  const live = () => {
14496
15456
  for (let i = sockets.length - 1; i >= 0; i--) if (sockets[i]._state === __SB_WS_CLOSED) sockets.splice(i, 1);
14497
15457
  return sockets;
14498
15458
  };
14499
15459
  __sbWsHibRestore(holder, sockets);
14500
15460
  state.acceptWebSocket = function (ws, tags) {
15461
+ if (holder.rec !== undefined && holder.rec !== null && holder.rec.dead) throw __sbDOZombie(holder.rec);
14501
15462
  if (!(ws instanceof __SbWebSocket)) throw new TypeError("acceptWebSocket needs a WebSocket");
14502
15463
  if (ws._accepted || ws._hib !== null || ws._host !== 0) throw new TypeError("This WebSocket was already accepted.");
14503
15464
  const list = [];
@@ -14572,7 +15533,8 @@ function __sbWsPump(ws) {
14572
15533
  for (;;) {
14573
15534
  const head = __sbWsOp("p", id, "", 0);
14574
15535
  if (head === "") {
14575
- const timer = setTimeout(() => __sbWsPump(ws), 2147483647);
15536
+ const timer = __sbSetTimeout(() => __sbWsPump(ws), 2147483647);
15537
+ ws._pumpTimer = timer;
14576
15538
  // From 2026-11-01 a parked socket is not background work (z): its app may be reaped.
14577
15539
  __sbWsOp(__sbWsHibOn() ? "z" : "w", id, "", timer);
14578
15540
  return;
@@ -14661,9 +15623,9 @@ function __sbTcpPump(st) {
14661
15623
  if (head === "") {
14662
15624
  if (st.id === 0) return;
14663
15625
  if (__sbWsCanWake()) {
14664
- st.timer = setTimeout(() => __sbTcpPump(st), 2147483647);
15626
+ st.timer = __sbSetTimeout(() => __sbTcpPump(st), 2147483647);
14665
15627
  __sbWsOp("w", st.id, "", st.timer);
14666
- } else st.timer = setTimeout(() => __sbTcpPump(st), 10);
15628
+ } else st.timer = __sbSetTimeout(() => __sbTcpPump(st), 10);
14667
15629
  return;
14668
15630
  }
14669
15631
  const parts = head.split(" ");
@@ -14728,7 +15690,7 @@ function __sbTcpRelease(st) {
14728
15690
  const id = st.id;
14729
15691
  if (id === 0) return;
14730
15692
  st.id = 0;
14731
- if (st.timer !== undefined) clearTimeout(st.timer);
15693
+ if (st.timer !== undefined) __sbClearTimeout(st.timer);
14732
15694
  __sbWsOp("r", id, "", 0);
14733
15695
  __sbTcpOpen--;
14734
15696
  }
@@ -14876,7 +15838,7 @@ function __sbTcpFacade(st, mode, upgrade) {
14876
15838
  // the old socket ends once the upgrade is under way (workerd resolves its `closed`)
14877
15839
  const oldResolve = st.closedResolve;
14878
15840
  const next = __sbTcpFacade(st, "on", upgrade);
14879
- setTimeout(() => oldResolve(undefined), 0);
15841
+ __sbSetTimeout(() => oldResolve(undefined), 0);
14880
15842
  return next;
14881
15843
  },
14882
15844
  };
@@ -14908,7 +15870,7 @@ function __sbConnect(address, options) {
14908
15870
  if (target.port === 25) {
14909
15871
  st.failed = new Error("proxy request failed, cannot connect to the specified address");
14910
15872
  st.eof = true;
14911
- setTimeout(() => __sbTcpSettle(st), 0);
15873
+ __sbSetTimeout(() => __sbTcpSettle(st), 0);
14912
15874
  return socket;
14913
15875
  }
14914
15876
  const spec = target.host + "\n" + target.port + "\n" + (mode === "on" ? "1" : mode === "starttls" ? "2" : "0") + "\n" + target.host;
@@ -14916,11 +15878,15 @@ function __sbConnect(address, options) {
14916
15878
  if (!(id > 0)) {
14917
15879
  st.failed = new Error(__sbTcpOpen >= 16 ? "Too many open connections" : "TCP sockets are not available on this host");
14918
15880
  st.eof = true;
14919
- setTimeout(() => __sbTcpSettle(st), 0);
15881
+ __sbSetTimeout(() => __sbTcpSettle(st), 0);
14920
15882
  return socket;
14921
15883
  }
14922
15884
  st.id = id;
14923
15885
  __sbTcpOpen++;
15886
+ if (__sbDOL !== null) {
15887
+ const rec = __sbDOFrameRec();
15888
+ if (rec !== undefined && !rec.dead) rec.tcp.push(st);
15889
+ }
14924
15890
  __sbTcpPump(st);
14925
15891
  return socket;
14926
15892
  }
@@ -14997,10 +15963,14 @@ function __sbWsConnectOutbound(ws, input, protocols) {
14997
15963
  ws._accepted = true;
14998
15964
  ws._state = 0;
14999
15965
  ws.url = target.url;
15966
+ if (__sbDOL !== null) {
15967
+ const rec = __sbDOFrameRec();
15968
+ if (rec !== undefined && !rec.dead) rec.plain.push(ws);
15969
+ }
15000
15970
  const id = __sbWsOpen(target, list, null);
15001
15971
  if (!(id > 0)) {
15002
15972
  ws._state = __SB_WS_CLOSED;
15003
- setTimeout(() => {
15973
+ __sbSetTimeout(() => {
15004
15974
  __sbWsFire(ws, { type: "error", target: ws });
15005
15975
  __sbWsFire(ws, { type: "close", code: 1006, reason: "", wasClean: false, target: ws });
15006
15976
  }, 0);
@@ -15019,9 +15989,9 @@ function __sbWsPumpOut(ws) {
15019
15989
  const head = __sbWsOp("p", id, "", 0);
15020
15990
  if (head === "") {
15021
15991
  if (__sbWsCanWake()) {
15022
- const timer = setTimeout(() => __sbWsPumpOut(ws), 2147483647);
15992
+ const timer = __sbSetTimeout(() => __sbWsPumpOut(ws), 2147483647);
15023
15993
  __sbWsOp("w", id, "", timer);
15024
- } else setTimeout(() => __sbWsPumpOut(ws), 15);
15994
+ } else __sbSetTimeout(() => __sbWsPumpOut(ws), 15);
15025
15995
  return;
15026
15996
  }
15027
15997
  const parts = head.split(" ");
@@ -15127,7 +16097,7 @@ function __sbWsOutClose(ws, code, text) {
15127
16097
  const pending = ws._pending;
15128
16098
  ws._pending = null;
15129
16099
  if (pending) pending.reject(new TypeError("WebSocket connection was closed before it opened"));
15130
- else setTimeout(() => {
16100
+ else __sbSetTimeout(() => {
15131
16101
  __sbWsFire(ws, { type: "error", target: ws });
15132
16102
  __sbWsFire(ws, { type: "close", code: 1006, reason: "", wasClean: false, target: ws });
15133
16103
  }, 0);
@@ -15136,7 +16106,7 @@ function __sbWsOutClose(ws, code, text) {
15136
16106
  ws._state = __SB_WS_CLOSING;
15137
16107
  __sbWsOp("c", id, text, code === undefined ? 1000 : code);
15138
16108
  // A peer that never answers the close is dropped after a while.
15139
- setTimeout(() => {
16109
+ __sbSetTimeout(() => {
15140
16110
  if (ws._host !== id) return;
15141
16111
  ws._host = 0;
15142
16112
  __sbWsOp("r", id, "", 0);
@@ -15329,8 +16299,7 @@ function __sbWsRestoreLoop() {
15329
16299
  __sbLogUncaught("websocket", error);
15330
16300
  }
15331
16301
  }
15332
- const timer = setTimeout(__sbWsRestoreLoop, 2147483647);
15333
- __sbWsOp("R", 0, "", timer);
16302
+ __sbWsRestorePark();
15334
16303
  }
15335
16304
  function __sbWsRestoreSocket(id) {
15336
16305
  const r = __sbRpc("do.storage.get", { cls: "__sbws", id: "index", key: "s" + id });
@@ -15349,6 +16318,10 @@ function __sbWsRestoreSocket(id) {
15349
16318
  return;
15350
16319
  }
15351
16320
  // Constructing the object restores its sockets (__sbWsHibRestore) and starts their pumps.
16321
+ if (__sbDOEvictOn()) {
16322
+ __sbDORun(target[0], target[1], undefined, () => undefined, false).then(undefined, (error) => __sbWsRestoreFailed(id, error));
16323
+ return;
16324
+ }
15352
16325
  __sbGetDOInstance(target[0], target[1]);
15353
16326
  }
15354
16327
 
@@ -15784,7 +16757,7 @@ function __sbIngressStream(request, id) {
15784
16757
  const state = Number(__sbReqBodyOp("p", id, ""));
15785
16758
  if (state === 0) {
15786
16759
  // Nothing yet: the server fires this timer when bytes, the end or an error arrive.
15787
- const timer = setTimeout(() => step(controller, resolve), 2147483647);
16760
+ const timer = __sbSetTimeout(() => step(controller, resolve), 2147483647);
15788
16761
  __sbReqBodyOp("w", id, timer);
15789
16762
  return;
15790
16763
  }
@@ -15891,7 +16864,164 @@ function __sbTrustedProxiesOnce() {
15891
16864
  return __sbTrustedCache;
15892
16865
  }
15893
16866
 
16867
+ // R2 transfer tickets (createUploadUrl, createDownloadUrl): /__sb/r2/transfer/BUCKET/TOKEN, scoped to one object and
16868
+ // good once. A standalone executable's native server answers them before JavaScript runs (patchR2Transfer, #195);
16869
+ // on a host that forwards every path to the worker (sproutd) the worker answers them here with the same statuses
16870
+ // and headers, before the app's fetch() sees the request. The body goes straight between the connection and the
16871
+ // object's file, one step at a time (__sbR2XferOp): at most a window of it is ever in the worker.
16872
+ const __SB_R2_TRANSFER = "/__sb/r2/transfer/";
16873
+ // Set by the embedded transport (it has the ticket contexts and their steps); the broker's own listener owns the path.
16874
+ // oxlint-disable-next-line no-unassigned-vars -- set by the embedded transport, above
16875
+ var __sbR2TransferOn; // no initializer: the embedded transport (above) sets it, and an initializer here would run after it
16876
+ // [bucket, token] when the request is for a ticket (the same shape the native server accepts), else null.
16877
+ function __sbR2TransferTarget(request) {
16878
+ const method = request.method;
16879
+ if (method !== "GET" && method !== "PUT" && method !== "HEAD" && method !== "POST" && method !== "DELETE") return null;
16880
+ const url = request.url;
16881
+ const at = url.indexOf(__SB_R2_TRANSFER);
16882
+ if (at < 0) return null;
16883
+ const hostEnd = url.indexOf("/", url.indexOf("//") + 2);
16884
+ if (at !== hostEnd) return null;
16885
+ let rest = url.slice(at + __SB_R2_TRANSFER.length);
16886
+ const cut = rest.search(/[?#]/);
16887
+ if (cut >= 0) rest = rest.slice(0, cut);
16888
+ const slash = rest.indexOf("/");
16889
+ if (slash <= 0 || slash > 255 || rest.indexOf("/", slash + 1) >= 0) return null;
16890
+ const bucket = rest.slice(0, slash);
16891
+ const token = rest.slice(slash + 1);
16892
+ if (token.length !== 24 || !/^[0-9a-f]+$/.test(token) || !/^[A-Za-z0-9_]+$/.test(bucket)) return null;
16893
+ return [bucket, token];
16894
+ }
16895
+ function __sbR2TransferError(status, text) {
16896
+ return new Response(text, { status, headers: { "content-type": "text/plain" } });
16897
+ }
16898
+ // A transfer status as a response (the native server's table: anything else is a 500).
16899
+ function __sbR2TransferFailed(status, text) {
16900
+ const known = status === 404 || status === 410 || status === 413 || status === 422 || status === 503 || status === 507;
16901
+ return __sbR2TransferError(known ? status : 500, text);
16902
+ }
16903
+ // Resolves when the streamed request body `body` has more (or ended or failed): the host fires the parked timer.
16904
+ function __sbR2BodyWait(body) {
16905
+ return new Promise((resolve) => {
16906
+ const timer = __sbSetTimeout(resolve, 2147483647);
16907
+ __sbReqBodyOp("w", body, timer);
16908
+ });
16909
+ }
16910
+ async function __sbR2TransferUpload(request, bucket, token, body) {
16911
+ let declared = 0;
16912
+ const length = request.headers.get("content-length");
16913
+ if (length !== null && length !== "") {
16914
+ if (!/^[0-9]+$/.test(length) || length.length > 15) return __sbR2TransferError(400, "invalid content-length");
16915
+ declared = Number(length);
16916
+ }
16917
+ const opened = __sbR2XferOp("u", bucket, token, declared).split(" ");
16918
+ if (opened[0] !== "0") return __sbR2TransferFailed(Number(opened[0]), "R2 transfer unavailable");
16919
+ const handle = opened[1];
16920
+ let status = 0;
16921
+ if (body === 0) {
16922
+ // The host delivered the body whole (or there is none): one write.
16923
+ const raw = __sbRawBody(request);
16924
+ if (raw != null && raw !== "") status = Number(__sbR2XferOp("b", handle, raw, 0));
16925
+ } else {
16926
+ for (;;) {
16927
+ const step = __sbR2XferOp("s", handle, "", body).split(" ");
16928
+ const kind = step[0];
16929
+ if (kind === "d") continue;
16930
+ if (kind === "w") {
16931
+ await __sbR2BodyWait(body);
16932
+ continue;
16933
+ }
16934
+ if (kind === "e") break;
16935
+ // 'f': the client went away; 's': a status refused the bytes (the ticket is released with it).
16936
+ status = kind === "s" ? Number(step[1]) : 500;
16937
+ if (kind === "f") __sbR2XferOp("x", "0", "", Number(handle));
16938
+ __sbReqBodyOp("c", body, "");
16939
+ return kind === "f" ? __sbR2TransferError(500, "R2 transfer failed") : __sbR2TransferFailed(status, "R2 transfer failed");
16940
+ }
16941
+ __sbReqBodyOp("c", body, "");
16942
+ }
16943
+ if (status !== 0) return __sbR2TransferFailed(status, "R2 transfer failed");
16944
+ const finished = Number(__sbR2XferOp("f", handle, "", 0));
16945
+ if (finished === 201) return new Response(null, { status: 201 });
16946
+ return __sbR2TransferFailed(finished, "R2 transfer failed");
16947
+ }
16948
+ function __sbR2TransferDownload(request, bucket, token) {
16949
+ const opened = __sbR2XferOp("d", bucket, token, 0).split(" ");
16950
+ if (opened[0] !== "0") return __sbR2TransferFailed(Number(opened[0]), "R2 transfer unavailable");
16951
+ const handle = Number(opened[1]);
16952
+ const size = Number(opened[2]);
16953
+ const etag = opened[3];
16954
+ const close = () => __sbR2XferOp("c", "0", "", handle);
16955
+ const match = request.headers.get("if-none-match");
16956
+ if (match === "*" || match === etag || match === '"' + etag + '"') {
16957
+ close();
16958
+ return new Response(null, { status: 304, headers: { etag } });
16959
+ }
16960
+ let first = 0;
16961
+ let last = size ? size - 1 : 0;
16962
+ let partial = false;
16963
+ const range = request.headers.get("range");
16964
+ if (range) {
16965
+ const refuse = (why) => {
16966
+ close();
16967
+ return __sbR2TransferError(416, why);
16968
+ };
16969
+ if (range.slice(0, 6) !== "bytes=") return refuse("invalid range");
16970
+ const spec = range.slice(6);
16971
+ const dash = spec.indexOf("-");
16972
+ if (dash <= 0) return refuse("invalid range");
16973
+ const from = spec.slice(0, dash);
16974
+ const to = spec.slice(dash + 1);
16975
+ if (!/^[0-9]+$/.test(from) || (to !== "" && !/^[0-9]+$/.test(to))) return refuse("invalid range");
16976
+ first = Number(from);
16977
+ if (to !== "") last = Number(to);
16978
+ if (first >= size || last < first) return refuse("range outside object");
16979
+ if (last >= size) last = size - 1;
16980
+ partial = true;
16981
+ }
16982
+ const total = size === 0 ? 0 : last - first + 1;
16983
+ const headers = { "content-type": "application/octet-stream", etag, "accept-ranges": "bytes", "content-length": String(total) };
16984
+ if (partial) headers["content-range"] = "bytes " + first + "-" + last + "/" + size;
16985
+ let sent = 0;
16986
+ const stream = new ReadableStream({
16987
+ pull(controller) {
16988
+ if (sent >= total) {
16989
+ close();
16990
+ controller.close();
16991
+ return;
16992
+ }
16993
+ const chunk = new Uint8Array(Math.min(__SB_R2_CHUNK, total - sent));
16994
+ const got = Number(__sbR2XferOp("r", String(handle), "", first + sent, chunk));
16995
+ if (got !== chunk.length) {
16996
+ close();
16997
+ controller.error(new Error("R2 transfer failed"));
16998
+ return;
16999
+ }
17000
+ sent += got;
17001
+ controller.enqueue(chunk);
17002
+ },
17003
+ cancel() {
17004
+ close();
17005
+ },
17006
+ });
17007
+ return new Response(stream, { status: partial ? 206 : 200, headers });
17008
+ }
17009
+ // The response for a ticket request, or a Promise of it.
17010
+ function __sbR2Transfer(request, target) {
17011
+ // The streamed body's id, taken now: a ticket request never reaches __sbDecodeIngress.
17012
+ const body = __sbReqBodyTake();
17013
+ const method = request.method;
17014
+ if (method === "GET") return __sbR2TransferDownload(request, target[0], target[1]);
17015
+ if (method !== "PUT") return __sbR2TransferError(405, "R2 transfer requires GET or PUT");
17016
+ return __sbR2TransferUpload(request, target[0], target[1], body);
17017
+ }
17018
+
15894
17019
  globalThis.__sbEntry = function (handlers, request) {
17020
+ if (__sbR2TransferOn) {
17021
+ const ticket = __sbR2TransferTarget(request);
17022
+ // Through the same finish as the app's answer (promise, then a stream body is sent as it is read).
17023
+ if (ticket !== null) return __sbFetchWithDrain(__sbR2Transfer(request, ticket), [], false, false, true, null, null);
17024
+ }
15895
17025
  __sbDecodeIngress(request);
15896
17026
  const trigger = __sbHeader(request.headers, "x-sb-trigger");
15897
17027
  if (!trigger) {
@@ -16025,6 +17155,22 @@ globalThis.__sbEntry = function (handlers, request) {
16025
17155
  const body = __sbReadJson(request);
16026
17156
  const __acls = String(body.cls || "");
16027
17157
  const __aid = String(body.id || "");
17158
+ if (__sbDOEvictOn()) {
17159
+ const none = {};
17160
+ return __sbDORun(
17161
+ __acls,
17162
+ __aid,
17163
+ undefined,
17164
+ (instance) => (__sbIsFn(instance.alarm) ? __sbDOAlarmCall(instance, __acls, __aid, body.scheduledTime, body.attempts) : none),
17165
+ true,
17166
+ ).then(
17167
+ (value) => (value === none ? new Response("no alarm handler", { status: 404 }) : new Response(null, { status: 204 })),
17168
+ (error) => {
17169
+ __sbLogUncaught("alarm", error);
17170
+ return error && error.overloaded === true ? new Response("alarm deferred: too many objects are awake", { status: 503 }) : new Response("alarm failed", { status: 500 });
17171
+ },
17172
+ );
17173
+ }
16028
17174
  const inst = __sbGetDOInstance(__acls, __aid);
16029
17175
  if (!__sbIsFn(inst.alarm)) return new Response("no alarm handler", { status: 404 });
16030
17176
  const __retries = __sbCompatDate() >= "2026-11-04";