sproutboat 0.5.0 → 0.6.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/README.md +67 -78
- package/SURFACE.md +1 -1
- package/package.json +25 -20
- package/src/assets.ts +29 -9
- package/src/broker.ts +143 -65
- package/src/build.ts +23 -5
- package/src/bundle.ts +2 -1
- package/src/compile.ts +9 -4
- package/src/config.ts +71 -29
- package/src/dev.ts +33 -16
- package/src/json.ts +9 -1
- package/src/main.ts +274 -77
- package/src/manifest.ts +63 -14
- package/src/native-fetch-prelude.js +256 -127
- package/src/patch-porffor.ts +5 -2
- package/src/report.ts +37 -17
- package/src/source.ts +4 -1
- package/src/style.ts +9 -6
- package/src/surface.ts +150 -42
- package/src/toolchain.ts +15 -4
- package/src/update-check.ts +20 -5
- package/src/wrap.ts +32 -8
|
@@ -14,15 +14,22 @@
|
|
|
14
14
|
// Duck-typing helpers, `typeof`-free (the repo's anti-slop lint bans `typeof`;
|
|
15
15
|
// these express the same spec-mandated checks and are verified under Porffor
|
|
16
16
|
// alpha-4 by examples/kitchen-sink/harness.ts).
|
|
17
|
-
function __sbIsStr(v) {
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
function __sbIsStr(v) {
|
|
18
|
+
return Object(v) !== v && v === String(v);
|
|
19
|
+
}
|
|
20
|
+
function __sbIsFn(v) {
|
|
21
|
+
return v instanceof Function;
|
|
22
|
+
}
|
|
23
|
+
function __sbIsObj(v) {
|
|
24
|
+
return v !== null && Object(v) === v;
|
|
25
|
+
}
|
|
20
26
|
|
|
21
27
|
// #41 — cold-start phase marker. Runs as the first thing in the bundle: writes
|
|
22
28
|
// the current wall-clock ms to $SB_STARTUP_FILE so the supervisor can split
|
|
23
29
|
// cold-start into "spawn -> JS starts" (process + runtime bootstrap) and
|
|
24
30
|
// "JS starts -> listening" (module eval + server bind). No-op when unset.
|
|
25
31
|
function __sbStartupMark() {
|
|
32
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
26
33
|
Porffor.c`
|
|
27
34
|
const char* __f = getenv("SB_STARTUP_FILE");
|
|
28
35
|
if (__f) {
|
|
@@ -43,7 +50,8 @@ __sbStartupMark();
|
|
|
43
50
|
// then parsed — Porffor's number boxing for a bare inline-C assignment is not
|
|
44
51
|
// relied on. `__sbEntry` samples it around the handler for per-invocation CPU.
|
|
45
52
|
function __sbCpuMs() {
|
|
46
|
-
let res =
|
|
53
|
+
let res = "";
|
|
54
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
47
55
|
Porffor.c`
|
|
48
56
|
struct timespec __ts;
|
|
49
57
|
clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &__ts);
|
|
@@ -52,7 +60,7 @@ function __sbCpuMs() {
|
|
|
52
60
|
int __n = snprintf(__b, sizeof(__b), "%.3f", __ms);
|
|
53
61
|
if (__n > 0) res = porf_box((f64)porf_native_fetch_alloc_bytestring(__b, (size_t)__n), 195);
|
|
54
62
|
`;
|
|
55
|
-
return res ===
|
|
63
|
+
return res === "" ? 0 : parseFloat(res);
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
// #28 — stamp `x-sb-cpu-ms` onto a handler Response. Porffor alpha-4's
|
|
@@ -66,13 +74,17 @@ function __sbTagCpu(res, t0) {
|
|
|
66
74
|
const cpu = __sbCpuMs() - t0;
|
|
67
75
|
try {
|
|
68
76
|
const body = res && res.body;
|
|
69
|
-
if (__sbIsStr(body) && !res.headers.has(
|
|
77
|
+
if (__sbIsStr(body) && !res.headers.has("set-cookie")) {
|
|
70
78
|
const headers = {};
|
|
71
|
-
res.headers.forEach(function (value, name) {
|
|
72
|
-
|
|
79
|
+
res.headers.forEach(function (value, name) {
|
|
80
|
+
headers[name] = value;
|
|
81
|
+
});
|
|
82
|
+
headers["x-sb-cpu-ms"] = (cpu >= 0 ? cpu : 0).toFixed(3);
|
|
73
83
|
return new Response(body, { status: res.status, headers: headers });
|
|
74
84
|
}
|
|
75
|
-
} catch {
|
|
85
|
+
} catch {
|
|
86
|
+
/* fall through to the original response */
|
|
87
|
+
}
|
|
76
88
|
return res;
|
|
77
89
|
}
|
|
78
90
|
|
|
@@ -80,53 +92,87 @@ class __SproutboatURLSearchParams {
|
|
|
80
92
|
constructor(init) {
|
|
81
93
|
this._keys = [];
|
|
82
94
|
this._vals = [];
|
|
83
|
-
let raw = init == null ?
|
|
95
|
+
let raw = init == null ? "" : String(init);
|
|
84
96
|
if (raw.charCodeAt(0) === 63) raw = raw.slice(1); // strip a leading '?'
|
|
85
97
|
if (raw.length === 0) return;
|
|
86
|
-
const pairs = raw.split(
|
|
98
|
+
const pairs = raw.split("&");
|
|
87
99
|
for (let i = 0; i < pairs.length; i++) {
|
|
88
100
|
const pair = pairs[i];
|
|
89
101
|
if (pair.length === 0) continue;
|
|
90
|
-
const eq = pair.indexOf(
|
|
102
|
+
const eq = pair.indexOf("=");
|
|
91
103
|
const k = eq === -1 ? pair : pair.slice(0, eq);
|
|
92
|
-
const v = eq === -1 ?
|
|
93
|
-
this._keys.push(decodeURIComponent(k.split(
|
|
94
|
-
this._vals.push(decodeURIComponent(v.split(
|
|
104
|
+
const v = eq === -1 ? "" : pair.slice(eq + 1);
|
|
105
|
+
this._keys.push(decodeURIComponent(k.split("+").join(" ")));
|
|
106
|
+
this._vals.push(decodeURIComponent(v.split("+").join(" ")));
|
|
95
107
|
}
|
|
96
108
|
}
|
|
97
|
-
get(name) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
109
|
+
get(name) {
|
|
110
|
+
for (let i = 0; i < this._keys.length; i++) if (this._keys[i] === name) return this._vals[i];
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
getAll(name) {
|
|
114
|
+
const out = [];
|
|
115
|
+
for (let i = 0; i < this._keys.length; i++) if (this._keys[i] === name) out.push(this._vals[i]);
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
has(name) {
|
|
119
|
+
for (let i = 0; i < this._keys.length; i++) if (this._keys[i] === name) return true;
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
forEach(cb) {
|
|
123
|
+
for (let i = 0; i < this._keys.length; i++) cb(this._vals[i], this._keys[i], this);
|
|
124
|
+
}
|
|
101
125
|
// Mutators: standalone `new URLSearchParams()` building works. They do NOT
|
|
102
126
|
// write back into a URL's `search` (Porffor's URL has no setter) — build the
|
|
103
127
|
// string with toString() and assign it yourself.
|
|
104
|
-
append(name, value) {
|
|
128
|
+
append(name, value) {
|
|
129
|
+
this._keys.push(String(name));
|
|
130
|
+
this._vals.push(String(value));
|
|
131
|
+
}
|
|
105
132
|
set(name, value) {
|
|
106
133
|
let found = false;
|
|
107
134
|
for (let i = 0; i < this._keys.length; i++) {
|
|
108
135
|
if (this._keys[i] !== name) continue;
|
|
109
|
-
if (found) {
|
|
110
|
-
|
|
136
|
+
if (found) {
|
|
137
|
+
this._keys.splice(i, 1);
|
|
138
|
+
this._vals.splice(i, 1);
|
|
139
|
+
i--;
|
|
140
|
+
} else {
|
|
141
|
+
this._vals[i] = String(value);
|
|
142
|
+
found = true;
|
|
143
|
+
}
|
|
111
144
|
}
|
|
112
145
|
if (!found) this.append(name, value);
|
|
113
146
|
}
|
|
114
147
|
delete(name) {
|
|
115
|
-
for (let i = 0; i < this._keys.length; i++)
|
|
148
|
+
for (let i = 0; i < this._keys.length; i++)
|
|
149
|
+
if (this._keys[i] === name) {
|
|
150
|
+
this._keys.splice(i, 1);
|
|
151
|
+
this._vals.splice(i, 1);
|
|
152
|
+
i--;
|
|
153
|
+
}
|
|
116
154
|
}
|
|
117
155
|
sort() {
|
|
118
|
-
const idx = this._keys
|
|
156
|
+
const idx = this._keys
|
|
157
|
+
.map((_, i) => i)
|
|
158
|
+
.sort((a, b) => (this._keys[a] < this._keys[b] ? -1 : this._keys[a] > this._keys[b] ? 1 : 0));
|
|
119
159
|
this._keys = idx.map((i) => this._keys[i]);
|
|
120
160
|
this._vals = idx.map((i) => this._vals[i]);
|
|
121
161
|
}
|
|
122
|
-
keys() {
|
|
123
|
-
|
|
124
|
-
|
|
162
|
+
keys() {
|
|
163
|
+
return this._keys.slice();
|
|
164
|
+
}
|
|
165
|
+
values() {
|
|
166
|
+
return this._vals.slice();
|
|
167
|
+
}
|
|
168
|
+
get size() {
|
|
169
|
+
return this._keys.length;
|
|
170
|
+
}
|
|
125
171
|
toString() {
|
|
126
|
-
let out =
|
|
172
|
+
let out = "";
|
|
127
173
|
for (let i = 0; i < this._keys.length; i++) {
|
|
128
|
-
if (i > 0) out +=
|
|
129
|
-
out += encodeURIComponent(this._keys[i]) +
|
|
174
|
+
if (i > 0) out += "&";
|
|
175
|
+
out += encodeURIComponent(this._keys[i]) + "=" + encodeURIComponent(this._vals[i]);
|
|
130
176
|
}
|
|
131
177
|
return out;
|
|
132
178
|
}
|
|
@@ -135,8 +181,8 @@ class __SproutboatURLSearchParams {
|
|
|
135
181
|
// URL and Response are always defined by Porffor's fetch-globals.js banner.
|
|
136
182
|
if (globalThis.URLSearchParams == null) globalThis.URLSearchParams = __SproutboatURLSearchParams;
|
|
137
183
|
|
|
138
|
-
if (!(
|
|
139
|
-
Object.defineProperty(URL.prototype,
|
|
184
|
+
if (!("searchParams" in URL.prototype)) {
|
|
185
|
+
Object.defineProperty(URL.prototype, "searchParams", {
|
|
140
186
|
configurable: true,
|
|
141
187
|
get() {
|
|
142
188
|
if (this.__sbSearchParams == null) this.__sbSearchParams = new __SproutboatURLSearchParams(this.search);
|
|
@@ -148,7 +194,7 @@ if (!('searchParams' in URL.prototype)) {
|
|
|
148
194
|
if (Response.json == null) {
|
|
149
195
|
Response.json = function (data, init) {
|
|
150
196
|
const response = new Response(JSON.stringify(data), init);
|
|
151
|
-
if (!response.headers.has(
|
|
197
|
+
if (!response.headers.has("content-type")) response.headers.set("content-type", "application/json;charset=utf-8");
|
|
152
198
|
return response;
|
|
153
199
|
};
|
|
154
200
|
}
|
|
@@ -160,27 +206,33 @@ if (Response.json == null) {
|
|
|
160
206
|
function __sbDefineURLAccessor(name, get) {
|
|
161
207
|
if (!(name in URL.prototype)) Object.defineProperty(URL.prototype, name, { configurable: true, get });
|
|
162
208
|
}
|
|
163
|
-
__sbDefineURLAccessor(
|
|
164
|
-
const i = this.origin.indexOf(
|
|
165
|
-
return i === -1 ?
|
|
209
|
+
__sbDefineURLAccessor("protocol", function () {
|
|
210
|
+
const i = this.origin.indexOf("://");
|
|
211
|
+
return i === -1 ? "" : this.origin.slice(0, i + 1);
|
|
166
212
|
});
|
|
167
|
-
__sbDefineURLAccessor(
|
|
168
|
-
const i = this.origin.indexOf(
|
|
169
|
-
return i === -1 ?
|
|
213
|
+
__sbDefineURLAccessor("host", function () {
|
|
214
|
+
const i = this.origin.indexOf("://");
|
|
215
|
+
return i === -1 ? "" : this.origin.slice(i + 3);
|
|
170
216
|
});
|
|
171
|
-
__sbDefineURLAccessor(
|
|
217
|
+
__sbDefineURLAccessor("hostname", function () {
|
|
172
218
|
const h = this.host;
|
|
173
|
-
const c = h.indexOf(
|
|
219
|
+
const c = h.indexOf(":");
|
|
174
220
|
return c === -1 ? h : h.slice(0, c);
|
|
175
221
|
});
|
|
176
|
-
__sbDefineURLAccessor(
|
|
222
|
+
__sbDefineURLAccessor("port", function () {
|
|
177
223
|
const h = this.host;
|
|
178
|
-
const c = h.indexOf(
|
|
179
|
-
return c === -1 ?
|
|
224
|
+
const c = h.indexOf(":");
|
|
225
|
+
return c === -1 ? "" : h.slice(c + 1);
|
|
226
|
+
});
|
|
227
|
+
__sbDefineURLAccessor("hash", function () {
|
|
228
|
+
return "";
|
|
229
|
+
});
|
|
230
|
+
__sbDefineURLAccessor("username", function () {
|
|
231
|
+
return "";
|
|
232
|
+
});
|
|
233
|
+
__sbDefineURLAccessor("password", function () {
|
|
234
|
+
return "";
|
|
180
235
|
});
|
|
181
|
-
__sbDefineURLAccessor('hash', function () { return ''; });
|
|
182
|
-
__sbDefineURLAccessor('username', function () { return ''; });
|
|
183
|
-
__sbDefineURLAccessor('password', function () { return ''; });
|
|
184
236
|
|
|
185
237
|
// crypto.randomUUID / getRandomValues are absent in native-fetch. Provide them
|
|
186
238
|
// backed by the OS CSPRNG (`__sbRandomBytes` -> inline C -> /dev/urandom), so
|
|
@@ -207,7 +259,9 @@ if (globalThis.structuredClone == null) {
|
|
|
207
259
|
// The suggested fix (use structuredClone) is circular — this IS the polyfill,
|
|
208
260
|
// and Porffor exposes no other deep-clone primitive.
|
|
209
261
|
// react-doctor-disable-next-line react-doctor/no-json-parse-stringify-clone
|
|
210
|
-
globalThis.structuredClone = function (value) {
|
|
262
|
+
globalThis.structuredClone = function (value) {
|
|
263
|
+
return JSON.parse(JSON.stringify(value));
|
|
264
|
+
};
|
|
211
265
|
}
|
|
212
266
|
|
|
213
267
|
if (globalThis.crypto.randomUUID == null) {
|
|
@@ -239,6 +293,7 @@ if (globalThis.crypto.randomUUID == null) {
|
|
|
239
293
|
// the old fresh-connection-per-call path just failed the call there instead.
|
|
240
294
|
// Binary values + AF_UNIX = v2.
|
|
241
295
|
|
|
296
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
242
297
|
Porffor.c`
|
|
243
298
|
#include <sys/socket.h>
|
|
244
299
|
#include <netinet/in.h>
|
|
@@ -366,8 +421,10 @@ static int sb_broker_roundtrip(const char* req, size_t req_len, char** resp_out,
|
|
|
366
421
|
|
|
367
422
|
// One request string in, one reply string out. `reqJson` is a parameter, so the
|
|
368
423
|
// generated C names it directly in the RawC block below.
|
|
424
|
+
// oxlint-disable-next-line no-unused-vars -- `reqJson` is read inside the RawC block below, not by JS.
|
|
369
425
|
function __sbCall(reqJson) {
|
|
370
|
-
let res =
|
|
426
|
+
let res = "";
|
|
427
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
371
428
|
Porffor.c`
|
|
372
429
|
const char* __req; size_t __reqlen; char* __reqowned = 0;
|
|
373
430
|
porf_native_fetch_read_value(reqJson, &__req, &__reqlen, &__reqowned);
|
|
@@ -388,8 +445,10 @@ function __sbCall(reqJson) {
|
|
|
388
445
|
|
|
389
446
|
// `nStr` is the decimal byte count as a string (same string-param pattern as
|
|
390
447
|
// __sbEnv). Returns a bytestring of that many CSPRNG bytes, or '' on failure.
|
|
448
|
+
// oxlint-disable-next-line no-unused-vars -- `nStr` is read inside the RawC block below, not by JS.
|
|
391
449
|
function __sbRandomBytes(nStr) {
|
|
392
|
-
let out =
|
|
450
|
+
let out = "";
|
|
451
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
393
452
|
Porffor.c`
|
|
394
453
|
const char* __ns; size_t __nsl; char* __nso = 0;
|
|
395
454
|
porf_native_fetch_read_value(nStr, &__ns, &__nsl, &__nso);
|
|
@@ -414,7 +473,7 @@ function __sbRpc(op, extra) {
|
|
|
414
473
|
const req = { op };
|
|
415
474
|
if (extra) for (const k in extra) req[k] = extra[k];
|
|
416
475
|
const reply = JSON.parse(__sbCall(JSON.stringify(req)));
|
|
417
|
-
if (reply && reply.ok === false) throw new Error(`sproutboat ${op}: ${reply.error ||
|
|
476
|
+
if (reply && reply.ok === false) throw new Error(`sproutboat ${op}: ${reply.error || "failed"}`);
|
|
418
477
|
return reply;
|
|
419
478
|
}
|
|
420
479
|
|
|
@@ -425,12 +484,16 @@ function __sbMakeD1(dbName) {
|
|
|
425
484
|
const s = {
|
|
426
485
|
__sql: sql,
|
|
427
486
|
__params: params,
|
|
428
|
-
bind() {
|
|
487
|
+
bind() {
|
|
488
|
+
return stmt(sql, Array.prototype.slice.call(arguments));
|
|
489
|
+
},
|
|
429
490
|
all() {
|
|
430
|
-
const r = __sbRpc(
|
|
491
|
+
const r = __sbRpc("d1.query", { db: dbName, sql, params });
|
|
431
492
|
return { results: r.results || [], success: true, meta: r.meta || {} };
|
|
432
493
|
},
|
|
433
|
-
run() {
|
|
494
|
+
run() {
|
|
495
|
+
return s.all();
|
|
496
|
+
},
|
|
434
497
|
raw() {
|
|
435
498
|
const rows = s.all().results;
|
|
436
499
|
const out = [];
|
|
@@ -450,17 +513,21 @@ function __sbMakeD1(dbName) {
|
|
|
450
513
|
return s;
|
|
451
514
|
}
|
|
452
515
|
return {
|
|
453
|
-
prepare(sql) {
|
|
516
|
+
prepare(sql) {
|
|
517
|
+
return stmt(String(sql), []);
|
|
518
|
+
},
|
|
454
519
|
batch(statements) {
|
|
455
520
|
const list = [];
|
|
456
|
-
for (let i = 0; i < (statements || []).length; i++)
|
|
457
|
-
|
|
521
|
+
for (let i = 0; i < (statements || []).length; i++)
|
|
522
|
+
list.push({ sql: statements[i].__sql, params: statements[i].__params });
|
|
523
|
+
const r = __sbRpc("d1.batch", { db: dbName, statements: list });
|
|
458
524
|
const out = [];
|
|
459
|
-
for (let i = 0; i < (r.results || []).length; i++)
|
|
525
|
+
for (let i = 0; i < (r.results || []).length; i++)
|
|
526
|
+
out.push({ results: r.results[i].results || [], success: true, meta: r.results[i].meta || {} });
|
|
460
527
|
return out;
|
|
461
528
|
},
|
|
462
529
|
exec(sql) {
|
|
463
|
-
__sbRpc(
|
|
530
|
+
__sbRpc("d1.exec", { db: dbName, sql: String(sql) });
|
|
464
531
|
return { count: (String(sql).match(/;/g) || []).length, duration: 0 };
|
|
465
532
|
},
|
|
466
533
|
};
|
|
@@ -480,8 +547,12 @@ function __sbR2Object(meta, body) {
|
|
|
480
547
|
};
|
|
481
548
|
if (body != null) {
|
|
482
549
|
obj.body = body;
|
|
483
|
-
obj.text = function () {
|
|
484
|
-
|
|
550
|
+
obj.text = function () {
|
|
551
|
+
return body;
|
|
552
|
+
};
|
|
553
|
+
obj.json = function () {
|
|
554
|
+
return JSON.parse(body);
|
|
555
|
+
};
|
|
485
556
|
}
|
|
486
557
|
return obj;
|
|
487
558
|
}
|
|
@@ -497,17 +568,17 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
497
568
|
const ns = bindings.kv[i];
|
|
498
569
|
target[ns] = {
|
|
499
570
|
get(key) {
|
|
500
|
-
const r = __sbRpc(
|
|
571
|
+
const r = __sbRpc("kv.get", { ns, key: String(key) });
|
|
501
572
|
return r.found ? r.value : null;
|
|
502
573
|
},
|
|
503
574
|
put(key, value) {
|
|
504
|
-
__sbRpc(
|
|
575
|
+
__sbRpc("kv.put", { ns, key: String(key), value: String(value) });
|
|
505
576
|
},
|
|
506
577
|
delete(key) {
|
|
507
|
-
__sbRpc(
|
|
578
|
+
__sbRpc("kv.delete", { ns, key: String(key) });
|
|
508
579
|
},
|
|
509
580
|
list(prefix) {
|
|
510
|
-
return __sbRpc(
|
|
581
|
+
return __sbRpc("kv.list", { ns, prefix: prefix == null ? "" : String(prefix) }).keys || [];
|
|
511
582
|
},
|
|
512
583
|
};
|
|
513
584
|
}
|
|
@@ -521,7 +592,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
521
592
|
Object.defineProperty(target, name, {
|
|
522
593
|
configurable: true,
|
|
523
594
|
get() {
|
|
524
|
-
const value = __sbRpc(
|
|
595
|
+
const value = __sbRpc("secret.get", { name }).value;
|
|
525
596
|
Object.defineProperty(target, name, { value, configurable: true, enumerable: true });
|
|
526
597
|
return value;
|
|
527
598
|
},
|
|
@@ -538,31 +609,31 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
538
609
|
target[name] = {
|
|
539
610
|
put(key, value, options) {
|
|
540
611
|
const o = options || {};
|
|
541
|
-
return __sbRpc(
|
|
612
|
+
return __sbRpc("r2.put", {
|
|
542
613
|
bucket: name,
|
|
543
614
|
key: String(key),
|
|
544
|
-
body: value == null ?
|
|
615
|
+
body: value == null ? "" : String(value),
|
|
545
616
|
httpMetadata: o.httpMetadata || {},
|
|
546
617
|
customMetadata: o.customMetadata || {},
|
|
547
618
|
}).object;
|
|
548
619
|
},
|
|
549
620
|
get(key) {
|
|
550
|
-
const r = __sbRpc(
|
|
551
|
-
return r.found ? __sbR2Object(r.object, r.body == null ?
|
|
621
|
+
const r = __sbRpc("r2.get", { bucket: name, key: String(key) });
|
|
622
|
+
return r.found ? __sbR2Object(r.object, r.body == null ? "" : r.body) : null;
|
|
552
623
|
},
|
|
553
624
|
head(key) {
|
|
554
|
-
const r = __sbRpc(
|
|
625
|
+
const r = __sbRpc("r2.head", { bucket: name, key: String(key) });
|
|
555
626
|
return r.found ? __sbR2Object(r.object, null) : null;
|
|
556
627
|
},
|
|
557
628
|
delete(key) {
|
|
558
|
-
__sbRpc(
|
|
629
|
+
__sbRpc("r2.delete", { bucket: name, key: String(key) });
|
|
559
630
|
},
|
|
560
631
|
list(options) {
|
|
561
632
|
const o = options || {};
|
|
562
|
-
const r = __sbRpc(
|
|
633
|
+
const r = __sbRpc("r2.list", {
|
|
563
634
|
bucket: name,
|
|
564
|
-
prefix: o.prefix == null ?
|
|
565
|
-
cursor: o.cursor == null ?
|
|
635
|
+
prefix: o.prefix == null ? "" : String(o.prefix),
|
|
636
|
+
cursor: o.cursor == null ? "" : String(o.cursor),
|
|
566
637
|
limit: o.limit == null ? 1000 : o.limit,
|
|
567
638
|
});
|
|
568
639
|
const objects = [];
|
|
@@ -577,15 +648,19 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
577
648
|
target[name] = {
|
|
578
649
|
send(body, options) {
|
|
579
650
|
const o = options || {};
|
|
580
|
-
__sbRpc(
|
|
651
|
+
__sbRpc("queue.send", {
|
|
652
|
+
queue: name,
|
|
653
|
+
body: __sbIsStr(body) ? body : JSON.stringify(body),
|
|
654
|
+
delaySeconds: o.delaySeconds || 0,
|
|
655
|
+
});
|
|
581
656
|
},
|
|
582
657
|
sendBatch(messages) {
|
|
583
658
|
const list = [];
|
|
584
659
|
for (let j = 0; j < (messages || []).length; j++) {
|
|
585
660
|
const m = messages[j];
|
|
586
|
-
list.push({ body: __sbIsStr(m.body) ? m.body : JSON.stringify(m.body), delaySeconds:
|
|
661
|
+
list.push({ body: __sbIsStr(m.body) ? m.body : JSON.stringify(m.body), delaySeconds: m.delaySeconds || 0 });
|
|
587
662
|
}
|
|
588
|
-
__sbRpc(
|
|
663
|
+
__sbRpc("queue.send_batch", { queue: name, messages: list });
|
|
589
664
|
},
|
|
590
665
|
};
|
|
591
666
|
}
|
|
@@ -595,7 +670,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
595
670
|
target[name] = {
|
|
596
671
|
writeDataPoint(event) {
|
|
597
672
|
const e = event || {};
|
|
598
|
-
__sbRpc(
|
|
673
|
+
__sbRpc("ae.write", {
|
|
599
674
|
dataset: name,
|
|
600
675
|
indexes: e.indexes || [],
|
|
601
676
|
blobs: e.blobs || [],
|
|
@@ -606,7 +681,7 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
606
681
|
// query it via the SQL API). Returns { count, rows }.
|
|
607
682
|
query(options) {
|
|
608
683
|
const o = options || {};
|
|
609
|
-
return __sbRpc(
|
|
684
|
+
return __sbRpc("ae.query", { dataset: name, limit: o.limit || 20 });
|
|
610
685
|
},
|
|
611
686
|
};
|
|
612
687
|
}
|
|
@@ -623,13 +698,17 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
623
698
|
if (bindings.assets) {
|
|
624
699
|
target[bindings.assets] = {
|
|
625
700
|
fetch(input) {
|
|
626
|
-
let path = __sbIsStr(input) ? input : String(input && input.url ||
|
|
627
|
-
try {
|
|
628
|
-
|
|
701
|
+
let path = __sbIsStr(input) ? input : String((input && input.url) || "/");
|
|
702
|
+
try {
|
|
703
|
+
path = new URL(path, "http://a").pathname;
|
|
704
|
+
} catch {
|
|
705
|
+
/* use as-is */
|
|
706
|
+
}
|
|
707
|
+
const r = __sbRpc("assets.get", { path });
|
|
629
708
|
const headers = {};
|
|
630
|
-
if (r.type) headers[
|
|
631
|
-
if (r.found) headers[
|
|
632
|
-
return new Response(r.body == null ?
|
|
709
|
+
if (r.type) headers["content-type"] = r.type;
|
|
710
|
+
if (r.found) headers["etag"] = '"' + r.hash + '"';
|
|
711
|
+
return new Response(r.body == null ? "" : r.body, { status: r.status || (r.found ? 200 : 404), headers });
|
|
633
712
|
},
|
|
634
713
|
};
|
|
635
714
|
}
|
|
@@ -643,15 +722,15 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
643
722
|
if (__sbIsFn(opts.headers.forEach)) opts.headers.forEach((v, k) => headers.push([k, v]));
|
|
644
723
|
else for (const k in opts.headers) headers.push([k, opts.headers[k]]);
|
|
645
724
|
}
|
|
646
|
-
const r = __sbRpc(
|
|
725
|
+
const r = __sbRpc("fetch", {
|
|
647
726
|
url,
|
|
648
|
-
method: opts.method ||
|
|
727
|
+
method: opts.method || "GET",
|
|
649
728
|
headers,
|
|
650
729
|
body: opts.body == null ? null : String(opts.body),
|
|
651
730
|
});
|
|
652
731
|
const respHeaders = new Headers();
|
|
653
732
|
for (let j = 0; j < (r.headers || []).length; j++) respHeaders.set(r.headers[j][0], r.headers[j][1]);
|
|
654
|
-
return new Response(r.body == null ?
|
|
733
|
+
return new Response(r.body == null ? "" : r.body, { status: r.status || 502, headers: respHeaders });
|
|
655
734
|
};
|
|
656
735
|
}
|
|
657
736
|
};
|
|
@@ -669,10 +748,27 @@ globalThis.__sbInstallBindings = function (target, bindings) {
|
|
|
669
748
|
|
|
670
749
|
function __sbMakeDONamespace(binding, className) {
|
|
671
750
|
return {
|
|
672
|
-
idFromName(name) {
|
|
673
|
-
|
|
751
|
+
idFromName(name) {
|
|
752
|
+
return {
|
|
753
|
+
toString() {
|
|
754
|
+
return "name:" + String(name);
|
|
755
|
+
},
|
|
756
|
+
name: String(name),
|
|
757
|
+
};
|
|
758
|
+
},
|
|
759
|
+
idFromString(hex) {
|
|
760
|
+
return {
|
|
761
|
+
toString() {
|
|
762
|
+
return String(hex);
|
|
763
|
+
},
|
|
764
|
+
};
|
|
765
|
+
},
|
|
674
766
|
newUniqueId() {
|
|
675
|
-
return {
|
|
767
|
+
return {
|
|
768
|
+
toString() {
|
|
769
|
+
return "uid:" + crypto.randomUUID();
|
|
770
|
+
},
|
|
771
|
+
};
|
|
676
772
|
},
|
|
677
773
|
get(id) {
|
|
678
774
|
const idStr = __sbIsStr(id) ? id : id.toString();
|
|
@@ -682,14 +778,14 @@ function __sbMakeDONamespace(binding, className) {
|
|
|
682
778
|
if (__sbIsObj(input) && __sbIsStr(input.url) && !init) {
|
|
683
779
|
req = input;
|
|
684
780
|
} else {
|
|
685
|
-
const url = __sbIsStr(input) ? input : String((input && input.url) ||
|
|
781
|
+
const url = __sbIsStr(input) ? input : String((input && input.url) || "https://do/");
|
|
686
782
|
const opts = init || {};
|
|
687
783
|
const headers = new Headers();
|
|
688
784
|
if (opts.headers) {
|
|
689
785
|
if (__sbIsFn(opts.headers.forEach)) opts.headers.forEach((v, k) => headers.set(k, v));
|
|
690
786
|
else for (const k in opts.headers) headers.set(k, opts.headers[k]);
|
|
691
787
|
}
|
|
692
|
-
req = new Request(url, { method: opts.method ||
|
|
788
|
+
req = new Request(url, { method: opts.method || "GET", headers });
|
|
693
789
|
if (opts.body != null) req.body = String(opts.body);
|
|
694
790
|
}
|
|
695
791
|
return __sbGetDOInstance(className, idStr).fetch(req);
|
|
@@ -707,14 +803,20 @@ globalThis.__sbRegisterDO = function (map) {
|
|
|
707
803
|
|
|
708
804
|
function __sbGetDOInstance(cls, id) {
|
|
709
805
|
const Ctor = __sbDOClasses[cls];
|
|
710
|
-
if (!Ctor) throw new Error(
|
|
711
|
-
const cacheKey = cls +
|
|
806
|
+
if (!Ctor) throw new Error("no such Durable Object class: " + cls);
|
|
807
|
+
const cacheKey = cls + " " + id;
|
|
712
808
|
let inst = __sbDOInstances[cacheKey];
|
|
713
809
|
if (!inst) {
|
|
714
810
|
const state = {
|
|
715
|
-
id: {
|
|
811
|
+
id: {
|
|
812
|
+
toString() {
|
|
813
|
+
return id;
|
|
814
|
+
},
|
|
815
|
+
},
|
|
716
816
|
storage: __sbDOStorage(cls, id),
|
|
717
|
-
blockConcurrencyWhile(fn) {
|
|
817
|
+
blockConcurrencyWhile(fn) {
|
|
818
|
+
return fn();
|
|
819
|
+
},
|
|
718
820
|
waitUntil() {},
|
|
719
821
|
};
|
|
720
822
|
inst = new Ctor(state, globalThis.env);
|
|
@@ -729,33 +831,41 @@ function __sbDOStorage(cls, id) {
|
|
|
729
831
|
if (Array.isArray(key)) {
|
|
730
832
|
const out = new Map();
|
|
731
833
|
for (let i = 0; i < key.length; i++) {
|
|
732
|
-
const r = __sbRpc(
|
|
834
|
+
const r = __sbRpc("do.storage.get", { cls, id, key: String(key[i]) });
|
|
733
835
|
if (r.found) out.set(key[i], JSON.parse(r.value));
|
|
734
836
|
}
|
|
735
837
|
return out;
|
|
736
838
|
}
|
|
737
|
-
const r = __sbRpc(
|
|
839
|
+
const r = __sbRpc("do.storage.get", { cls, id, key: String(key) });
|
|
738
840
|
return r.found ? JSON.parse(r.value) : undefined;
|
|
739
841
|
},
|
|
740
842
|
put(key, value) {
|
|
741
843
|
if (key != null && __sbIsObj(key)) {
|
|
742
|
-
for (const k in key) __sbRpc(
|
|
844
|
+
for (const k in key) __sbRpc("do.storage.put", { cls, id, key: String(k), value: JSON.stringify(key[k]) });
|
|
743
845
|
return;
|
|
744
846
|
}
|
|
745
|
-
__sbRpc(
|
|
847
|
+
__sbRpc("do.storage.put", { cls, id, key: String(key), value: JSON.stringify(value) });
|
|
746
848
|
},
|
|
747
849
|
delete(key) {
|
|
748
850
|
if (Array.isArray(key)) {
|
|
749
851
|
let n = 0;
|
|
750
|
-
for (let i = 0; i < key.length; i++)
|
|
852
|
+
for (let i = 0; i < key.length; i++)
|
|
853
|
+
n += __sbRpc("do.storage.delete", { cls, id, key: String(key[i]) }).deleted ? 1 : 0;
|
|
751
854
|
return n;
|
|
752
855
|
}
|
|
753
|
-
return !!__sbRpc(
|
|
856
|
+
return !!__sbRpc("do.storage.delete", { cls, id, key: String(key) }).deleted;
|
|
857
|
+
},
|
|
858
|
+
deleteAll() {
|
|
859
|
+
__sbRpc("do.storage.delete_all", { cls, id });
|
|
754
860
|
},
|
|
755
|
-
deleteAll() { __sbRpc('do.storage.delete_all', { cls, id }); },
|
|
756
861
|
list(options) {
|
|
757
862
|
const o = options || {};
|
|
758
|
-
const r = __sbRpc(
|
|
863
|
+
const r = __sbRpc("do.storage.list", {
|
|
864
|
+
cls,
|
|
865
|
+
id,
|
|
866
|
+
prefix: o.prefix == null ? "" : String(o.prefix),
|
|
867
|
+
limit: o.limit == null ? 1000 : o.limit,
|
|
868
|
+
});
|
|
759
869
|
const out = new Map();
|
|
760
870
|
for (let i = 0; i < (r.entries || []).length; i++) out.set(r.entries[i][0], JSON.parse(r.entries[i][1]));
|
|
761
871
|
return out;
|
|
@@ -769,8 +879,10 @@ function __sbDOStorage(cls, id) {
|
|
|
769
879
|
// with SB_BROKER_TOKEN) to the right user handler, and everything else to
|
|
770
880
|
// `handlers.fetch`.
|
|
771
881
|
|
|
882
|
+
// oxlint-disable-next-line no-unused-vars -- `name` is read inside the RawC block below, not by JS.
|
|
772
883
|
function __sbEnv(name) {
|
|
773
|
-
let res =
|
|
884
|
+
let res = "";
|
|
885
|
+
// oxlint-disable-next-line no-unused-expressions -- Porffor.c`...` is inline C the compiler consumes, not a JS expression.
|
|
774
886
|
Porffor.c`
|
|
775
887
|
const char* __n; size_t __nl; char* __no = 0;
|
|
776
888
|
porf_native_fetch_read_value(name, &__n, &__nl, &__no);
|
|
@@ -785,13 +897,13 @@ function __sbEnv(name) {
|
|
|
785
897
|
}
|
|
786
898
|
|
|
787
899
|
function __sbTriggerAuthed(request) {
|
|
788
|
-
const want = __sbEnv(
|
|
900
|
+
const want = __sbEnv("SB_BROKER_TOKEN");
|
|
789
901
|
if (!want) return true; // no token configured (local/dev)
|
|
790
|
-
return request.headers.get(
|
|
902
|
+
return request.headers.get("x-sb-token") === want;
|
|
791
903
|
}
|
|
792
904
|
|
|
793
905
|
globalThis.__sbEntry = function (handlers, request) {
|
|
794
|
-
const trigger = request.headers.get(
|
|
906
|
+
const trigger = request.headers.get("x-sb-trigger");
|
|
795
907
|
if (!trigger) {
|
|
796
908
|
// #28 — per-invocation CPU time. One fetch turn per process (serial), so the
|
|
797
909
|
// process CPU delta across the handler is this invocation's CPU.
|
|
@@ -808,17 +920,17 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
808
920
|
if (__res && __sbIsFn(__res.then)) return __res;
|
|
809
921
|
return __sbTagCpu(__res, __t0);
|
|
810
922
|
}
|
|
811
|
-
if (!__sbTriggerAuthed(request)) return new Response(
|
|
923
|
+
if (!__sbTriggerAuthed(request)) return new Response("forbidden", { status: 403 });
|
|
812
924
|
|
|
813
|
-
if (trigger ===
|
|
814
|
-
if (!__sbIsFn(handlers.scheduled)) return new Response(
|
|
925
|
+
if (trigger === "scheduled") {
|
|
926
|
+
if (!__sbIsFn(handlers.scheduled)) return new Response("no scheduled handler", { status: 404 });
|
|
815
927
|
const body = __sbReadJson(request);
|
|
816
|
-
handlers.scheduled({ cron: body.cron ||
|
|
817
|
-
return new Response(
|
|
928
|
+
handlers.scheduled({ cron: body.cron || "", scheduledTime: body.scheduledTime || Date.now(), noRetry() {} });
|
|
929
|
+
return new Response("", { status: 204 });
|
|
818
930
|
}
|
|
819
931
|
|
|
820
|
-
if (trigger ===
|
|
821
|
-
if (!__sbIsFn(handlers.queue)) return new Response(
|
|
932
|
+
if (trigger === "queue") {
|
|
933
|
+
if (!__sbIsFn(handlers.queue)) return new Response("no queue handler", { status: 404 });
|
|
822
934
|
const body = __sbReadJson(request);
|
|
823
935
|
const acked = [];
|
|
824
936
|
const retried = [];
|
|
@@ -831,32 +943,49 @@ globalThis.__sbEntry = function (handlers, request) {
|
|
|
831
943
|
timestamp: m.timestamp,
|
|
832
944
|
attempts: m.attempts || 1,
|
|
833
945
|
body: __sbTryParse(m.body),
|
|
834
|
-
ack() {
|
|
835
|
-
|
|
946
|
+
ack() {
|
|
947
|
+
if (acked.indexOf(m.id) === -1) acked.push(m.id);
|
|
948
|
+
},
|
|
949
|
+
retry() {
|
|
950
|
+
if (retried.indexOf(m.id) === -1) retried.push(m.id);
|
|
951
|
+
},
|
|
836
952
|
};
|
|
837
953
|
messages.push(msg);
|
|
838
954
|
}
|
|
839
955
|
const batch = {
|
|
840
|
-
queue: body.queue ||
|
|
956
|
+
queue: body.queue || "",
|
|
841
957
|
messages,
|
|
842
|
-
ackAll() {
|
|
843
|
-
|
|
958
|
+
ackAll() {
|
|
959
|
+
for (let i = 0; i < messages.length; i++) messages[i].ack();
|
|
960
|
+
},
|
|
961
|
+
retryAll() {
|
|
962
|
+
for (let i = 0; i < messages.length; i++) messages[i].retry();
|
|
963
|
+
},
|
|
844
964
|
};
|
|
845
965
|
handlers.queue(batch);
|
|
846
966
|
// default: any message neither acked nor retried is treated as acked
|
|
847
967
|
for (let i = 0; i < messages.length; i++) {
|
|
848
968
|
if (acked.indexOf(messages[i].id) === -1 && retried.indexOf(messages[i].id) === -1) acked.push(messages[i].id);
|
|
849
969
|
}
|
|
850
|
-
return new Response(JSON.stringify({ ack: acked, retry: retried }), {
|
|
970
|
+
return new Response(JSON.stringify({ ack: acked, retry: retried }), {
|
|
971
|
+
headers: { "content-type": "application/json" },
|
|
972
|
+
});
|
|
851
973
|
}
|
|
852
974
|
|
|
853
|
-
|
|
854
|
-
return new Response('unknown trigger', { status: 400 });
|
|
975
|
+
return new Response("unknown trigger", { status: 400 });
|
|
855
976
|
};
|
|
856
977
|
|
|
857
978
|
function __sbReadJson(request) {
|
|
858
|
-
try {
|
|
979
|
+
try {
|
|
980
|
+
return JSON.parse(request.body == null ? "{}" : String(request.body));
|
|
981
|
+
} catch {
|
|
982
|
+
return {};
|
|
983
|
+
}
|
|
859
984
|
}
|
|
860
985
|
function __sbTryParse(s) {
|
|
861
|
-
try {
|
|
986
|
+
try {
|
|
987
|
+
return JSON.parse(s);
|
|
988
|
+
} catch {
|
|
989
|
+
return s;
|
|
990
|
+
}
|
|
862
991
|
}
|