@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.
- package/CHANGELOG.md +36 -0
- package/package.json +1 -1
- package/src/d1-shim.test.js +215 -0
- package/src/native-fetch-prelude.js +989 -87
- package/src/queues.test.js +136 -0
- package/src/transport-embedded.js +156 -35
- package/test/concurrent-driver.ts +291 -0
- package/test/fixtures/als-app.js +136 -0
- package/test/fixtures/concurrent-app.js +229 -0
- package/test/native-async-local-storage.ts +97 -0
- package/test/native-concurrent-requests.ts +87 -0
- package/test/native-cpu-requests.ts +156 -0
- 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
|
@@ -3104,16 +3104,14 @@ function __sbCpuMs() {
|
|
|
3104
3104
|
return ms;
|
|
3105
3105
|
}
|
|
3106
3106
|
|
|
3107
|
-
// The `x-sb-cpu-ms` value for
|
|
3108
|
-
//
|
|
3109
|
-
// oxlint-disable-next-line no-unused-vars -- `
|
|
3110
|
-
function __sbCpuStamp(
|
|
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
|
-
|
|
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,
|
|
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(
|
|
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
|
|
3622
|
-
|
|
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
|
-
|
|
3631
|
-
|
|
3632
|
-
const
|
|
3633
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5554
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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:
|
|
7251
|
-
|
|
7252
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
7529
|
-
|
|
7530
|
-
|
|
7531
|
-
|
|
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 =
|
|
7534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7597
|
-
|
|
7598
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7849
|
-
|
|
7850
|
-
|
|
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
|
-
|
|
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
|
|
7896
|
-
|
|
7897
|
-
|
|
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
|
|
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))
|
|
9541
|
-
|
|
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
|
|
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()
|
|
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
|
-
|
|
9744
|
-
|
|
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 {};
|