@sproutboat/runtime 0.9.0 → 0.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +15 -0
- package/package.json +1 -1
- package/src/transport-embedded.js +56 -0
- package/src/wrap.ts +11 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# @sproutboat/runtime
|
|
2
2
|
|
|
3
|
+
## 0.9.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d8b7877: Add native standalone R2 direct-download tickets with bounded, backpressure-aware
|
|
8
|
+
file reads, including upgrades for existing patched Porffor caches.
|
|
9
|
+
|
|
10
|
+
## 0.9.1
|
|
11
|
+
|
|
12
|
+
### Patch Changes
|
|
13
|
+
|
|
14
|
+
- e0944a4: Provide the native direct-transfer ABI in broker builds so the shared
|
|
15
|
+
uWebSockets patch does not introduce unresolved symbols outside standalone
|
|
16
|
+
mode.
|
|
17
|
+
|
|
3
18
|
## 0.9.0
|
|
4
19
|
|
|
5
20
|
### Minor Changes
|
package/package.json
CHANGED
|
@@ -746,6 +746,62 @@ void sb_r2_transfer_abort(sb_r2_transfer_ctx* ctx) {
|
|
|
746
746
|
free(ctx);
|
|
747
747
|
}
|
|
748
748
|
|
|
749
|
+
// Claims a download ticket and opens its immutable blob generation. The C++
|
|
750
|
+
// socket shim reads this handle at its current write offset, so no object-sized
|
|
751
|
+
// buffer is ever allocated in either the JS or C++ heap.
|
|
752
|
+
int sb_r2_transfer_download_open(const char* bucket, const char* token, sb_r2_transfer_ctx** out) {
|
|
753
|
+
*out = 0;
|
|
754
|
+
if (!bucket || !token || strlen(bucket) >= 256 || strlen(token) != 24) return 404;
|
|
755
|
+
sb_r2_transfer_ctx* ctx = (sb_r2_transfer_ctx*)calloc(1, sizeof(*ctx));
|
|
756
|
+
if (!ctx) return 503;
|
|
757
|
+
sb_r2_transfer_store_path(ctx->store, sizeof(ctx->store));
|
|
758
|
+
int index = sb_db_for(ctx->store);
|
|
759
|
+
if (index < 0) { free(ctx); return 503; }
|
|
760
|
+
ctx->db = sb_dbs[index];
|
|
761
|
+
sqlite3_stmt* st = 0;
|
|
762
|
+
const char* ticket_sql = "SELECT key FROM r2_transfer WHERE token=?1 AND bucket=?2 AND method='download' AND used=0 AND expires>=?3";
|
|
763
|
+
if (sqlite3_prepare_v2(ctx->db, ticket_sql, -1, &st, 0) != 0 || !st) { free(ctx); return 503; }
|
|
764
|
+
sqlite3_bind_text(st, 1, token, -1, SB_SQLITE_TRANSIENT);
|
|
765
|
+
sqlite3_bind_text(st, 2, bucket, -1, SB_SQLITE_TRANSIENT);
|
|
766
|
+
sqlite3_bind_int64(st, 3, (int64_t)time(0) * 1000);
|
|
767
|
+
if (sqlite3_step(st) != SB_SQLITE_ROW) { sqlite3_finalize(st); free(ctx); return 410; }
|
|
768
|
+
const char* key = (const char*)sqlite3_column_text(st, 0);
|
|
769
|
+
if (!key || strlen(key) >= sizeof(ctx->key)) { sqlite3_finalize(st); free(ctx); return 404; }
|
|
770
|
+
snprintf(ctx->bucket, sizeof(ctx->bucket), "%s", bucket);
|
|
771
|
+
snprintf(ctx->key, sizeof(ctx->key), "%s", key);
|
|
772
|
+
sqlite3_finalize(st);
|
|
773
|
+
st = 0;
|
|
774
|
+
if (sqlite3_prepare_v2(ctx->db, "SELECT blob_id FROM r2 WHERE bucket=?1 AND key=?2", -1, &st, 0) != 0 || !st) { free(ctx); return 503; }
|
|
775
|
+
sqlite3_bind_text(st, 1, bucket, -1, SB_SQLITE_TRANSIENT);
|
|
776
|
+
sqlite3_bind_text(st, 2, ctx->key, -1, SB_SQLITE_TRANSIENT);
|
|
777
|
+
if (sqlite3_step(st) != SB_SQLITE_ROW) { sqlite3_finalize(st); free(ctx); return 404; }
|
|
778
|
+
const char* generation = (const char*)sqlite3_column_text(st, 0);
|
|
779
|
+
char blobpath[1024];
|
|
780
|
+
sb_r2_blob_path(blobpath, sizeof(blobpath), ctx->store, bucket, ctx->key, generation ? generation : "");
|
|
781
|
+
sqlite3_finalize(st);
|
|
782
|
+
ctx->file = fopen(blobpath, "rb");
|
|
783
|
+
if (!ctx->file) { free(ctx); return 404; }
|
|
784
|
+
fseek(ctx->file, 0, SEEK_END); long length = ftell(ctx->file); rewind(ctx->file);
|
|
785
|
+
if (length < 0) { fclose(ctx->file); free(ctx); return 500; }
|
|
786
|
+
ctx->size = (size_t)length;
|
|
787
|
+
st = 0;
|
|
788
|
+
if (sqlite3_prepare_v2(ctx->db, "UPDATE r2_transfer SET used=1 WHERE token=?1 AND used=0", -1, &st, 0) != 0 || !st) { fclose(ctx->file); free(ctx); return 503; }
|
|
789
|
+
sqlite3_bind_text(st, 1, token, -1, SB_SQLITE_TRANSIENT);
|
|
790
|
+
int claimed = sqlite3_step(st) == SB_SQLITE_DONE && sqlite3_changes(ctx->db) == 1;
|
|
791
|
+
sqlite3_finalize(st);
|
|
792
|
+
if (!claimed) { fclose(ctx->file); free(ctx); return 410; }
|
|
793
|
+
*out = ctx;
|
|
794
|
+
return 0;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx* ctx) { return ctx ? ctx->size : 0; }
|
|
798
|
+
size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx* ctx, size_t offset, char* out, size_t cap) {
|
|
799
|
+
if (!ctx || !ctx->file || offset >= ctx->size || fseek(ctx->file, (long)offset, SEEK_SET) != 0) return 0;
|
|
800
|
+
size_t wanted = ctx->size - offset < cap ? ctx->size - offset : cap;
|
|
801
|
+
return fread(out, 1, wanted, ctx->file);
|
|
802
|
+
}
|
|
803
|
+
void sb_r2_transfer_download_close(sb_r2_transfer_ctx* ctx) { if (ctx) { if (ctx->file) fclose(ctx->file); free(ctx); } }
|
|
804
|
+
|
|
749
805
|
// Run a script: one or more statements separated by semicolons. sqlite3_exec
|
|
750
806
|
// handles the whole string, which prepare/step does not — it compiles the first
|
|
751
807
|
// statement and silently ignores the rest, so a schema built from one exec call
|
package/src/wrap.ts
CHANGED
|
@@ -175,6 +175,8 @@ export function wrapNativeFetchHandler(
|
|
|
175
175
|
appName: string = "app",
|
|
176
176
|
/** #15 — assets baked into the module for a binary that has no files beside it. */
|
|
177
177
|
assets?: { manifest: unknown; files: Record<string, string> },
|
|
178
|
+
/** The selected transport determines whether the direct-transfer C ABI is real or stubbed. */
|
|
179
|
+
transport: Transport = "broker",
|
|
178
180
|
): string {
|
|
179
181
|
const neutralised = neutraliseExports(source);
|
|
180
182
|
if (neutralised === null || !/\bfetch\s*\(/.test(source)) {
|
|
@@ -191,9 +193,14 @@ export function wrapNativeFetchHandler(
|
|
|
191
193
|
// #15 — and enforces the outbound allowlist itself, with no broker to do it.
|
|
192
194
|
`globalThis.__sbOutbound = ${JSON.stringify(bindings.outbound)};\n` +
|
|
193
195
|
(assets ? `globalThis.__sbAssets = ${JSON.stringify(assets)};\n` : "");
|
|
194
|
-
// The native transfer hook runs before JavaScript receives its Request
|
|
195
|
-
//
|
|
196
|
-
|
|
196
|
+
// The native transfer hook runs before JavaScript receives its Request. It
|
|
197
|
+
// has a real file-backed implementation only in embedded builds. The broker
|
|
198
|
+
// transport supplies ABI-compatible stubs because its separate transfer
|
|
199
|
+
// listener owns these URLs, while the shared uWebSockets patch is global.
|
|
200
|
+
const nativeTransferAbi =
|
|
201
|
+
transport === "embedded"
|
|
202
|
+
? `Porffor.c\`const char* sb_standalone_app_name(void) { return ${JSON.stringify(appName)}; }\`;\n`
|
|
203
|
+
: "Porffor.c`typedef struct sb_r2_transfer_ctx { int unused; } sb_r2_transfer_ctx; int sb_r2_transfer_open(const char* a, const char* b, size_t c, sb_r2_transfer_ctx** d) { if (d) *d = 0; return 404; } int sb_r2_transfer_write(sb_r2_transfer_ctx* a, const char* b, size_t c) { return 404; } int sb_r2_transfer_finish(sb_r2_transfer_ctx* a) { return 404; } void sb_r2_transfer_abort(sb_r2_transfer_ctx* a) {} int sb_r2_transfer_download_open(const char* a, const char* b, sb_r2_transfer_ctx** c) { if (c) *c = 0; return 404; } size_t sb_r2_transfer_download_size(sb_r2_transfer_ctx* a) { return 0; } size_t sb_r2_transfer_download_read(sb_r2_transfer_ctx* a, size_t b, char* c, size_t d) { return 0; } void sb_r2_transfer_download_close(sb_r2_transfer_ctx* a) {}`;\n";
|
|
197
204
|
const wire = hasBindings(bindings)
|
|
198
205
|
? `__sbInstallBindings(env, ${JSON.stringify(bindings)});\n`
|
|
199
206
|
: "";
|
|
@@ -208,7 +215,7 @@ export function wrapNativeFetchHandler(
|
|
|
208
215
|
: "";
|
|
209
216
|
|
|
210
217
|
return (
|
|
211
|
-
`${prelude}\n${
|
|
218
|
+
`${prelude}\n${nativeTransferAbi}${compat}${env}${wire}` +
|
|
212
219
|
`${neutralised}\n` +
|
|
213
220
|
`${registerDO}${triggers}` +
|
|
214
221
|
`export default {\n port: ${port},\n fetch(request) { return __sbEntry(__sbHandlers, request); }\n};\n`
|