@sproutboat/toolchain 0.7.0 → 0.8.1
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 +67 -0
- package/PORFFOR_ALPHA17.md +7 -0
- package/PORFFOR_ALPHA18.md +64 -0
- package/PORFFOR_SCORECARD.md +5 -5
- package/package.json +1 -1
- package/scorecards/alpha-17-731f1b2.json +72 -0
- package/src/acquire.test.ts +84 -10
- package/src/acquire.ts +11 -6
- package/src/alpha18-native.test.ts +43 -0
- package/src/case-map.ts +9 -0
- package/src/fetch-classes.ts +242 -0
- package/src/fingerprint.test.ts +28 -0
- package/src/fingerprint.ts +44 -0
- package/src/gen-case-map.ts +35 -0
- package/src/groupby-private-native.test.ts +37 -0
- package/src/headers-class.ts +120 -0
- package/src/idle-gc.test.ts +38 -0
- package/src/json-cycles-native.test.ts +39 -0
- package/src/patch.test.ts +487 -16
- package/src/patch.ts +1092 -195
- package/src/pin.ts +3 -3
- package/src/repeat-native.test.ts +34 -0
- package/src/streaming.ts +125 -0
- package/src/text-decoder.ts +92 -0
- package/src/url-class.ts +342 -0
- package/vendor/porffor-deda494.tar.gz +0 -0
- package/vendor/porffor-731f1b2.tar.gz +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,72 @@
|
|
|
1
1
|
# @sproutboat/toolchain
|
|
2
2
|
|
|
3
|
+
## 0.8.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 742e61c: A CLI compiled with `bun build --compile` builds again. 0.8.0 embedded the patch sources with text imports to fingerprint them, but Bun bundles a text import of `patch.ts` as the module `patch.ts` is everywhere else, so compiling failed with `No matching export ... for import "default"`. The fingerprint now hashes the sources when they are on disk, and falls back to the fingerprint they were released with (`RELEASED_FINGERPRINT`, kept current by a test). The value is unchanged, so existing caches stay valid.
|
|
8
|
+
|
|
9
|
+
## 0.8.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- 9fdf3fa: Upgrade the verified vendored Porffor pin to Alpha 18 (`deda494`). Alpha 18 brings spec BigInt arithmetic and bitwise operators, iterator helpers, Set methods, `Array.fromAsync`, Atomics, resizable ArrayBuffers, explicit resource management, a rewritten Temporal, an on-demand Intl polyfill, and many RegExp, Promise and Object spec fixes. Three downstream workarounds are dropped because upstream now covers them: the typed-array read and write bounds checks (#238, #241), the `toJSON` step in `JSON.stringify` (Alpha 18 also adds the replacer argument), and the Web Streams adapter for dynamic array iterator lookup. The remaining patches are unchanged.
|
|
14
|
+
- 6e88627: Standalone sprouts stream `ReadableStream` response bodies. The status and headers go out as soon as the handler returns, and each chunk reaches the client as it is produced (chunked encoding), so Server-Sent Events, progress output and long responses work. A client that disconnects cancels the stream. Hosts without the streaming server (sproutd's workers, for now) still read the stream to the end and send it buffered.
|
|
15
|
+
|
|
16
|
+
### Patch Changes
|
|
17
|
+
|
|
18
|
+
- 6880f60: Add an opt-in bounded multi-session broker worker with private supervisor control.
|
|
19
|
+
Fence queue acknowledgements to durable delivery claims and retain recoverable alarm deliveries across process crashes in broker and embedded transports.
|
|
20
|
+
|
|
21
|
+
Resume native R2 downloads until completion or renewed backpressure instead of leaving large responses stalled.
|
|
22
|
+
|
|
23
|
+
Retain partially written TCP replies until drain and serialize pipelined replies so large broker-backed R2 reads cannot truncate or interleave frames.
|
|
24
|
+
|
|
25
|
+
Honor wildcard, weak and list-based If-None-Match conditions on broker direct downloads.
|
|
26
|
+
|
|
27
|
+
Move D1 queries, batches, exec and backups off the shared broker event loop into two lazy SQL threads per broker process. Preserve database affinity, transaction rollback and session isolation, and bound queued work with per-session admission and queue deadlines. Running SQLite statements remain non-preemptible; keep shared mode opt-in.
|
|
28
|
+
|
|
29
|
+
Enforce shared D1 execution budgets through a capability-protected native SQLite progress hook. Interrupted batches roll back and lanes remain usable. A broker watchdog revokes all sessions if native work cannot be interrupted; uncertain SQL outcomes are never automatically retried. Build the small SQLite extension during host/image preparation.
|
|
30
|
+
- 846519f: Fixes found by the new differential conformance suite against workerd:
|
|
31
|
+
|
|
32
|
+
- `JSON.stringify` of a cyclic object throws `TypeError` instead of crashing the process (it recursed until the native stack overflowed), so one request can no longer take a worker down.
|
|
33
|
+
- `structuredClone` follows the structured-clone algorithm: cycles, `Map`, `Set`, `Date`, `RegExp`, typed arrays, `DataView` and errors survive the copy, and functions and symbols throw `DataCloneError`. It was a JSON round-trip that lost every non-JSON type and also crashed on cycles.
|
|
34
|
+
- `Event`, `CustomEvent`, `EventTarget`, `AbortSignal` (`abort`, `timeout`, `any`, `throwIfAborted`) and `AbortController` exist.
|
|
35
|
+
- `Object.prototype.toString` names typed arrays (`[object Uint8Array]`, not `[object Object]`).
|
|
36
|
+
- 853f8c9: `console.log` colours values only when stdout is a terminal. Piped or redirected output, like sproutd's per-app logs or a standalone sprout's output collected by a service manager, no longer contains ANSI escape codes such as `\x1b[33m42\x1b[0m`.
|
|
37
|
+
- 262690d: Allocation-heavy handlers are up to 4.5x faster in standalone sprouts (a regex scan went from 140 to 31 ms per request). Full garbage collections under load no longer return free pages to the OS, which made the next allocations fault them back in; the idle collection returns them instead, now also for small sprouts that never reach a full collection. Memory after 3 s idle is unchanged; during traffic a sprout keeps more of its heap (baronunread/sproutboat#287).
|
|
38
|
+
- ddb0f9d: Response, Request and Blob follow the Fetch spec, and binary bodies reach the client intact:
|
|
39
|
+
|
|
40
|
+
- A `Uint8Array` or `ArrayBuffer` response body is sent as its bytes. It was sent as text: `new Response(new Uint8Array([104, 105]))` reached the client as `104,105`, and an `ArrayBuffer` as `[object ArrayBuffer]`.
|
|
41
|
+
- `Response` has `statusText`, `type`, `bodyUsed` and `Response.error()`. A status outside 200–599 throws `RangeError`, a body with 101/204/205/304 throws `TypeError`, and `arrayBuffer()`/`text()` handle byte bodies (UTF-8 both ways).
|
|
42
|
+
- `Request` normalizes its URL and rejects relative or invalid ones, has `redirect` and `bodyUsed`, rejects a body on GET/HEAD, and sets the default content type for string and `URLSearchParams` bodies.
|
|
43
|
+
- A body can be read once: a second `text()`/`json()`/`arrayBuffer()`/`blob()` throws `TypeError`.
|
|
44
|
+
- `Blob` holds bytes, with `size`, `type`, `slice` (negative indices too), and promise-returning `text`/`arrayBuffer`/`bytes`. `File` exists. `FormData` stores a `Blob` value as a `File` named by its filename, and `new Response(formData)` sends multipart/form-data.
|
|
45
|
+
- 50bea68: `Map.groupBy` works (it threw a TypeError), and private class fields (`#x`) no longer show up in `Object.keys`, `Object.entries`, `for...in` or `JSON.stringify`. Porffor stores a private field as an ordinary property named `__#x`; it is now installed non-enumerable, like a method, so `Object.getOwnPropertyNames` still lists the internal name (baronunread/sproutboat#286).
|
|
46
|
+
- 6d6ae43: `Headers` is iterable (`new Map(headers)`, `[...headers]`, `Object.fromEntries(headers)`), and `new Headers([["name", "value"]])` reads the pairs instead of producing headers named "0", "1"...
|
|
47
|
+
- 2a4d70d: Builds are about 1.3 s faster, and parallel builds no longer fail with `PrecompiledBuiltins.BuiltinFuncs is not a function`. Every compile re-ran the patch chain on the shared compiler cache, and its builtin precompile rewrote `builtins_precompiled.js` in place, so a concurrent build could import a half-written table. A cache entry patched by the same toolchain version is now used as is.
|
|
48
|
+
- 4c25aa2: The Porffor cache entry is keyed by a fingerprint of the patch chain as well as the toolchain release. A patch edit that ships under the same version now gets a freshly patched compiler, instead of reusing an entry an older chain marked complete. Builds against such an entry failed to link once the runtime began calling `sb_stream_*`. Old entries stay on disk and are no longer used.
|
|
49
|
+
- ec5d49f: A sprout that returns a `fetch()` response unchanged (`return fetch(url)`, or a proxy) sends its body again. The body could reach the server as a UTF-16 string, which the raw-bytes reader rejected, so the client got an empty body with the upstream's headers.
|
|
50
|
+
- 5859a82: Reclaim large native heaps after 500 ms without a completed request, and release oversized GC mark-queue scratch storage. Preserve the existing request collector and live application state. Extra collection is limited to heaps over 8 MiB or mark queues over 65,536 entries.
|
|
51
|
+
- 56b9ba6: `String.prototype.repeat` copies in doubling chunks instead of once per repetition: repeating a 600 KB string went from about 2.4 ms to under 0.1 ms.
|
|
52
|
+
- 4c78d83: Match the Fetch spec and workerd for common responses: string bodies default to `text/plain;charset=UTF-8`, `Response.json` sets `application/json`, and `Response.redirect` serializes its URL (`https://example.com` becomes `https://example.com/`) with a null body.
|
|
53
|
+
- 088cad0: Shared-object (split) builds no longer crash on start or lose the Web Streams classes.
|
|
54
|
+
|
|
55
|
+
- **Toolchain:** split output prefixes module globals with `pg_`. A global named like a libc symbol (the prelude's `clone`, or an app's `hash` or `search`) bound to libc instead, so the runtime wrote into libc's code and every worker crashed. Internal `__` globals keep their names. Standalone builds are unchanged, because their globals are `static`.
|
|
56
|
+
- **Runtime:** the Web Streams polyfill installs each class with its own `globalThis.X =` assignment. Where the prelude is a separate unit, Porffor resolved `new ReadableStream()` in app code at compile time and, seeing only a dynamic install, compiled it to a ReferenceError. `gen-streams.ts` now fails if an export of the polyfill is not installed.
|
|
57
|
+
- 28b0c39: `URL` behaves like the WHATWG URL: `new URL(path, base)`, setters for every part (`url.hostname = ...`, `port`, `protocol`, `host`, `search`, `hash`, `href`), default ports dropped, dot segments resolved, percent-encoding, `URL.canParse`, and `searchParams` changes written back into the URL. `JSON.stringify` calls `toJSON`, so a `Date` serializes as its ISO string (it was `{}`) and a `URL` as its href.
|
|
58
|
+
- eec94f1: Web API fixes found by the conformance suite against workerd:
|
|
59
|
+
|
|
60
|
+
- `Headers` follows the Fetch spec: `get` joins repeated values with `", "`, iteration is sorted by name with values combined (each `Set-Cookie` listed separately), `getSetCookie()` exists, values are trimmed, and invalid names or values throw `TypeError`. Each appended header still reaches the wire as its own line.
|
|
61
|
+
- `TextDecoder` is the WHATWG UTF-8 decoder. Characters outside the BMP (emoji) decode correctly, invalid bytes become U+FFFD (or throw with `fatal`), the BOM is stripped unless `ignoreBOM`, and `{ stream: true }` keeps an incomplete sequence for the next chunk.
|
|
62
|
+
- `toLowerCase`/`toUpperCase` map non-ASCII letters (Latin, Greek, Cyrillic and the rest of the BMP's simple mappings). They mapped ASCII only.
|
|
63
|
+
- `new URLSearchParams(init)` accepts a record, a sequence of pairs or another `URLSearchParams`. Only strings worked; `{ a: "1" }` gave `%5Bobject+Object%5D=`.
|
|
64
|
+
- `atob` ignores ASCII whitespace and throws `InvalidCharacterError` on invalid input, and `btoa` throws on characters above U+00FF.
|
|
65
|
+
- `queueMicrotask` exists.
|
|
66
|
+
- URL hosts are converted to ASCII (Punycode): `new URL("http://münchen.test").host` is `xn--mnchen-3ya.test`.
|
|
67
|
+
- `crypto.subtle.digest` supports SHA-1 and MD5, and HMAC works with SHA-1. `exportKey("raw")`, `generateKey` for HMAC and workerd's `crypto.subtle.timingSafeEqual` exist, and imported keys report `algorithm.length`. Errors carry WebCrypto's names (`NotSupportedError`, `InvalidAccessError`), and `getRandomValues` over 65,536 bytes throws `QuotaExceededError`.
|
|
68
|
+
- `setTimeout(fn, 0)` and `setInterval(fn, 0)` fire on Linux. uSockets arms timers with `timerfd_settime`, and a zero expiry disarms the timer, so a zero-delay timer never ran (macOS fired it). Timers are armed at 1 ms at least.
|
|
69
|
+
|
|
3
70
|
## 0.7.0
|
|
4
71
|
|
|
5
72
|
### Minor Changes
|
package/PORFFOR_ALPHA17.md
CHANGED
|
@@ -57,3 +57,10 @@ exposed on globalThis for libraries using host-task yields.
|
|
|
57
57
|
The stream ponyfill IIFE still fails with a prototype TypeError on Alpha 17;
|
|
58
58
|
its module-scope adaptation remains necessary. The saved standalone auth probe
|
|
59
59
|
also passes with fresh noble hashes 2.4 and zod 4.6.5 dependencies.
|
|
60
|
+
|
|
61
|
+
The isolated release consumer passes 103 CLI tests, 35 broker checks, 38
|
|
62
|
+
standalone checks and all 21 examples. The standalone Better Auth flow passes
|
|
63
|
+
with the locked fresh dependency tree. All 32 platform handlers compile and
|
|
64
|
+
30 match Bun; the two existing non-ISO date parsing mismatches remain. Rechecked
|
|
65
|
+
qs nested parse/stringify, IttyRouter GET/Unicode POST/404 and the basic tRPC
|
|
66
|
+
server caller pass. Standard itty Router still fails at labelled iterator breaks.
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# Porffor Alpha 18 evaluation
|
|
2
|
+
|
|
3
|
+
Verified on 2026-10-19. Released 2026-10-19:
|
|
4
|
+
https://github.com/CanadaHonk/porffor/releases/tag/alpha-18
|
|
5
|
+
|
|
6
|
+
Pinned commit: `deda4945a526d3974ff79e499e8c1dcef0873f3a`.
|
|
7
|
+
Vendored archive SHA256:
|
|
8
|
+
`4626ffba0d5bd417ba2c5b96506d40a152dbbeeaa8a698c904054bdef6bc6e64`.
|
|
9
|
+
uWebSockets stays on `360c276d`, so the vendored uWebSockets archive is unchanged.
|
|
10
|
+
|
|
11
|
+
## Changes since Alpha 17
|
|
12
|
+
|
|
13
|
+
About 60 commits. The ones that matter to handlers:
|
|
14
|
+
|
|
15
|
+
- BigInt: exact arithmetic, `toString`, equality, bitwise and shift operators,
|
|
16
|
+
`Number()` of BigInts. Programs without BigInts drop the BigInt dispatch.
|
|
17
|
+
- Iterator helpers and the builtin iterator prototypes; Set methods
|
|
18
|
+
(`union`, `intersection`, ...); spec `Array.from` and `Array.fromAsync`.
|
|
19
|
+
- `JSON.stringify` calls `toJSON` and takes a replacer.
|
|
20
|
+
- Typed-array numeric keys are integer-indexed: out-of-range reads are
|
|
21
|
+
`undefined` and out-of-range writes are ignored.
|
|
22
|
+
- Promise capabilities, species and generic combinators; `await` always
|
|
23
|
+
suspends; async generator request queue.
|
|
24
|
+
- Spec errors, explicit resource management (`using`), FinalizationRegistry,
|
|
25
|
+
Atomics, resizable ArrayBuffer and growable SharedArrayBuffer, Float16 DataView.
|
|
26
|
+
- RegExp: real `lastIndex`, symbol protocol, `Script_Extensions`, `\q`,
|
|
27
|
+
modifiers, case folding.
|
|
28
|
+
- Temporal rewritten to follow the spec; an on-demand ECMA-402 (Intl) polyfill,
|
|
29
|
+
compiled only for the services a program uses; named time zones.
|
|
30
|
+
- Builtin subclassing, spec relational and loose equality, spec `ToPrimitive`.
|
|
31
|
+
- Dynamic `import()` in the compiler. Handlers are still bundled first, so an
|
|
32
|
+
`import()` that survives bundling has nothing to load and stays rejected.
|
|
33
|
+
- `TextEncoder` encodes astral characters and lone surrogates.
|
|
34
|
+
|
|
35
|
+
## Downstream patches
|
|
36
|
+
|
|
37
|
+
Each workaround was rebuilt out of the patch chain and the full native suite
|
|
38
|
+
(12 runtime regressions plus the toolchain native tests) run without it.
|
|
39
|
+
|
|
40
|
+
Dropped, now upstream:
|
|
41
|
+
|
|
42
|
+
- Typed-array read and write bounds (#238, #241). Its anchor is gone.
|
|
43
|
+
- `toJSON` in `JSON.stringify`. Alpha 18 ships a helper with our helper's name,
|
|
44
|
+
so our patch already skipped itself.
|
|
45
|
+
- The Web Streams generator's array iterator lookup adapter. The ponyfill
|
|
46
|
+
still fails inside an IIFE, so the module-scope adaptation stays.
|
|
47
|
+
|
|
48
|
+
Retained, still needed on Alpha 18:
|
|
49
|
+
|
|
50
|
+
- Local function types: unpatched Alpha 18 throws a TypeError when a local
|
|
51
|
+
function shares a name with an unrelated async function. The model-resolver
|
|
52
|
+
regression no longer reaches this, so `alpha18-native.test.ts` covers it.
|
|
53
|
+
- Class self name (#256), ISO date offsets, `replaceAll` (#237, still ~38 s on
|
|
54
|
+
a 900 KB string unpatched), Uint8Array base64 options, Unicode encoder,
|
|
55
|
+
Headers array init, URL class, Response defaults: their regressions fail
|
|
56
|
+
without them.
|
|
57
|
+
- Promise resolve with `null` (#168): no regression reproduces it, but
|
|
58
|
+
Alpha 18's `then` probe still lacks the null guard.
|
|
59
|
+
- The native-fetch server patches (#172 bytestring encoding, raw body, HTTP/1.0,
|
|
60
|
+
headers, quiet collection) are Sproutboat features, not Porffor bugs.
|
|
61
|
+
|
|
62
|
+
Still open upstream, with no patch: `Map.groupBy`, private fields listed by
|
|
63
|
+
`Object.keys` (baronunread/sproutboat#286), per-collection GC page discard
|
|
64
|
+
(#287) and `Error.stack` (the runtime prelude supplies it).
|
package/PORFFOR_SCORECARD.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# Porffor pin scorecard
|
|
2
2
|
|
|
3
|
-
Pin: alpha-
|
|
3
|
+
Pin: alpha-17 (731f1b2a150d441ed22da3d14f8b1e0daaae8ee4). Compatibility report: 2026-10-11T10:13:12.165Z.
|
|
4
4
|
|
|
5
5
|
| Measure | Result |
|
|
6
6
|
| --- | ---: |
|
|
7
7
|
| Fixtures compiled | 32/32 |
|
|
8
8
|
| Behavior matches | 30/32 |
|
|
9
|
-
| Median / p95 compile, ms |
|
|
10
|
-
| Median / p95 binary, bytes |
|
|
9
|
+
| Median / p95 compile, ms | 7765 / 12684 |
|
|
10
|
+
| Median / p95 binary, bytes | 1497648 / 1514208 |
|
|
11
11
|
| Patch markers | 35 |
|
|
12
12
|
| Upstream files written | 9 |
|
|
13
13
|
| Fresh patch application | pass |
|
|
14
14
|
|
|
15
|
-
Previous snapshot: alpha-
|
|
15
|
+
Previous snapshot: alpha-16-43087d4.json. Matches 30/32 to 30/32; patch markers 35 to 35.
|
|
16
16
|
|
|
17
17
|
## Patch inventory
|
|
18
18
|
|
|
@@ -26,7 +26,7 @@ Previous snapshot: alpha-15-72d048d.json. Matches 30/32 to 30/32; patch markers
|
|
|
26
26
|
- `compiler/uwebsockets.js`: HTTP10_SHIM_MARKER, BODY_MARKER, STATUS_SIG_MARKER, STATUS_303_MARKER, STATUS_DEFAULT_MARKER, HDR_SIG_MARKER, HDR_CALL_MARKER, HDR_CAP_MARKER, HDR_SKIP_MARKER, HDR_APPEND_MARKER, READ_RAW_DECL_MARKER, FORBIDDEN_HDR_MARKER, WRITE_RESPONSE_MARKER, R2_TRANSFER_MARKER, R2_TRANSFER_CALL_MARKER, R2_DOWNLOAD_CACHE_MARKER, R2_RANGE_CACHE_MARKER, R2_CONDITIONAL_CACHE_MARKER, R2_STREAMING_CACHE_MARKER, R2_RANGE_HEADER_CACHE_MARKER
|
|
27
27
|
- `compiler/builtins_precompiled.js`: regenerated builtin table
|
|
28
28
|
|
|
29
|
-
Patch source SHA-256: `
|
|
29
|
+
Patch source SHA-256: `9cc722463148b4f861b95a0a8c01ad44201e471381b99bfeab8986e95e13bcca`.
|
|
30
30
|
|
|
31
31
|
## Known behavior gaps
|
|
32
32
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pin": "alpha-17 (731f1b2a150d441ed22da3d14f8b1e0daaae8ee4)",
|
|
3
|
+
"reportGeneratedAt": "2026-10-11T10:13:12.165Z",
|
|
4
|
+
"fixtures": 32,
|
|
5
|
+
"compiled": 32,
|
|
6
|
+
"matched": 30,
|
|
7
|
+
"medianCompileMs": 7765,
|
|
8
|
+
"p95CompileMs": 12684,
|
|
9
|
+
"medianBinaryBytes": 1497648,
|
|
10
|
+
"p95BinaryBytes": 1514208,
|
|
11
|
+
"patchSourceSha256": "9cc722463148b4f861b95a0a8c01ad44201e471381b99bfeab8986e95e13bcca",
|
|
12
|
+
"patchMarkerCount": 35,
|
|
13
|
+
"patchTargets": {
|
|
14
|
+
"compiler/render.js": [
|
|
15
|
+
"PORT_MARKER",
|
|
16
|
+
"CONSOLE_MARKER",
|
|
17
|
+
"BYTESTRING_MARKER",
|
|
18
|
+
"READ_RAW_MARKER"
|
|
19
|
+
],
|
|
20
|
+
"compiler/index.js": [
|
|
21
|
+
"LINK_MARKER",
|
|
22
|
+
"CFLAGS_MARKER",
|
|
23
|
+
"FP_CONTRACT_MARKER",
|
|
24
|
+
"HTTP10_IMPORT_MARKER",
|
|
25
|
+
"HTTP10_CALL_MARKER"
|
|
26
|
+
],
|
|
27
|
+
"compiler/builtins/date.ts": [
|
|
28
|
+
"DATE_PARSER_MARKER"
|
|
29
|
+
],
|
|
30
|
+
"compiler/builtins/promise.ts": [
|
|
31
|
+
"PROMISE_NULL_MARKER"
|
|
32
|
+
],
|
|
33
|
+
"compiler/builtins/string.ts": [
|
|
34
|
+
"REPLACE_ALL_MARKER"
|
|
35
|
+
],
|
|
36
|
+
"compiler/codegen.js": [
|
|
37
|
+
"TA_GET_MARKER",
|
|
38
|
+
"TA_SET_BOUNDS_MARKER"
|
|
39
|
+
],
|
|
40
|
+
"compiler/parse.js": [
|
|
41
|
+
"CLASS_SELF_MARKER"
|
|
42
|
+
],
|
|
43
|
+
"compiler/uwebsockets.js": [
|
|
44
|
+
"HTTP10_SHIM_MARKER",
|
|
45
|
+
"BODY_MARKER",
|
|
46
|
+
"STATUS_SIG_MARKER",
|
|
47
|
+
"STATUS_303_MARKER",
|
|
48
|
+
"STATUS_DEFAULT_MARKER",
|
|
49
|
+
"HDR_SIG_MARKER",
|
|
50
|
+
"HDR_CALL_MARKER",
|
|
51
|
+
"HDR_CAP_MARKER",
|
|
52
|
+
"HDR_SKIP_MARKER",
|
|
53
|
+
"HDR_APPEND_MARKER",
|
|
54
|
+
"READ_RAW_DECL_MARKER",
|
|
55
|
+
"FORBIDDEN_HDR_MARKER",
|
|
56
|
+
"WRITE_RESPONSE_MARKER",
|
|
57
|
+
"R2_TRANSFER_MARKER",
|
|
58
|
+
"R2_TRANSFER_CALL_MARKER",
|
|
59
|
+
"R2_DOWNLOAD_CACHE_MARKER",
|
|
60
|
+
"R2_RANGE_CACHE_MARKER",
|
|
61
|
+
"R2_CONDITIONAL_CACHE_MARKER",
|
|
62
|
+
"R2_STREAMING_CACHE_MARKER",
|
|
63
|
+
"R2_RANGE_HEADER_CACHE_MARKER"
|
|
64
|
+
],
|
|
65
|
+
"compiler/builtins_precompiled.js": []
|
|
66
|
+
},
|
|
67
|
+
"patchApplication": "pass",
|
|
68
|
+
"mismatches": [
|
|
69
|
+
"15-date-iso.js: request 3 mismatch: expected {\"status\":200,\"body\":\"2008-03-04T23:00:00.000Z\",\"content-type\":null}, got {\"status\":200,\"body\":\"0003-05-08T00:00:00.000Z\",\"content-type\":null}",
|
|
70
|
+
"16-date-parts.js: request 3 mismatch: expected {\"status\":200,\"body\":\"{\\\"year\\\":2008,\\\"month\\\":3,\\\"day\\\":4}\",\"content-type\":\"application/json;charset=utf-8\"}, got {\"status\":200,\"body\":\"{\\\"year\\\":3,\\\"month\\\":5,\\\"day\\\":8}\",\"content-type\":\"application/json;charset=utf-8\"}"
|
|
71
|
+
]
|
|
72
|
+
}
|
package/src/acquire.test.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { mkdtemp, mkdir, readFile, readdir, rm, utimes, writeFile } from "node:f
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { basename, join } from "node:path";
|
|
6
6
|
import { ensurePorffor, PORFFOR_COMMIT_FULL, PorfforToolchainError } from "./index";
|
|
7
|
+
import { PATCH_FINGERPRINT } from "./fingerprint";
|
|
7
8
|
|
|
8
9
|
const temporary: string[] = [];
|
|
9
10
|
afterEach(async () => {
|
|
@@ -17,6 +18,13 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
17
18
|
const files = {
|
|
18
19
|
"runtime/index.js": "fixture:runtime/index.js\n",
|
|
19
20
|
"compiler/render.js": "// sb_native_utf8_scalar_v1\n" +
|
|
21
|
+
"static i32 porf_gc_mark_queue_cap = 0;\n porf_gc_discard_free_runs();\n" +
|
|
22
|
+
" porf_native_fetch_timer_start(id, (i32)delay_ms, timer->repeat ? (i32)delay_ms : 0);\n" +
|
|
23
|
+
"void porf_native_fetch_collect_normal_from(void* stack_top) {\n" +
|
|
24
|
+
" if (porf_heap_base == 0) return;\n" +
|
|
25
|
+
" void* prev_stack_top = porf_c_stack_top;\n porf_c_stack_top = stack_top;\n" +
|
|
26
|
+
" porf_gc_collect_idle();\n porf_c_stack_top = prev_stack_top;\n}\n" +
|
|
27
|
+
"\nint porf_native_fetch_should_collect(void) { return 0; }\n" +
|
|
20
28
|
"void porf_native_fetch_runtime_init(void) {\n signal(SIGPIPE, SIG_IGN);\n porf_init(0, NULL);\n}\n" +
|
|
21
29
|
"f64 porf_native_fetch_get_port(void) {\nfixture:compiler/render.js\n" +
|
|
22
30
|
"int porf_native_fetch_read_value(jsval value, const char** out_buf, size_t* out_len, char** out_owned) {\n" +
|
|
@@ -27,7 +35,18 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
27
35
|
" return 0;\n" +
|
|
28
36
|
" }\n" +
|
|
29
37
|
" return -1;\n" +
|
|
30
|
-
"}\n"
|
|
38
|
+
"}\n" +
|
|
39
|
+
// patchSplitGlobals' anchors.
|
|
40
|
+
" const st = split ? '' : 'static ';\n" +
|
|
41
|
+
" case K.Global:\n cur.globals[node[N_A]] = true;\n case K.Local:\n return [sanitize(node[N_A]), P_PRIM];\n" +
|
|
42
|
+
" emit(`${ind()}${sanitize(node[N_A][N_A])} = ${rx(node[N_B], P_COMMA)};\\n`);\n" +
|
|
43
|
+
" ? `${sanitize(update[N_A][N_A])} = ${rx(update[N_B], P_COMMA)}`\n" +
|
|
44
|
+
" const t = sanitize(target[N_A]);\n" +
|
|
45
|
+
" for (const g of globals) link.push(`${st}${CT[g.type]} ${sanitize(g.name)};`);\n" +
|
|
46
|
+
" const name = sanitize(g.name);\n" +
|
|
47
|
+
" const buckets = sanitize(g.name.replace(/underlyingStore$/, 'underlyingBuckets'));\n" +
|
|
48
|
+
" const bucketsCap = sanitize(g.name.replace(/underlyingStore$/, 'underlyingBucketsCap'));\n" +
|
|
49
|
+
" for (const x of Object.keys(parts.globals).sort()) text.push(`extern ${CT[globalTypes[x]]} ${sanitize(x)};\\n`);\n",
|
|
31
50
|
"compiler/parse.js":
|
|
32
51
|
"import link from './modules.js';\n" +
|
|
33
52
|
"export default (input) => {\n" +
|
|
@@ -58,6 +77,9 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
58
77
|
" }\n\n arr.length = len;\n\n return new ${name}(arr);\n};\n",
|
|
59
78
|
"compiler/uwebsockets.js":
|
|
60
79
|
'export const makeUWebSocketsShimSource = () => `\n#include "App.h"\n' +
|
|
80
|
+
"void porf_native_fetch_collect_normal_from(void* stack_top);\n" +
|
|
81
|
+
"static void collect_after_request(void) {\n volatile int porf_stack_anchor = 0;\n" +
|
|
82
|
+
" porf_native_fetch_collect_normal_from((void*)&porf_stack_anchor);\n}\n" +
|
|
61
83
|
"static const size_t REQUEST_BODY_MAX_BYTES = 1024u * 1024u;\n" +
|
|
62
84
|
"int porf_native_fetch_read_value(struct jsval value, const char** out_buf, size_t* out_len, char** out_owned);\n" +
|
|
63
85
|
"static std::string_view lookup_status_line(i32 status) {\n" +
|
|
@@ -126,11 +148,31 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
126
148
|
"\n" +
|
|
127
149
|
" if (body_owned) free(body_owned);\n" +
|
|
128
150
|
"}\n",
|
|
129
|
-
"compiler/builtins/json.ts":
|
|
130
|
-
|
|
151
|
+
"compiler/builtins/json.ts":
|
|
152
|
+
"let __Porffor_json_bufLimit: i32 = 0;\n depth += 1;\n depth -= 1;\n depth += 1;\n depth -= 1;\n let out: i32 = __Porffor_json_serialize(buffer, value, 0, space);\n" +
|
|
153
|
+
"// sb_json_utf16_v1\n" +
|
|
154
|
+
"export const __Porffor_json_serialize = (_buffer: i32, value: any): i32 => {\n" +
|
|
155
|
+
" for (const x of (value as any[])) {\n }\n" +
|
|
156
|
+
" const val: any = value[key];\n" +
|
|
157
|
+
"};\nexport const __JSON_stringify = (value: any) => {\n let cap: i32 = 4096;\n};\n",
|
|
158
|
+
"runtime/native-fetch.js":
|
|
159
|
+
"export function finalize(response) {\n const body = response.body == null ? '' : String(response.body);\n const headersEntries = response.headers._entries;\n}\n",
|
|
160
|
+
"runtime/fetch-globals.js":
|
|
161
|
+
"// sb_text_encoder_scalar_v1\n" +
|
|
162
|
+
"class Blob {\n}\nclass TextEncoder {\n}\n" +
|
|
163
|
+
"class Headers {\n" +
|
|
164
|
+
" if (Porffor.type(init.entries) == Porffor.TYPES.function) {\n this._entries = Porffor.array.new(8);\n for (const entry of init.entries()) {\n" +
|
|
165
|
+
"}\nfunction setTimeout() {}\nclass TextDecoder {\n}\n" +
|
|
166
|
+
"class URL {\n constructor(input) {\n this.href = '';\n }\n}\n\nclass Request {\n}\n" +
|
|
167
|
+
"class Response {\n constructor(body = null, init = undefined) {\n this.body = body;\n this.status = Number(init?.status ?? 200);\n this.headers = new Headers(init?.headers);\n }\n}\n",
|
|
131
168
|
"compiler/builtins/uint8array_base64.ts": "let alphabet: string = 'base64';\nlet lastChunkHandling: string = 'loose';\nconst fullChunks: i32 = (len / 3) * 3;\n",
|
|
169
|
+
"compiler/builtins/object.ts": " return '[object ' + builtinTag + ']';\n};\n",
|
|
170
|
+
"compiler/builtins/console.ts": "export const __Porffor_consolePrint = (arg: any): void => {\n __Porffor_print(arg);\n};\n",
|
|
171
|
+
"compiler/builtins/map.ts": "export const Map = function (iterable: any): Map {\n};\n",
|
|
132
172
|
"compiler/codegen.js":
|
|
133
173
|
"if (!hoistRead && directCallOnlyFunctionBinding(scope, kind, name, pattern, func)) {\n return valUndefined();\n}\nconst func = resolveNamedFunction(scope, name) ?? funcByName(name);\n" +
|
|
174
|
+
" builtinCall(func, '__Porffor_object_class_value', [\n" +
|
|
175
|
+
" if (kind === 'get' || kind === 'set') initKind = kind;\n" +
|
|
134
176
|
" stmt(scope, Store(ctype, addr, 4, ctype === 'f64' || ctype === 'f32' ? f : toUint32(scope, f)));\n" +
|
|
135
177
|
" const taGet = (ctype, size, signed = true) => () => {\n" +
|
|
136
178
|
" const loaded = Load(ctype, taAddr(size), 4);\n" +
|
|
@@ -138,6 +180,13 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
|
|
|
138
180
|
" return Box(f, Const(T.i32, TYPES.number));\n" +
|
|
139
181
|
" };\n",
|
|
140
182
|
"compiler/builtins/string.ts":
|
|
183
|
+
"export const __String_prototype_toUpperCase = function (this: string) {\n" +
|
|
184
|
+
" if (chr >= 97) if (chr <= 122) chr -= 32;\n\n Porffor.IR.storeU16(j, 4, chr);\n" +
|
|
185
|
+
" if (chr >= 97) if (chr <= 122) chr -= 32;\n\n Porffor.IR.storeU8(j++, 4, chr);\n" +
|
|
186
|
+
" if (chr >= 65) if (chr <= 90) chr += 32;\n\n Porffor.IR.storeU16(j, 4, chr);\n" +
|
|
187
|
+
" if (chr >= 65) if (chr <= 90) chr += 32;\n\n Porffor.IR.storeU8(j++, 4, chr);\n" +
|
|
188
|
+
" for (let i: i32 = 0; i < count; i++) {\n Porffor.IR.copy(Porffor.IR.ptr(out) + 4 + i * thisLen, Porffor.IR.ptr(this) + 4, thisLen);\n }\n" +
|
|
189
|
+
" for (let i: i32 = 0; i < count; i++) {\n Porffor.IR.copy(Porffor.IR.ptr(out) + 4 + i * thisLen, Porffor.IR.ptr(this) + 4, thisLen);\n }\n" +
|
|
141
190
|
"export const __Porffor_string_replaceAll = (str: any, searchValue: any, replaceValue: any) => {\n" +
|
|
142
191
|
" let out: any = __Porffor_string_emptyLike(str);\n let appendIndex: i32 = 0;\n let searchIndex: i32 = 0;\n let matched: boolean = false;\n" +
|
|
143
192
|
" out = __Porffor_strcat(out, __Porffor_string_substringLike(str, appendIndex, matchIndex));\n out = __Porffor_strcat(out, __Porffor_string_applyReplacer(str, match, matchIndex, replaceValue));\n" +
|
|
@@ -233,12 +282,29 @@ test("the default call (no url/expectedSha256 override) never touches the networ
|
|
|
233
282
|
expect(await readFile(join(dir, "compiler/builtins/typedarray.js"), "utf8")).toContain(
|
|
234
283
|
"const targetOffsetNum: number = ecma262.ToIntegerOrInfinity(offset);",
|
|
235
284
|
);
|
|
236
|
-
// #238
|
|
237
|
-
//
|
|
238
|
-
|
|
239
|
-
expect(
|
|
240
|
-
//
|
|
241
|
-
expect(
|
|
285
|
+
// #238/#241 (out-of-range typed-array reads and writes) are upstream's own
|
|
286
|
+
// integer-indexed element access since alpha-18; no codegen patch remains.
|
|
287
|
+
// Request, Response, Blob and File to the Fetch spec; byte bodies sent as bytes.
|
|
288
|
+
expect(await readFile(join(dir, "runtime/native-fetch.js"), "utf8")).toContain("sproutboat: byte bodies go out as their bytes");
|
|
289
|
+
// Unicode case mapping, the WHATWG TextDecoder and Fetch-spec Headers.
|
|
290
|
+
expect(await readFile(join(dir, "compiler/builtins/string.ts"), "utf8")).toContain("sproutboat: Unicode case mapping");
|
|
291
|
+
const globals = await readFile(join(dir, "runtime/fetch-globals.js"), "utf8");
|
|
292
|
+
expect(globals).toContain("sproutboat: WHATWG UTF-8 decoder");
|
|
293
|
+
expect(globals).toContain("sproutboat: Fetch-spec Headers");
|
|
294
|
+
// B19: the native server streams ReadableStream bodies.
|
|
295
|
+
expect(await readFile(join(dir, "compiler/uwebsockets.js"), "utf8")).toContain("sproutboat B19: streamed responses");
|
|
296
|
+
// console colours only on a terminal.
|
|
297
|
+
expect(await readFile(join(dir, "compiler/builtins/console.ts"), "utf8")).toContain("__Porffor_consoleColors");
|
|
298
|
+
// JSON.stringify throws on cycles; typed arrays have their own toString tag.
|
|
299
|
+
expect(await readFile(join(dir, "compiler/builtins/json.ts"), "utf8")).toContain("sproutboat: JSON cycle check");
|
|
300
|
+
expect(await readFile(join(dir, "compiler/builtins/object.ts"), "utf8")).toContain("sproutboat: typed array toStringTag");
|
|
301
|
+
// B16: String.prototype.repeat doubles its copies.
|
|
302
|
+
expect(await readFile(join(dir, "compiler/builtins/string.ts"), "utf8")).toContain("sproutboat B16: doubling repeat");
|
|
303
|
+
// #287: full collections under load keep their free pages; the idle collection returns them.
|
|
304
|
+
expect(await readFile(join(dir, "compiler/render.js"), "utf8")).toContain("if (porf_gc_discard_now) porf_gc_discard_free_runs(); else porf_gc_discard_pending = 1;");
|
|
305
|
+
// #286: Map.groupBy, and private fields installed as non-enumerable.
|
|
306
|
+
expect(await readFile(join(dir, "compiler/builtins/map.ts"), "utf8")).toContain("export const __Map_groupBy");
|
|
307
|
+
expect(await readFile(join(dir, "compiler/codegen.js"), "utf8")).toContain("sproutboat #286: private fields");
|
|
242
308
|
// #256: named class expressions inside functions are rewritten at parse time.
|
|
243
309
|
expect(await readFile(join(dir, "compiler/parse.js"), "utf8")).toContain("sbClassSelf(ast); // sproutboat #256");
|
|
244
310
|
expect(await readFile(join(dir, "compiler/sb-class-self.js"), "utf8")).toContain("sproutboat #256");
|
|
@@ -307,13 +373,21 @@ test("a cache built by an older toolchain version is rebuilt, not silently reuse
|
|
|
307
373
|
expect(requests).toBe(2);
|
|
308
374
|
const rebuilt = JSON.parse(await readFile(manifestPath, "utf8"));
|
|
309
375
|
expect(rebuilt.toolchainVersion).toBe(manifest.toolchainVersion);
|
|
376
|
+
// B19: an unreleased patch edit keeps the version; the patch fingerprint still rebuilds.
|
|
377
|
+
expect(rebuilt.patchFingerprint).toBe(PATCH_FINGERPRINT);
|
|
378
|
+
expect(root).toEndWith(`-${PATCH_FINGERPRINT}`);
|
|
379
|
+
await rm(manifestPath);
|
|
380
|
+
await writeFile(manifestPath, JSON.stringify({ ...rebuilt, patchFingerprint: "000000000000" }));
|
|
381
|
+
await ensurePorffor(options);
|
|
382
|
+
expect(requests).toBe(3);
|
|
383
|
+
expect(JSON.parse(await readFile(manifestPath, "utf8")).patchFingerprint).toBe(PATCH_FINGERPRINT);
|
|
310
384
|
});
|
|
311
385
|
|
|
312
386
|
test("an interrupted stale lock is recovered", async () => {
|
|
313
387
|
const { archive, sha256 } = await fixture();
|
|
314
388
|
const cacheRoot = await mkdtemp(join(tmpdir(), "sb-porffor-interrupted-"));
|
|
315
389
|
temporary.push(cacheRoot);
|
|
316
|
-
const lock = join(cacheRoot, `porffor-${PORFFOR_COMMIT_FULL}.lock`);
|
|
390
|
+
const lock = join(cacheRoot, `porffor-${PORFFOR_COMMIT_FULL}-${PATCH_FINGERPRINT}.lock`);
|
|
317
391
|
await mkdir(lock);
|
|
318
392
|
const stale = new Date(Date.now() - 10 * 60_000);
|
|
319
393
|
await utimes(lock, stale, stale);
|
package/src/acquire.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { homedir } from "node:os";
|
|
|
5
5
|
import { resolve } from "node:path";
|
|
6
6
|
// @ts-expect-error Bun's file loader supplies a path; see the pinned-commit
|
|
7
7
|
// check below for why this is safe to trust unconditionally.
|
|
8
|
-
import vendoredPorfforArchive from "../vendor/porffor-
|
|
8
|
+
import vendoredPorfforArchive from "../vendor/porffor-deda494.tar.gz" with { type: "file" };
|
|
9
9
|
import { ensurePorfforPatched } from "./patch";
|
|
10
10
|
import { PORFFOR_ARCHIVE_SHA256, PORFFOR_ARCHIVE_URL, PORFFOR_COMMIT_FULL } from "./pin";
|
|
11
11
|
// Folded into the cache manifest below so a toolchain release that changes
|
|
@@ -14,6 +14,9 @@ import { PORFFOR_ARCHIVE_SHA256, PORFFOR_ARCHIVE_URL, PORFFOR_COMMIT_FULL } from
|
|
|
14
14
|
// to sit unused behind a still-"complete" cache keyed only on the upstream
|
|
15
15
|
// Porffor commit).
|
|
16
16
|
import { version as TOOLCHAIN_VERSION } from "../package.json" with { type: "json" };
|
|
17
|
+
// The release is not enough: unreleased patch edits share its version. The
|
|
18
|
+
// entry's directory and manifest carry the patch chain's fingerprint too.
|
|
19
|
+
import { PATCH_FINGERPRINT } from "./fingerprint";
|
|
17
20
|
|
|
18
21
|
export class PorfforToolchainError extends Error {
|
|
19
22
|
constructor(
|
|
@@ -56,12 +59,14 @@ async function complete(dir: string, expectedArchive = PORFFOR_ARCHIVE_SHA256):
|
|
|
56
59
|
commit?: string;
|
|
57
60
|
archiveSha256?: string;
|
|
58
61
|
toolchainVersion?: string;
|
|
62
|
+
patchFingerprint?: string;
|
|
59
63
|
files?: Record<string, string>;
|
|
60
64
|
};
|
|
61
65
|
if (
|
|
62
66
|
manifest.commit !== PORFFOR_COMMIT_FULL ||
|
|
63
67
|
manifest.archiveSha256 !== expectedArchive ||
|
|
64
|
-
manifest.toolchainVersion !== TOOLCHAIN_VERSION
|
|
68
|
+
manifest.toolchainVersion !== TOOLCHAIN_VERSION ||
|
|
69
|
+
manifest.patchFingerprint !== PATCH_FINGERPRINT
|
|
65
70
|
)
|
|
66
71
|
return false;
|
|
67
72
|
for (const file of required)
|
|
@@ -145,7 +150,7 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
145
150
|
process.env.SPROUTBOAT_TOOLCHAIN_CACHE ??
|
|
146
151
|
resolve(homedir(), ".cache/sproutboat"),
|
|
147
152
|
);
|
|
148
|
-
const dir = resolve(root, `porffor-${PORFFOR_COMMIT_FULL}`);
|
|
153
|
+
const dir = resolve(root, `porffor-${PORFFOR_COMMIT_FULL}-${PATCH_FINGERPRINT}`);
|
|
149
154
|
const expected = options.expectedSha256 ?? PORFFOR_ARCHIVE_SHA256;
|
|
150
155
|
if (await complete(dir, expected)) return dir;
|
|
151
156
|
await mkdir(root, { recursive: true });
|
|
@@ -159,7 +164,7 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
159
164
|
}
|
|
160
165
|
const stage = resolve(
|
|
161
166
|
root,
|
|
162
|
-
`.porffor-${PORFFOR_COMMIT_FULL}-${process.pid}-${crypto.randomUUID()}`,
|
|
167
|
+
`.porffor-${PORFFOR_COMMIT_FULL}-${PATCH_FINGERPRINT}-${process.pid}-${crypto.randomUUID()}`,
|
|
163
168
|
);
|
|
164
169
|
try {
|
|
165
170
|
if (await complete(dir, expected)) return dir;
|
|
@@ -200,7 +205,7 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
200
205
|
);
|
|
201
206
|
await writeFile(
|
|
202
207
|
resolve(stage, ".sproutboat-complete"),
|
|
203
|
-
JSON.stringify({ commit: PORFFOR_COMMIT_FULL, archiveSha256: actual, toolchainVersion: TOOLCHAIN_VERSION, files }),
|
|
208
|
+
JSON.stringify({ commit: PORFFOR_COMMIT_FULL, archiveSha256: actual, toolchainVersion: TOOLCHAIN_VERSION, patchFingerprint: PATCH_FINGERPRINT, files }),
|
|
204
209
|
{ mode: 0o444 },
|
|
205
210
|
);
|
|
206
211
|
await rename(stage, dir);
|
|
@@ -214,5 +219,5 @@ export async function ensurePorffor(options: AcquireOptions = {}): Promise<strin
|
|
|
214
219
|
export function cachedPorfforRoot(
|
|
215
220
|
root = process.env.SPROUTBOAT_TOOLCHAIN_CACHE ?? resolve(homedir(), ".cache/sproutboat"),
|
|
216
221
|
): string {
|
|
217
|
-
return resolve(root, `porffor-${PORFFOR_COMMIT_FULL}`);
|
|
222
|
+
return resolve(root, `porffor-${PORFFOR_COMMIT_FULL}-${PATCH_FINGERPRINT}`);
|
|
218
223
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
import { ensurePorffor } from "./acquire";
|
|
6
|
+
|
|
7
|
+
// Alpha 18 made our typed-array bounds (#238/#241) and JSON toJSON patches
|
|
8
|
+
// redundant; these cases keep the upstream behaviour we now rely on. The local
|
|
9
|
+
// function cases still need patchLocalFunctionTypes: unpatched Alpha 18 throws
|
|
10
|
+
// a TypeError, inferring each call's type from the unrelated same-named async
|
|
11
|
+
// function instead of the local binding.
|
|
12
|
+
const source = `async function resolve(x) { return x; }
|
|
13
|
+
function outer() { const resolve = function (v) { return v * 2; }; return resolve(5) + 1; }
|
|
14
|
+
const models = { user: "u" };
|
|
15
|
+
function pick(name) { const getModel = (n) => models[n] ?? n; return getModel(name).toUpperCase(); }
|
|
16
|
+
async function getModel() { return null; }
|
|
17
|
+
const a = new Uint8Array(4); a[1] = 7; a[10] = 9; a[-1] = 5;
|
|
18
|
+
const b = new Uint8Array(4);
|
|
19
|
+
console.log(JSON.stringify({
|
|
20
|
+
local: [outer(), pick("user"), pick("post")],
|
|
21
|
+
typed: [String(a[10]), String(a[-1]), String(a[1.5]), a.join(","), b.join(",")],
|
|
22
|
+
json: [JSON.stringify({ d: new Date(0), o: { toJSON() { return "x"; } }, n: [1, { toJSON(k) { return k; } }] }),
|
|
23
|
+
JSON.stringify({ a: 1, b: 2 }, (k, v) => (k === "a" ? undefined : v))],
|
|
24
|
+
iterators: [[1, 2, 3].values().map((x) => x * 2).toArray().join(","), [...new Set([1, 2]).union(new Set([3]))].join(",")],
|
|
25
|
+
}));`;
|
|
26
|
+
|
|
27
|
+
test("alpha-18: local function types, typed-array bounds, toJSON and iterator helpers match Bun", async () => {
|
|
28
|
+
const dir = await mkdtemp(resolve(tmpdir(), "sb-alpha18-"));
|
|
29
|
+
try {
|
|
30
|
+
const porffor = await ensurePorffor();
|
|
31
|
+
const input = resolve(dir, "alpha18.js"), binary = resolve(dir, "alpha18");
|
|
32
|
+
await writeFile(input, source);
|
|
33
|
+
const compile = Bun.spawnSync(["bun", resolve(porffor, "runtime/index.js"), "native", input, "-o", binary, "-s", "-O0"], { stdout: "pipe", stderr: "pipe" });
|
|
34
|
+
expect(compile.exitCode, new TextDecoder().decode(compile.stderr)).toBe(0);
|
|
35
|
+
const run = Bun.spawnSync([binary], { stdout: "pipe", stderr: "pipe", timeout: 10_000 });
|
|
36
|
+
expect(run.exitCode, new TextDecoder().decode(run.stderr)).toBe(0);
|
|
37
|
+
const reference = Bun.spawnSync(["bun", input], { stdout: "pipe" });
|
|
38
|
+
const strip = (bytes: Uint8Array) => new TextDecoder().decode(bytes).replaceAll("\x1b[33m", "").replaceAll("\x1b[0m", "").trim();
|
|
39
|
+
expect(JSON.parse(strip(run.stdout))).toEqual(JSON.parse(strip(reference.stdout)));
|
|
40
|
+
} finally {
|
|
41
|
+
await rm(dir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}, 60_000);
|
package/src/case-map.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unicode simple case mappings for BMP code units (one unit to one unit), generated by
|
|
3
|
+
* gen-case-map.ts from Bun 1.4.3's String.prototype.toLowerCase/toUpperCase and checked
|
|
4
|
+
* in, so patched builds do not depend on the patching engine's Unicode version. Runs of four
|
|
5
|
+
* UTF-16 code units: start, end, the start's mapping, step (1, or 2 for alternating pairs);
|
|
6
|
+
* a unit in a run maps to that mapping plus its distance from the start.
|
|
7
|
+
*/
|
|
8
|
+
export const CASE_LOWER_RUNS = "\u00c0\u00d6\u00e0\u0001\u00d8\u00de\u00f8\u0001\u0100\u012e\u0101\u0002\u0132\u0136\u0133\u0002\u0139\u0147\u013a\u0002\u014a\u0176\u014b\u0002\u0178\u0178\u00ff\u0001\u0179\u017d\u017a\u0002\u0181\u0181\u0253\u0001\u0182\u0184\u0183\u0002\u0186\u0186\u0254\u0001\u0187\u0187\u0188\u0001\u0189\u018a\u0256\u0001\u018b\u018b\u018c\u0001\u018e\u018e\u01dd\u0001\u018f\u018f\u0259\u0001\u0190\u0190\u025b\u0001\u0191\u0191\u0192\u0001\u0193\u0193\u0260\u0001\u0194\u0194\u0263\u0001\u0196\u0196\u0269\u0001\u0197\u0197\u0268\u0001\u0198\u0198\u0199\u0001\u019c\u019c\u026f\u0001\u019d\u019d\u0272\u0001\u019f\u019f\u0275\u0001\u01a0\u01a4\u01a1\u0002\u01a6\u01a6\u0280\u0001\u01a7\u01a7\u01a8\u0001\u01a9\u01a9\u0283\u0001\u01ac\u01ac\u01ad\u0001\u01ae\u01ae\u0288\u0001\u01af\u01af\u01b0\u0001\u01b1\u01b2\u028a\u0001\u01b3\u01b5\u01b4\u0002\u01b7\u01b7\u0292\u0001\u01b8\u01b8\u01b9\u0001\u01bc\u01bc\u01bd\u0001\u01c4\u01c4\u01c6\u0001\u01c5\u01c5\u01c6\u0001\u01c7\u01c7\u01c9\u0001\u01c8\u01c8\u01c9\u0001\u01ca\u01ca\u01cc\u0001\u01cb\u01db\u01cc\u0002\u01de\u01ee\u01df\u0002\u01f1\u01f1\u01f3\u0001\u01f2\u01f4\u01f3\u0002\u01f6\u01f6\u0195\u0001\u01f7\u01f7\u01bf\u0001\u01f8\u021e\u01f9\u0002\u0220\u0220\u019e\u0001\u0222\u0232\u0223\u0002\u023a\u023a\u2c65\u0001\u023b\u023b\u023c\u0001\u023d\u023d\u019a\u0001\u023e\u023e\u2c66\u0001\u0241\u0241\u0242\u0001\u0243\u0243\u0180\u0001\u0244\u0244\u0289\u0001\u0245\u0245\u028c\u0001\u0246\u024e\u0247\u0002\u0370\u0372\u0371\u0002\u0376\u0376\u0377\u0001\u037f\u037f\u03f3\u0001\u0386\u0386\u03ac\u0001\u0388\u038a\u03ad\u0001\u038c\u038c\u03cc\u0001\u038e\u038f\u03cd\u0001\u0391\u03a1\u03b1\u0001\u03a3\u03ab\u03c3\u0001\u03cf\u03cf\u03d7\u0001\u03d8\u03ee\u03d9\u0002\u03f4\u03f4\u03b8\u0001\u03f7\u03f7\u03f8\u0001\u03f9\u03f9\u03f2\u0001\u03fa\u03fa\u03fb\u0001\u03fd\u03ff\u037b\u0001\u0400\u040f\u0450\u0001\u0410\u042f\u0430\u0001\u0460\u0480\u0461\u0002\u048a\u04be\u048b\u0002\u04c0\u04c0\u04cf\u0001\u04c1\u04cd\u04c2\u0002\u04d0\u052e\u04d1\u0002\u0531\u0556\u0561\u0001\u10a0\u10c5\u2d00\u0001\u10c7\u10c7\u2d27\u0001\u10cd\u10cd\u2d2d\u0001\u13a0\u13ef\uab70\u0001\u13f0\u13f5\u13f8\u0001\u1c90\u1cba\u10d0\u0001\u1cbd\u1cbf\u10fd\u0001\u1e00\u1e94\u1e01\u0002\u1e9e\u1e9e\u00df\u0001\u1ea0\u1efe\u1ea1\u0002\u1f08\u1f0f\u1f00\u0001\u1f18\u1f1d\u1f10\u0001\u1f28\u1f2f\u1f20\u0001\u1f38\u1f3f\u1f30\u0001\u1f48\u1f4d\u1f40\u0001\u1f59\u1f5f\u1f51\u0002\u1f68\u1f6f\u1f60\u0001\u1f88\u1f8f\u1f80\u0001\u1f98\u1f9f\u1f90\u0001\u1fa8\u1faf\u1fa0\u0001\u1fb8\u1fb9\u1fb0\u0001\u1fba\u1fbb\u1f70\u0001\u1fbc\u1fbc\u1fb3\u0001\u1fc8\u1fcb\u1f72\u0001\u1fcc\u1fcc\u1fc3\u0001\u1fd8\u1fd9\u1fd0\u0001\u1fda\u1fdb\u1f76\u0001\u1fe8\u1fe9\u1fe0\u0001\u1fea\u1feb\u1f7a\u0001\u1fec\u1fec\u1fe5\u0001\u1ff8\u1ff9\u1f78\u0001\u1ffa\u1ffb\u1f7c\u0001\u1ffc\u1ffc\u1ff3\u0001\u2126\u2126\u03c9\u0001\u212a\u212a\u006b\u0001\u212b\u212b\u00e5\u0001\u2132\u2132\u214e\u0001\u2160\u216f\u2170\u0001\u2183\u2183\u2184\u0001\u24b6\u24cf\u24d0\u0001\u2c00\u2c2f\u2c30\u0001\u2c60\u2c60\u2c61\u0001\u2c62\u2c62\u026b\u0001\u2c63\u2c63\u1d7d\u0001\u2c64\u2c64\u027d\u0001\u2c67\u2c6b\u2c68\u0002\u2c6d\u2c6d\u0251\u0001\u2c6e\u2c6e\u0271\u0001\u2c6f\u2c6f\u0250\u0001\u2c70\u2c70\u0252\u0001\u2c72\u2c72\u2c73\u0001\u2c75\u2c75\u2c76\u0001\u2c7e\u2c7f\u023f\u0001\u2c80\u2ce2\u2c81\u0002\u2ceb\u2ced\u2cec\u0002\u2cf2\u2cf2\u2cf3\u0001\ua640\ua66c\ua641\u0002\ua680\ua69a\ua681\u0002\ua722\ua72e\ua723\u0002\ua732\ua76e\ua733\u0002\ua779\ua77b\ua77a\u0002\ua77d\ua77d\u1d79\u0001\ua77e\ua786\ua77f\u0002\ua78b\ua78b\ua78c\u0001\ua78d\ua78d\u0265\u0001\ua790\ua792\ua791\u0002\ua796\ua7a8\ua797\u0002\ua7aa\ua7aa\u0266\u0001\ua7ab\ua7ab\u025c\u0001\ua7ac\ua7ac\u0261\u0001\ua7ad\ua7ad\u026c\u0001\ua7ae\ua7ae\u026a\u0001\ua7b0\ua7b0\u029e\u0001\ua7b1\ua7b1\u0287\u0001\ua7b2\ua7b2\u029d\u0001\ua7b3\ua7b3\uab53\u0001\ua7b4\ua7c2\ua7b5\u0002\ua7c4\ua7c4\ua794\u0001\ua7c5\ua7c5\u0282\u0001\ua7c6\ua7c6\u1d8e\u0001\ua7c7\ua7c9\ua7c8\u0002\ua7d0\ua7d0\ua7d1\u0001\ua7d6\ua7d8\ua7d7\u0002\ua7f5\ua7f5\ua7f6\u0001\uff21\uff3a\uff41\u0001";
|
|
9
|
+
export const CASE_UPPER_RUNS = "\u00b5\u00b5\u039c\u0001\u00e0\u00f6\u00c0\u0001\u00f8\u00fe\u00d8\u0001\u00ff\u00ff\u0178\u0001\u0101\u012f\u0100\u0002\u0131\u0131\u0049\u0001\u0133\u0137\u0132\u0002\u013a\u0148\u0139\u0002\u014b\u0177\u014a\u0002\u017a\u017e\u0179\u0002\u017f\u017f\u0053\u0001\u0180\u0180\u0243\u0001\u0183\u0185\u0182\u0002\u0188\u0188\u0187\u0001\u018c\u018c\u018b\u0001\u0192\u0192\u0191\u0001\u0195\u0195\u01f6\u0001\u0199\u0199\u0198\u0001\u019a\u019a\u023d\u0001\u019e\u019e\u0220\u0001\u01a1\u01a5\u01a0\u0002\u01a8\u01a8\u01a7\u0001\u01ad\u01ad\u01ac\u0001\u01b0\u01b0\u01af\u0001\u01b4\u01b6\u01b3\u0002\u01b9\u01b9\u01b8\u0001\u01bd\u01bd\u01bc\u0001\u01bf\u01bf\u01f7\u0001\u01c5\u01c5\u01c4\u0001\u01c6\u01c6\u01c4\u0001\u01c8\u01c8\u01c7\u0001\u01c9\u01c9\u01c7\u0001\u01cb\u01cb\u01ca\u0001\u01cc\u01cc\u01ca\u0001\u01ce\u01dc\u01cd\u0002\u01dd\u01dd\u018e\u0001\u01df\u01ef\u01de\u0002\u01f2\u01f2\u01f1\u0001\u01f3\u01f3\u01f1\u0001\u01f5\u01f5\u01f4\u0001\u01f9\u021f\u01f8\u0002\u0223\u0233\u0222\u0002\u023c\u023c\u023b\u0001\u023f\u0240\u2c7e\u0001\u0242\u0242\u0241\u0001\u0247\u024f\u0246\u0002\u0250\u0250\u2c6f\u0001\u0251\u0251\u2c6d\u0001\u0252\u0252\u2c70\u0001\u0253\u0253\u0181\u0001\u0254\u0254\u0186\u0001\u0256\u0257\u0189\u0001\u0259\u0259\u018f\u0001\u025b\u025b\u0190\u0001\u025c\u025c\ua7ab\u0001\u0260\u0260\u0193\u0001\u0261\u0261\ua7ac\u0001\u0263\u0263\u0194\u0001\u0265\u0265\ua78d\u0001\u0266\u0266\ua7aa\u0001\u0268\u0268\u0197\u0001\u0269\u0269\u0196\u0001\u026a\u026a\ua7ae\u0001\u026b\u026b\u2c62\u0001\u026c\u026c\ua7ad\u0001\u026f\u026f\u019c\u0001\u0271\u0271\u2c6e\u0001\u0272\u0272\u019d\u0001\u0275\u0275\u019f\u0001\u027d\u027d\u2c64\u0001\u0280\u0280\u01a6\u0001\u0282\u0282\ua7c5\u0001\u0283\u0283\u01a9\u0001\u0287\u0287\ua7b1\u0001\u0288\u0288\u01ae\u0001\u0289\u0289\u0244\u0001\u028a\u028b\u01b1\u0001\u028c\u028c\u0245\u0001\u0292\u0292\u01b7\u0001\u029d\u029d\ua7b2\u0001\u029e\u029e\ua7b0\u0001\u0345\u0345\u0399\u0001\u0371\u0373\u0370\u0002\u0377\u0377\u0376\u0001\u037b\u037d\u03fd\u0001\u03ac\u03ac\u0386\u0001\u03ad\u03af\u0388\u0001\u03b1\u03c1\u0391\u0001\u03c2\u03c2\u03a3\u0001\u03c3\u03cb\u03a3\u0001\u03cc\u03cc\u038c\u0001\u03cd\u03ce\u038e\u0001\u03d0\u03d0\u0392\u0001\u03d1\u03d1\u0398\u0001\u03d5\u03d5\u03a6\u0001\u03d6\u03d6\u03a0\u0001\u03d7\u03d7\u03cf\u0001\u03d9\u03ef\u03d8\u0002\u03f0\u03f0\u039a\u0001\u03f1\u03f1\u03a1\u0001\u03f2\u03f2\u03f9\u0001\u03f3\u03f3\u037f\u0001\u03f5\u03f5\u0395\u0001\u03f8\u03f8\u03f7\u0001\u03fb\u03fb\u03fa\u0001\u0430\u044f\u0410\u0001\u0450\u045f\u0400\u0001\u0461\u0481\u0460\u0002\u048b\u04bf\u048a\u0002\u04c2\u04ce\u04c1\u0002\u04cf\u04cf\u04c0\u0001\u04d1\u052f\u04d0\u0002\u0561\u0586\u0531\u0001\u10d0\u10fa\u1c90\u0001\u10fd\u10ff\u1cbd\u0001\u13f8\u13fd\u13f0\u0001\u1c80\u1c80\u0412\u0001\u1c81\u1c81\u0414\u0001\u1c82\u1c82\u041e\u0001\u1c83\u1c84\u0421\u0001\u1c85\u1c85\u0422\u0001\u1c86\u1c86\u042a\u0001\u1c87\u1c87\u0462\u0001\u1c88\u1c88\ua64a\u0001\u1d79\u1d79\ua77d\u0001\u1d7d\u1d7d\u2c63\u0001\u1d8e\u1d8e\ua7c6\u0001\u1e01\u1e95\u1e00\u0002\u1e9b\u1e9b\u1e60\u0001\u1ea1\u1eff\u1ea0\u0002\u1f00\u1f07\u1f08\u0001\u1f10\u1f15\u1f18\u0001\u1f20\u1f27\u1f28\u0001\u1f30\u1f37\u1f38\u0001\u1f40\u1f45\u1f48\u0001\u1f51\u1f57\u1f59\u0002\u1f60\u1f67\u1f68\u0001\u1f70\u1f71\u1fba\u0001\u1f72\u1f75\u1fc8\u0001\u1f76\u1f77\u1fda\u0001\u1f78\u1f79\u1ff8\u0001\u1f7a\u1f7b\u1fea\u0001\u1f7c\u1f7d\u1ffa\u0001\u1fb0\u1fb1\u1fb8\u0001\u1fbe\u1fbe\u0399\u0001\u1fd0\u1fd1\u1fd8\u0001\u1fe0\u1fe1\u1fe8\u0001\u1fe5\u1fe5\u1fec\u0001\u214e\u214e\u2132\u0001\u2170\u217f\u2160\u0001\u2184\u2184\u2183\u0001\u24d0\u24e9\u24b6\u0001\u2c30\u2c5f\u2c00\u0001\u2c61\u2c61\u2c60\u0001\u2c65\u2c65\u023a\u0001\u2c66\u2c66\u023e\u0001\u2c68\u2c6c\u2c67\u0002\u2c73\u2c73\u2c72\u0001\u2c76\u2c76\u2c75\u0001\u2c81\u2ce3\u2c80\u0002\u2cec\u2cee\u2ceb\u0002\u2cf3\u2cf3\u2cf2\u0001\u2d00\u2d25\u10a0\u0001\u2d27\u2d27\u10c7\u0001\u2d2d\u2d2d\u10cd\u0001\ua641\ua66d\ua640\u0002\ua681\ua69b\ua680\u0002\ua723\ua72f\ua722\u0002\ua733\ua76f\ua732\u0002\ua77a\ua77c\ua779\u0002\ua77f\ua787\ua77e\u0002\ua78c\ua78c\ua78b\u0001\ua791\ua793\ua790\u0002\ua794\ua794\ua7c4\u0001\ua797\ua7a9\ua796\u0002\ua7b5\ua7c3\ua7b4\u0002\ua7c8\ua7ca\ua7c7\u0002\ua7d1\ua7d1\ua7d0\u0001\ua7d7\ua7d9\ua7d6\u0002\ua7f6\ua7f6\ua7f5\u0001\uab53\uab53\ua7b3\u0001\uab70\uabbf\u13a0\u0001\uff41\uff5a\uff21\u0001";
|