@sproutboat/toolchain 0.4.12 → 0.4.13

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @sproutboat/toolchain
2
2
 
3
+ ## 0.4.13
4
+
5
+ ### Patch Changes
6
+
7
+ - bb1b6a2: Parse ISO date timezone offsets correctly in native Porffor builds, including zero fields and fractional seconds.
8
+
3
9
  ## 0.4.12
4
10
 
5
11
  ### Patch Changes
@@ -0,0 +1,35 @@
1
+ # Porffor pin scorecard
2
+
3
+ Pin: alpha-9 (de4eb588264885b3a1596f75010e371a2052033f). Compatibility report: 2026-09-26T00:32:42.739Z.
4
+
5
+ | Measure | Result |
6
+ | --- | ---: |
7
+ | Fixtures compiled | 32/32 |
8
+ | Behavior matches | 30/32 |
9
+ | Median / p95 compile, ms | 3139 / 3472 |
10
+ | Median / p95 binary, bytes | 983392 / 1016464 |
11
+ | Patch markers | 27 |
12
+ | Upstream files written | 6 |
13
+ | Fresh patch application | pass |
14
+
15
+ This is the first recorded pin. Compare the next pin against this snapshot.
16
+
17
+ ## Patch inventory
18
+
19
+ - `compiler/render.js`: PORT_MARKER, CONSOLE_MARKER, BYTESTRING_MARKER, READ_RAW_MARKER
20
+ - `compiler/index.js`: LINK_MARKER, CFLAGS_MARKER
21
+ - `compiler/builtins/typedarray.js`: TYPED_ARRAY_FROM_MARKER
22
+ - `compiler/builtins/date.ts`: DATE_PARSER_MARKER
23
+ - `compiler/uwebsockets.js`: 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
24
+ - `compiler/builtins_precompiled.js`: regenerated builtin table
25
+
26
+ Patch source SHA-256: `69cf5d0bcf3f4d4c05b81df0ff04b83479af622539fa19566155531bf3ee469c`.
27
+
28
+ ## Known behavior gaps
29
+
30
+ - 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}
31
+ - 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"}
32
+
33
+ ## Review
34
+
35
+ Use this scorecard with the full compatibility report and issue #35's build telemetry. If behavior regresses or patch work grows, review the individual patches and measure an alternate backend with the same fixtures.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sproutboat/toolchain",
3
- "version": "0.4.12",
3
+ "version": "0.4.13",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,58 @@
1
+ {
2
+ "pin": "alpha-9 (de4eb588264885b3a1596f75010e371a2052033f)",
3
+ "reportGeneratedAt": "2026-09-26T00:32:42.739Z",
4
+ "fixtures": 32,
5
+ "compiled": 32,
6
+ "matched": 30,
7
+ "medianCompileMs": 3139,
8
+ "p95CompileMs": 3472,
9
+ "medianBinaryBytes": 983392,
10
+ "p95BinaryBytes": 1016464,
11
+ "patchSourceSha256": "69cf5d0bcf3f4d4c05b81df0ff04b83479af622539fa19566155531bf3ee469c",
12
+ "patchMarkerCount": 27,
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
+ ],
24
+ "compiler/builtins/typedarray.js": [
25
+ "TYPED_ARRAY_FROM_MARKER"
26
+ ],
27
+ "compiler/builtins/date.ts": [
28
+ "DATE_PARSER_MARKER"
29
+ ],
30
+ "compiler/uwebsockets.js": [
31
+ "BODY_MARKER",
32
+ "STATUS_SIG_MARKER",
33
+ "STATUS_303_MARKER",
34
+ "STATUS_DEFAULT_MARKER",
35
+ "HDR_SIG_MARKER",
36
+ "HDR_CALL_MARKER",
37
+ "HDR_CAP_MARKER",
38
+ "HDR_SKIP_MARKER",
39
+ "HDR_APPEND_MARKER",
40
+ "READ_RAW_DECL_MARKER",
41
+ "FORBIDDEN_HDR_MARKER",
42
+ "WRITE_RESPONSE_MARKER",
43
+ "R2_TRANSFER_MARKER",
44
+ "R2_TRANSFER_CALL_MARKER",
45
+ "R2_DOWNLOAD_CACHE_MARKER",
46
+ "R2_RANGE_CACHE_MARKER",
47
+ "R2_CONDITIONAL_CACHE_MARKER",
48
+ "R2_STREAMING_CACHE_MARKER",
49
+ "R2_RANGE_HEADER_CACHE_MARKER"
50
+ ],
51
+ "compiler/builtins_precompiled.js": []
52
+ },
53
+ "patchApplication": "pass",
54
+ "mismatches": [
55
+ "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}",
56
+ "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\"}"
57
+ ]
58
+ }
@@ -34,6 +34,7 @@ async function fixture(): Promise<{ archive: string; sha256: string }> {
34
34
  "const path = require('node:path');\n" +
35
35
  "fs.writeFileSync(path.join(__dirname, 'builtins_precompiled.js'), fs.readFileSync(path.join(__dirname, 'builtins/typedarray.js')));\n",
36
36
  "compiler/builtins_precompiled.js": "fixture:precompiled\n",
37
+ "compiler/builtins/date.ts": "export const __ecma262_ParseDTSF = (string: string) => {\n let n: number = 0;\n let nInd: number = 0;\n\n const len: i32 = string.length;\n const endPtr: i32 = Porffor.IR.ptr(string) + len;\n let ptr: i32 = Porffor.IR.ptr(string);\n\n while (ptr <= endPtr) { // <= to include extra null byte to set last n\n const chr: i32 = Porffor.IR.loadU8(ptr++, 4);\n if (Porffor.fastAnd(chr >= 48, chr <= 57)) { // 0-9\n n *= 10;\n n += chr - 48;\n continue;\n }\n\n if (chr == 45) { // -\n if (Porffor.fastOr(ptr == Porffor.IR.ptr(string), nInd == 7)) n = -n;\n }\n\n if (n > 0) {\n if (nInd == 0) y = n;\n else if (nInd == 1) m = n - 1;\n else if (nInd == 2) dt = n;\n else if (nInd == 3) h = n;\n else if (nInd == 4) min = n;\n else if (nInd == 5) s = n;\n else if (nInd == 6) milli = n;\n else if (nInd == 7) tzHour = n;\n else if (nInd == 8) tzMin = n;\n\n n = 0;\n nInd++;\n }\n }\n\n h += tzHour;\n min += tzMin;\n};\n// RFC 7231 or Date.prototype.toString() parser\n",
37
38
  "compiler/builtins/typedarray.js":
38
39
  "export const __${name}_from = (arg: any, mapFn: any): ${name} => {\n" +
39
40
  " const arr: any[] = Porffor.array.new(4);\n" +
package/src/acquire.ts CHANGED
@@ -38,6 +38,7 @@ const required = [
38
38
  "compiler/index.js",
39
39
  "compiler/uwebsockets.js",
40
40
  "compiler/precompile.js",
41
+ "compiler/builtins/date.ts",
41
42
  "compiler/builtins/typedarray.js",
42
43
  "compiler/builtins_precompiled.js",
43
44
  ];
@@ -0,0 +1,37 @@
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
+ test("native ISO dates honor signed offsets, zero fields, and fractional seconds", async () => {
8
+ const root = await mkdtemp(resolve(tmpdir(), "sb-date-native-"));
9
+ const samples = [
10
+ "2024-01-02T03:04:05Z",
11
+ "2024-01-02T03:04:05+02:00",
12
+ "2024-01-02T03:04:05-05:00",
13
+ "2024-01-02T03:04:05.250+01:00",
14
+ "2024-01-02T00:00:00Z",
15
+ "2024-01-02T03:04:05.2+02:30",
16
+ "2024-01-02T03:04:05.25-00:30",
17
+ ];
18
+ try {
19
+ const porffor = await ensurePorffor({ cacheRoot: resolve(root, "cache") });
20
+ const source = resolve(root, "date.js");
21
+ const binary = resolve(root, "date-native");
22
+ await writeFile(source, `for (const value of ${JSON.stringify(samples)}) console.log(new Date(value).toISOString());\n`);
23
+ const compile = Bun.spawnSync(["bun", resolve(porffor, "runtime/index.js"), "native", source, "-o", binary, "-s"], {
24
+ cwd: root,
25
+ stdout: "pipe",
26
+ stderr: "pipe",
27
+ });
28
+ expect(compile.exitCode, Buffer.from(compile.stderr).toString()).toBe(0);
29
+ const run = Bun.spawnSync([binary], { stdout: "pipe", stderr: "pipe" });
30
+ expect(run.exitCode, Buffer.from(run.stderr).toString()).toBe(0);
31
+ expect(Buffer.from(run.stdout).toString().trim().split("\n")).toEqual(
32
+ samples.map((value) => new Date(value).toISOString()),
33
+ );
34
+ } finally {
35
+ await rm(root, { recursive: true, force: true });
36
+ }
37
+ }, 30_000);
package/src/patch.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Idempotent, marker-guarded in-place edits to Porffor's generated C
3
3
  * (`compiler/render.js`, `compiler/index.js`, `compiler/uwebsockets.js`,
4
- * `compiler/builtins/typedarray.js`). Run
4
+ * `compiler/builtins/typedarray.js`, `compiler/builtins/date.ts`). Run
5
5
  * from the build path, not a `postinstall` hook: package managers block
