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