@sproutboat/runtime 0.36.0 → 0.37.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.
@@ -3642,9 +3642,17 @@ __sbDefineURLAccessor("password", function () {
3642
3642
  // fallback — a silent downgrade to a weak source is worse than throwing.
3643
3643
  if (globalThis.crypto == null) globalThis.crypto = {};
3644
3644
  if (globalThis.crypto.getRandomValues == null) {
3645
+ // Every byte of the view, as WebCrypto fills it: a Uint32Array gets 4 random bytes per element.
3646
+ // Only integer views are accepted (a Float32Array or a DataView throws TypeMismatchError), and one
3647
+ // call may ask for at most 65536 bytes.
3645
3648
  globalThis.crypto.getRandomValues = function (view) {
3646
- const n = view.length >>> 0;
3647
- // WebCrypto caps a single call at 65536 bytes.
3649
+ const integer = ArrayBuffer.isView(view) && !(view instanceof DataView) && !(view instanceof Float32Array) && !(view instanceof Float64Array);
3650
+ if (!integer) {
3651
+ const e = new Error("The data argument must be an integer-type TypedArray");
3652
+ e.name = "TypeMismatchError";
3653
+ throw e;
3654
+ }
3655
+ const n = view.byteLength >>> 0;
3648
3656
  if (n > 65536) {
3649
3657
  // WebCrypto's error for an oversized request (a DOMException named QuotaExceededError).
3650
3658
  const e = new Error("crypto.getRandomValues: byte length exceeds 65536");
@@ -3652,11 +3660,10 @@ if (globalThis.crypto.getRandomValues == null) {
3652
3660
  throw e;
3653
3661
  }
3654
3662
  if (n === 0) return view;
3655
- // One CSPRNG byte per element. Correct for Uint8Array (and randomUUID); a
3656
- // wider view gets its low byte filled, matching the previous polyfill's shape.
3657
- const bytes = __sbRandomBytes(String(n));
3658
- if (bytes.length !== n) throw new Error("crypto.getRandomValues: OS entropy source unavailable");
3659
- for (let i = 0; i < n; i++) view[i] = bytes.charCodeAt(i) & 0xff;
3663
+ const random = __sbRandomBytes(String(n));
3664
+ if (random.length !== n) throw new Error("crypto.getRandomValues: OS entropy source unavailable");
3665
+ const out = new Uint8Array(view.buffer, view.byteOffset, n);
3666
+ for (let i = 0; i < n; i++) out[i] = random.charCodeAt(i) & 0xff;
3660
3667
  return view;
3661
3668
  };
3662
3669
  }
@@ -3738,6 +3745,40 @@ if (globalThis.crypto.getRandomValues == null) {
3738
3745
  };
3739
3746
  }
3740
3747
 
3748
+ // DOMException. Porffor's own global is a stub that reports typeof "function" but cannot be
3749
+ // constructed (`new DOMException("m", "AbortError")` throws "not a constructor"), so the runtime
3750
+ // installs a real one: legacy codes, the named constants, the options form with `cause`, and
3751
+ // instanceof Error, as in workerd. AbortSignal reasons, the stream polyfill and WebCrypto errors
3752
+ // use it, so a library can test `error instanceof DOMException` and `error.name`.
3753
+ const __sbDomCodes = {
3754
+ IndexSizeError: 1, HierarchyRequestError: 3, WrongDocumentError: 4, InvalidCharacterError: 5,
3755
+ NoModificationAllowedError: 7, NotFoundError: 8, NotSupportedError: 9, InUseAttributeError: 10,
3756
+ InvalidStateError: 11, SyntaxError: 12, InvalidModificationError: 13, NamespaceError: 14,
3757
+ InvalidAccessError: 15, TypeMismatchError: 17, SecurityError: 18, NetworkError: 19,
3758
+ AbortError: 20, URLMismatchError: 21, QuotaExceededError: 22, TimeoutError: 23,
3759
+ InvalidNodeTypeError: 24, DataCloneError: 25,
3760
+ };
3761
+ /* oxlint-disable anti-slop/no-runtime-typeof -- the second argument is a name or an options bag, as in WebIDL */
3762
+ class __SbDOMException extends Error {
3763
+ constructor(message = "", options = "Error") {
3764
+ super(message === undefined ? "" : String(message));
3765
+ let name = "Error";
3766
+ if (options !== null && typeof options === "object") {
3767
+ name = options.name === undefined ? "Error" : String(options.name);
3768
+ if ("cause" in options) this.cause = options.cause;
3769
+ } else if (options !== undefined) name = String(options);
3770
+ this.name = name;
3771
+ const code = __sbDomCodes[name];
3772
+ this.code = code === undefined ? 0 : code;
3773
+ }
3774
+ }
3775
+ for (const key of Object.keys(__sbDomCodes)) {
3776
+ const constant = key.replace(/([a-z])([A-Z])/g, "$1_$2").toUpperCase().replace(/_ERROR$/, "_ERR");
3777
+ __SbDOMException[constant] = __sbDomCodes[key];
3778
+ }
3779
+ /* oxlint-enable anti-slop/no-runtime-typeof */
3780
+ globalThis.DOMException = __SbDOMException;
3781
+
3741
3782
  /* oxlint-disable anti-slop/no-runtime-typeof -- these Web API polyfills branch on JavaScript types
3742
3783
  exactly where their specs do (listener: function or { handleEvent }; structured clone: by value type). */
3743
3784
  // Event, CustomEvent, EventTarget, AbortSignal and AbortController, as workerd has
@@ -3745,11 +3786,7 @@ if (globalThis.crypto.getRandomValues == null) {
3745
3786
  // conformance suite; libraries use AbortController for fetch timeouts and EventTarget
3746
3787
  // for their own events.
3747
3788
  if (typeof globalThis.EventTarget !== "function") {
3748
- const namedError = (name, message) => {
3749
- const e = new Error(message);
3750
- e.name = name;
3751
- return e;
3752
- };
3789
+ const namedError = (name, message) => new globalThis.DOMException(message, name);
3753
3790
  class Event {
3754
3791
  constructor(type, init) {
3755
3792
  if (arguments.length === 0) throw new TypeError("Event requires a type");
@@ -4268,9 +4305,7 @@ function __sbHexOrBytes(value) {
4268
4305
 
4269
4306
  // Errors as WebCrypto names them (DOMException names; plain Errors here).
4270
4307
  function __sbCryptoError(name, message) {
4271
- const e = new Error(message);
4272
- e.name = name;
4273
- return e;
4308
+ return new globalThis.DOMException(message, name);
4274
4309
  }
4275
4310
  // An HMAC key as importKey and generateKey return it. MD5 is digest-only, as in workerd.
4276
4311
  function __sbHmacKey(bytes, bits, extractable, usages) {
@@ -5356,6 +5391,7 @@ function __sbRpcDirect(op, extra) {
5356
5391
  function __sbD1Rpc(op, extra, strict) {
5357
5392
  if (!strict) return __sbRpcDirect(op, extra);
5358
5393
  extra.strict = true;
5394
+ if (__sbD1Parity()) extra.parity = true;
5359
5395
  try {
5360
5396
  return __sbRpcDirect(op, extra);
5361
5397
  } catch (err) {
@@ -5420,10 +5456,104 @@ function __sbD1IsTransaction(text) {
5420
5456
  word === "begin" || word === "commit" || word === "end" || word === "rollback" || word === "savepoint" || word === "release"
5421
5457
  );
5422
5458
  }
5459
+ // From 2026-11-03: workerd's D1 authorizer lets a PRAGMA run only if it is one of these (the name as
5460
+ // written: PRAGMA TABLE_INFO is refused, as is any schema but main). Any other reads "not authorized".
5461
+ // Checked in the shim, so every transport answers alike. A query or batch statement is only looked at
5462
+ // when it starts with P (after spaces and comments); an exec line is scanned statement by statement.
5463
+ function __sbD1Parity() {
5464
+ return __sbCompatDate() >= "2026-11-03";
5465
+ }
5466
+ var __sbD1PragmaAllowed =
5467
+ " table_list table_info table_xinfo index_list index_info index_xinfo foreign_keys foreign_key_list foreign_key_check quick_check optimize defer_foreign_keys case_sensitive_like recursive_triggers data_version legacy_alter_table reverse_unordered_selects ignore_check_constraints ";
5468
+ var __sbD1NotAuthorized = "D1_ERROR: not authorized: SQLITE_AUTH";
5469
+ function __sbD1IsIdent(c) {
5470
+ return (c >= 48 && c <= 57) || (c >= 65 && c <= 90) || c === 95 || (c >= 97 && c <= 122) || c > 127;
5471
+ }
5472
+ // Index of the first character at or after i that is not whitespace, a comment, or (when
5473
+ // semicolons) a semicolon.
5474
+ function __sbD1SkipBlank(s, i, semicolons) {
5475
+ for (;;) {
5476
+ const c = s.charCodeAt(i);
5477
+ if (c === 32 || c === 9 || c === 10 || c === 13 || c === 12 || (semicolons && c === 59)) i++;
5478
+ else if (c === 45 && s.charCodeAt(i + 1) === 45) {
5479
+ while (i < s.length && s.charCodeAt(i) !== 10) i++;
5480
+ } else if (c === 47 && s.charCodeAt(i + 1) === 42) {
5481
+ const end = s.indexOf("*/", i + 2);
5482
+ i = end < 0 ? s.length : end + 2;
5483
+ } else return i;
5484
+ }
5485
+ }
5486
+ // An identifier at i (plain, or quoted with " ` or [ ]): its text and where it ends.
5487
+ function __sbD1Name(s, i) {
5488
+ const q = s.charCodeAt(i);
5489
+ if (q === 34 || q === 96 || q === 91) {
5490
+ const end = s.indexOf(q === 91 ? "]" : String.fromCharCode(q), i + 1);
5491
+ if (end < 0) return { text: s.slice(i + 1), end: s.length };
5492
+ return { text: s.slice(i + 1, end), end: end + 1 };
5493
+ }
5494
+ let end = i;
5495
+ while (end < s.length && __sbD1IsIdent(s.charCodeAt(end))) end++;
5496
+ return { text: s.slice(i, end), end };
5497
+ }
5498
+ // The statement at i (already past blanks): 0 when it is not a PRAGMA or one D1 allows, 1 when D1 refuses
5499
+ // it, 2 when it sets foreign_keys (which D1 never lets a call switch off: the pragma is a no-op inside
5500
+ // the transaction every D1 call runs in, so the caller puts it back).
5501
+ function __sbD1PragmaKind(s, i) {
5502
+ if ((s.charCodeAt(i) | 32) !== 112 || s.slice(i, i + 6).toLowerCase() !== "pragma" || __sbD1IsIdent(s.charCodeAt(i + 6))) return 0;
5503
+ let at = __sbD1SkipBlank(s, i + 6, false);
5504
+ let name = __sbD1Name(s, at);
5505
+ at = __sbD1SkipBlank(s, name.end, false);
5506
+ if (s.charCodeAt(at) === 46) {
5507
+ // schema.name: SQLite itself answers for a schema that does not exist
5508
+ const schema = name.text.toLowerCase();
5509
+ if (schema !== "main") return schema === "temp" ? 1 : 0;
5510
+ name = __sbD1Name(s, __sbD1SkipBlank(s, at + 1, false));
5511
+ at = __sbD1SkipBlank(s, name.end, false);
5512
+ }
5513
+ if (__sbD1PragmaAllowed.indexOf(" " + name.text + " ") < 0) return 1;
5514
+ const next = s.charCodeAt(at);
5515
+ return name.text === "foreign_keys" && (next === 61 || next === 40) ? 2 : 0;
5516
+ }
5517
+ // A query or batch statement (its first character is P, - ; or /): only its first statement is looked
5518
+ // at. Returns 2 for a foreign_keys assignment.
5519
+ function __sbD1CheckPragma(text) {
5520
+ const at = __sbD1SkipBlank(text, 0, true);
5521
+ if (at >= text.length) throw __sbD1Fail("D1_ERROR: SQL code did not contain a statement.");
5522
+ const kind = __sbD1PragmaKind(text, at);
5523
+ if (kind === 1) throw __sbD1Fail(__sbD1NotAuthorized);
5524
+ return kind;
5525
+ }
5526
+ // An exec line can hold several statements: look at the start of each. A ';' inside a quoted name or
5527
+ // string or a comment does not end one.
5528
+ function __sbD1LinePragmaDenied(line) {
5529
+ if (line.toLowerCase().indexOf("pragma") < 0) return false;
5530
+ let i = __sbD1SkipBlank(line, 0, true);
5531
+ while (i < line.length) {
5532
+ if (__sbD1PragmaKind(line, i) === 1) return true;
5533
+ let c = line.charCodeAt(i);
5534
+ while (i < line.length && c !== 59) {
5535
+ if (c === 39 || c === 34 || c === 96 || c === 91) {
5536
+ const end = line.indexOf(c === 91 ? "]" : String.fromCharCode(c), i + 1);
5537
+ i = end < 0 ? line.length : end + 1;
5538
+ } else if ((c === 45 && line.charCodeAt(i + 1) === 45) || (c === 47 && line.charCodeAt(i + 1) === 42)) i = __sbD1SkipBlank(line, i, false);
5539
+ else i++;
5540
+ c = line.charCodeAt(i);
5541
+ }
5542
+ i = __sbD1SkipBlank(line, i, true);
5543
+ }
5544
+ return false;
5545
+ }
5423
5546
  function __sbD1Check(sql) {
5424
5547
  const text = String(sql).trim();
5425
5548
  if (!text) throw __sbD1Fail("D1_ERROR: No SQL statements detected.");
5426
5549
  if (__sbD1IsTransaction(text)) throw __sbD1Fail(__sbD1NoTransactions);
5550
+ const first = text.charCodeAt(0);
5551
+ let kind = 0;
5552
+ if (((first | 32) === 112 || first === 45 || first === 47 || first === 59) && __sbD1Parity()) kind = __sbD1CheckPragma(text);
5553
+ // more statements behind the first run too (2026-11-03), each one through the allow-list
5554
+ const semi = text.indexOf(";");
5555
+ if (semi >= 0 && semi < text.length - 1 && __sbD1Parity() && __sbD1LinePragmaDenied(text)) throw __sbD1Fail(__sbD1NotAuthorized);
5556
+ return kind;
5427
5557
  }
5428
5558
  function __sbD1Meta(meta, started) {
5429
5559
  const changes = Number(meta.changes) || 0;
@@ -5475,10 +5605,11 @@ function __sbD1Direct(name, sql, params) {
5475
5605
  // when the caller will hand it out (all() and run()): raw() and first() drop it, and building it
5476
5606
  // reads the clock twice.
5477
5607
  function __sbD1Query(dbName, sql, params, strict, wantMeta) {
5478
- if (strict) __sbD1Check(sql);
5608
+ const foreignKeys = strict ? __sbD1Check(sql) : 0;
5479
5609
  const started = !strict || wantMeta ? Date.now() : 0;
5480
5610
  const r = strict && __sbD1QueryDirect ? __sbD1Direct(dbName, sql, params) : __sbD1Rpc("d1.query", { db: dbName, sql, params }, strict);
5481
5611
  if (!strict) return { results: r.results || [], success: true, meta: r.meta || {} };
5612
+ if (foreignKeys === 2) __sbD1Rpc("d1.query", { db: dbName, sql: "PRAGMA foreign_keys = ON", params: [] }, true);
5482
5613
  const table = __sbD1Table(r);
5483
5614
  return { columns: table.columns, rows: table.rows, meta: wantMeta ? __sbD1Meta(r.meta || {}, started) : null };
5484
5615
  }
@@ -5575,8 +5706,16 @@ function __sbMakeD1(dbName) {
5575
5706
  const all = String(sql).trim().split("\n");
5576
5707
  const lines = [];
5577
5708
  for (let i = 0; i < all.length; i++) if (all[i].trim()) lines.push(all[i]);
5578
- if (!lines.length) throw __sbD1Fail("D1_ERROR: No SQL statements detected.");
5579
- __sbD1Rpc("d1.exec", { db: dbName, sql: lines.join("\n"), lines }, true);
5709
+ const parity = __sbD1Parity();
5710
+ if (!lines.length)
5711
+ throw __sbD1Fail(parity ? "D1_EXEC_ERROR: Error in line 1: : No SQL statements detected." : "D1_ERROR: No SQL statements detected.");
5712
+ if (parity)
5713
+ for (let i = 0; i < lines.length; i++) {
5714
+ const head = "D1_EXEC_ERROR: Error in line " + (i + 1) + ": " + lines[i] + ": ";
5715
+ if (__sbD1SkipBlank(lines[i], 0, true) >= lines[i].length) throw __sbD1Fail(head + "SQL code did not contain a statement.");
5716
+ if (__sbD1LinePragmaDenied(lines[i])) throw __sbD1Fail(head + "not authorized: SQLITE_AUTH");
5717
+ }
5718
+ __sbD1Rpc("d1.exec", parity ? { db: dbName, sql: lines.join("\n"), lines, parity: true } : { db: dbName, sql: lines.join("\n"), lines }, true);
5580
5719
  return { count: lines.length, duration: Date.now() - started };
5581
5720
  }
5582
5721
  const db = {
@@ -6043,6 +6182,13 @@ function __sbAssetResponse(path, r) {
6043
6182
  function __sbQueuesStrict() {
6044
6183
  return __sbCompatDate() >= "2026-10-31";
6045
6184
  }
6185
+ // From 2026-11-05: the v8 codec keeps shared and circular references, array holes and invalid
6186
+ // dates; arguments are checked as workerd checks them; delaySeconds is limited to a day; a
6187
+ // delivery that never came back counts as a retry (to the dead letter queue after max_retries);
6188
+ // a consumer that runs past the lease keeps its messages.
6189
+ function __sbQueuesFixed() {
6190
+ return __sbCompatDate() >= "2026-11-05";
6191
+ }
6046
6192
 
6047
6193
  // binding -> { binding, queue, maxBatchSize?, maxBatchTimeout?, maxRetries?, retryDelay?, deadLetterQueue? }
6048
6194
  var __sbQueueConf = {};
@@ -6081,7 +6227,7 @@ function __sbQueueTypeName(value) {
6081
6227
 
6082
6228
  // A body as the stored text "#sbq1:TYPE:PAYLOAD", and its size as Queues counts it.
6083
6229
  function __sbQueueEncode(body, contentType) {
6084
- const type = contentType === undefined ? "json" : contentType;
6230
+ let type = contentType === undefined ? "json" : contentType;
6085
6231
  let payload;
6086
6232
  let size;
6087
6233
  if (type === "json") {
@@ -6101,13 +6247,18 @@ function __sbQueueEncode(body, contentType) {
6101
6247
  payload = body;
6102
6248
  size = new TextEncoder().encode(body).length;
6103
6249
  } else if (type === "bytes") {
6104
- const bytes = __sbQueueBytesOf(body);
6250
+ const bytes = __sbQueuesFixed() && !ArrayBuffer.isView(body) ? null : __sbQueueBytesOf(body);
6105
6251
  if (bytes === null)
6106
- throw new TypeError('Content Type "bytes" requires a value of type ArrayBuffer, but received: ' + __sbQueueTypeName(body));
6252
+ throw new TypeError(
6253
+ 'Content Type "bytes" requires a value of type ' + (__sbQueuesFixed() ? "ArrayBufferView" : "ArrayBuffer") + ", but received: " + __sbQueueTypeName(body),
6254
+ );
6107
6255
  payload = __sbDOHex(bytes);
6108
6256
  size = bytes.length;
6109
6257
  } else if (type === "v8") {
6110
- payload = JSON.stringify(__sbDOPack(body, []));
6258
+ if (__sbQueuesFixed()) {
6259
+ payload = JSON.stringify(__sbQV8Pack(body, []));
6260
+ type = "v8r";
6261
+ } else payload = JSON.stringify(__sbDOPack(body, []));
6111
6262
  size = new TextEncoder().encode(payload).length;
6112
6263
  } else throw new TypeError("Unsupported queue message content type: " + type);
6113
6264
  if (size > __sbQueueMaxMessage)
@@ -6126,10 +6277,124 @@ function __sbQueueDecode(raw) {
6126
6277
  if (type === "json") return JSON.parse(payload);
6127
6278
  if (type === "bytes") return __sbDOUnhex(payload).buffer;
6128
6279
  if (type === "v8") return __sbDOUnpack(JSON.parse(payload));
6280
+ if (type === "v8r") return __sbQV8Unpack(JSON.parse(payload), []);
6129
6281
  }
6130
6282
  return __sbTryParse(raw);
6131
6283
  }
6132
6284
 
6285
+ /* oxlint-disable anti-slop/no-runtime-typeof -- structured clone branches on the value's type, as the HTML spec's StructuredSerialize does. */
6286
+ // The v8 content type from 2026-11-05: __sbDOPack's JSON, plus references. Every object is
6287
+ // numbered when first met (before its contents); meeting it again is ["@", n], which makes
6288
+ // shared and circular values come back shared and circular. An array is ["A", length, ...] with
6289
+ // ["H"] for a hole; an invalid Date is ["D", "NaN"]. Typed arrays other than Uint8Array and
6290
+ // DataView stay unsupported (see __sbDOView).
6291
+ function __sbQV8Pack(v, objs) {
6292
+ if (v === null || v === true || v === false || __sbIsStr(v)) return v;
6293
+ const t = typeof v;
6294
+ if (t === "number") {
6295
+ if (v === 0 && 1 / v < 0) return ["N", "-0"];
6296
+ return Number.isFinite(v) ? v : ["N", String(v)];
6297
+ }
6298
+ if (t === "bigint") return ["B", String(v)];
6299
+ if (t === "undefined") return ["U"];
6300
+ if (t !== "object") throw __sbDOCloneError(t === "symbol" ? v.toString() : v);
6301
+ const seen = objs.indexOf(v);
6302
+ if (seen !== -1) return ["@", seen];
6303
+ objs.push(v);
6304
+ if (v instanceof Date) {
6305
+ const time = v.getTime();
6306
+ return ["D", time === time ? time : "NaN"];
6307
+ }
6308
+ if (v instanceof Map) {
6309
+ const out = ["M"];
6310
+ v.forEach((x, k) => {
6311
+ out.push(__sbQV8Pack(k, objs), __sbQV8Pack(x, objs));
6312
+ });
6313
+ return out;
6314
+ }
6315
+ if (v instanceof Set) {
6316
+ const out = ["S"];
6317
+ v.forEach((x) => {
6318
+ out.push(__sbQV8Pack(x, objs));
6319
+ });
6320
+ return out;
6321
+ }
6322
+ if (v instanceof ArrayBuffer) return ["AB", __sbDOHex(new Uint8Array(v))];
6323
+ if (ArrayBuffer.isView(v)) {
6324
+ const name = Object.prototype.toString.call(v).slice(8, -1);
6325
+ if (name !== "Uint8Array" && name !== "DataView") {
6326
+ const error = new Error(name + " is not supported in a v8 queue message here yet; send a Uint8Array of its bytes.");
6327
+ error.name = "DataCloneError";
6328
+ throw error;
6329
+ }
6330
+ return ["V", name, __sbDOHex(new Uint8Array(v.buffer, v.byteOffset, v.byteLength))];
6331
+ }
6332
+ if (v instanceof RegExp) return ["R", v.source, v.flags];
6333
+ if (Array.isArray(v)) {
6334
+ const out = ["A", v.length];
6335
+ for (let i = 0; i < v.length; i++) out.push(i in v ? __sbQV8Pack(v[i], objs) : ["H"]);
6336
+ return out;
6337
+ }
6338
+ if (v instanceof Error) return ["E", String(v.name), String(v.message)];
6339
+ const out = ["O"];
6340
+ const keys = Object.keys(v);
6341
+ for (let i = 0; i < keys.length; i++) out.push(keys[i], __sbQV8Pack(v[keys[i]], objs));
6342
+ return out;
6343
+ }
6344
+ /* oxlint-enable anti-slop/no-runtime-typeof */
6345
+
6346
+ function __sbQV8Unpack(p, objs) {
6347
+ if (!Array.isArray(p)) return p;
6348
+ const tag = p[0];
6349
+ if (tag === "@") return objs[p[1]];
6350
+ if (tag === "U") return undefined;
6351
+ if (tag === "N") return p[1] === "-0" ? -0 : Number(p[1]);
6352
+ if (tag === "B") return BigInt(p[1]);
6353
+ let out;
6354
+ if (tag === "D") {
6355
+ out = new Date(p[1] === "NaN" ? NaN : p[1]);
6356
+ objs.push(out);
6357
+ } else if (tag === "AB") {
6358
+ out = __sbDOUnhex(p[1]).buffer;
6359
+ objs.push(out);
6360
+ } else if (tag === "V") {
6361
+ out = __sbDOView(p[1], __sbDOUnhex(p[2]));
6362
+ objs.push(out);
6363
+ } else if (tag === "R") {
6364
+ out = new RegExp(p[1], p[2]);
6365
+ objs.push(out);
6366
+ } else if (tag === "E") {
6367
+ out = p[1] === "TypeError" ? new TypeError(p[2]) : p[1] === "RangeError" ? new RangeError(p[2]) : new Error(p[2]);
6368
+ out.name = p[1];
6369
+ objs.push(out);
6370
+ } else if (tag === "M") {
6371
+ out = new Map();
6372
+ objs.push(out);
6373
+ for (let i = 1; i + 1 < p.length; i += 2) {
6374
+ const k = __sbQV8Unpack(p[i], objs);
6375
+ out.set(k, __sbQV8Unpack(p[i + 1], objs));
6376
+ }
6377
+ } else if (tag === "S") {
6378
+ out = new Set();
6379
+ objs.push(out);
6380
+ for (let i = 1; i < p.length; i++) out.add(__sbQV8Unpack(p[i], objs));
6381
+ } else if (tag === "A") {
6382
+ out = [];
6383
+ objs.push(out);
6384
+ out.length = p[1];
6385
+ for (let i = 2; i < p.length; i++) {
6386
+ const e = p[i];
6387
+ if (Array.isArray(e) && e[0] === "H") continue;
6388
+ out[i - 2] = __sbQV8Unpack(e, objs);
6389
+ }
6390
+ } else {
6391
+ out = {};
6392
+ objs.push(out);
6393
+ for (let i = 1; i + 1 < p.length; i += 2) out[p[i]] = __sbQV8Unpack(p[i + 1], objs);
6394
+ }
6395
+ return out;
6396
+ }
6397
+
6133
6398
  function __sbQueueMetrics(metrics) {
6134
6399
  const m = metrics || {};
6135
6400
  const oldest = Number(m.oldestMessageTimestamp) || 0;
@@ -6144,12 +6409,30 @@ function __sbQueueMetrics(metrics) {
6144
6409
  function __sbQueueDelay(value) {
6145
6410
  return value == null ? 0 : Math.max(0, Number(value) || 0);
6146
6411
  }
6412
+ // From 2026-11-05 a delay outside 0 to 86400 s (a day) is refused, not clamped or kept.
6413
+ function __sbQueueCheckDelay(value) {
6414
+ if (!__sbQueuesFixed() || value == null) return;
6415
+ const n = Number(value);
6416
+ if (n < 0 || n > 86400) throw new Error("Queue send failed: delaySeconds must be between 0 and 86400, got " + String(value));
6417
+ }
6418
+ // workerd's check that an options argument is a dictionary (null and undefined are fine).
6419
+ function __sbQueueCheckOptions(options, name, position, type) {
6420
+ if (!__sbQueuesFixed() || options == null) return;
6421
+ if (__sbQueueTypeName(options) !== "object")
6422
+ throw new TypeError("Failed to execute '" + name + "' on 'WorkerQueue': parameter " + position + " is not of type '" + type + "'.");
6423
+ }
6147
6424
 
6148
6425
  function __sbMakeQueue(binding) {
6149
6426
  return {
6150
- send(body, options) {
6427
+ send(...args) {
6151
6428
  try {
6429
+ const body = args[0];
6430
+ const options = args[1];
6431
+ if (args.length === 0 && __sbQueuesFixed())
6432
+ throw new TypeError("Failed to execute 'send' on 'WorkerQueue': parameter 1 is not of type 'JsValue'.");
6433
+ __sbQueueCheckOptions(options, "send", 2, "SendOptions");
6152
6434
  const o = options || {};
6435
+ __sbQueueCheckDelay(o.delaySeconds);
6153
6436
  const encoded = __sbQueueEncode(body, o.contentType);
6154
6437
  const reply = __sbRpc("queue.send", {
6155
6438
  queue: binding,
@@ -6166,11 +6449,16 @@ function __sbMakeQueue(binding) {
6166
6449
  try {
6167
6450
  if (messages == null || !__sbIsFn(messages[Symbol.iterator]))
6168
6451
  throw new TypeError("Failed to execute 'sendBatch' on 'WorkerQueue': parameter 1 is not of type 'Sequence'.");
6452
+ __sbQueueCheckOptions(options, "sendBatch", 2, "SendBatchOptions");
6453
+ __sbQueueCheckDelay(options && options.delaySeconds);
6169
6454
  const batchDelay = __sbQueueDelay(options && options.delaySeconds);
6170
6455
  const list = [];
6171
6456
  let total = 0;
6172
6457
  for (const m of messages) {
6458
+ if (__sbQueuesFixed() && (m == null || __sbQueueTypeName(m) !== "object"))
6459
+ throw new TypeError("Incorrect type: the provided value is not of type 'MessageSendRequest'.");
6173
6460
  const item = m || {};
6461
+ __sbQueueCheckDelay(item.delaySeconds);
6174
6462
  const encoded = __sbQueueEncode(item.body, item.contentType);
6175
6463
  total += encoded.size;
6176
6464
  // A message delay of 0 (or none) takes the batch delay, as miniflare does.
@@ -7068,6 +7356,11 @@ function __sbDOv2() {
7068
7356
  return __sbCompatDate() >= "2026-10-29";
7069
7357
  }
7070
7358
 
7359
+ // Storage, SQL, id and alarm behaviours that follow workerd more closely, from 2026-11-04.
7360
+ function __sbDOv3() {
7361
+ return __sbCompatDate() >= "2026-11-04";
7362
+ }
7363
+
7071
7364
  function __sbDOSha(text) {
7072
7365
  return __sbR2HexOf(__sbDigestRaw("256", __sbToBytes(text)));
7073
7366
  }
@@ -7077,7 +7370,27 @@ function __sbDOSeal(cls, body) {
7077
7370
  return body + __sbDOSha("sbdo-id:" + cls + ":" + body).slice(0, 16);
7078
7371
  }
7079
7372
 
7373
+ // workerd's DurableObjectId (2026-11-04): a class, so constructor.name and instanceof agree.
7374
+ if (globalThis.DurableObjectId == null) {
7375
+ class DurableObjectId {
7376
+ constructor(hex, name) {
7377
+ this.name = name;
7378
+ this.__sbHex = hex;
7379
+ }
7380
+ toString() {
7381
+ return this.__sbHex;
7382
+ }
7383
+ equals(other) {
7384
+ if (!(other instanceof DurableObjectId))
7385
+ throw new TypeError("Failed to execute 'equals' on 'DurableObjectId': parameter 1 is not of type 'DurableObjectId'.");
7386
+ return other.__sbHex === this.__sbHex;
7387
+ }
7388
+ }
7389
+ globalThis.DurableObjectId = DurableObjectId;
7390
+ }
7391
+
7080
7392
  function __sbDOMakeId(hex, name) {
7393
+ if (__sbDOv3()) return new globalThis.DurableObjectId(hex, name);
7081
7394
  return {
7082
7395
  name,
7083
7396
  toString() {
@@ -7123,6 +7436,8 @@ function __sbMakeDONamespaceV2(className) {
7123
7436
  return __sbDOMakeId(__sbDOSeal(className, __sbDOHex(crypto.getRandomValues(new Uint8Array(24)))), undefined);
7124
7437
  },
7125
7438
  get(id) {
7439
+ if (__sbDOv3() && !(id instanceof globalThis.DurableObjectId))
7440
+ throw new TypeError("Failed to execute 'get' on 'DurableObjectNamespace': parameter 1 is not of type 'DurableObjectId'.");
7126
7441
  return __sbDOStub(className, __sbIsStr(id) ? ns.idFromString(id) : id);
7127
7442
  },
7128
7443
  getByName(name) {
@@ -7272,13 +7587,19 @@ function __sbGetDOInstance(cls, idText, idObj) {
7272
7587
  const __sbTasks = [];
7273
7588
  const gate = __sbDOMakeGate();
7274
7589
  const state = {
7275
- id: stateId || {
7276
- toString() {
7277
- return id;
7278
- },
7279
- },
7590
+ id:
7591
+ stateId ||
7592
+ (__sbDOv3()
7593
+ ? __sbDOMakeId(id, undefined)
7594
+ : {
7595
+ toString() {
7596
+ return id;
7597
+ },
7598
+ }),
7280
7599
  storage: __sbDOGated() ? __sbDOAsyncStorage(cls, id, gate) : __sbDOStorage(cls, id),
7281
7600
  blockConcurrencyWhile(fn) {
7601
+ if (__sbDOv3() && !__sbIsFn(fn))
7602
+ throw new TypeError("Failed to execute 'blockConcurrencyWhile' on 'DurableObjectState': parameter 1 is not of type 'function'.");
7282
7603
  if (!__sbDOGated()) return fn();
7283
7604
  // No event enters until fn settles (2026-10-28).
7284
7605
  gate.locks++;
@@ -7308,6 +7629,15 @@ function __sbGetDOInstance(cls, idText, idObj) {
7308
7629
  if (p && __sbIsFn(p.then)) __sbTasks.push(p);
7309
7630
  },
7310
7631
  };
7632
+ if (__sbDOv3()) {
7633
+ state.props = {};
7634
+ // Forgets this object's memory (the next request builds it afresh) and fails the event with
7635
+ // the reason, as workerd's abort() does. Storage is kept.
7636
+ state.abort = (reason) => {
7637
+ __sbDOInstances[cacheKey] = undefined;
7638
+ throw new Error(reason == null ? "Durable Object was aborted." : String(reason));
7639
+ };
7640
+ }
7311
7641
  // The hibernation API, from 2026-10-30 (where WebSocketPair exists).
7312
7642
  const holder = { inst: null, auto: null, cls, id, restored: [] };
7313
7643
  if (__sbWsEnabled()) __sbDOWebSocketState(state, holder);
@@ -7409,7 +7739,7 @@ function __sbDOGated() {
7409
7739
  }
7410
7740
 
7411
7741
  function __sbDOMakeGate() {
7412
- return { locks: 0, waiting: [] };
7742
+ return { locks: 0, waiting: [], txnBusy: false, txnQueue: [] };
7413
7743
  }
7414
7744
 
7415
7745
  // Deliver queued events while the gate is open. A delivered event that reads storage shuts
@@ -7448,6 +7778,35 @@ function __sbDOEnter(gate, run) {
7448
7778
  });
7449
7779
  }
7450
7780
 
7781
+ // alarm(alarmInfo) from compatibility date 2026-11-04: { retryCount, isRetry }, as workerd
7782
+ // passes it. retryCount counts the runs of this alarm that threw or rejected. The three hosts
7783
+ // that deliver alarms (the broker, the embedded timer, sproutd) all end up in this prelude, so
7784
+ // the count lives here, on the object's instance and per scheduled time; it starts over when the alarm is set
7785
+ // for a new time or a run succeeds, and a host that knows its own count passes it as
7786
+ // attempts. A prelude that restarts between two runs counts from zero again.
7787
+ // (kept on the instance, as __sbAlarmAt and __sbAlarmFails)
7788
+ function __sbDOAlarmCall(inst, cls, id, at, attempts) {
7789
+ if (__sbCompatDate() < "2026-11-04") return inst.alarm();
7790
+ const when = Number(at) || 0;
7791
+ if (inst.__sbAlarmAt !== when) {
7792
+ inst.__sbAlarmAt = when;
7793
+ inst.__sbAlarmFails = 0;
7794
+ }
7795
+ const retryCount = attempts != null && Number(attempts) > 0 ? Number(attempts) : inst.__sbAlarmFails;
7796
+ return __sbDOAlarmRun(inst, { retryCount, isRetry: retryCount > 0 });
7797
+ }
7798
+
7799
+ async function __sbDOAlarmRun(inst, info) {
7800
+ try {
7801
+ const res = inst.alarm(info);
7802
+ if (res && __sbIsFn(res.then)) await res;
7803
+ } catch (error) {
7804
+ inst.__sbAlarmFails = inst.__sbAlarmFails + 1;
7805
+ throw error;
7806
+ }
7807
+ inst.__sbAlarmFails = 0;
7808
+ }
7809
+
7451
7810
  const __sbDOTag = "#sb1:";
7452
7811
 
7453
7812
  function __sbDOCloneError(value) {
@@ -7486,7 +7845,10 @@ function __sbDOView(name, bytes) {
7486
7845
  // else is an array whose first element names its type, so no plain value is ambiguous.
7487
7846
  /* oxlint-disable anti-slop/no-runtime-typeof -- structured clone branches on the value's type, as the HTML spec's StructuredSerialize does. */
7488
7847
  // fns (RPC only): functions go there, packed as ["F", index]; storage passes none.
7489
- function __sbDOPack(v, seen, fns) {
7848
+ // refs (storage, 2026-11-04): an object met again is ["X", n], n its place in first-seen order, so
7849
+ // shared and circular references survive as structured clone keeps them; without it a circular
7850
+ // value throws and a shared one is copied.
7851
+ function __sbDOPack(v, seen, fns, refs) {
7490
7852
  if (v === null || v === true || v === false || __sbIsStr(v)) return v;
7491
7853
  const t = typeof v;
7492
7854
  if (t === "number") {
@@ -7501,19 +7863,23 @@ function __sbDOPack(v, seen, fns) {
7501
7863
  }
7502
7864
  if (t === "symbol") throw __sbDOCloneError(v.toString());
7503
7865
  if (t !== "object") throw __sbDOCloneError(v);
7504
- if (seen.indexOf(v) !== -1) throw __sbDOCloneError("a circular value");
7866
+ const was = seen.indexOf(v);
7867
+ if (was !== -1) {
7868
+ if (refs) return ["X", was];
7869
+ throw __sbDOCloneError("a circular value");
7870
+ }
7505
7871
  seen.push(v);
7506
7872
  let out;
7507
- if (v instanceof Date) out = ["D", v.getTime()];
7873
+ if (v instanceof Date) out = ["D", refs && Number.isNaN(v.getTime()) ? "NaN" : v.getTime()];
7508
7874
  else if (v instanceof Map) {
7509
7875
  out = ["M"];
7510
7876
  v.forEach((x, k) => {
7511
- out.push(__sbDOPack(k, seen, fns), __sbDOPack(x, seen, fns));
7877
+ out.push(__sbDOPack(k, seen, fns, refs), __sbDOPack(x, seen, fns, refs));
7512
7878
  });
7513
7879
  } else if (v instanceof Set) {
7514
7880
  out = ["S"];
7515
7881
  v.forEach((x) => {
7516
- out.push(__sbDOPack(x, seen, fns));
7882
+ out.push(__sbDOPack(x, seen, fns, refs));
7517
7883
  });
7518
7884
  } else if (v instanceof ArrayBuffer) out = ["AB", __sbDOHex(new Uint8Array(v))];
7519
7885
  else if (ArrayBuffer.isView(v)) {
@@ -7528,21 +7894,38 @@ function __sbDOPack(v, seen, fns) {
7528
7894
  else if (v instanceof RegExp) out = ["R", v.source, v.flags];
7529
7895
  else if (Array.isArray(v)) {
7530
7896
  out = ["A"];
7531
- for (let i = 0; i < v.length; i++) out.push(__sbDOPack(v[i], seen, fns));
7897
+ for (let i = 0; i < v.length; i++) out.push(__sbDOPack(v[i], seen, fns, refs));
7532
7898
  } else if (v instanceof Error) out = ["E", String(v.name), String(v.message)];
7533
7899
  else {
7534
7900
  out = ["O"];
7535
7901
  const keys = Object.keys(v);
7536
- for (let i = 0; i < keys.length; i++) out.push(keys[i], __sbDOPack(v[keys[i]], seen, fns));
7902
+ for (let i = 0; i < keys.length; i++) out.push(keys[i], __sbDOPack(v[keys[i]], seen, fns, refs));
7537
7903
  }
7538
- seen.pop();
7904
+ if (!refs) seen.pop();
7539
7905
  return out;
7540
7906
  }
7541
7907
  /* oxlint-enable anti-slop/no-runtime-typeof */
7542
7908
 
7543
- function __sbDOUnpack(p, fns) {
7909
+ // An error of the type its name says, for the names workerd's structured clone keeps.
7910
+ function __sbDOMakeError(name, message) {
7911
+ if (__sbDOv3()) {
7912
+ if (name === "TypeError") return new TypeError(message);
7913
+ if (name === "RangeError") return new RangeError(message);
7914
+ if (name === "SyntaxError") return new SyntaxError(message);
7915
+ if (name === "ReferenceError") return new ReferenceError(message);
7916
+ if (name === "EvalError") return new EvalError(message);
7917
+ if (name === "URIError") return new URIError(message);
7918
+ }
7919
+ const e = new Error(message);
7920
+ e.name = name;
7921
+ return e;
7922
+ }
7923
+
7924
+ // refs: the objects made so far in first-seen order (see __sbDOPack); undefined for RPC.
7925
+ function __sbDOUnpack(p, fns, refs) {
7544
7926
  if (!Array.isArray(p)) return p;
7545
7927
  const tag = p[0];
7928
+ if (tag === "X") return refs[p[1]];
7546
7929
  if (tag === "U") return undefined;
7547
7930
  if (tag === "F" && fns) {
7548
7931
  const fn = fns[p[1]];
@@ -7550,49 +7933,82 @@ function __sbDOUnpack(p, fns) {
7550
7933
  }
7551
7934
  if (tag === "N") return p[1] === "-0" ? -0 : Number(p[1]);
7552
7935
  if (tag === "B") return BigInt(p[1]);
7553
- if (tag === "D") return new Date(p[1]);
7554
- if (tag === "AB") return __sbDOUnhex(p[1]).buffer;
7555
- if (tag === "V") return __sbDOView(p[1], __sbDOUnhex(p[2]));
7556
- if (tag === "R") return new RegExp(p[1], p[2]);
7936
+ if (tag === "D") {
7937
+ const d = new Date(p[1] === "NaN" ? NaN : p[1]);
7938
+ if (refs) refs.push(d);
7939
+ return d;
7940
+ }
7941
+ if (tag === "AB") {
7942
+ const b = __sbDOUnhex(p[1]).buffer;
7943
+ if (refs) refs.push(b);
7944
+ return b;
7945
+ }
7946
+ if (tag === "V") {
7947
+ const view = __sbDOView(p[1], __sbDOUnhex(p[2]));
7948
+ if (refs) refs.push(view);
7949
+ return view;
7950
+ }
7951
+ if (tag === "R") {
7952
+ const r = new RegExp(p[1], p[2]);
7953
+ if (refs) refs.push(r);
7954
+ return r;
7955
+ }
7557
7956
  if (tag === "E") {
7558
- const e = new Error(p[2]);
7559
- e.name = p[1];
7957
+ const e = __sbDOMakeError(p[1], p[2]);
7958
+ if (refs) refs.push(e);
7560
7959
  return e;
7561
7960
  }
7562
7961
  if (tag === "M") {
7563
7962
  const m = new Map();
7564
- for (let i = 1; i + 1 < p.length; i += 2) m.set(__sbDOUnpack(p[i], fns), __sbDOUnpack(p[i + 1], fns));
7963
+ if (refs) refs.push(m);
7964
+ for (let i = 1; i + 1 < p.length; i += 2) m.set(__sbDOUnpack(p[i], fns, refs), __sbDOUnpack(p[i + 1], fns, refs));
7565
7965
  return m;
7566
7966
  }
7567
7967
  if (tag === "S") {
7568
7968
  const s = new Set();
7569
- for (let i = 1; i < p.length; i++) s.add(__sbDOUnpack(p[i], fns));
7969
+ if (refs) refs.push(s);
7970
+ for (let i = 1; i < p.length; i++) s.add(__sbDOUnpack(p[i], fns, refs));
7570
7971
  return s;
7571
7972
  }
7572
7973
  if (tag === "A") {
7573
7974
  const a = [];
7574
- for (let i = 1; i < p.length; i++) a.push(__sbDOUnpack(p[i], fns));
7975
+ if (refs) refs.push(a);
7976
+ for (let i = 1; i < p.length; i++) a.push(__sbDOUnpack(p[i], fns, refs));
7575
7977
  return a;
7576
7978
  }
7577
7979
  const o = {};
7578
- for (let i = 1; i + 1 < p.length; i += 2) o[p[i]] = __sbDOUnpack(p[i + 1], fns);
7980
+ if (refs) refs.push(o);
7981
+ for (let i = 1; i + 1 < p.length; i += 2) o[p[i]] = __sbDOUnpack(p[i + 1], fns, refs);
7579
7982
  return o;
7580
7983
  }
7581
7984
 
7582
7985
  function __sbDOEncode(value) {
7583
7986
  if (value === undefined) throw new TypeError("put() called with undefined value.");
7584
- return __sbDOTag + JSON.stringify(__sbDOPack(value, []));
7987
+ return __sbDOTag + JSON.stringify(__sbDOPack(value, [], undefined, __sbDOv3()));
7585
7988
  }
7586
7989
 
7587
7990
  // Reads both formats at every date: tagged (2026-10-28 writes) and plain JSON (earlier ones).
7588
7991
  function __sbDODecode(text) {
7589
- if (__sbIsStr(text) && text.indexOf(__sbDOTag) === 0) return __sbDOUnpack(JSON.parse(text.slice(__sbDOTag.length)));
7992
+ if (__sbIsStr(text) && text.indexOf(__sbDOTag) === 0) return __sbDOUnpack(JSON.parse(text.slice(__sbDOTag.length)), undefined, []);
7590
7993
  return JSON.parse(text);
7591
7994
  }
7592
7995
 
7593
7996
  function __sbDOListLimit(options) {
7594
7997
  const o = options || {};
7595
7998
  if (o.limit != null && !(Number(o.limit) > 0)) throw new TypeError("List limit must be positive.");
7999
+ if (__sbDOv3()) {
8000
+ if (o.start != null && o.startAfter != null) throw new TypeError("list() cannot be called with both start and startAfter values.");
8001
+ // A fractional limit counts whole entries (the datatype of a SQL LIMIT must be an integer).
8002
+ if (o.limit != null)
8003
+ return {
8004
+ prefix: o.prefix,
8005
+ start: o.start,
8006
+ startAfter: o.startAfter,
8007
+ end: o.end,
8008
+ reverse: o.reverse,
8009
+ limit: Math.max(1, Math.trunc(Number(o.limit))),
8010
+ };
8011
+ }
7596
8012
  return o;
7597
8013
  }
7598
8014
 
@@ -7604,6 +8020,31 @@ function __sbDOInRange(key, o) {
7604
8020
  return true;
7605
8021
  }
7606
8022
 
8023
+ // The keys of a multi-key get, in key order and without repeats.
8024
+ function __sbDOSortedKeys(keys) {
8025
+ const out = [];
8026
+ for (let i = 0; i < keys.length; i++) {
8027
+ const k = String(keys[i]);
8028
+ if (out.indexOf(k) === -1) out.push(k);
8029
+ }
8030
+ out.sort();
8031
+ return out;
8032
+ }
8033
+
8034
+ // put(key, value) and put({ key: value }) as [key, encoded] pairs (2026-11-04). An array or
8035
+ // undefined is no key; an entry whose value is undefined is skipped in the object form.
8036
+ function __sbDOPutPairs(key, value) {
8037
+ if (key === undefined)
8038
+ throw new TypeError("Failed to execute 'put' on 'DurableObjectStorage': parameter 1 is not of type 'string or object'.");
8039
+ const pairs = [];
8040
+ if (key !== null && __sbIsObj(key) && !Array.isArray(key)) {
8041
+ // Encode every value before writing any, so one bad value writes nothing.
8042
+ const keys = Object.keys(key);
8043
+ for (let i = 0; i < keys.length; i++) if (key[keys[i]] !== undefined) pairs.push([keys[i], __sbDOEncode(key[keys[i]])]);
8044
+ } else pairs.push([String(key), __sbDOEncode(value)]);
8045
+ return pairs;
8046
+ }
8047
+
7607
8048
  // The synchronous core of the 2026-10-28 storage: structured values, list() ranges.
7608
8049
  function __sbDOStrictStorage(cls, id) {
7609
8050
  const get = (key) => {
@@ -7618,13 +8059,20 @@ function __sbDOStrictStorage(cls, id) {
7618
8059
  get(key) {
7619
8060
  if (!Array.isArray(key)) return get(key);
7620
8061
  const out = new Map();
7621
- for (let i = 0; i < key.length; i++) {
7622
- const v = get(key[i]);
7623
- if (v !== undefined) out.set(key[i], v);
8062
+ // workerd answers in key order, whatever the order asked.
8063
+ const asked = __sbDOv3() ? __sbDOSortedKeys(key) : key;
8064
+ for (let i = 0; i < asked.length; i++) {
8065
+ const v = get(asked[i]);
8066
+ if (v !== undefined) out.set(asked[i], v);
7624
8067
  }
7625
8068
  return out;
7626
8069
  },
7627
8070
  put(key, value) {
8071
+ if (__sbDOv3()) {
8072
+ const pairs = __sbDOPutPairs(key, value);
8073
+ for (let i = 0; i < pairs.length; i++) __sbRpc("do.storage.put", { cls, id, key: pairs[i][0], value: pairs[i][1] });
8074
+ return;
8075
+ }
7628
8076
  if (key != null && __sbIsObj(key)) {
7629
8077
  // Encode every value before writing any, so one bad value writes nothing.
7630
8078
  const keys = Object.keys(key);
@@ -7648,6 +8096,15 @@ function __sbDOStrictStorage(cls, id) {
7648
8096
  },
7649
8097
  setAlarm(when) {
7650
8098
  const at = when instanceof Date ? when.getTime() : when;
8099
+ if (__sbDOv3()) {
8100
+ const inst = __sbDOInstances[cls + " " + id];
8101
+ if (inst && !__sbIsFn(inst.alarm))
8102
+ throw new TypeError("Your Durable Object class must have an alarm() handler in order to call setAlarm()");
8103
+ if (when instanceof Date || Object(when) instanceof Number) {
8104
+ if (!Number.isFinite(at)) throw new TypeError("The value cannot be converted because it is not a valid Date.");
8105
+ if (at <= 0) throw new TypeError("setAlarm() cannot be called with an alarm time <= 0");
8106
+ }
8107
+ }
7651
8108
  if (!Number.isFinite(at))
7652
8109
  throw new TypeError("Failed to execute 'setAlarm' on 'DurableObjectStorage': parameter 1 is not of type 'date'.");
7653
8110
  __sbRpc("do.alarm.set", { cls, id, at: Number(at) });
@@ -7711,6 +8168,28 @@ function __sbDOAsyncStorage(cls, id, gate) {
7711
8168
  const sql = __sbDOSql(cls, id);
7712
8169
  storage.sql = sql;
7713
8170
  storage.transactionSync = (fn) => sql.__sbTransaction(fn);
8171
+ if (__sbDOv3()) {
8172
+ // deleteAll() on a SQLite-backed object drops its tables too.
8173
+ storage.deleteAll = () =>
8174
+ settle(() => {
8175
+ s.deleteAll();
8176
+ sql.__sbDropAll();
8177
+ });
8178
+ // The synchronous key-value API, over the same keys as get() and put().
8179
+ storage.kv = {
8180
+ get: (key) => s.get(key),
8181
+ put: (key, value) => {
8182
+ s.put(key, value);
8183
+ },
8184
+ delete: (key) => s.delete(key),
8185
+ list: (options) => s.list(options).entries(),
8186
+ };
8187
+ // Point-in-time recovery needs the storage engine's log, which this one does not keep.
8188
+ const pitr = () => Promise.reject(new Error("Point-in-time recovery is not supported by this Durable Object storage."));
8189
+ storage.getCurrentBookmark = pitr;
8190
+ storage.getBookmarkForTime = pitr;
8191
+ storage.onNextSessionRestoreBookmark = pitr;
8192
+ }
7714
8193
  }
7715
8194
  return storage;
7716
8195
  }
@@ -7749,6 +8228,135 @@ function __sbDOSqlVerb(q) {
7749
8228
  return q.slice(i, j).toUpperCase();
7750
8229
  }
7751
8230
 
8231
+ // exec() with several statements (compatibility date 2026-11-04). Splits the text at the
8232
+ // semicolons SQLite would, past quoted names and strings, comments, and the BEGIN ... END body
8233
+ // of CREATE TRIGGER. Statements with nothing in them but spaces and comments are dropped.
8234
+ // Returns { texts, params }: the statements' text, and for each 1 when it has a ? or a
8235
+ // :name, @name or $name outside quotes (which only the last statement may have), else 0.
8236
+ function __sbDOSqlSplit(q) {
8237
+ const texts = [];
8238
+ const params = [];
8239
+ const n = q.length;
8240
+ let start = 0;
8241
+ let i = 0;
8242
+ let words = 0; // keywords seen at this statement's top level, to spot CREATE TRIGGER
8243
+ let trigger = false;
8244
+ let inBody = false;
8245
+ let cases = 0;
8246
+ let param = false;
8247
+ let content = false;
8248
+ while (i <= n) {
8249
+ const c = i < n ? q.charCodeAt(i) : 59;
8250
+ if (c === 45 && q.charCodeAt(i + 1) === 45) {
8251
+ const nl = q.indexOf("\n", i);
8252
+ i = nl < 0 ? n : nl + 1;
8253
+ } else if (c === 47 && q.charCodeAt(i + 1) === 42) {
8254
+ const end = q.indexOf("*/", i + 2);
8255
+ i = end < 0 ? n : end + 2;
8256
+ } else if (c === 39 || c === 34 || c === 96 || c === 91) {
8257
+ // 'string', "name", `name`, [name]: a doubled quote stays inside (not for [name]).
8258
+ const close = c === 91 ? 93 : c;
8259
+ content = true;
8260
+ i++;
8261
+ for (;;) {
8262
+ if (i >= n) break;
8263
+ if (q.charCodeAt(i) === close) {
8264
+ if (close !== 93 && q.charCodeAt(i + 1) === close) i += 2;
8265
+ else {
8266
+ i++;
8267
+ break;
8268
+ }
8269
+ } else i++;
8270
+ }
8271
+ } else if (c === 63 || c === 58 || c === 64 || c === 36) {
8272
+ param = true;
8273
+ content = true;
8274
+ i++;
8275
+ } else if ((c | 32) >= 97 && (c | 32) <= 122) {
8276
+ let j = i;
8277
+ while (j < n) {
8278
+ const d = q.charCodeAt(j);
8279
+ if (((d | 32) >= 97 && (d | 32) <= 122) || (d >= 48 && d <= 57) || d === 95 || d === 36) j++;
8280
+ else break;
8281
+ }
8282
+ const word = q.slice(i, j).toUpperCase();
8283
+ content = true;
8284
+ if (words < 4 && !inBody) {
8285
+ words++;
8286
+ if (word === "TRIGGER") trigger = true;
8287
+ }
8288
+ if (trigger && word === "BEGIN") inBody = true;
8289
+ else if (inBody && word === "CASE") cases++;
8290
+ else if (inBody && word === "END") {
8291
+ if (cases > 0) cases--;
8292
+ else inBody = false;
8293
+ }
8294
+ i = j;
8295
+ } else if (c === 59 && !inBody) {
8296
+ if (content) {
8297
+ texts.push(__sbDOSqlTrimStart(q.slice(start, i)));
8298
+ params.push(param ? 1 : 0);
8299
+ }
8300
+ start = i + 1;
8301
+ i++;
8302
+ words = 0;
8303
+ trigger = false;
8304
+ cases = 0;
8305
+ param = false;
8306
+ content = false;
8307
+ } else {
8308
+ if (c > 32 && c !== 59) content = true;
8309
+ i++;
8310
+ }
8311
+ }
8312
+ if (content) {
8313
+ texts.push(__sbDOSqlTrimStart(q.slice(start)));
8314
+ params.push(param ? 1 : 0);
8315
+ }
8316
+ return { texts, params };
8317
+ }
8318
+
8319
+ function __sbDOSqlTrimStart(text) {
8320
+ let i = 0;
8321
+ while (i < text.length && text.charCodeAt(i) <= 32) i++;
8322
+ return i === 0 ? text : text.slice(i);
8323
+ }
8324
+
8325
+ // A binding as workerd's sql.exec() takes it (2026-11-04): null and undefined are NULL, strings and
8326
+ // numbers themselves (NaN is NULL), binary data a BLOB, a BigInt is refused, and anything else
8327
+ // (booleans, Dates, objects, arrays) is its String(): true binds the text "true".
8328
+ function __sbDOSqlParam(v) {
8329
+ if (v == null) return null;
8330
+ if (__sbIsStr(v)) return v;
8331
+ if (v instanceof ArrayBuffer || ArrayBuffer.isView(v)) return __sbD1Param(v, false);
8332
+ if (Object(v) instanceof Number) return v !== v ? null : v;
8333
+ /* oxlint-disable-next-line anti-slop/no-runtime-typeof -- a BigInt binding has its own error */
8334
+ if (typeof v === "bigint") throw new TypeError("Cannot convert a BigInt value to a number");
8335
+ return String(v);
8336
+ }
8337
+
8338
+ const __sbDOSqlPragmas =
8339
+ " foreign_keys table_list table_info table_xinfo index_list index_info index_xinfo foreign_key_list data_version " +
8340
+ "defer_foreign_keys quick_check optimize case_sensitive_like recursive_triggers reverse_unordered_selects ";
8341
+
8342
+ // What workerd's SQLite authorizer refuses (2026-11-04): other PRAGMAs, ATTACH, temporary tables,
8343
+ // VACUUM, and the functions that report the build. Throws the error workerd gives.
8344
+ function __sbDOSqlPolicy(text) {
8345
+ const verb = __sbDOSqlVerb(text);
8346
+ if (verb === "PRAGMA") {
8347
+ const m = /pragma\s+(?:\w+\s*\.\s*)?(\w+)/i.exec(text);
8348
+ if (!m || __sbDOSqlPragmas.indexOf(" " + m[1].toLowerCase() + " ") === -1) throw new Error("not authorized: SQLITE_AUTH");
8349
+ } else if (verb === "ATTACH" || verb === "DETACH") throw new Error("not authorized: SQLITE_AUTH");
8350
+ else if (verb === "VACUUM") throw new Error("cannot VACUUM from within a transaction: SQLITE_ERROR");
8351
+ else if (verb === "CREATE" && /^\s*create\s+temp(orary)?\s/i.test(text)) throw new Error("not authorized: SQLITE_AUTH");
8352
+ const f = /\b(sqlite_version|sqlite_source_id|sqlite_compileoption_used|sqlite_compileoption_get|fts5_source_id|load_extension)\s*\(/i.exec(text);
8353
+ if (f) {
8354
+ const name = f[1].toLowerCase();
8355
+ if (name === "load_extension") throw new Error("not authorized: SQLITE_ERROR");
8356
+ throw new Error("not authorized to use function: " + name + " at offset " + f.index + ": SQLITE_ERROR");
8357
+ }
8358
+ }
8359
+
7752
8360
  function __sbDOSql(cls, id) {
7753
8361
  const file = __sbDOIsHex64(id) ? id.toLowerCase() : __sbDOSha("sbdo-file:" + id);
7754
8362
  const run = (sql, params) => {
@@ -7768,9 +8376,50 @@ function __sbDOSql(cls, id) {
7768
8376
  if (verb === "BEGIN" || verb === "COMMIT" || verb === "END" || verb === "ROLLBACK" || verb === "SAVEPOINT" || verb === "RELEASE")
7769
8377
  throw new Error(__sbDOSqlTxnError);
7770
8378
  const params = [];
7771
- for (let i = 0; i < bindings.length; i++) params.push(__sbD1Param(bindings[i], true));
8379
+ const v3 = __sbDOv3();
8380
+ for (let i = 0; i < bindings.length; i++) params.push(v3 ? __sbDOSqlParam(bindings[i]) : __sbD1Param(bindings[i], true));
8381
+ if (v3) {
8382
+ // workerd runs every statement and the cursor is the last one's. Only that one may take
8383
+ // parameters. A statement that fails stops the script; the ones before it stay done.
8384
+ const split = __sbDOSqlSplit(q);
8385
+ const parts = split.texts;
8386
+ const hasParam = split.params;
8387
+ if (parts.length === 0) throw new Error("SQL code did not contain a statement.");
8388
+ for (let i = 0; i < parts.length - 1; i++)
8389
+ if (hasParam[i] === 1) throw new Error("When executing multiple SQL statements in a single call, only the last statement can have parameters.");
8390
+ for (let i = 0; i < parts.length; i++) {
8391
+ const verb = __sbDOSqlVerb(parts[i]);
8392
+ if (verb === "BEGIN" || verb === "COMMIT" || verb === "END" || verb === "ROLLBACK" || verb === "SAVEPOINT" || verb === "RELEASE")
8393
+ throw new Error(__sbDOSqlTxnError);
8394
+ }
8395
+ let last = null;
8396
+ for (let i = 0; i < parts.length; i++) {
8397
+ __sbDOSqlPolicy(parts[i]);
8398
+ last = run(parts[i], i === parts.length - 1 ? params : []);
8399
+ }
8400
+ return __sbDOCursor(last);
8401
+ }
7772
8402
  return __sbDOCursor(run(q, params));
7773
8403
  },
8404
+ // deleteAll() drops every table, view and trigger, as workerd does for a SQLite-backed object.
8405
+ // A table another one refers to can refuse while that one is still there: go round again.
8406
+ __sbDropAll() {
8407
+ const quote = (name) => '"' + String(name).split('"').join('""') + '"';
8408
+ for (let pass = 0; pass < 6; pass++) {
8409
+ const rows = run("SELECT type, name FROM sqlite_master WHERE name NOT LIKE 'sqlite_%' AND type IN ('view', 'trigger', 'table')", []).rows || [];
8410
+ if (rows.length === 0) return;
8411
+ let dropped = 0;
8412
+ for (let i = 0; i < rows.length; i++) {
8413
+ try {
8414
+ run("DROP " + String(rows[i][0]).toUpperCase() + " IF EXISTS " + quote(rows[i][1]), []);
8415
+ dropped++;
8416
+ } catch {
8417
+ /* a shadow table goes with its virtual table; one with a reference goes on a later pass */
8418
+ }
8419
+ }
8420
+ if (dropped === 0) return;
8421
+ }
8422
+ },
7774
8423
  get databaseSize() {
7775
8424
  return Number(run("PRAGMA page_count", []).rows[0][0]) * Number(run("PRAGMA page_size", []).rows[0][0]);
7776
8425
  },
@@ -7805,6 +8454,9 @@ function __sbDOCursor(r) {
7805
8454
  return o;
7806
8455
  };
7807
8456
  const cursor = {
8457
+ get [Symbol.toStringTag]() {
8458
+ return "Cursor";
8459
+ },
7808
8460
  columnNames: columns,
7809
8461
  rowsRead: rows.length,
7810
8462
  rowsWritten: Number(r.meta && r.meta.changes) || 0,
@@ -7839,7 +8491,15 @@ function __sbDOCursor(r) {
7839
8491
  return it;
7840
8492
  },
7841
8493
  [Symbol.iterator]() {
7842
- return cursor;
8494
+ // workerd hands out a new iterator over the same rows (2026-11-04), not the cursor itself.
8495
+ if (!__sbDOv3()) return cursor;
8496
+ const it = {
8497
+ next: () => cursor.next(),
8498
+ [Symbol.iterator]() {
8499
+ return it;
8500
+ },
8501
+ };
8502
+ return it;
7843
8503
  },
7844
8504
  };
7845
8505
  return cursor;
@@ -7849,9 +8509,49 @@ function __sbDOCursor(r) {
7849
8509
  // memory (read back by its own get and list) and are written when fn resolves, unless it
7850
8510
  // threw or called txn.rollback(). The input gate stays shut meanwhile, so no other event
7851
8511
  // sees a half-written transaction.
8512
+ // From 2026-11-04 transactions on one object run one after another, so two that read, then
8513
+ // write the same key (a counter) do not lose an update; workerd gets that by retrying the loser.
8514
+ // A transaction() started inside another one waits for it, so it must not be awaited there.
7852
8515
  function __sbDOTransaction(cls, id, s, gate, fn) {
8516
+ if (!__sbDOv3()) return __sbDOTransactionRun(cls, id, s, gate, fn);
8517
+ if (!gate.txnBusy) {
8518
+ gate.txnBusy = true;
8519
+ return __sbDOTransactionStart(cls, id, s, gate, fn);
8520
+ }
8521
+ // Nothing but the waiting starts are kept on the gate, and they go once they have run.
8522
+ return new Promise((resolve, reject) => {
8523
+ gate.txnQueue.push(() => {
8524
+ __sbDOTransactionStart(cls, id, s, gate, fn).then(resolve, reject);
8525
+ });
8526
+ });
8527
+ }
8528
+
8529
+ function __sbDOTransactionStart(cls, id, s, gate, fn) {
8530
+ const p = __sbDOTransactionRun(cls, id, s, gate, fn);
8531
+ const next = () => {
8532
+ if (gate.txnQueue.length > 0) gate.txnQueue.shift()();
8533
+ else gate.txnBusy = false;
8534
+ };
8535
+ p.then(next, next);
8536
+ return p;
8537
+ }
8538
+
8539
+ function __sbDOTransactionRun(cls, id, s, gate, fn) {
7853
8540
  const writes = new Map(); // key -> encoded value, or null for a delete
7854
8541
  let rolledBack = false;
8542
+ let committed = false;
8543
+ const v3 = __sbDOv3();
8544
+ // From 2026-11-04 workerd refuses a transaction used after txn.rollback() or after its closure
8545
+ // finished; the refusal is a rejection naming the call.
8546
+ const refused = (name) => {
8547
+ if (!v3) return null;
8548
+ if (rolledBack) return new Error("Cannot " + name + "() on rolled back transaction");
8549
+ if (committed)
8550
+ return new Error(
8551
+ "Cannot call " + name + "() on transaction that has already committed: did you move `txn` outside of the closure?",
8552
+ );
8553
+ return null;
8554
+ };
7855
8555
  const getOne = (key) => {
7856
8556
  const k = String(key);
7857
8557
  if (!writes.has(k)) return s.get(k);
@@ -7861,6 +8561,15 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7861
8561
  const putOne = (key, value) => {
7862
8562
  writes.set(String(key), __sbDOEncode(value));
7863
8563
  };
8564
+ const putAny = (key, value) => {
8565
+ if (v3) {
8566
+ const pairs = __sbDOPutPairs(key, value);
8567
+ for (let i = 0; i < pairs.length; i++) writes.set(pairs[i][0], pairs[i][1]);
8568
+ } else if (key != null && __sbIsObj(key)) {
8569
+ const keys = Object.keys(key);
8570
+ for (let i = 0; i < keys.length; i++) putOne(keys[i], key[keys[i]]);
8571
+ } else putOne(key, value);
8572
+ };
7864
8573
  const delOne = (key) => {
7865
8574
  const existed = getOne(key) !== undefined;
7866
8575
  writes.set(String(key), null);
@@ -7868,32 +8577,38 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7868
8577
  };
7869
8578
  const txn = {
7870
8579
  get(key) {
8580
+ const no = refused("get");
8581
+ if (no) return Promise.reject(no);
7871
8582
  if (!Array.isArray(key)) return Promise.resolve(getOne(key));
7872
8583
  const out = new Map();
7873
- for (let i = 0; i < key.length; i++) {
7874
- const v = getOne(key[i]);
7875
- if (v !== undefined) out.set(key[i], v);
8584
+ const asked = v3 ? __sbDOSortedKeys(key) : key;
8585
+ for (let i = 0; i < asked.length; i++) {
8586
+ const v = getOne(asked[i]);
8587
+ if (v !== undefined) out.set(asked[i], v);
7876
8588
  }
7877
8589
  return Promise.resolve(out);
7878
8590
  },
7879
8591
  put(key, value) {
8592
+ const no = refused("put");
8593
+ if (no) return Promise.reject(no);
7880
8594
  try {
7881
- if (key != null && __sbIsObj(key)) {
7882
- const keys = Object.keys(key);
7883
- for (let i = 0; i < keys.length; i++) putOne(keys[i], key[keys[i]]);
7884
- } else putOne(key, value);
8595
+ putAny(key, value);
7885
8596
  return Promise.resolve();
7886
8597
  } catch (error) {
7887
8598
  return Promise.reject(error);
7888
8599
  }
7889
8600
  },
7890
8601
  delete(key) {
8602
+ const no = refused("delete");
8603
+ if (no) return Promise.reject(no);
7891
8604
  if (!Array.isArray(key)) return Promise.resolve(delOne(key));
7892
8605
  let n = 0;
7893
8606
  for (let i = 0; i < key.length; i++) n += delOne(key[i]) ? 1 : 0;
7894
8607
  return Promise.resolve(n);
7895
8608
  },
7896
8609
  list(options) {
8610
+ const no = refused("list");
8611
+ if (no) return Promise.reject(no);
7897
8612
  try {
7898
8613
  const o = __sbDOListLimit(options);
7899
8614
  const merged = s.list({ prefix: o.prefix, start: o.start, startAfter: o.startAfter, end: o.end });
@@ -7917,10 +8632,31 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7917
8632
  rollback() {
7918
8633
  rolledBack = true;
7919
8634
  },
7920
- getAlarm: () => Promise.resolve(s.getAlarm()),
7921
- setAlarm: (when) => Promise.resolve(s.setAlarm(when)),
7922
- deleteAlarm: () => Promise.resolve(s.deleteAlarm()),
8635
+ getAlarm() {
8636
+ const no = refused("getAlarm");
8637
+ if (no) return Promise.reject(no);
8638
+ return Promise.resolve(s.getAlarm());
8639
+ },
8640
+ setAlarm(when) {
8641
+ const no = refused("setAlarm");
8642
+ if (no) return Promise.reject(no);
8643
+ try {
8644
+ return Promise.resolve(s.setAlarm(when));
8645
+ } catch (error) {
8646
+ return Promise.reject(error);
8647
+ }
8648
+ },
8649
+ deleteAlarm() {
8650
+ const no = refused("deleteAlarm");
8651
+ if (no) return Promise.reject(no);
8652
+ return Promise.resolve(s.deleteAlarm());
8653
+ },
7923
8654
  };
8655
+ // workerd has txn.deleteAll() and refuses it; earlier dates have no such method.
8656
+ if (v3)
8657
+ txn.deleteAll = function () {
8658
+ return Promise.reject(new Error("Cannot call deleteAll() within a transaction"));
8659
+ };
7924
8660
  gate.locks++;
7925
8661
  const done = () => {
7926
8662
  gate.locks--;
@@ -7935,6 +8671,7 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7935
8671
  }
7936
8672
  return Promise.resolve(r).then(
7937
8673
  (v) => {
8674
+ committed = true;
7938
8675
  try {
7939
8676
  if (!rolledBack)
7940
8677
  writes.forEach((value, key) => {
@@ -7947,6 +8684,7 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7947
8684
  return v;
7948
8685
  },
7949
8686
  (error) => {
8687
+ rolledBack = true;
7950
8688
  done();
7951
8689
  throw error;
7952
8690
  },
@@ -9412,6 +10150,21 @@ async function __sb204WithDrain(res, tasks) {
9412
10150
  return new Response(null, { status: 204 });
9413
10151
  }
9414
10152
 
10153
+ // The alarm trigger's reply from 2026-11-04: 204 once alarm() and its waitUntil tasks have
10154
+ // settled, 500 if alarm() threw or rejected (the broker then retries it).
10155
+ async function __sbAlarmWithDrain(res, tasks) {
10156
+ let failed = 0;
10157
+ try {
10158
+ if (res && __sbIsFn(res.then)) await res;
10159
+ } catch (error) {
10160
+ failed = 1;
10161
+ __sbLogUncaught("alarm", error);
10162
+ }
10163
+ await __sbDrainWaitUntil(tasks);
10164
+ if (failed === 1) return new Response("alarm failed", { status: 500 });
10165
+ return new Response(null, { status: 204 });
10166
+ }
10167
+
9415
10168
  async function __sbQueueWithDrain(result, tasks) {
9416
10169
  const r = result && __sbIsFn(result.then) ? await result : result;
9417
10170
  await __sbDrainWaitUntil(tasks);
@@ -9706,23 +10459,31 @@ globalThis.__sbEntry = function (handlers, request) {
9706
10459
  // Always a promise now (`__sbRunQueueBatch` is async, to await the
9707
10460
  // handler above), so always tail-call the drain wrapper — same
9708
10461
  // promise-identity reasoning as `__sbFetchWithDrain`.
9709
- return __sbQueueWithDrain(__sbRunQueueBatch(__sbReceiver(handlers, ctx), __sbReadJson(request), ctx), __sbTasks);
10462
+ return __sbQueueWithDrain(__sbRunQueueBatch(__sbReceiver(handlers, ctx), __sbReadJson(request, __sbQueuesFixed()), ctx), __sbTasks);
9710
10463
  }
9711
10464
 
9712
10465
  if (trigger === "alarm") {
9713
10466
  const body = __sbReadJson(request);
9714
- const inst = __sbGetDOInstance(String(body.cls || ""), String(body.id || ""));
10467
+ const __acls = String(body.cls || "");
10468
+ const __aid = String(body.id || "");
10469
+ const inst = __sbGetDOInstance(__acls, __aid);
9715
10470
  if (!__sbIsFn(inst.alarm)) return new Response("no alarm handler", { status: 404 });
10471
+ const __retries = __sbCompatDate() >= "2026-11-04";
9716
10472
  let __ares;
9717
10473
  try {
9718
- __ares = __sbDOGated() ? __sbDOEnter(inst.__sbGate, () => inst.alarm()) : inst.alarm();
10474
+ __ares = __sbDOGated()
10475
+ ? __sbDOEnter(inst.__sbGate, () => __sbDOAlarmCall(inst, __acls, __aid, body.scheduledTime, body.attempts))
10476
+ : __sbDOAlarmCall(inst, __acls, __aid, body.scheduledTime, body.attempts);
9719
10477
  } catch (error) {
9720
10478
  // #179 — same rule as fetch: don't let a throw here crash the process.
9721
10479
  __sbLogUncaught("alarm", error);
10480
+ // From 2026-11-04 a failed alarm answers 500, so the broker retries it as workerd does.
10481
+ if (__retries) return new Response("alarm failed", { status: 500 });
9722
10482
  return new Response(null, { status: 204 });
9723
10483
  }
9724
10484
  // Was fire-and-forget before: an async `alarm()` and any `state.waitUntil()`
9725
10485
  // it queued were both dropped the moment 204 went out. Await both.
10486
+ if (__retries) return __sbAlarmWithDrain(__ares, inst.__sbTasks);
9726
10487
  if (inst.__sbTasks.length || (__ares && __sbIsFn(__ares.then))) return __sb204WithDrain(__ares, inst.__sbTasks);
9727
10488
  return new Response(null, { status: 204 });
9728
10489
  }
@@ -9751,6 +10512,16 @@ async function __sbRunQueueBatch(handlers, body, ctx) {
9751
10512
  const strict = __sbQueuesStrict();
9752
10513
  const binding = String(body.queue || "");
9753
10514
  const settings = __sbQueueSettings(binding);
10515
+ if (__sbQueuesFixed()) {
10516
+ // Tell the store this consumer follows the 2026-11-05 delivery rules, before the handler
10517
+ // runs: a delivery that never comes back is then counted as a retry. An older broker
10518
+ // doesn't know the call and keeps its own rules.
10519
+ try {
10520
+ __sbRpc("queue.consumer", { queue: binding });
10521
+ } catch {
10522
+ /* an older broker */
10523
+ }
10524
+ }
9754
10525
  const acked = [];
9755
10526
  const retried = [];
9756
10527
  // id -> seconds, from retry({ delaySeconds }) (2026-10-31)
@@ -9852,16 +10623,24 @@ async function __sbRunQueueBatch(handlers, body, ctx) {
9852
10623
  // A delivered message and batch from 2026-10-31: their data are own properties, as in
9853
10624
  // workerd, and ack/retry live on the prototype, out of Object.keys().
9854
10625
  function __SbQueueMessage(m, ack, retry) {
9855
- this.id = String(m.id);
9856
- this.timestamp = new Date(Number(m.timestamp) || Date.now());
9857
10626
  let decoded;
9858
10627
  try {
9859
10628
  decoded = __sbQueueDecode(m.body);
9860
10629
  } catch {
9861
10630
  decoded = m.body;
9862
10631
  }
9863
- this.body = decoded;
9864
- this.attempts = Number(m.attempts) || 1;
10632
+ if (__sbQueuesFixed()) {
10633
+ // Read-only data properties, as in workerd.
10634
+ Object.defineProperty(this, "id", { value: String(m.id), enumerable: true, configurable: true, writable: false });
10635
+ Object.defineProperty(this, "timestamp", { value: new Date(Number(m.timestamp) || Date.now()), enumerable: true, configurable: true, writable: false });
10636
+ Object.defineProperty(this, "body", { value: decoded, enumerable: true, configurable: true, writable: false });
10637
+ Object.defineProperty(this, "attempts", { value: Number(m.attempts) || 1, enumerable: true, configurable: true, writable: false });
10638
+ } else {
10639
+ this.id = String(m.id);
10640
+ this.timestamp = new Date(Number(m.timestamp) || Date.now());
10641
+ this.body = decoded;
10642
+ this.attempts = Number(m.attempts) || 1;
10643
+ }
9865
10644
  Object.defineProperty(this, "__sbDecide", { value: { ack, retry }, enumerable: false });
9866
10645
  }
9867
10646
  __SbQueueMessage.prototype.ack = function () {
@@ -9883,9 +10662,12 @@ __SbQueueBatch.prototype.retryAll = function (options) {
9883
10662
  for (let i = 0; i < this.messages.length; i++) this.__sbDecide.retry(this.messages[i].id, options);
9884
10663
  };
9885
10664
 
9886
- function __sbReadJson(request) {
10665
+ function __sbReadJson(request, utf8) {
9887
10666
  try {
9888
10667
  const body = __sbRawBody(request);
10668
+ // The native server hands over one character per byte; from 2026-11-05 a queue delivery
10669
+ // decodes them as UTF-8 (non-ASCII message bodies used to arrive as mojibake).
10670
+ if (utf8 && body != null && request._bodyLatin1 === true) return JSON.parse(__sbFromUtf8(String(body)));
9889
10671
  return JSON.parse(body == null ? "{}" : String(body));
9890
10672
  } catch {
9891
10673
  return {};