6
6
  * dependency lifecycle scripts by default, so a published `postinstall` would
7
7
  * silently not run.
@@ -193,6 +193,93 @@ const TYPED_ARRAY_FROM_INJECT =
193
193
  " }\n\n arr.length = len;\n\n return new ${name}(arr);\n};";
194
194
  const TYPED_ARRAY_FROM_MARKER = "sproutboat: TypedArray.from accepts array-like objects";
195
195
 
196
+ // Porffor alpha 9 treats +02:00 as two milliseconds and adds offset hours
197
+ // instead of subtracting them. Count fields even when they contain zero, and
198
+ // apply the signed offset after parsing the local wall-clock fields.
199
+ const DATE_PARSER_MARKER = "sproutboat: ISO timezone offset";
200
+ const DATE_PARSER_ANCHOR = ` let n: number = 0;
201
+ let nInd: number = 0;
202
+
203
+ const len: i32 = string.length;
204
+ const endPtr: i32 = Porffor.IR.ptr(string) + len;
205
+ let ptr: i32 = Porffor.IR.ptr(string);
206
+
207
+ while (ptr <= endPtr) { // <= to include extra null byte to set last n
208
+ const chr: i32 = Porffor.IR.loadU8(ptr++, 4);
209
+ if (Porffor.fastAnd(chr >= 48, chr <= 57)) { // 0-9
210
+ n *= 10;
211
+ n += chr - 48;
212
+ continue;
213
+ }
214
+
215
+ if (chr == 45) { // -
216
+ if (Porffor.fastOr(ptr == Porffor.IR.ptr(string), nInd == 7)) n = -n;
217
+ }
218
+
219
+ if (n > 0) {
220
+ if (nInd == 0) y = n;
221
+ else if (nInd == 1) m = n - 1;
222
+ else if (nInd == 2) dt = n;
223
+ else if (nInd == 3) h = n;
224
+ else if (nInd == 4) min = n;
225
+ else if (nInd == 5) s = n;
226
+ else if (nInd == 6) milli = n;
227
+ else if (nInd == 7) tzHour = n;
228
+ else if (nInd == 8) tzMin = n;
229
+
230
+ n = 0;
231
+ nInd++;
232
+ }
233
+ }
234
+
235
+ h += tzHour;
236
+ min += tzMin;`;
237
+ const DATE_PARSER_INJECT = ` // sproutboat: ISO timezone offset
238
+ let n: number = 0;
239
+ let nInd: number = 0;
240
+ let digits: i32 = 0;
241
+ let offsetSign: i32 = 0;
242
+
243
+ const len: i32 = string.length;
244
+ const endPtr: i32 = Porffor.IR.ptr(string) + len;
245
+ let ptr: i32 = Porffor.IR.ptr(string);
246
+
247
+ while (ptr <= endPtr) {
248
+ const chr: i32 = Porffor.IR.loadU8(ptr++, 4);
249
+ if (Porffor.fastAnd(chr >= 48, chr <= 57)) {
250
+ n = n * 10 + chr - 48;
251
+ digits++;
252
+ continue;
253
+ }
254
+
255
+ if (digits > 0) {
256
+ if (nInd == 0) y = n;
257
+ else if (nInd == 1) m = n - 1;
258
+ else if (nInd == 2) dt = n;
259
+ else if (nInd == 3) h = n;
260
+ else if (nInd == 4) min = n;
261
+ else if (nInd == 5) s = n;
262
+ else if (nInd == 6) {
263
+ while (digits < 3) { n *= 10; digits++; }
264
+ while (digits > 3) { n = Math.trunc(n / 10); digits--; }
265
+ milli = n;
266
+ }
267
+ else if (nInd == 7) tzHour = n;
268
+ else if (nInd == 8) tzMin = n;
269
+ n = 0;
270
+ nInd++;
271
+ digits = 0;
272
+ }
273
+
274
+ if (Porffor.fastOr(chr == 43, chr == 45) && nInd >= 6) {
275
+ offsetSign = chr == 43 ? 1 : -1;
276
+ nInd = 7;
277
+ }
278
+ }
279
+
280
+ h -= offsetSign * tzHour;
281
+ min -= offsetSign * tzMin;`;
282
+
196
283
  /**
197
284
  * #56 — make the inbound request-body limit configurable.
198
285
  *
@@ -856,6 +943,19 @@ async function patchCompilerArgs(root: string): Promise<void> {
856
943
  if (changed) await writeFile(file, src);
857
944
  }
858
945
 
946
+ export async function patchDateParser(root: string): Promise<void> {
947
+ const file = resolve(root, "compiler/builtins/date.ts");
948
+ const src = await readFile(file, "utf8");
949
+ if (src.includes(DATE_PARSER_MARKER)) return;
950
+ const start = src.indexOf("export const __ecma262_ParseDTSF");
951
+ const end = src.indexOf("// RFC 7231 or Date.prototype.toString() parser", start);
952
+ if (start < 0 || end < 0) throw new Error(`could not find Porffor's ISO date parser in ${file}`);
953
+ const parser = src.slice(start, end);
954
+ if (!parser.includes(DATE_PARSER_ANCHOR))
955
+ throw new Error(`could not patch Porffor's ISO date parser: anchor not found in ${file}`);
956
+ await writeFile(file, src.slice(0, start) + parser.replace(DATE_PARSER_ANCHOR, DATE_PARSER_INJECT) + src.slice(end));
957
+ }
958
+
859
959
  export async function patchTypedArrayFrom(root: string): Promise<void> {
860
960
  const file = resolve(root, "compiler/builtins/typedarray.js");
861
961
  const src = await readFile(file, "utf8");
@@ -930,6 +1030,7 @@ export async function ensurePorfforPatched(root: string): Promise<void> {
930
1030
  await patchCompilerArgs(root);
931
1031
  await patchUwebsockets(root);
932
1032
  await patchRenderJs(root);
1033
+ await patchDateParser(root);
933
1034
  await patchTypedArrayFrom(root);
934
1035
  done.add(root);
935
1036
  }