@sproutboat/runtime 0.34.5 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/package.json +1 -1
- package/src/d1-shim.test.js +120 -16
- package/src/native-fetch-prelude.js +313 -86
- package/src/transport-embedded.js +128 -31
- 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 +36 -6
- package/test/native-headers.ts +182 -0
- package/test/native-sqlite-lock.ts +94 -0
|
@@ -3092,18 +3092,59 @@ __sbStartupMark();
|
|
|
3092
3092
|
// string via the same primitives as __sbEnv / __sbRandomBytes (proven working),
|
|
3093
3093
|
// then parsed — Porffor's number boxing for a bare inline-C assignment is not
|
|
3094
3094
|
// relied on. `__sbEntry` samples it around the handler for per-invocation CPU.
|
|
3095
|
+
// The number comes back as a number: no text and no parse around it.
|
|
3095
3096
|
function __sbCpuMs() {
|
|
3096
|
-
let
|
|
3097
|
+
let ms = 0;
|
|
3097
3098
|
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
3098
3099
|
Porffor.c`
|
|
3099
3100
|
struct timespec __ts;
|
|
3100
3101
|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &__ts);
|
|
3101
|
-
|
|
3102
|
+
ms = (f64)((double)__ts.tv_sec * 1000.0 + (double)__ts.tv_nsec / 1000000.0);
|
|
3103
|
+
`;
|
|
3104
|
+
return ms;
|
|
3105
|
+
}
|
|
3106
|
+
|
|
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
|
+
let res = "0.000";
|
|
3112
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
3113
|
+
Porffor.c`
|
|
3114
|
+
double __d = cpu.type == 1 ? (double)cpu.val : 0.0;
|
|
3115
|
+
if (!(__d >= 0)) __d = 0;
|
|
3102
3116
|
char __b[32];
|
|
3103
|
-
int __n = snprintf(__b, sizeof(__b), "%.3f",
|
|
3117
|
+
int __n = snprintf(__b, sizeof(__b), "%.3f", __d);
|
|
3104
3118
|
if (__n > 0) res = porf_box((f64)porf_native_fetch_alloc_bytestring(__b, (size_t)__n), 195);
|
|
3105
3119
|
`;
|
|
3106
|
-
return res
|
|
3120
|
+
return res;
|
|
3121
|
+
}
|
|
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
|
+
`;
|
|
3107
3148
|
}
|
|
3108
3149
|
|
|
3109
3150
|
// #28 — stamp `x-sb-cpu-ms` onto a handler Response. Porffor alpha-10's
|
|
@@ -3113,8 +3154,7 @@ function __sbCpuMs() {
|
|
|
3113
3154
|
// with one extra header. Safe only for a string body (the norm under
|
|
3114
3155
|
// http-sync-v0) with no Set-Cookie to comma-fold; anything else is returned
|
|
3115
3156
|
// untouched and the edge simply omits cpuMs for that request.
|
|
3116
|
-
function __sbTagCpu(res,
|
|
3117
|
-
const cpu = __sbCpuMs() - t0;
|
|
3157
|
+
function __sbTagCpu(res, cpu) {
|
|
3118
3158
|
try {
|
|
3119
3159
|
// A string body and no Set-Cookie, as before. The native writer reads Headers' flat `_entries`,
|
|
3120
3160
|
// so the stamp is appended to the handler's own Response instead of building a second one (a
|
|
@@ -3122,7 +3162,7 @@ function __sbTagCpu(res, t0) {
|
|
|
3122
3162
|
// the handler set itself is replaced by the measured one, as before.
|
|
3123
3163
|
const headers = res && res.headers;
|
|
3124
3164
|
if (headers && __sbIsStr(__sbRawBody(res)) && !headers.has("set-cookie")) {
|
|
3125
|
-
const stamp = (cpu
|
|
3165
|
+
const stamp = __sbCpuStamp(cpu);
|
|
3126
3166
|
const entries = headers._entries;
|
|
3127
3167
|
let set = false;
|
|
3128
3168
|
for (let i = 0; i < entries.length; i += 2) {
|
|
@@ -5295,6 +5335,11 @@ function __sbD1Param(value, strict) {
|
|
|
5295
5335
|
// The broker transport leaves it unset and keeps the string hop. (No initializer: the transport is placed ahead of the prelude, and `= null` would run after its assignment.)
|
|
5296
5336
|
// oxlint-disable-next-line no-unassigned-vars -- the embedded transport assigns it; the broker transport does not.
|
|
5297
5337
|
var __sbCallDirect;
|
|
5338
|
+
// The embedded transport also answers a strict D1 query without the generic dispatch around it
|
|
5339
|
+
// (a request object, a walk down the op list, a result object): (db, sql, params) -> the reply
|
|
5340
|
+
// __sbD1Run builds. Unset elsewhere, and the query goes through __sbD1Rpc as before.
|
|
5341
|
+
// oxlint-disable-next-line no-unassigned-vars -- the embedded transport assigns it; the broker transport does not.
|
|
5342
|
+
var __sbD1QueryDirect;
|
|
5298
5343
|
function __sbRpcDirect(op, extra) {
|
|
5299
5344
|
if (!__sbCallDirect) return __sbRpc(op, extra);
|
|
5300
5345
|
const req = { v: 1, id: ++__sbReqId, op };
|
|
@@ -5355,10 +5400,30 @@ function __sbD1Fail(message) {
|
|
|
5355
5400
|
// transaction statements are refused, exec() runs line by line, and withSession() exists.
|
|
5356
5401
|
var __sbD1NoTransactions =
|
|
5357
5402
|
"D1_ERROR: To execute a transaction, please use the state.storage.transaction() or state.storage.transactionSync() APIs instead of the SQL BEGIN TRANSACTION or SAVEPOINT statements. The JavaScript API is safer because it will automatically roll back on exceptions, and because it interacts correctly with Durable Objects' automatic atomic write coalescing.";
|
|
5403
|
+
// A statement's first word is one of the transaction keywords: read by character code, where the
|
|
5404
|
+
// regular expression this replaced was compiled on every query. The word ends at the first character
|
|
5405
|
+
// that is not a letter, digit or underscore (the regular expression's \b after a keyword).
|
|
5406
|
+
function __sbD1IsTransaction(text) {
|
|
5407
|
+
let end = 0;
|
|
5408
|
+
while (end < text.length && end < 10) {
|
|
5409
|
+
const c = text.charCodeAt(end) | 32;
|
|
5410
|
+
if (c < 97 || c > 122) break;
|
|
5411
|
+
end++;
|
|
5412
|
+
}
|
|
5413
|
+
if (end < 3 || end > 9) return false;
|
|
5414
|
+
if (end < text.length) {
|
|
5415
|
+
const c = text.charCodeAt(end);
|
|
5416
|
+
if ((c >= 48 && c <= 57) || (c >= 65 && c <= 90) || c === 95 || (c >= 97 && c <= 122)) return false;
|
|
5417
|
+
}
|
|
5418
|
+
const word = text.slice(0, end).toLowerCase();
|
|
5419
|
+
return (
|
|
5420
|
+
word === "begin" || word === "commit" || word === "end" || word === "rollback" || word === "savepoint" || word === "release"
|
|
5421
|
+
);
|
|
5422
|
+
}
|
|
5358
5423
|
function __sbD1Check(sql) {
|
|
5359
5424
|
const text = String(sql).trim();
|
|
5360
5425
|
if (!text) throw __sbD1Fail("D1_ERROR: No SQL statements detected.");
|
|
5361
|
-
if (
|
|
5426
|
+
if (__sbD1IsTransaction(text)) throw __sbD1Fail(__sbD1NoTransactions);
|
|
5362
5427
|
}
|
|
5363
5428
|
function __sbD1Meta(meta, started) {
|
|
5364
5429
|
const changes = Number(meta.changes) || 0;
|
|
@@ -5394,70 +5459,91 @@ function __sbD1Promise(run) {
|
|
|
5394
5459
|
return Promise.reject(err);
|
|
5395
5460
|
}
|
|
5396
5461
|
}
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
const
|
|
5405
|
-
|
|
5406
|
-
|
|
5407
|
-
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
5420
|
-
|
|
5421
|
-
|
|
5422
|
-
|
|
5423
|
-
|
|
5424
|
-
|
|
5425
|
-
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5456
|
-
|
|
5457
|
-
|
|
5458
|
-
|
|
5459
|
-
|
|
5462
|
+
// A strict query's reply from the embedded transport's direct lane, or through the generic
|
|
5463
|
+
// dispatch. The errors read the same either way: "D1_ERROR: ..." for SQLite's, and the
|
|
5464
|
+
// transport's own prefix for anything else.
|
|
5465
|
+
function __sbD1Direct(name, sql, params) {
|
|
5466
|
+
try {
|
|
5467
|
+
return __sbD1QueryDirect(name, sql, params);
|
|
5468
|
+
} catch (err) {
|
|
5469
|
+
const message = "sproutboat d1.query: " + (String((err && err.message) || err) || "failed");
|
|
5470
|
+
const at = message.indexOf("D1_");
|
|
5471
|
+
throw new Error(at > 0 ? message.slice(at) : message);
|
|
5472
|
+
}
|
|
5473
|
+
}
|
|
5474
|
+
// The synchronous core: the answer to one statement as columns, rows and meta. meta is only built
|
|
5475
|
+
// when the caller will hand it out (all() and run()): raw() and first() drop it, and building it
|
|
5476
|
+
// reads the clock twice.
|
|
5477
|
+
function __sbD1Query(dbName, sql, params, strict, wantMeta) {
|
|
5478
|
+
if (strict) __sbD1Check(sql);
|
|
5479
|
+
const started = !strict || wantMeta ? Date.now() : 0;
|
|
5480
|
+
const r = strict && __sbD1QueryDirect ? __sbD1Direct(dbName, sql, params) : __sbD1Rpc("d1.query", { db: dbName, sql, params }, strict);
|
|
5481
|
+
if (!strict) return { results: r.results || [], success: true, meta: r.meta || {} };
|
|
5482
|
+
const table = __sbD1Table(r);
|
|
5483
|
+
return { columns: table.columns, rows: table.rows, meta: wantMeta ? __sbD1Meta(r.meta || {}, started) : null };
|
|
5484
|
+
}
|
|
5485
|
+
// A prepared statement. A class, so a prepare() or bind() does not allocate five closures.
|
|
5486
|
+
class __SbD1Statement {
|
|
5487
|
+
constructor(dbName, sql, params) {
|
|
5488
|
+
this.__name = dbName;
|
|
5489
|
+
this.__sql = sql;
|
|
5490
|
+
this.__params = params;
|
|
5491
|
+
}
|
|
5492
|
+
bind() {
|
|
5493
|
+
const strict = __sbD1Strict();
|
|
5494
|
+
const list = [];
|
|
5495
|
+
for (let i = 0; i < arguments.length; i++) list.push(__sbD1Param(arguments[i], strict));
|
|
5496
|
+
return new __SbD1Statement(this.__name, this.__sql, list);
|
|
5497
|
+
}
|
|
5498
|
+
all() {
|
|
5499
|
+
if (!__sbD1Strict()) return __sbD1Query(this.__name, this.__sql, this.__params, false, true);
|
|
5500
|
+
try {
|
|
5501
|
+
const t = __sbD1Query(this.__name, this.__sql, this.__params, true, true);
|
|
5502
|
+
return Promise.resolve({ success: true, meta: t.meta, results: __sbD1Objects(t.columns, t.rows) });
|
|
5503
|
+
} catch (err) {
|
|
5504
|
+
return Promise.reject(err);
|
|
5505
|
+
}
|
|
5506
|
+
}
|
|
5507
|
+
run() {
|
|
5508
|
+
return this.all();
|
|
5509
|
+
}
|
|
5510
|
+
raw(options) {
|
|
5511
|
+
if (!__sbD1Strict()) {
|
|
5512
|
+
const rows = __sbD1Query(this.__name, this.__sql, this.__params, false, true).results;
|
|
5513
|
+
const out = [];
|
|
5514
|
+
for (let i = 0; i < rows.length; i++) {
|
|
5515
|
+
const cols = [];
|
|
5516
|
+
for (const k in rows[i]) cols.push(rows[i][k]);
|
|
5517
|
+
out.push(cols);
|
|
5518
|
+
}
|
|
5519
|
+
return out;
|
|
5520
|
+
}
|
|
5521
|
+
try {
|
|
5522
|
+
const t = __sbD1Query(this.__name, this.__sql, this.__params, true, false);
|
|
5523
|
+
return Promise.resolve(options && options.columnNames ? [t.columns].concat(t.rows) : t.rows);
|
|
5524
|
+
} catch (err) {
|
|
5525
|
+
return Promise.reject(err);
|
|
5526
|
+
}
|
|
5527
|
+
}
|
|
5528
|
+
first(column) {
|
|
5529
|
+
if (!__sbD1Strict()) {
|
|
5530
|
+
const rows = __sbD1Query(this.__name, this.__sql, this.__params, false, true).results;
|
|
5531
|
+
if (rows.length === 0) return null;
|
|
5532
|
+
return column == null ? rows[0] : rows[0][column];
|
|
5533
|
+
}
|
|
5534
|
+
try {
|
|
5535
|
+
const t = __sbD1Query(this.__name, this.__sql, this.__params, true, false);
|
|
5536
|
+
if (column == null) return Promise.resolve(t.rows.length ? __sbD1Objects(t.columns, [t.rows[0]])[0] : null);
|
|
5537
|
+
const at = t.columns.indexOf(String(column));
|
|
5538
|
+
if (!t.rows.length) return Promise.resolve(null);
|
|
5539
|
+
if (at < 0) throw __sbD1Fail("D1_COLUMN_NOTFOUND: Column not found (" + column + ")");
|
|
5540
|
+
return Promise.resolve(t.rows[0][t.columns.lastIndexOf(String(column))]);
|
|
5541
|
+
} catch (err) {
|
|
5542
|
+
return Promise.reject(err);
|
|
5543
|
+
}
|
|
5460
5544
|
}
|
|
5545
|
+
}
|
|
5546
|
+
function __sbMakeD1(dbName) {
|
|
5461
5547
|
function batch(statements) {
|
|
5462
5548
|
const strict = __sbD1Strict();
|
|
5463
5549
|
const list = [];
|
|
@@ -5495,7 +5581,7 @@ function __sbMakeD1(dbName) {
|
|
|
5495
5581
|
}
|
|
5496
5582
|
const db = {
|
|
5497
5583
|
prepare(sql) {
|
|
5498
|
-
return
|
|
5584
|
+
return new __SbD1Statement(dbName, String(sql), []);
|
|
5499
5585
|
},
|
|
5500
5586
|
batch(statements) {
|
|
5501
5587
|
if (!__sbD1Strict()) return batch(statements);
|
|
@@ -9099,7 +9185,7 @@ function __sbInstallWebSockets() {
|
|
|
9099
9185
|
globalThis.WebSocketRequestResponsePair = __SbWebSocketRequestResponsePair;
|
|
9100
9186
|
}
|
|
9101
9187
|
|
|
9102
|
-
async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top) {
|
|
9188
|
+
async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top, cpuCell) {
|
|
9103
9189
|
let r;
|
|
9104
9190
|
try {
|
|
9105
9191
|
r = res && __sbIsFn(res.then) ? await res : res;
|
|
@@ -9111,16 +9197,22 @@ async function __sbFetchWithDrain(res, tasks, bindingCall, rethrow, top) {
|
|
|
9111
9197
|
} catch (error) {
|
|
9112
9198
|
// A Durable Object's stub.fetch() from 2026-10-29: the caller gets the exception.
|
|
9113
9199
|
if (rethrow) {
|
|
9114
|
-
__sbDrainWaitUntil(tasks);
|
|
9200
|
+
if (tasks.length) __sbDrainWaitUntil(tasks);
|
|
9115
9201
|
throw error;
|
|
9116
9202
|
}
|
|
9117
9203
|
// #179 — a rejected handler promise must not take the process down with it.
|
|
9118
9204
|
r = __sbErrorResponse(error, bindingCall);
|
|
9119
9205
|
}
|
|
9206
|
+
// From 2026-11-02 an async handler reports its CPU too: the thread time of the slices in which this
|
|
9207
|
+
// request ran, not its neighbours' (before it, only a handler that answered synchronously did).
|
|
9208
|
+
if (cpuCell) {
|
|
9209
|
+
if (__sbCompatDate() >= "2026-11-02") __sbTagCpu(r, __sbCpuTake(cpuCell));
|
|
9210
|
+
__sbCpuEnd(cpuCell);
|
|
9211
|
+
}
|
|
9120
9212
|
// The response goes out now and ctx.waitUntil work keeps running after it, as in
|
|
9121
9213
|
// workerd; it was awaited first, so a 1.5 s background task delayed the response by
|
|
9122
|
-
// 1.5 s. The 25 s cap still bounds it.
|
|
9123
|
-
__sbDrainWaitUntil(tasks);
|
|
9214
|
+
// 1.5 s. The 25 s cap still bounds it. Not called for no tasks: an async call is a coroutine and a promise.
|
|
9215
|
+
if (tasks.length) __sbDrainWaitUntil(tasks);
|
|
9124
9216
|
return r;
|
|
9125
9217
|
}
|
|
9126
9218
|
|
|
@@ -9151,7 +9243,7 @@ function __sbErrorResponse(error, bindingCall) {
|
|
|
9151
9243
|
// sees it. Returns whether the edge vouched for this one.
|
|
9152
9244
|
function __sbBindingCallIngress(request) {
|
|
9153
9245
|
const headers = request.headers;
|
|
9154
|
-
const want =
|
|
9246
|
+
const want = __sbEdgeToken();
|
|
9155
9247
|
const vouched = !!want && headers.get("x-sb-binding-call") === "1" && __sbEqCt(headers.get("x-sb-edge-token") || "", want);
|
|
9156
9248
|
if (vouched) {
|
|
9157
9249
|
const url = headers.get("x-sb-url");
|
|
@@ -9159,12 +9251,46 @@ function __sbBindingCallIngress(request) {
|
|
|
9159
9251
|
if (url && URL.canParse(url) && __sbCompatDate() >= "2026-10-22") request.url = url;
|
|
9160
9252
|
headers.delete("x-sb-remote-addr");
|
|
9161
9253
|
}
|
|
9162
|
-
|
|
9163
|
-
|
|
9254
|
+
// Nearly every request carries none of the three: one look at the stored names tells, where
|
|
9255
|
+
// three lookups (each validating and lowercasing its name) and an array of them did.
|
|
9256
|
+
const stored = headers._entries;
|
|
9257
|
+
let present = stored === undefined;
|
|
9258
|
+
for (let i = 0; !present && i < stored.length; i += 2) {
|
|
9259
|
+
const key = stored[i];
|
|
9260
|
+
if (key === "x-sb-binding-call" || key === "x-sb-url" || key === "x-sb-edge-token") present = true;
|
|
9261
|
+
}
|
|
9262
|
+
if (present) {
|
|
9263
|
+
for (const name of ["x-sb-binding-call", "x-sb-url", "x-sb-edge-token"]) {
|
|
9264
|
+
if (headers.get(name) != null) headers.delete(name);
|
|
9265
|
+
}
|
|
9164
9266
|
}
|
|
9165
9267
|
return vouched;
|
|
9166
9268
|
}
|
|
9167
9269
|
|
|
9270
|
+
// SB_EDGE_TOKEN is fixed for the life of a process, like SB_TRUSTED_PROXIES above: read once.
|
|
9271
|
+
let __sbEdgeTokenCache;
|
|
9272
|
+
function __sbEdgeToken() {
|
|
9273
|
+
if (__sbEdgeTokenCache === undefined) __sbEdgeTokenCache = __sbEnv("SB_EDGE_TOKEN");
|
|
9274
|
+
return __sbEdgeTokenCache;
|
|
9275
|
+
}
|
|
9276
|
+
|
|
9277
|
+
// One header of a request the native server built, by a name that is already lowercase and valid:
|
|
9278
|
+
// its stored names are scanned directly, where get() validates and lowercases the name first. A
|
|
9279
|
+
// name stored twice, or a headers object that is not the runtime's, goes through get().
|
|
9280
|
+
function __sbHeader(headers, name) {
|
|
9281
|
+
const stored = headers._entries;
|
|
9282
|
+
if (stored === undefined) return headers.get(name);
|
|
9283
|
+
let found = null;
|
|
9284
|
+
let count = 0;
|
|
9285
|
+
for (let i = 0; i < stored.length; i += 2) {
|
|
9286
|
+
if (stored[i] === name) {
|
|
9287
|
+
found = stored[i + 1];
|
|
9288
|
+
count++;
|
|
9289
|
+
}
|
|
9290
|
+
}
|
|
9291
|
+
return count > 1 ? headers.get(name) : found;
|
|
9292
|
+
}
|
|
9293
|
+
|
|
9168
9294
|
function __sbLogUncaught(where, error) {
|
|
9169
9295
|
try {
|
|
9170
9296
|
let text = "";
|
|
@@ -9192,6 +9318,87 @@ globalThis.__sbOnUnhandledRejection = function (reason, promise) {
|
|
|
9192
9318
|
}
|
|
9193
9319
|
};
|
|
9194
9320
|
|
|
9321
|
+
// AsyncLocalStorage (node:async_hooks, as Workers has it: run, getStore, exit and the static snapshot
|
|
9322
|
+
// (no bind, enterWith or disable). The patched Porffor (toolchain async-context.ts) keeps one
|
|
9323
|
+
// "current context" value that every promise reaction, await and timer records when it is created and
|
|
9324
|
+
// sets when it runs. Here that value is a frame: the stores of the AsyncLocalStorage instances
|
|
9325
|
+
// entered so far, in two parallel arrays (a frame is never changed, run() makes a new one).
|
|
9326
|
+
function __sbCtxGet() {
|
|
9327
|
+
let frame;
|
|
9328
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
9329
|
+
Porffor.c`
|
|
9330
|
+
frame = porf_unpack(porf_async_ctx_bits);
|
|
9331
|
+
`;
|
|
9332
|
+
return frame;
|
|
9333
|
+
}
|
|
9334
|
+
// oxlint-disable-next-line no-unused-vars -- `frame` is read inside the RawC block below, not by JS.
|
|
9335
|
+
function __sbCtxSet(frame) {
|
|
9336
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
9337
|
+
Porffor.c`
|
|
9338
|
+
porf_async_ctx_bits = porf_pack(frame);
|
|
9339
|
+
`;
|
|
9340
|
+
}
|
|
9341
|
+
function __sbFrameIndex(frame, als) {
|
|
9342
|
+
if (!frame) return -1;
|
|
9343
|
+
const keys = frame.keys;
|
|
9344
|
+
for (let i = 0; i < keys.length; i++) if (keys[i] === als) return i;
|
|
9345
|
+
return -1;
|
|
9346
|
+
}
|
|
9347
|
+
function __sbFrameWith(frame, als, store) {
|
|
9348
|
+
const keys = [];
|
|
9349
|
+
const vals = [];
|
|
9350
|
+
if (frame) {
|
|
9351
|
+
for (let i = 0; i < frame.keys.length; i++) {
|
|
9352
|
+
if (frame.keys[i] === als) continue;
|
|
9353
|
+
keys.push(frame.keys[i]);
|
|
9354
|
+
vals.push(frame.vals[i]);
|
|
9355
|
+
}
|
|
9356
|
+
}
|
|
9357
|
+
keys.push(als);
|
|
9358
|
+
vals.push(store);
|
|
9359
|
+
return { keys, vals };
|
|
9360
|
+
}
|
|
9361
|
+
function __sbRunInFrame(frame, fn, args) {
|
|
9362
|
+
const previous = __sbCtxGet();
|
|
9363
|
+
__sbCtxSet(frame);
|
|
9364
|
+
try {
|
|
9365
|
+
return fn.apply(undefined, args);
|
|
9366
|
+
} finally {
|
|
9367
|
+
__sbCtxSet(previous);
|
|
9368
|
+
}
|
|
9369
|
+
}
|
|
9370
|
+
// A function constructor, not a class: Porffor refuses `new` on a class reached through an alias
|
|
9371
|
+
// (const { AsyncLocalStorage } = globalThis), which is how a bundled import arrives.
|
|
9372
|
+
function __SbAsyncLocalStorage() {}
|
|
9373
|
+
__SbAsyncLocalStorage.prototype.run = function (store, fn, ...args) {
|
|
9374
|
+
if (!__sbIsFn(fn)) throw new TypeError("The \"callback\" argument must be of type function.");
|
|
9375
|
+
return __sbRunInFrame(__sbFrameWith(__sbCtxGet(), this, store), fn, args);
|
|
9376
|
+
};
|
|
9377
|
+
__SbAsyncLocalStorage.prototype.exit = function (fn, ...args) {
|
|
9378
|
+
if (!__sbIsFn(fn)) throw new TypeError("The \"callback\" argument must be of type function.");
|
|
9379
|
+
return __sbRunInFrame(__sbFrameWith(__sbCtxGet(), this, undefined), fn, args);
|
|
9380
|
+
};
|
|
9381
|
+
__SbAsyncLocalStorage.prototype.getStore = function () {
|
|
9382
|
+
const frame = __sbCtxGet();
|
|
9383
|
+
const at = __sbFrameIndex(frame, this);
|
|
9384
|
+
return at === -1 ? undefined : frame.vals[at];
|
|
9385
|
+
};
|
|
9386
|
+
__SbAsyncLocalStorage.prototype.enterWith = function () {
|
|
9387
|
+
throw new Error("AsyncLocalStorage.enterWith() is not supported");
|
|
9388
|
+
};
|
|
9389
|
+
__SbAsyncLocalStorage.prototype.disable = function () {
|
|
9390
|
+
throw new Error("AsyncLocalStorage.disable() is not supported");
|
|
9391
|
+
};
|
|
9392
|
+
// AsyncLocalStorage.bind(fn) is not provided: Porffor compiles a call of .bind on a function value to
|
|
9393
|
+
// Function.prototype.bind whatever properties the function has. snapshot() does the same job.
|
|
9394
|
+
__SbAsyncLocalStorage.snapshot = function () {
|
|
9395
|
+
const frame = __sbCtxGet();
|
|
9396
|
+
return function (fn, ...args) {
|
|
9397
|
+
return __sbRunInFrame(frame, fn, args);
|
|
9398
|
+
};
|
|
9399
|
+
};
|
|
9400
|
+
globalThis.AsyncLocalStorage = __SbAsyncLocalStorage;
|
|
9401
|
+
|
|
9195
9402
|
// Shared by alarm and scheduled: both reply with an empty 204 once their
|
|
9196
9403
|
// handler (and anything it queued via `waitUntil`) has settled.
|
|
9197
9404
|
async function __sb204WithDrain(res, tasks) {
|
|
@@ -9328,12 +9535,24 @@ function __sbIngressStream(request, id) {
|
|
|
9328
9535
|
request.bytes = async () => await all();
|
|
9329
9536
|
}
|
|
9330
9537
|
|
|
9538
|
+
// The id of the current request's streamed body, 0 when it has none (op "t" of __sbReqBodyOp),
|
|
9539
|
+
// as a number: every request asks, and the text round trip was a format and a parse.
|
|
9540
|
+
function __sbReqBodyTake() {
|
|
9541
|
+
let id = 0;
|
|
9542
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
9543
|
+
Porffor.c`
|
|
9544
|
+
extern unsigned int sb_reqbody_take_current(void);
|
|
9545
|
+
id = (f64)sb_reqbody_take_current();
|
|
9546
|
+
`;
|
|
9547
|
+
return id;
|
|
9548
|
+
}
|
|
9549
|
+
|
|
9331
9550
|
// Native HTTP ingress stores the body as one character per raw byte. Keep
|
|
9332
9551
|
// that representation for binary consumers, but decode text lazily before
|
|
9333
9552
|
// handing the request to user code. Request.text() in Porffor otherwise treats
|
|
9334
9553
|
// the raw bytes as already-decoded characters and corrupts Unicode/HMAC input.
|
|
9335
9554
|
function __sbDecodeIngress(request) {
|
|
9336
|
-
const streamed = __sbCompatDate() >= "2026-10-27" ?
|
|
9555
|
+
const streamed = __sbCompatDate() >= "2026-10-27" ? __sbReqBodyTake() : 0;
|
|
9337
9556
|
if (streamed > 0) {
|
|
9338
9557
|
__sbIngressStream(request, streamed);
|
|
9339
9558
|
return;
|
|
@@ -9369,7 +9588,7 @@ function __sbTrustedProxiesOnce() {
|
|
|
9369
9588
|
|
|
9370
9589
|
globalThis.__sbEntry = function (handlers, request) {
|
|
9371
9590
|
__sbDecodeIngress(request);
|
|
9372
|
-
const trigger = request.headers
|
|
9591
|
+
const trigger = __sbHeader(request.headers, "x-sb-trigger");
|
|
9373
9592
|
if (!trigger) {
|
|
9374
9593
|
const bindingCall = __sbBindingCallIngress(request);
|
|
9375
9594
|
// #163/#128 — expose the request metadata the edge already has, the
|
|
@@ -9379,7 +9598,7 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
9379
9598
|
const __cf = request.cf || {};
|
|
9380
9599
|
if (__sbTrustedProxiesOnce().length === 0) {
|
|
9381
9600
|
// No trusted proxy (the usual case): the peer is the client and no forwarded header counts.
|
|
9382
|
-
__cf.clientIp = __sbNormalizeIp(request.headers
|
|
9601
|
+
__cf.clientIp = __sbNormalizeIp(__sbHeader(request.headers, "x-sb-remote-addr") || "");
|
|
9383
9602
|
} else {
|
|
9384
9603
|
__cf.clientIp = __sbClientIp(request);
|
|
9385
9604
|
const __httpProtocol = __sbTrustedProxyHeader(request, "x-sb-http-protocol");
|
|
@@ -9402,7 +9621,8 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
9402
9621
|
// itself returned, but never one derived from `.then()`, so chaining the
|
|
9403
9622
|
// tag on hangs the request forever. cpuMs is documented as absent for
|
|
9404
9623
|
// async handlers (see LogEvent in services/edge) — that is this.
|
|
9405
|
-
const
|
|
9624
|
+
const __cpuCell = __sbCpuBegin();
|
|
9625
|
+
const __t0 = __cpuCell ? 0 : __sbCpuMs();
|
|
9406
9626
|
const __sbTasks = [];
|
|
9407
9627
|
const ctx = {
|
|
9408
9628
|
waitUntil(p) {
|
|
@@ -9424,14 +9644,21 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
9424
9644
|
} catch (error) {
|
|
9425
9645
|
// #179 — a synchronous throw anywhere in the handler must not take the
|
|
9426
9646
|
// whole process (and every other in-flight request) down with it.
|
|
9647
|
+
__sbCpuEnd(__cpuCell);
|
|
9427
9648
|
return __sbErrorResponse(error, bindingCall);
|
|
9428
9649
|
}
|
|
9429
9650
|
// Same promise-identity rule as above applies to `__sbFetchWithDrain`'s
|
|
9430
9651
|
// own return, which is why it's tail-called rather than chained on here.
|
|
9431
|
-
if (__sbTasks.length || (__res && __sbIsFn(__res.then)) || __sbHasStreamBody(__res)) return __sbFetchWithDrain(__res, __sbTasks, bindingCall, false, true);
|
|
9652
|
+
if (__sbTasks.length || (__res && __sbIsFn(__res.then)) || __sbHasStreamBody(__res)) return __sbFetchWithDrain(__res, __sbTasks, bindingCall, false, true, __cpuCell);
|
|
9432
9653
|
// A WebSocket handshake answer (from 2026-10-30): its client end becomes the host socket.
|
|
9433
|
-
if (__sbHasWebSocket(__res))
|
|
9434
|
-
|
|
9654
|
+
if (__sbHasWebSocket(__res)) {
|
|
9655
|
+
__sbCpuEnd(__cpuCell);
|
|
9656
|
+
return __sbWsHostResponse(__res);
|
|
9657
|
+
}
|
|
9658
|
+
// The request ran alone from start to here: its own slices, or the process delta where no slot was free.
|
|
9659
|
+
const __cpu = __cpuCell ? __sbCpuTake(__cpuCell) : __sbCpuMs() - __t0;
|
|
9660
|
+
__sbCpuEnd(__cpuCell);
|
|
9661
|
+
return __sbTagCpu(__res, __cpu);
|
|
9435
9662
|
}
|
|
9436
9663
|
if (!__sbTriggerAuthed(request)) return new Response("forbidden", { status: 403 });
|
|
9437
9664
|
|