@sproutboat/runtime 0.35.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.
@@ -3104,16 +3104,14 @@ function __sbCpuMs() {
3104
3104
  return ms;
3105
3105
  }
3106
3106
 
3107
- // The `x-sb-cpu-ms` value for the CPU time since `t0`, as three decimals, floored at zero: one
3108
- // clock read and one format call in C, where it was a read, a format, a parse and a toFixed.
3109
- // oxlint-disable-next-line no-unused-vars -- `t0` is read inside the RawC block below, not by JS.
3110
- function __sbCpuStamp(t0) {
3107
+ // The `x-sb-cpu-ms` value for a CPU time in ms, as three decimals, floored at zero: one format call
3108
+ // in C, where it was a format, a parse and a toFixed.
3109
+ // oxlint-disable-next-line no-unused-vars -- `cpu` is read inside the RawC block below, not by JS.
3110
+ function __sbCpuStamp(cpu) {
3111
3111
  let res = "0.000";
3112
3112
  // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
3113
3113
  Porffor.c`
3114
- struct timespec __ts;
3115
- clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &__ts);
3116
- double __d = ((double)__ts.tv_sec * 1000.0 + (double)__ts.tv_nsec / 1000000.0) - (t0.type == 1 ? (double)t0.val : 0.0);
3114
+ double __d = cpu.type == 1 ? (double)cpu.val : 0.0;
3117
3115
  if (!(__d >= 0)) __d = 0;
3118
3116
  char __b[32];
3119
3117
  int __n = snprintf(__b, sizeof(__b), "%.3f", __d);
@@ -3122,6 +3120,33 @@ function __sbCpuStamp(t0) {
3122
3120
  return res;
3123
3121
  }
3124
3122
 
3123
+ // Per-request CPU (toolchain async-context.ts): a request is a small id whose thread CPU time is
3124
+ // summed over the slices in which it ran, however requests interleave. 0 when no slot is free.
3125
+ function __sbCpuBegin() {
3126
+ let id = 0;
3127
+ // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
3128
+ Porffor.c`
3129
+ id = (f64)porf_cpu_begin();
3130
+ `;
3131
+ return id;
3132
+ }
3133
+ // oxlint-disable-next-line no-unused-vars -- `cell` is read inside the RawC block below, not by JS.
3134
+ function __sbCpuTake(cell) {
3135
+ let ms = 0;
3136
+ // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
3137
+ Porffor.c`
3138
+ ms = porf_cpu_read_ms((u32)cell.val);
3139
+ `;
3140
+ return ms;
3141
+ }
3142
+ // oxlint-disable-next-line no-unused-vars -- `cell` is read inside the RawC block below, not by JS.
3143
+ function __sbCpuEnd(cell) {
3144
+ // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
3145
+ Porffor.c`
3146
+ porf_cpu_end((u32)cell.val);
3147
+ `;
3148
+ }
3149
+
3125
3150
  // #28 — stamp `x-sb-cpu-ms` onto a handler Response. Porffor alpha-10's
3126
3151
  // native-fetch serializer only reads headers from the plain object passed to
3127
3152
  // `new Response(body, { headers })` — a later `.set()` or a `Headers` instance
@@ -3129,7 +3154,7 @@ function __sbCpuStamp(t0) {
3129
3154
  // with one extra header. Safe only for a string body (the norm under
3130
3155
  // http-sync-v0) with no Set-Cookie to comma-fold; anything else is returned
3131
3156
  // untouched and the edge simply omits cpuMs for that request.
3132
- function __sbTagCpu(res, t0) {
3157
+ function __sbTagCpu(res, cpu) {
3133
3158
  try {
3134
3159
  // A string body and no Set-Cookie, as before. The native writer reads Headers' flat `_entries`,
3135
3160
  // so the stamp is appended to the handler's own Response instead of building a second one (a
@@ -3137,7 +3162,7 @@ function __sbTagCpu(res, t0) {
3137
3162
  // the handler set itself is replaced by the measured one, as before.
3138
3163
  const headers = res && res.headers;
3139
3164
  if (headers && __sbIsStr(__sbRawBody(res)) && !headers.has("set-cookie")) {
3140
- const stamp = __sbCpuStamp(t0);
3165
+ const stamp = __sbCpuStamp(cpu);
3141
3166
  const entries = headers._entries;
3142
3167
  let set = false;
3143
3168
  for (let i = 0; i < entries.length; i += 2) {
@@ -3617,9 +3642,17 @@ __sbDefineURLAccessor("password", function () {
3617
3642
  // fallback — a silent downgrade to a weak source is worse than throwing.
3618
3643
  if (globalThis.crypto == null) globalThis.crypto = {};
3619
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.
3620
3648
  globalThis.crypto.getRandomValues = function (view) {
3621
- const n = view.length >>> 0;
3622
- // 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;
3623
3656
  if (n > 65536) {
3624
3657
  // WebCrypto's error for an oversized request (a DOMException named QuotaExceededError).
3625
3658
  const e = new Error("crypto.getRandomValues: byte length exceeds 65536");
@@ -3627,11 +3660,10 @@ if (globalThis.crypto.getRandomValues == null) {
3627
3660
  throw e;
3628
3661
  }
3629
3662
  if (n === 0) return view;
3630
- // One CSPRNG byte per element. Correct for Uint8Array (and randomUUID); a
3631
- // wider view gets its low byte filled, matching the previous polyfill's shape.
3632
- const bytes = __sbRandomBytes(String(n));
3633
- if (bytes.length !== n) throw new Error("crypto.getRandomValues: OS entropy source unavailable");
3634
- 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;
3635
3667
  return view;
3636
3668
  };
3637
3669
  }
@@ -3713,6 +3745,40 @@ if (globalThis.crypto.getRandomValues == null) {
3713
3745
  };
3714
3746
  }
3715
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
+
3716
3782
  /* oxlint-disable anti-slop/no-runtime-typeof -- these Web API polyfills branch on JavaScript types
3717
3783
  exactly where their specs do (listener: function or { handleEvent }; structured clone: by value type). */
3718
3784
  // Event, CustomEvent, EventTarget, AbortSignal and AbortController, as workerd has
@@ -3720,11 +3786,7 @@ if (globalThis.crypto.getRandomValues == null) {
3720
3786
  // conformance suite; libraries use AbortController for fetch timeouts and EventTarget
3721
3787
  // for their own events.
3722
3788
  if (typeof globalThis.EventTarget !== "function") {
3723
- const namedError = (name, message) => {
3724
- const e = new Error(message);
3725
- e.name = name;
3726
- return e;
3727
- };
3789
+ const namedError = (name, message) => new globalThis.DOMException(message, name);
3728
3790
  class Event {
3729
3791
  constructor(type, init) {
3730
3792
  if (arguments.length === 0) throw new TypeError("Event requires a type");
@@ -4243,9 +4305,7 @@ function __sbHexOrBytes(value) {
4243
4305
 
4244
4306
  // Errors as WebCrypto names them (DOMException names; plain Errors here).
4245
4307
  function __sbCryptoError(name, message) {
4246
- const e = new Error(message);
4247
- e.name = name;
4248
- return e;
4308
+ return new globalThis.DOMException(message, name);
4249
4309
  }
4250
4310
  // An HMAC key as importKey and generateKey return it. MD5 is digest-only, as in workerd.
4251
4311
  function __sbHmacKey(bytes, bits, extractable, usages) {
@@ -5331,6 +5391,7 @@ function __sbRpcDirect(op, extra) {
5331
5391
  function __sbD1Rpc(op, extra, strict) {
5332
5392
  if (!strict) return __sbRpcDirect(op, extra);
5333
5393
  extra.strict = true;
5394
+ if (__sbD1Parity()) extra.parity = true;
5334
5395
  try {
5335
5396
  return __sbRpcDirect(op, extra);
5336
5397
  } catch (err) {
@@ -5395,10 +5456,104 @@ function __sbD1IsTransaction(text) {
5395
5456
  word === "begin" || word === "commit" || word === "end" || word === "rollback" || word === "savepoint" || word === "release"
5396
5457
  );
5397
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
+ }
5398
5546
  function __sbD1Check(sql) {
5399
5547
  const text = String(sql).trim();
5400
5548
  if (!text) throw __sbD1Fail("D1_ERROR: No SQL statements detected.");
5401
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;
5402
5557
  }
5403
5558
  function __sbD1Meta(meta, started) {
5404
5559
  const changes = Number(meta.changes) || 0;
@@ -5450,10 +5605,11 @@ function __sbD1Direct(name, sql, params) {
5450
5605
  // when the caller will hand it out (all() and run()): raw() and first() drop it, and building it
5451
5606
  // reads the clock twice.
5452
5607
  function __sbD1Query(dbName, sql, params, strict, wantMeta) {
5453
- if (strict) __sbD1Check(sql);
5608
+ const foreignKeys = strict ? __sbD1Check(sql) : 0;
5454
5609
  const started = !strict || wantMeta ? Date.now() : 0;
5455
5610
  const r = strict && __sbD1QueryDirect ? __sbD1Direct(dbName, sql, params) : __sbD1Rpc("d1.query", { db: dbName, sql, params }, strict);
5456
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);
5457
5613
  const table = __sbD1Table(r);
5458
5614
  return { columns: table.columns, rows: table.rows, meta: wantMeta ? __sbD1Meta(r.meta || {}, started) : null };
5459
5615
  }
@@ -5550,8 +5706,16 @@ function __sbMakeD1(dbName) {
5550
5706
  const all = String(sql).trim().split("\n");
5551
5707
  const lines = [];
5552
5708
  for (let i = 0; i < all.length; i++) if (all[i].trim()) lines.push(all[i]);
5553
- if (!lines.length) throw __sbD1Fail("D1_ERROR: No SQL statements detected.");
5554
- __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);
5555
5719
  return { count: lines.length, duration: Date.now() - started };
5556
5720
  }
5557
5721
  const db = {
@@ -6018,6 +6182,13 @@ function __sbAssetResponse(path, r) {
6018
6182
  function __sbQueuesStrict() {
6019
6183
  return __sbCompatDate() >= "2026-10-31";
6020
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
+ }
6021
6192
 
6022
6193
  // binding -> { binding, queue, maxBatchSize?, maxBatchTimeout?, maxRetries?, retryDelay?, deadLetterQueue? }
6023
6194
  var __sbQueueConf = {};
@@ -6056,7 +6227,7 @@ function __sbQueueTypeName(value) {
6056
6227
 
6057
6228
  // A body as the stored text "#sbq1:TYPE:PAYLOAD", and its size as Queues counts it.
6058
6229
  function __sbQueueEncode(body, contentType) {
6059
- const type = contentType === undefined ? "json" : contentType;
6230
+ let type = contentType === undefined ? "json" : contentType;
6060
6231
  let payload;
6061
6232
  let size;
6062
6233
  if (type === "json") {
@@ -6076,13 +6247,18 @@ function __sbQueueEncode(body, contentType) {
6076
6247
  payload = body;
6077
6248
  size = new TextEncoder().encode(body).length;
6078
6249
  } else if (type === "bytes") {
6079
- const bytes = __sbQueueBytesOf(body);
6250
+ const bytes = __sbQueuesFixed() && !ArrayBuffer.isView(body) ? null : __sbQueueBytesOf(body);
6080
6251
  if (bytes === null)
6081
- 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
+ );
6082
6255
  payload = __sbDOHex(bytes);
6083
6256
  size = bytes.length;
6084
6257
  } else if (type === "v8") {
6085
- payload = JSON.stringify(__sbDOPack(body, []));
6258
+ if (__sbQueuesFixed()) {
6259
+ payload = JSON.stringify(__sbQV8Pack(body, []));
6260
+ type = "v8r";
6261
+ } else payload = JSON.stringify(__sbDOPack(body, []));
6086
6262
  size = new TextEncoder().encode(payload).length;
6087
6263
  } else throw new TypeError("Unsupported queue message content type: " + type);
6088
6264
  if (size > __sbQueueMaxMessage)
@@ -6101,10 +6277,124 @@ function __sbQueueDecode(raw) {
6101
6277
  if (type === "json") return JSON.parse(payload);
6102
6278
  if (type === "bytes") return __sbDOUnhex(payload).buffer;
6103
6279
  if (type === "v8") return __sbDOUnpack(JSON.parse(payload));
6280
+ if (type === "v8r") return __sbQV8Unpack(JSON.parse(payload), []);
6104
6281
  }
6105
6282
  return __sbTryParse(raw);
6106
6283
  }
6107
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
+
6108
6398
  function __sbQueueMetrics(metrics) {
6109
6399
  const m = metrics || {};
6110
6400
  const oldest = Number(m.oldestMessageTimestamp) || 0;
@@ -6119,12 +6409,30 @@ function __sbQueueMetrics(metrics) {
6119
6409
  function __sbQueueDelay(value) {
6120
6410
  return value == null ? 0 : Math.max(0, Number(value) || 0);
6121
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
+ }
6122
6424
 
6123
6425
  function __sbMakeQueue(binding) {
6124
6426
  return {
6125
- send(body, options) {
6427
+ send(...args) {
6126
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");
6127
6434
  const o = options || {};
6435
+ __sbQueueCheckDelay(o.delaySeconds);
6128
6436
  const encoded = __sbQueueEncode(body, o.contentType);
6129
6437
  const reply = __sbRpc("queue.send", {
6130
6438
  queue: binding,
@@ -6141,11 +6449,16 @@ function __sbMakeQueue(binding) {
6141
6449
  try {
6142
6450
  if (messages == null || !__sbIsFn(messages[Symbol.iterator]))
6143
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);
6144
6454
  const batchDelay = __sbQueueDelay(options && options.delaySeconds);
6145
6455
  const list = [];
6146
6456
  let total = 0;
6147
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'.");
6148
6460
  const item = m || {};
6461
+ __sbQueueCheckDelay(item.delaySeconds);
6149
6462
  const encoded = __sbQueueEncode(item.body, item.contentType);
6150
6463
  total += encoded.size;
6151
6464
  // A message delay of 0 (or none) takes the batch delay, as miniflare does.
@@ -7043,6 +7356,11 @@ function __sbDOv2() {
7043
7356
  return __sbCompatDate() >= "2026-10-29";
7044
7357
  }
7045
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
+
7046
7364
  function __sbDOSha(text) {
7047
7365
  return __sbR2HexOf(__sbDigestRaw("256", __sbToBytes(text)));
7048
7366
  }
@@ -7052,7 +7370,27 @@ function __sbDOSeal(cls, body) {
7052
7370
  return body + __sbDOSha("sbdo-id:" + cls + ":" + body).slice(0, 16);
7053
7371
  }
7054
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
+
7055
7392
  function __sbDOMakeId(hex, name) {
7393
+ if (__sbDOv3()) return new globalThis.DurableObjectId(hex, name);
7056
7394
  return {
7057
7395
  name,
7058
7396
  toString() {
@@ -7098,6 +7436,8 @@ function __sbMakeDONamespaceV2(className) {
7098
7436
  return __sbDOMakeId(__sbDOSeal(className, __sbDOHex(crypto.getRandomValues(new Uint8Array(24)))), undefined);
7099
7437
  },
7100
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'.");
7101
7441
  return __sbDOStub(className, __sbIsStr(id) ? ns.idFromString(id) : id);
7102
7442
  },
7103
7443
  getByName(name) {
@@ -7247,13 +7587,19 @@ function __sbGetDOInstance(cls, idText, idObj) {
7247
7587
  const __sbTasks = [];
7248
7588
  const gate = __sbDOMakeGate();
7249
7589
  const state = {
7250
- id: stateId || {
7251
- toString() {
7252
- return id;
7253
- },
7254
- },
7590
+ id:
7591
+ stateId ||
7592
+ (__sbDOv3()
7593
+ ? __sbDOMakeId(id, undefined)
7594
+ : {
7595
+ toString() {
7596
+ return id;
7597
+ },
7598
+ }),
7255
7599
  storage: __sbDOGated() ? __sbDOAsyncStorage(cls, id, gate) : __sbDOStorage(cls, id),
7256
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'.");
7257
7603
  if (!__sbDOGated()) return fn();
7258
7604
  // No event enters until fn settles (2026-10-28).
7259
7605
  gate.locks++;
@@ -7283,6 +7629,15 @@ function __sbGetDOInstance(cls, idText, idObj) {
7283
7629
  if (p && __sbIsFn(p.then)) __sbTasks.push(p);
7284
7630
  },
7285
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
+ }
7286
7641
  // The hibernation API, from 2026-10-30 (where WebSocketPair exists).
7287
7642
  const holder = { inst: null, auto: null, cls, id, restored: [] };
7288
7643
  if (__sbWsEnabled()) __sbDOWebSocketState(state, holder);
@@ -7384,7 +7739,7 @@ function __sbDOGated() {
7384
7739
  }
7385
7740
 
7386
7741
  function __sbDOMakeGate() {
7387
- return { locks: 0, waiting: [] };
7742
+ return { locks: 0, waiting: [], txnBusy: false, txnQueue: [] };
7388
7743
  }
7389
7744
 
7390
7745
  // Deliver queued events while the gate is open. A delivered event that reads storage shuts
@@ -7423,6 +7778,35 @@ function __sbDOEnter(gate, run) {
7423
7778
  });
7424
7779
  }
7425
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
+
7426
7810
  const __sbDOTag = "#sb1:";
7427
7811
 
7428
7812
  function __sbDOCloneError(value) {
@@ -7461,7 +7845,10 @@ function __sbDOView(name, bytes) {
7461
7845
  // else is an array whose first element names its type, so no plain value is ambiguous.
7462
7846
  /* oxlint-disable anti-slop/no-runtime-typeof -- structured clone branches on the value's type, as the HTML spec's StructuredSerialize does. */
7463
7847
  // fns (RPC only): functions go there, packed as ["F", index]; storage passes none.
7464
- 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) {
7465
7852
  if (v === null || v === true || v === false || __sbIsStr(v)) return v;
7466
7853
  const t = typeof v;
7467
7854
  if (t === "number") {
@@ -7476,19 +7863,23 @@ function __sbDOPack(v, seen, fns) {
7476
7863
  }
7477
7864
  if (t === "symbol") throw __sbDOCloneError(v.toString());
7478
7865
  if (t !== "object") throw __sbDOCloneError(v);
7479
- 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
+ }
7480
7871
  seen.push(v);
7481
7872
  let out;
7482
- if (v instanceof Date) out = ["D", v.getTime()];
7873
+ if (v instanceof Date) out = ["D", refs && Number.isNaN(v.getTime()) ? "NaN" : v.getTime()];
7483
7874
  else if (v instanceof Map) {
7484
7875
  out = ["M"];
7485
7876
  v.forEach((x, k) => {
7486
- out.push(__sbDOPack(k, seen, fns), __sbDOPack(x, seen, fns));
7877
+ out.push(__sbDOPack(k, seen, fns, refs), __sbDOPack(x, seen, fns, refs));
7487
7878
  });
7488
7879
  } else if (v instanceof Set) {
7489
7880
  out = ["S"];
7490
7881
  v.forEach((x) => {
7491
- out.push(__sbDOPack(x, seen, fns));
7882
+ out.push(__sbDOPack(x, seen, fns, refs));
7492
7883
  });
7493
7884
  } else if (v instanceof ArrayBuffer) out = ["AB", __sbDOHex(new Uint8Array(v))];
7494
7885
  else if (ArrayBuffer.isView(v)) {
@@ -7503,21 +7894,38 @@ function __sbDOPack(v, seen, fns) {
7503
7894
  else if (v instanceof RegExp) out = ["R", v.source, v.flags];
7504
7895
  else if (Array.isArray(v)) {
7505
7896
  out = ["A"];
7506
- 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));
7507
7898
  } else if (v instanceof Error) out = ["E", String(v.name), String(v.message)];
7508
7899
  else {
7509
7900
  out = ["O"];
7510
7901
  const keys = Object.keys(v);
7511
- 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));
7512
7903
  }
7513
- seen.pop();
7904
+ if (!refs) seen.pop();
7514
7905
  return out;
7515
7906
  }
7516
7907
  /* oxlint-enable anti-slop/no-runtime-typeof */
7517
7908
 
7518
- 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) {
7519
7926
  if (!Array.isArray(p)) return p;
7520
7927
  const tag = p[0];
7928
+ if (tag === "X") return refs[p[1]];
7521
7929
  if (tag === "U") return undefined;
7522
7930
  if (tag === "F" && fns) {
7523
7931
  const fn = fns[p[1]];
@@ -7525,49 +7933,82 @@ function __sbDOUnpack(p, fns) {
7525
7933
  }
7526
7934
  if (tag === "N") return p[1] === "-0" ? -0 : Number(p[1]);
7527
7935
  if (tag === "B") return BigInt(p[1]);
7528
- if (tag === "D") return new Date(p[1]);
7529
- if (tag === "AB") return __sbDOUnhex(p[1]).buffer;
7530
- if (tag === "V") return __sbDOView(p[1], __sbDOUnhex(p[2]));
7531
- 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
+ }
7532
7956
  if (tag === "E") {
7533
- const e = new Error(p[2]);
7534
- e.name = p[1];
7957
+ const e = __sbDOMakeError(p[1], p[2]);
7958
+ if (refs) refs.push(e);
7535
7959
  return e;
7536
7960
  }
7537
7961
  if (tag === "M") {
7538
7962
  const m = new Map();
7539
- 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));
7540
7965
  return m;
7541
7966
  }
7542
7967
  if (tag === "S") {
7543
7968
  const s = new Set();
7544
- 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));
7545
7971
  return s;
7546
7972
  }
7547
7973
  if (tag === "A") {
7548
7974
  const a = [];
7549
- 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));
7550
7977
  return a;
7551
7978
  }
7552
7979
  const o = {};
7553
- 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);
7554
7982
  return o;
7555
7983
  }
7556
7984
 
7557
7985
  function __sbDOEncode(value) {
7558
7986
  if (value === undefined) throw new TypeError("put() called with undefined value.");
7559
- return __sbDOTag + JSON.stringify(__sbDOPack(value, []));
7987
+ return __sbDOTag + JSON.stringify(__sbDOPack(value, [], undefined, __sbDOv3()));
7560
7988
  }
7561
7989
 
7562
7990
  // Reads both formats at every date: tagged (2026-10-28 writes) and plain JSON (earlier ones).
7563
7991
  function __sbDODecode(text) {
7564
- 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, []);
7565
7993
  return JSON.parse(text);
7566
7994
  }
7567
7995
 
7568
7996
  function __sbDOListLimit(options) {
7569
7997
  const o = options || {};
7570
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
+ }
7571
8012
  return o;
7572
8013
  }
7573
8014
 
@@ -7579,6 +8020,31 @@ function __sbDOInRange(key, o) {
7579
8020
  return true;
7580
8021
  }
7581
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
+
7582
8048
  // The synchronous core of the 2026-10-28 storage: structured values, list() ranges.
7583
8049
  function __sbDOStrictStorage(cls, id) {
7584
8050
  const get = (key) => {
@@ -7593,13 +8059,20 @@ function __sbDOStrictStorage(cls, id) {
7593
8059
  get(key) {
7594
8060
  if (!Array.isArray(key)) return get(key);
7595
8061
  const out = new Map();
7596
- for (let i = 0; i < key.length; i++) {
7597
- const v = get(key[i]);
7598
- 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);
7599
8067
  }
7600
8068
  return out;
7601
8069
  },
7602
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
+ }
7603
8076
  if (key != null && __sbIsObj(key)) {
7604
8077
  // Encode every value before writing any, so one bad value writes nothing.
7605
8078
  const keys = Object.keys(key);
@@ -7623,6 +8096,15 @@ function __sbDOStrictStorage(cls, id) {
7623
8096
  },
7624
8097
  setAlarm(when) {
7625
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
+ }
7626
8108
  if (!Number.isFinite(at))
7627
8109
  throw new TypeError("Failed to execute 'setAlarm' on 'DurableObjectStorage': parameter 1 is not of type 'date'.");
7628
8110
  __sbRpc("do.alarm.set", { cls, id, at: Number(at) });
@@ -7686,6 +8168,28 @@ function __sbDOAsyncStorage(cls, id, gate) {
7686
8168
  const sql = __sbDOSql(cls, id);
7687
8169
  storage.sql = sql;
7688
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
+ }
7689
8193
  }
7690
8194
  return storage;
7691
8195
  }
@@ -7724,6 +8228,135 @@ function __sbDOSqlVerb(q) {
7724
8228
  return q.slice(i, j).toUpperCase();
7725
8229
  }
7726
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
+
7727
8360
  function __sbDOSql(cls, id) {
7728
8361
  const file = __sbDOIsHex64(id) ? id.toLowerCase() : __sbDOSha("sbdo-file:" + id);
7729
8362
  const run = (sql, params) => {
@@ -7743,9 +8376,50 @@ function __sbDOSql(cls, id) {
7743
8376
  if (verb === "BEGIN" || verb === "COMMIT" || verb === "END" || verb === "ROLLBACK" || verb === "SAVEPOINT" || verb === "RELEASE")
7744
8377
  throw new Error(__sbDOSqlTxnError);
7745
8378
  const params = [];
7746
- 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
+ }
7747
8402
  return __sbDOCursor(run(q, params));
7748
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
+ },
7749
8423
  get databaseSize() {
7750
8424
  return Number(run("PRAGMA page_count", []).rows[0][0]) * Number(run("PRAGMA page_size", []).rows[0][0]);
7751
8425
  },
@@ -7780,6 +8454,9 @@ function __sbDOCursor(r) {
7780
8454
  return o;
7781
8455
  };
7782
8456
  const cursor = {
8457
+ get [Symbol.toStringTag]() {
8458
+ return "Cursor";
8459
+ },
7783
8460
  columnNames: columns,
7784
8461
  rowsRead: rows.length,
7785
8462
  rowsWritten: Number(r.meta && r.meta.changes) || 0,
@@ -7814,7 +8491,15 @@ function __sbDOCursor(r) {
7814
8491
  return it;
7815
8492
  },
7816
8493
  [Symbol.iterator]() {
7817
- 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;
7818
8503
  },
7819
8504
  };
7820
8505
  return cursor;
@@ -7824,9 +8509,49 @@ function __sbDOCursor(r) {
7824
8509
  // memory (read back by its own get and list) and are written when fn resolves, unless it
7825
8510
  // threw or called txn.rollback(). The input gate stays shut meanwhile, so no other event
7826
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.
7827
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) {
7828
8540
  const writes = new Map(); // key -> encoded value, or null for a delete
7829
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
+ };
7830
8555
  const getOne = (key) => {
7831
8556
  const k = String(key);
7832
8557
  if (!writes.has(k)) return s.get(k);
@@ -7836,6 +8561,15 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7836
8561
  const putOne = (key, value) => {
7837
8562
  writes.set(String(key), __sbDOEncode(value));
7838
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
+ };
7839
8573
  const delOne = (key) => {
7840
8574
  const existed = getOne(key) !== undefined;
7841
8575
  writes.set(String(key), null);
@@ -7843,32 +8577,38 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7843
8577
  };
7844
8578
  const txn = {
7845
8579
  get(key) {
8580
+ const no = refused("get");
8581
+ if (no) return Promise.reject(no);
7846
8582
  if (!Array.isArray(key)) return Promise.resolve(getOne(key));
7847
8583
  const out = new Map();
7848
- for (let i = 0; i < key.length; i++) {
7849
- const v = getOne(key[i]);
7850
- 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);
7851
8588
  }
7852
8589
  return Promise.resolve(out);
7853
8590
  },
7854
8591
  put(key, value) {
8592
+ const no = refused("put");
8593
+ if (no) return Promise.reject(no);
7855
8594
  try {
7856
- if (key != null && __sbIsObj(key)) {
7857
- const keys = Object.keys(key);
7858
- for (let i = 0; i < keys.length; i++) putOne(keys[i], key[keys[i]]);
7859
- } else putOne(key, value);
8595
+ putAny(key, value);
7860
8596
  return Promise.resolve();
7861
8597
  } catch (error) {
7862
8598
  return Promise.reject(error);
7863
8599
  }
7864
8600
  },
7865
8601
  delete(key) {
8602
+ const no = refused("delete");
8603
+ if (no) return Promise.reject(no);
7866
8604
  if (!Array.isArray(key)) return Promise.resolve(delOne(key));
7867
8605
  let n = 0;
7868
8606
  for (let i = 0; i < key.length; i++) n += delOne(key[i]) ? 1 : 0;
7869
8607
  return Promise.resolve(n);
7870
8608
  },
7871
8609
  list(options) {
8610
+ const no = refused("list");
8611
+ if (no) return Promise.reject(no);
7872
8612
  try {
7873
8613
  const o = __sbDOListLimit(options);
7874
8614
  const merged = s.list({ prefix: o.prefix, start: o.start, startAfter: o.startAfter, end: o.end });
@@ -7892,10 +8632,31 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7892
8632
  rollback() {
7893
8633
  rolledBack = true;
7894
8634
  },
7895
- getAlarm: () => Promise.resolve(s.getAlarm()),
7896
- setAlarm: (when) => Promise.resolve(s.setAlarm(when)),
7897
- 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
+ },
7898
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
+ };
7899
8660
  gate.locks++;
7900
8661
  const done = () => {
7901
8662
  gate.locks--;
@@ -7910,6 +8671,7 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7910
8671
  }
7911
8672
  return Promise.resolve(r).then(
7912
8673
  (v) => {
8674
+ committed = true;
7913
8675
  try {
7914
8676
  if (!rolledBack)
7915
8677
  writes.forEach((value, key) => {
@@ -7922,6 +8684,7 @@ function __sbDOTransaction(cls, id, s, gate, fn) {
7922
8684
  return v;
7923
8685
  },
7924
8686
  (error) => {
8687
+ rolledBack = true;
7925
8688
  done();
7926
8689
  throw error;
7927
8690
  },
@@ -9160,7 +9923,7 @@ function __sbInstallWebSockets() {
9160
9923
  globalThis.WebSocketRequestResponsePair = __SbWebSocketRequestResponsePair;
9161
9924
  }
9162
9925
 
9163
- async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top) {
9926
+ async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top, cpuCell) {
9164
9927
  let r;
9165
9928
  try {
9166
9929
  r = res && __sbIsFn(res.then) ? await res : res;
@@ -9178,6 +9941,12 @@ async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top) {
9178
9941
  // #179 — a rejected handler promise must not take the process down with it.
9179
9942
  r = __sbErrorResponse(error, bindingCall);
9180
9943
  }
9944
+ // From 2026-11-02 an async handler reports its CPU too: the thread time of the slices in which this
9945
+ // request ran, not its neighbours' (before it, only a handler that answered synchronously did).
9946
+ if (cpuCell) {
9947
+ if (__sbCompatDate() >= "2026-11-02") __sbTagCpu(r, __sbCpuTake(cpuCell));
9948
+ __sbCpuEnd(cpuCell);
9949
+ }
9181
9950
  // The response goes out now and ctx.waitUntil work keeps running after it, as in
9182
9951
  // workerd; it was awaited first, so a 1.5 s background task delayed the response by
9183
9952
  // 1.5 s. The 25 s cap still bounds it. Not called for no tasks: an async call is a coroutine and a promise.
@@ -9287,6 +10056,87 @@ globalThis.__sbOnUnhandledRejection = function (reason, promise) {
9287
10056
  }
9288
10057
  };
9289
10058
 
10059
+ // AsyncLocalStorage (node:async_hooks, as Workers has it: run, getStore, exit and the static snapshot
10060
+ // (no bind, enterWith or disable). The patched Porffor (toolchain async-context.ts) keeps one
10061
+ // "current context" value that every promise reaction, await and timer records when it is created and
10062
+ // sets when it runs. Here that value is a frame: the stores of the AsyncLocalStorage instances
10063
+ // entered so far, in two parallel arrays (a frame is never changed, run() makes a new one).
10064
+ function __sbCtxGet() {
10065
+ let frame;
10066
+ // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
10067
+ Porffor.c`
10068
+ frame = porf_unpack(porf_async_ctx_bits);
10069
+ `;
10070
+ return frame;
10071
+ }
10072
+ // oxlint-disable-next-line no-unused-vars -- `frame` is read inside the RawC block below, not by JS.
10073
+ function __sbCtxSet(frame) {
10074
+ // oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
10075
+ Porffor.c`
10076
+ porf_async_ctx_bits = porf_pack(frame);
10077
+ `;
10078
+ }
10079
+ function __sbFrameIndex(frame, als) {
10080
+ if (!frame) return -1;
10081
+ const keys = frame.keys;
10082
+ for (let i = 0; i < keys.length; i++) if (keys[i] === als) return i;
10083
+ return -1;
10084
+ }
10085
+ function __sbFrameWith(frame, als, store) {
10086
+ const keys = [];
10087
+ const vals = [];
10088
+ if (frame) {
10089
+ for (let i = 0; i < frame.keys.length; i++) {
10090
+ if (frame.keys[i] === als) continue;
10091
+ keys.push(frame.keys[i]);
10092
+ vals.push(frame.vals[i]);
10093
+ }
10094
+ }
10095
+ keys.push(als);
10096
+ vals.push(store);
10097
+ return { keys, vals };
10098
+ }
10099
+ function __sbRunInFrame(frame, fn, args) {
10100
+ const previous = __sbCtxGet();
10101
+ __sbCtxSet(frame);
10102
+ try {
10103
+ return fn.apply(undefined, args);
10104
+ } finally {
10105
+ __sbCtxSet(previous);
10106
+ }
10107
+ }
10108
+ // A function constructor, not a class: Porffor refuses `new` on a class reached through an alias
10109
+ // (const { AsyncLocalStorage } = globalThis), which is how a bundled import arrives.
10110
+ function __SbAsyncLocalStorage() {}
10111
+ __SbAsyncLocalStorage.prototype.run = function (store, fn, ...args) {
10112
+ if (!__sbIsFn(fn)) throw new TypeError("The \"callback\" argument must be of type function.");
10113
+ return __sbRunInFrame(__sbFrameWith(__sbCtxGet(), this, store), fn, args);
10114
+ };
10115
+ __SbAsyncLocalStorage.prototype.exit = function (fn, ...args) {
10116
+ if (!__sbIsFn(fn)) throw new TypeError("The \"callback\" argument must be of type function.");
10117
+ return __sbRunInFrame(__sbFrameWith(__sbCtxGet(), this, undefined), fn, args);
10118
+ };
10119
+ __SbAsyncLocalStorage.prototype.getStore = function () {
10120
+ const frame = __sbCtxGet();
10121
+ const at = __sbFrameIndex(frame, this);
10122
+ return at === -1 ? undefined : frame.vals[at];
10123
+ };
10124
+ __SbAsyncLocalStorage.prototype.enterWith = function () {
10125
+ throw new Error("AsyncLocalStorage.enterWith() is not supported");
10126
+ };
10127
+ __SbAsyncLocalStorage.prototype.disable = function () {
10128
+ throw new Error("AsyncLocalStorage.disable() is not supported");
10129
+ };
10130
+ // AsyncLocalStorage.bind(fn) is not provided: Porffor compiles a call of .bind on a function value to
10131
+ // Function.prototype.bind whatever properties the function has. snapshot() does the same job.
10132
+ __SbAsyncLocalStorage.snapshot = function () {
10133
+ const frame = __sbCtxGet();
10134
+ return function (fn, ...args) {
10135
+ return __sbRunInFrame(frame, fn, args);
10136
+ };
10137
+ };
10138
+ globalThis.AsyncLocalStorage = __SbAsyncLocalStorage;
10139
+
9290
10140
  // Shared by alarm and scheduled: both reply with an empty 204 once their
9291
10141
  // handler (and anything it queued via `waitUntil`) has settled.
9292
10142
  async function __sb204WithDrain(res, tasks) {
@@ -9300,6 +10150,21 @@ async function __sb204WithDrain(res, tasks) {
9300
10150
  return new Response(null, { status: 204 });
9301
10151
  }
9302
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
+
9303
10168
  async function __sbQueueWithDrain(result, tasks) {
9304
10169
  const r = result && __sbIsFn(result.then) ? await result : result;
9305
10170
  await __sbDrainWaitUntil(tasks);
@@ -9509,7 +10374,8 @@ globalThis.__sbEntry = function (handlers, request) {
9509
10374
  // itself returned, but never one derived from `.then()`, so chaining the
9510
10375
  // tag on hangs the request forever. cpuMs is documented as absent for
9511
10376
  // async handlers (see LogEvent in services/edge) — that is this.
9512
- const __t0 = __sbCpuMs();
10377
+ const __cpuCell = __sbCpuBegin();
10378
+ const __t0 = __cpuCell ? 0 : __sbCpuMs();
9513
10379
  const __sbTasks = [];
9514
10380
  const ctx = {
9515
10381
  waitUntil(p) {
@@ -9531,14 +10397,21 @@ globalThis.__sbEntry = function (handlers, request) {
9531
10397
  } catch (error) {
9532
10398
  // #179 — a synchronous throw anywhere in the handler must not take the
9533
10399
  // whole process (and every other in-flight request) down with it.
10400
+ __sbCpuEnd(__cpuCell);
9534
10401
  return __sbErrorResponse(error, bindingCall);
9535
10402
  }
9536
10403
  // Same promise-identity rule as above applies to `__sbFetchWithDrain`'s
9537
10404
  // own return, which is why it's tail-called rather than chained on here.
9538
- if (__sbTasks.length || (__res && __sbIsFn(__res.then)) || __sbHasStreamBody(__res)) return __sbFetchWithDrain(__res, __sbTasks, bindingCall, false, true);
10405
+ if (__sbTasks.length || (__res && __sbIsFn(__res.then)) || __sbHasStreamBody(__res)) return __sbFetchWithDrain(__res, __sbTasks, bindingCall, false, true, __cpuCell);
9539
10406
  // A WebSocket handshake answer (from 2026-10-30): its client end becomes the host socket.
9540
- if (__sbHasWebSocket(__res)) return __sbWsHostResponse(__res);
9541
- return __sbTagCpu(__res, __t0);
10407
+ if (__sbHasWebSocket(__res)) {
10408
+ __sbCpuEnd(__cpuCell);
10409
+ return __sbWsHostResponse(__res);
10410
+ }
10411
+ // The request ran alone from start to here: its own slices, or the process delta where no slot was free.
10412
+ const __cpu = __cpuCell ? __sbCpuTake(__cpuCell) : __sbCpuMs() - __t0;
10413
+ __sbCpuEnd(__cpuCell);
10414
+ return __sbTagCpu(__res, __cpu);
9542
10415
  }
9543
10416
  if (!__sbTriggerAuthed(request)) return new Response("forbidden", { status: 403 });
9544
10417
 
@@ -9586,23 +10459,31 @@ globalThis.__sbEntry = function (handlers, request) {
9586
10459
  // Always a promise now (`__sbRunQueueBatch` is async, to await the
9587
10460
  // handler above), so always tail-call the drain wrapper — same
9588
10461
  // promise-identity reasoning as `__sbFetchWithDrain`.
9589
- return __sbQueueWithDrain(__sbRunQueueBatch(__sbReceiver(handlers, ctx), __sbReadJson(request), ctx), __sbTasks);
10462
+ return __sbQueueWithDrain(__sbRunQueueBatch(__sbReceiver(handlers, ctx), __sbReadJson(request, __sbQueuesFixed()), ctx), __sbTasks);
9590
10463
  }
9591
10464
 
9592
10465
  if (trigger === "alarm") {
9593
10466
  const body = __sbReadJson(request);
9594
- 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);
9595
10470
  if (!__sbIsFn(inst.alarm)) return new Response("no alarm handler", { status: 404 });
10471
+ const __retries = __sbCompatDate() >= "2026-11-04";
9596
10472
  let __ares;
9597
10473
  try {
9598
- __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);
9599
10477
  } catch (error) {
9600
10478
  // #179 — same rule as fetch: don't let a throw here crash the process.
9601
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 });
9602
10482
  return new Response(null, { status: 204 });
9603
10483
  }
9604
10484
  // Was fire-and-forget before: an async `alarm()` and any `state.waitUntil()`
9605
10485
  // it queued were both dropped the moment 204 went out. Await both.
10486
+ if (__retries) return __sbAlarmWithDrain(__ares, inst.__sbTasks);
9606
10487
  if (inst.__sbTasks.length || (__ares && __sbIsFn(__ares.then))) return __sb204WithDrain(__ares, inst.__sbTasks);
9607
10488
  return new Response(null, { status: 204 });
9608
10489
  }
@@ -9631,6 +10512,16 @@ async function __sbRunQueueBatch(handlers, body, ctx) {
9631
10512
  const strict = __sbQueuesStrict();
9632
10513
  const binding = String(body.queue || "");
9633
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
+ }
9634
10525
  const acked = [];
9635
10526
  const retried = [];
9636
10527
  // id -> seconds, from retry({ delaySeconds }) (2026-10-31)
@@ -9732,16 +10623,24 @@ async function __sbRunQueueBatch(handlers, body, ctx) {
9732
10623
  // A delivered message and batch from 2026-10-31: their data are own properties, as in
9733
10624
  // workerd, and ack/retry live on the prototype, out of Object.keys().
9734
10625
  function __SbQueueMessage(m, ack, retry) {
9735
- this.id = String(m.id);
9736
- this.timestamp = new Date(Number(m.timestamp) || Date.now());
9737
10626
  let decoded;
9738
10627
  try {
9739
10628
  decoded = __sbQueueDecode(m.body);
9740
10629
  } catch {
9741
10630
  decoded = m.body;
9742
10631
  }
9743
- this.body = decoded;
9744
- 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
+ }
9745
10644
  Object.defineProperty(this, "__sbDecide", { value: { ack, retry }, enumerable: false });
9746
10645
  }
9747
10646
  __SbQueueMessage.prototype.ack = function () {
@@ -9763,9 +10662,12 @@ __SbQueueBatch.prototype.retryAll = function (options) {
9763
10662
  for (let i = 0; i < this.messages.length; i++) this.__sbDecide.retry(this.messages[i].id, options);
9764
10663
  };
9765
10664
 
9766
- function __sbReadJson(request) {
10665
+ function __sbReadJson(request, utf8) {
9767
10666
  try {
9768
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)));
9769
10671
  return JSON.parse(body == null ? "{}" : String(body));
9770
10672
  } catch {
9771
10673
  return {};