@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.
@@ -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 res = "";
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
- double __ms = (double)__ts.tv_sec * 1000.0 + (double)__ts.tv_nsec / 1000000.0;
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", __ms);
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 === "" ? 0 : parseFloat(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, t0) {
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 >= 0 ? cpu : 0).toFixed(3);
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 (/^(BEGIN|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i.test(text)) throw __sbD1Fail(__sbD1NoTransactions);
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
- function __sbMakeD1(dbName) {
5398
- // The synchronous core: the answer to one statement as columns, rows and meta.
5399
- function query(sql, params, strict) {
5400
- if (strict) __sbD1Check(sql);
5401
- const started = Date.now();
5402
- const r = __sbD1Rpc("d1.query", { db: dbName, sql, params }, strict);
5403
- if (!strict) return { results: r.results || [], success: true, meta: r.meta || {} };
5404
- const table = __sbD1Table(r);
5405
- return { columns: table.columns, rows: table.rows, meta: __sbD1Meta(r.meta || {}, started) };
5406
- }
5407
- function stmt(sql, params) {
5408
- const s = {
5409
- __sql: sql,
5410
- __params: params,
5411
- bind() {
5412
- const strict = __sbD1Strict();
5413
- const list = [];
5414
- for (let i = 0; i < arguments.length; i++) list.push(__sbD1Param(arguments[i], strict));
5415
- return stmt(sql, list);
5416
- },
5417
- all() {
5418
- if (!__sbD1Strict()) return query(sql, params, false);
5419
- return __sbD1Promise(() => {
5420
- const t = query(sql, params, true);
5421
- return { success: true, meta: t.meta, results: __sbD1Objects(t.columns, t.rows) };
5422
- });
5423
- },
5424
- run() {
5425
- return s.all();
5426
- },
5427
- raw(options) {
5428
- if (!__sbD1Strict()) {
5429
- const rows = query(sql, params, false).results;
5430
- const out = [];
5431
- for (let i = 0; i < rows.length; i++) {
5432
- const cols = [];
5433
- for (const k in rows[i]) cols.push(rows[i][k]);
5434
- out.push(cols);
5435
- }
5436
- return out;
5437
- }
5438
- return __sbD1Promise(() => {
5439
- const t = query(sql, params, true);
5440
- return options && options.columnNames ? [t.columns].concat(t.rows) : t.rows;
5441
- });
5442
- },
5443
- first(column) {
5444
- if (!__sbD1Strict()) {
5445
- const rows = query(sql, params, false).results;
5446
- if (rows.length === 0) return null;
5447
- return column == null ? rows[0] : rows[0][column];
5448
- }
5449
- return __sbD1Promise(() => {
5450
- const t = query(sql, params, true);
5451
- if (column == null) return t.rows.length ? __sbD1Objects(t.columns, [t.rows[0]])[0] : null;
5452
- const at = t.columns.indexOf(String(column));
5453
- if (!t.rows.length) return null;
5454
- if (at < 0) throw __sbD1Fail("D1_COLUMN_NOTFOUND: Column not found (" + column + ")");
5455
- return t.rows[0][t.columns.lastIndexOf(String(column))];
5456
- });
5457
- },
5458
- };
5459
- return s;
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 stmt(String(sql), []);
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 = __sbEnv("SB_EDGE_TOKEN");
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
- for (const name of ["x-sb-binding-call", "x-sb-url", "x-sb-edge-token"]) {
9163
- if (headers.get(name) != null) headers.delete(name);
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" ? Number(__sbReqBodyOp("t", 0, "")) : 0;
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.get("x-sb-trigger");
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.get("x-sb-remote-addr") || "");
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 __t0 = __sbCpuMs();
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)) return __sbWsHostResponse(__res);
9434
- return __sbTagCpu(__res, __t0);
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