@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.
- package/CHANGELOG.md +29 -0
- package/package.json +1 -1
- package/src/d1-shim.test.js +215 -0
- package/src/native-fetch-prelude.js +855 -73
- package/src/queues.test.js +136 -0
- package/src/transport-embedded.js +156 -35
- package/test/native-d1.ts +71 -0
- package/test/native-domexception.ts +114 -0
- package/test/native-getrandomvalues.ts +117 -0
- package/test/native-textdecoder-encodings.ts +123 -0
|
@@ -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
|
|
3647
|
-
|
|
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
|
-
|
|
3656
|
-
|
|
3657
|
-
const
|
|
3658
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5579
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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:
|
|
7276
|
-
|
|
7277
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
7554
|
-
|
|
7555
|
-
|
|
7556
|
-
|
|
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 =
|
|
7559
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7622
|
-
|
|
7623
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7874
|
-
|
|
7875
|
-
|
|
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
|
-
|
|
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
|
|
7921
|
-
|
|
7922
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
9864
|
-
|
|
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 {};
|