fastscript 2.0.0 → 3.0.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/CHANGELOG.md CHANGED
@@ -14,6 +14,12 @@
14
14
  - `spec/V2_0_MIGRATION_PROOF_REPORT.md`
15
15
  - `spec/V2_0_PERFORMANCE_PROTOCOL_REPORT.md`
16
16
 
17
+ ## v3.0.0 - 2026-04-15
18
+ - Launch FastScript v3 as the active public line with `.fs` positioned as a universal JS/TS container
19
+ - Publish v3 website/docs alignment, `/docs/v3` latest-track routing, and proof-backed benchmark messaging
20
+ - Elevate JS/TS syntax proof and `.fs` parity proof into release discipline and proof-pack publishing
21
+ - Refresh the proprietary/source-available product story and position the next FastScript AI assistant wave on the roadmap
22
+
17
23
  ## v2.0.0 - 2026-04-14
18
24
  - Ratify the FastScript v2.0 language/runtime surface and freeze the public spec pack
19
25
  - Complete ambient runtime, standard library, DOM/platform, inference, and zero-JS authored app proof gates
package/README.md CHANGED
@@ -1,22 +1,32 @@
1
1
  # FastScript
2
2
 
3
- FastScript is a JavaScript-first full-stack language runtime built around a first-class `.fs` file format.
3
+ FastScript is a proprietary, source-available JavaScript-first full-stack runtime built around a first-class `.fs` file format.
4
4
 
5
5
  It is designed to feel easier to read than heavyweight stack combinations while staying compatible with the JavaScript ecosystem developers already use.
6
6
 
7
+ - Write normal JavaScript or TypeScript in `.fs`
7
8
  - Write pages, APIs, middleware, jobs, and database workflows in `.fs`
8
9
  - Keep compatibility with normal `.js` packages and modules
10
+ - Treat FastScript-specific syntax as optional sugar, not a requirement
9
11
  - Compile to optimized JavaScript for production deployment
10
12
  - Deploy the same app to Node, Vercel, or Cloudflare
11
13
  - Run one quality gate for formatting, linting, typecheck, tests, smoke checks, benchmarks, and interop
14
+ - Build on the same language/runtime foundation that powers the future FastScript AI coding products
12
15
 
13
- FastScript v2.0 is built for product teams that want a simpler, faster full-stack pipeline without surrendering compatibility with the ground-level JavaScript platform.
16
+ FastScript v3 is built for product teams that want a simpler, faster full-stack pipeline without surrendering compatibility with the ground-level JavaScript platform.
17
+
18
+ The v3 product contract is simple:
19
+
20
+ - `.fs` is a universal JS/TS container for the FastScript runtime
21
+ - FastScript-specific syntax is optional sugar
22
+ - valid JS/TS failures in `.fs` are FastScript compatibility bugs
23
+ - the speed story is earned by the runtime/compiler/toolchain and backed by release proof artifacts
14
24
 
15
25
  ## What FastScript Is
16
26
 
17
27
  FastScript combines:
18
28
 
19
- 1. A source language: `.fs`
29
+ 1. A JS/TS-compatible source container: `.fs`
20
30
  2. A compiler and CLI
21
31
  3. A full-stack app runtime
22
32
  4. A deployment pipeline for multiple targets
@@ -58,6 +68,15 @@ See:
58
68
  - `benchmarks/latest-report.md`
59
69
  - `benchmarks/suite-latest.json`
60
70
  - `benchmarks/interop-latest.json`
71
+ - `docs/PROOF_PACK.md`
72
+
73
+ ## Why Teams Pick FastScript
74
+
75
+ - Smaller client payloads than heavier framework baselines
76
+ - Faster build loops and stricter release gates in one toolchain
77
+ - One runtime-native source container for pages, APIs, jobs, and middleware
78
+ - Package compatibility without giving up a proprietary language/runtime moat
79
+ - A direct path into the next FastScript AI assistant stack
61
80
 
62
81
  ## Install
63
82
 
@@ -333,6 +352,9 @@ npm run validate
333
352
 
334
353
  ```bash
335
354
  npm run migrate
355
+ npm run convert
356
+ npm run migrate:rollback
357
+ npm run manifest
336
358
  npm run wizard:migrate
337
359
  npm run export:js
338
360
  npm run export:ts
@@ -341,10 +363,21 @@ npm run typecheck
341
363
  npm run typecheck:pass
342
364
  npm run lint:fs
343
365
  npm run lint:fs:pass
366
+ npm run diagnostics
367
+ npm run permissions
344
368
  npm run format
345
369
  npm run format:check
346
370
  ```
347
371
 
372
+ `migrate` now runs strict compatibility-first conversion (rename-only + import extension rewrites + manifest/validation/fidelity reports).
373
+ Example: `npm run migrate -- app --dry-run`
374
+ Full proof mode: `npm run migrate -- app --fidelity-level full --fail-on-unproven-fidelity`
375
+
376
+ Latest trust artifacts are written to `.fastscript/conversion/latest`:
377
+ `conversion-manifest.json`, `diff-preview.json`, `validation-report.json`, `fidelity-report.json`.
378
+
379
+ Authored `.fs` now accepts normal JS/TS/JSX/TSX-style code directly. Proof artifacts for the current parity contract are written to `.fastscript/proofs/js-ts-syntax-proof.json` and `.fastscript/proofs/fs-parity-matrix.json`.
380
+
348
381
  ### Database and data
349
382
 
350
383
  ```bash
@@ -382,6 +415,10 @@ npm run qa:all
382
415
 
383
416
  ```bash
384
417
  npm run bench
418
+ npm run bench:discipline
419
+ npm run regression
420
+ npm run profile
421
+ npm run trace
385
422
  npm run bench:report
386
423
  npm run benchmark:suite
387
424
  npm run interop:report
@@ -551,6 +588,7 @@ Key docs in this repo:
551
588
  - `docs/KNOWN_LIMITATIONS.md`
552
589
  - `docs/CONTRIBUTING.md`
553
590
  - `docs/OBSERVABILITY.md` (public stub; full version lives in private core)
591
+ - `docs/RUNTIME_PERMISSIONS.md`
554
592
  - `docs/ROLLOUT_GUIDE.md` (public stub; full version lives in private core)
555
593
  - `SECURITY.md`
556
594
 
@@ -16,8 +16,8 @@ export const FS_ERROR_CODES = Object.freeze({
16
16
  },
17
17
  FS1004: {
18
18
  severity: "error",
19
- message: "Type declarations are not valid runtime FastScript syntax.",
20
- hint: "Move `type`, `interface`, and `enum` definitions to `.d.ts` files or remove them from `.fs` files.",
19
+ message: "Legacy compatibility frontend conflict.",
20
+ hint: "Ordinary TS type-only syntax in `.fs` should parse. If this appears, treat it as a FastScript compatibility bug.",
21
21
  },
22
22
  FS1005: {
23
23
  severity: "error",
@@ -60,28 +60,58 @@ export function stripTypeScriptHints(source) {
60
60
  continue;
61
61
  }
62
62
 
63
+ if (/^\s*declare\s+(global|module|namespace)\b/.test(next)) {
64
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
65
+ if (next.includes("{")) {
66
+ skippingBlock = true;
67
+ const opens = (next.match(/{/g) || []).length;
68
+ const closes = (next.match(/}/g) || []).length;
69
+ blockDepth = Math.max(1, opens - closes);
70
+ }
71
+ continue;
72
+ }
73
+
74
+ if (/^\s*declare\s+/.test(next)) {
75
+ out.push(`// ${next.trim()} (removed by fastscript migrate)`);
76
+ continue;
77
+ }
78
+
63
79
  next = next.replace(/\bimport\s+type\b/g, "import");
64
80
  next = next.replace(/\bexport\s+type\b/g, "export");
81
+ next = next.replace(/\btype\s+([A-Za-z_$][\w$]*)\s+as\s+/g, "$1 as ");
82
+ next = next.replace(/\b(?:public|private|protected|readonly|declare|override|abstract)\s+/g, "");
83
+ next = next.replace(/\s+implements\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]+/g, "");
84
+ next = next.replace(/([A-Za-z_$][\w$]*)!([?:;=,\)])/g, "$1$2");
65
85
 
66
86
  next = next.replace(
67
87
  /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*:\s*([^=;]+)([=;].*)$/,
68
88
  "$1$2 $3 $5",
69
89
  );
90
+ next = next.replace(
91
+ /^(\s*)(const|let|var)\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*=\s*/,
92
+ "$1$2 $3 = ",
93
+ );
70
94
 
71
- if (/\bfunction\b/.test(next) || /\)\s*=>/.test(next)) {
95
+ if (/\bfunction\b/.test(next) || /\bfn\b/.test(next) || /\)\s*=>/.test(next)) {
72
96
  next = next.replace(/\(([^)]*)\)/, (_, params) => {
73
97
  const cleaned = params.replace(
74
- /([A-Za-z_$][\w$]*)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)/g,
98
+ /([A-Za-z_$][\w$]*)(\?)?\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)/g,
75
99
  "$1",
76
100
  );
77
101
  return `(${cleaned})`;
78
102
  });
79
- next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.]*)\s*\{/g, ") {");
103
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*\{/g, ") {");
104
+ next = next.replace(/\)\s*:\s*([A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*)\s*=>/g, ") =>");
80
105
  next = next.replace(/\bfunction\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "function $1(");
106
+ next = next.replace(/\bfn\s+([A-Za-z_$][\w$]*)\s*<[^>]+>\s*\(/g, "fn $1(");
107
+ next = next.replace(/=\s*async\s*<[^>]+>\s*\(/g, "= async (");
108
+ next = next.replace(/=\s*<[^>]+>\s*\(/g, "= (");
81
109
  }
82
110
 
83
111
  next = next.replace(/^\s*<([A-Za-z_$][\w$,\s]*)>\s*\(/, "(");
84
112
  next = next.replace(/\)\s*=>\s*<[A-Za-z_$][\w$<>\[\]\|&, ?.]*>/g, ") =>");
113
+ next = next.replace(/\s+as\s+const\b/g, "");
114
+ next = next.replace(/\s+as\s+[A-Za-z_$][\w$<>\[\]\|&, ?.:{}=]*/g, "");
85
115
  next = next.replace(/\sas\s+const\b/g, "");
86
116
  next = next.replace(/\s+satisfies\s+[A-Za-z_$][\w$<>\[\]\|&, ?.]*/g, "");
87
117
  out.push(next);
@@ -1,10 +1,12 @@
1
1
  import { parse as acornParse, tokenizer as acornTokenizer } from "acorn";
2
+ import * as esbuild from "esbuild";
2
3
  import { resolveErrorMeta } from "./fs-error-codes.mjs";
3
4
 
4
5
  export const FASTSCRIPT_AST_VERSION = "1.0.0";
5
6
 
6
7
  const STATEMENT_BOUNDARY_TOKENS = new Set([";", "{", "}"]);
7
- const TYPE_DECLARATION_KEYWORDS = new Set(["type", "interface", "enum"]);
8
+ const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
9
+ const BASE64_LOOKUP = new Map(Array.from(BASE64).map((char, index) => [char, index]));
8
10
 
9
11
  function createLineStarts(source) {
10
12
  const text = String(source ?? "");
@@ -436,9 +438,23 @@ function collectRewriteOperations(source, { file = "", lineStarts, significant }
436
438
 
437
439
  if (token.value === "fn" && token.type === "identifier") {
438
440
  const prev = previousSignificant(significant, idx);
441
+ const asyncToken = prev?.value === "async" ? prev : null;
442
+ const asyncIndex = asyncToken ? idx - 1 : -1;
443
+ const beforeAsync = asyncToken ? previousSignificant(significant, asyncIndex) : null;
439
444
  const startsStatement = isStatementStart(significant, idx);
440
445
  const followsExport = prev?.value === "export" && prev.end.line === token.start.line;
441
- if (startsStatement || followsExport) {
446
+ const followsDefaultExport =
447
+ prev?.value === "default" &&
448
+ previousSignificant(significant, idx - 1)?.value === "export" &&
449
+ previousSignificant(significant, idx - 1)?.end.line === token.start.line;
450
+ const followsAsync =
451
+ asyncToken &&
452
+ (
453
+ isStatementStart(significant, asyncIndex) ||
454
+ beforeAsync?.value === "export" ||
455
+ beforeAsync?.value === "default"
456
+ );
457
+ if (startsStatement || followsExport || followsDefaultExport || followsAsync) {
442
458
  const nameToken = nextSignificant(significant, idx, 1);
443
459
  const openParenToken = nextSignificant(significant, idx, 2);
444
460
  if (isIdentifierToken(nameToken) && openParenToken?.value === "(") {
@@ -465,40 +481,6 @@ function collectRewriteOperations(source, { file = "", lineStarts, significant }
465
481
  continue;
466
482
  }
467
483
 
468
- if (TYPE_DECLARATION_KEYWORDS.has(token.value) && token.type === "identifier" && isStatementStart(significant, idx)) {
469
- const nextToken = nextSignificant(significant, idx, 1);
470
- if (!isIdentifierToken(nextToken)) continue;
471
- const shouldHandleType = token.value === "type" && nextSignificant(significant, idx, 2)?.value === "=";
472
- const shouldHandleBlock = (token.value === "interface" || token.value === "enum") && nextSignificant(significant, idx, 2);
473
- if (!shouldHandleType && !shouldHandleBlock) continue;
474
-
475
- const span = declarationSpanFromKeyword(source, lineStarts, sourceLength, significant, idx);
476
- const snippet = source.slice(span.start, span.end).trim();
477
- diagnostics.push(
478
- createDiagnostic({
479
- source,
480
- lineStarts,
481
- file,
482
- code: "FS1004",
483
- span,
484
- fixes: [
485
- {
486
- message: "Remove declaration from runtime source.",
487
- span,
488
- text: "",
489
- },
490
- ],
491
- }),
492
- );
493
- ops.push(
494
- operation(
495
- span.start,
496
- span.end,
497
- `/* ${snippet || token.value} (removed by fastscript compiler) */`,
498
- "erase-type",
499
- ),
500
- );
501
- }
502
484
  }
503
485
 
504
486
  const lexicalTodoPattern = /\bTODO_ERROR\b/g;
@@ -589,7 +571,104 @@ function applyRewriteOperations(source, ops) {
589
571
  return { code, mapGeneratedToSource, rewrites };
590
572
  }
591
573
 
592
- const BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
574
+ function decodeVlqValue(mapping, startIndex) {
575
+ let result = 0;
576
+ let shift = 0;
577
+ let index = startIndex;
578
+
579
+ while (index < mapping.length) {
580
+ const value = BASE64_LOOKUP.get(mapping[index]);
581
+ if (value == null) break;
582
+ index += 1;
583
+ const continuation = (value & 32) !== 0;
584
+ result |= (value & 31) << shift;
585
+ shift += 5;
586
+ if (!continuation) {
587
+ const signed = (result & 1) ? -(result >> 1) : result >> 1;
588
+ return { value: signed, next: index };
589
+ }
590
+ }
591
+
592
+ return { value: 0, next: index };
593
+ }
594
+
595
+ function decodeSourceMapMappings(mappings) {
596
+ const lines = [];
597
+ let sourceIndex = 0;
598
+ let sourceLine = 0;
599
+ let sourceColumn = 0;
600
+ let generatedLine = 0;
601
+ let generatedColumn = 0;
602
+
603
+ for (const line of String(mappings || "").split(";")) {
604
+ const segments = [];
605
+ generatedColumn = 0;
606
+ if (line) {
607
+ for (const rawSegment of line.split(",")) {
608
+ if (!rawSegment) continue;
609
+ let cursor = 0;
610
+ const values = [];
611
+ while (cursor < rawSegment.length) {
612
+ const decoded = decodeVlqValue(rawSegment, cursor);
613
+ values.push(decoded.value);
614
+ cursor = decoded.next;
615
+ }
616
+ if (!values.length) continue;
617
+ generatedColumn += values[0];
618
+ if (values.length >= 4) {
619
+ sourceIndex += values[1];
620
+ sourceLine += values[2];
621
+ sourceColumn += values[3];
622
+ segments.push({ generatedLine, generatedColumn, sourceIndex, sourceLine, sourceColumn });
623
+ }
624
+ }
625
+ }
626
+ lines.push(segments);
627
+ generatedLine += 1;
628
+ }
629
+
630
+ return lines;
631
+ }
632
+
633
+ function buildGeneratedOffsetMapFromSourceMap({ generated, mapText, sourceLength }) {
634
+ const parsed = JSON.parse(String(mapText || "{}"));
635
+ const lineStarts = createLineStarts(generated);
636
+ const sourceLineStarts = createLineStarts(parsed?.sourcesContent?.[0] || "");
637
+ const decoded = decodeSourceMapMappings(parsed.mappings || "");
638
+ const out = [];
639
+
640
+ for (let lineIndex = 0; lineIndex < lineStarts.length; lineIndex += 1) {
641
+ const lineStart = lineStarts[lineIndex];
642
+ const lineEnd = lineStarts[lineIndex + 1] ?? generated.length;
643
+ const segments = decoded[lineIndex] || [];
644
+ let active = segments[0] || null;
645
+ let segmentIndex = 0;
646
+
647
+ for (let offset = lineStart; offset < lineEnd; offset += 1) {
648
+ const column = offset - lineStart;
649
+ while (segmentIndex + 1 < segments.length && segments[segmentIndex + 1].generatedColumn <= column) {
650
+ segmentIndex += 1;
651
+ active = segments[segmentIndex];
652
+ }
653
+
654
+ if (!active) {
655
+ out.push(clamp(offset, 0, sourceLength));
656
+ continue;
657
+ }
658
+
659
+ const delta = Math.max(0, column - active.generatedColumn);
660
+ const mappedBase = sourceLineStarts[active.sourceLine] ?? 0;
661
+ out.push(clamp(mappedBase + active.sourceColumn + delta, 0, sourceLength));
662
+ }
663
+ }
664
+
665
+ out.push(sourceLength);
666
+ return out;
667
+ }
668
+
669
+ function composeOffsetMaps(finalToIntermediate, intermediateToSource, sourceLength) {
670
+ return finalToIntermediate.map((offset) => mapGeneratedOffsetToSourceOffset(intermediateToSource, offset, sourceLength));
671
+ }
593
672
 
594
673
  function encodeVlq(value) {
595
674
  let num = value < 0 ? ((-value) << 1) + 1 : value << 1;
@@ -796,9 +875,66 @@ function parseGeneratedJavaScript(generated, { file = "" } = {}) {
796
875
  onToken: parseTokens,
797
876
  onComment: parseComments,
798
877
  });
799
- return { estree, parseTokens, parseComments, error: null };
878
+ return {
879
+ estree,
880
+ parseTokens,
881
+ parseComments,
882
+ error: null,
883
+ frontend: {
884
+ usedCompatibilityFrontend: false,
885
+ mapGeneratedToInput: null,
886
+ generatedCode: generated,
887
+ },
888
+ };
800
889
  } catch (error) {
801
- return { estree: null, parseTokens, parseComments, error };
890
+ try {
891
+ const transformed = esbuild.transformSync(generated, {
892
+ loader: "tsx",
893
+ format: "esm",
894
+ target: "esnext",
895
+ sourcefile: file || "<memory>",
896
+ sourcemap: "external",
897
+ jsx: "transform",
898
+ });
899
+ const compatTokens = [];
900
+ const compatComments = [];
901
+ const estree = acornParse(transformed.code, {
902
+ ecmaVersion: "latest",
903
+ sourceType: "module",
904
+ allowHashBang: true,
905
+ locations: true,
906
+ ranges: true,
907
+ onToken: compatTokens,
908
+ onComment: compatComments,
909
+ });
910
+ return {
911
+ estree,
912
+ parseTokens: compatTokens,
913
+ parseComments: compatComments,
914
+ error: null,
915
+ frontend: {
916
+ usedCompatibilityFrontend: true,
917
+ mapGeneratedToInput: buildGeneratedOffsetMapFromSourceMap({
918
+ generated: transformed.code,
919
+ mapText: transformed.map,
920
+ sourceLength: generated.length,
921
+ }),
922
+ generatedCode: transformed.code,
923
+ },
924
+ };
925
+ } catch {
926
+ return {
927
+ estree: null,
928
+ parseTokens,
929
+ parseComments,
930
+ error,
931
+ frontend: {
932
+ usedCompatibilityFrontend: false,
933
+ mapGeneratedToInput: null,
934
+ generatedCode: generated,
935
+ },
936
+ };
937
+ }
802
938
  }
803
939
  }
804
940
 
@@ -842,7 +978,21 @@ export function parseFastScript(source, { file = "", mode = "lenient", recover =
842
978
 
843
979
  const diagnostics = [];
844
980
 
845
- if (tokenization.lexicalError) {
981
+ const rewrites = collectRewriteOperations(text, {
982
+ file,
983
+ lineStarts,
984
+ significant: tokenization.significant,
985
+ });
986
+ diagnostics.push(...rewrites.diagnostics);
987
+
988
+ const transformed = applyRewriteOperations(text, rewrites.ops);
989
+ const parsedGenerated = parseGeneratedJavaScript(transformed.code, { file });
990
+ const finalGeneratedCode = parsedGenerated.frontend?.generatedCode || transformed.code;
991
+ const finalMapGeneratedToSource = parsedGenerated.frontend?.mapGeneratedToInput
992
+ ? composeOffsetMaps(parsedGenerated.frontend.mapGeneratedToInput, transformed.mapGeneratedToSource, text.length)
993
+ : transformed.mapGeneratedToSource;
994
+
995
+ if (tokenization.lexicalError && !parsedGenerated.frontend?.usedCompatibilityFrontend) {
846
996
  const loc = tokenization.lexicalError.loc || { line: 1, column: 0 };
847
997
  const lineStart = lineStarts[clamp((loc.line || 1) - 1, 0, lineStarts.length - 1)] ?? 0;
848
998
  const offset = clamp(lineStart + (loc.column || 0), 0, text.length);
@@ -858,19 +1008,9 @@ export function parseFastScript(source, { file = "", mode = "lenient", recover =
858
1008
  );
859
1009
  }
860
1010
 
861
- const rewrites = collectRewriteOperations(text, {
862
- file,
863
- lineStarts,
864
- significant: tokenization.significant,
865
- });
866
- diagnostics.push(...rewrites.diagnostics);
867
-
868
- const transformed = applyRewriteOperations(text, rewrites.ops);
869
- const parsedGenerated = parseGeneratedJavaScript(transformed.code, { file });
870
-
871
1011
  if (parsedGenerated.error) {
872
- const parseOffset = clamp(parsedGenerated.error.pos ?? 0, 0, transformed.code.length);
873
- const sourceOffset = mapGeneratedOffsetToSourceOffset(transformed.mapGeneratedToSource, parseOffset, text.length);
1012
+ const parseOffset = clamp(parsedGenerated.error.pos ?? 0, 0, finalGeneratedCode.length);
1013
+ const sourceOffset = mapGeneratedOffsetToSourceOffset(finalMapGeneratedToSource, parseOffset, text.length);
874
1014
  diagnostics.push(
875
1015
  createDiagnostic({
876
1016
  source: text,
@@ -884,13 +1024,13 @@ export function parseFastScript(source, { file = "", mode = "lenient", recover =
884
1024
  }
885
1025
 
886
1026
  const estree = ensureProgramShape(parsedGenerated.estree);
887
- remapAstNodeLocations(estree, text, lineStarts, transformed.mapGeneratedToSource);
1027
+ remapAstNodeLocations(estree, text, lineStarts, finalMapGeneratedToSource);
888
1028
 
889
1029
  const cst = buildCst({ source: text, file, lineStarts, tokens, estree });
890
1030
  const sourceMap = buildSourceMap({
891
1031
  source: text,
892
- generated: transformed.code,
893
- mapGeneratedToSource: transformed.mapGeneratedToSource,
1032
+ generated: finalGeneratedCode,
1033
+ mapGeneratedToSource: finalMapGeneratedToSource,
894
1034
  file,
895
1035
  });
896
1036
 
@@ -913,9 +1053,9 @@ export function parseFastScript(source, { file = "", mode = "lenient", recover =
913
1053
  cst,
914
1054
  tokens,
915
1055
  diagnostics: normalizedDiagnostics,
916
- transformedCode: transformed.code,
1056
+ transformedCode: finalGeneratedCode,
917
1057
  sourceMap,
918
- mapGeneratedToSource: transformed.mapGeneratedToSource,
1058
+ mapGeneratedToSource: finalMapGeneratedToSource,
919
1059
  rewrites: transformed.rewrites,
920
1060
  source: text,
921
1061
  };
@@ -3,6 +3,7 @@ import { extname, join, resolve } from "node:path";
3
3
  import { parseFastScript } from "./fs-parser.mjs";
4
4
  import { resolveErrorMeta } from "./fs-error-codes.mjs";
5
5
  import { inferRouteMeta, inferRouteParamTypes, sortRoutesByPriority } from "./routes.mjs";
6
+ import { stripTypeScriptHints } from "./fs-normalize.mjs";
6
7
 
7
8
  const APP_DIR = resolve("app");
8
9
  const PAGES_DIR = resolve("app/pages");
@@ -1087,8 +1088,9 @@ function analyzeStatement(node, scope, state, fnContext) {
1087
1088
  }
1088
1089
 
1089
1090
  function analyzeFileTypes(file, source) {
1090
- const lineStarts = createLineStarts(source);
1091
- const parsed = parseFastScript(source, { file, mode: "lenient", recover: true });
1091
+ const text = String(source ?? "");
1092
+ const lineStarts = createLineStarts(text);
1093
+ const parsed = parseFastScript(text, { file, mode: "lenient", recover: true });
1092
1094
  const diagnostics = [...parsed.diagnostics];
1093
1095
 
1094
1096
  if (!parsed.estree || !Array.isArray(parsed.estree.body)) {
@@ -1097,7 +1099,7 @@ function analyzeFileTypes(file, source) {
1097
1099
 
1098
1100
  scopeCounter = 0;
1099
1101
  const rootScope = new Scope(null, "module");
1100
- const state = { file, source, lineStarts, diagnostics, scopes: [rootScope], allowedRuntimes: inferAllowedRuntimeContextsFromFile(file) };
1102
+ const state = { file, source: text, lineStarts, diagnostics, scopes: [rootScope], allowedRuntimes: inferAllowedRuntimeContextsFromFile(file) };
1101
1103
 
1102
1104
  declareBuiltins(rootScope, file);
1103
1105
  hoistDeclarations(parsed.estree.body, rootScope, state);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "fastscript",
3
- "version": "2.0.0",
4
- "description": "JavaScript-first full-stack language runtime with first-class .fs source.",
3
+ "version": "3.0.0",
4
+ "description": "FastScript v3 universal JS/TS runtime with first-class .fs source.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "type": "module",
7
7
  "publishConfig": {
@@ -27,8 +27,13 @@
27
27
  "create:fullstack": "node ./src/cli.mjs create fullstack --template fullstack",
28
28
  "check": "node ./src/cli.mjs check",
29
29
  "migrate": "node ./src/cli.mjs migrate",
30
+ "convert": "node ./src/cli.mjs convert",
31
+ "migrate:rollback": "node ./src/cli.mjs migrate:rollback",
32
+ "manifest": "node ./src/cli.mjs manifest",
30
33
  "wizard:migrate": "node ./src/cli.mjs wizard:migrate app/pages/index.fs",
31
34
  "bench": "node ./src/cli.mjs bench",
35
+ "bench:discipline": "node ./src/cli.mjs bench:discipline",
36
+ "regression": "node ./src/cli.mjs regression --mode all",
32
37
  "export:js": "node ./src/cli.mjs export --to js --out exported-js-app",
33
38
  "export:ts": "node ./src/cli.mjs export --to ts --out exported-ts-app",
34
39
  "compat": "node ./src/cli.mjs compat",
@@ -36,6 +41,10 @@
36
41
  "repo:lock": "node ./scripts/ensure-canonical-repo.mjs",
37
42
  "typecheck": "node ./src/cli.mjs typecheck --mode fail",
38
43
  "typecheck:pass": "node ./src/cli.mjs typecheck --mode pass",
44
+ "profile": "node ./src/cli.mjs profile --command build --runs 1",
45
+ "trace": "node ./src/cli.mjs trace --pipeline check,typecheck,build,bench",
46
+ "diagnostics": "node ./src/cli.mjs diagnostics --mode report",
47
+ "permissions": "node ./src/cli.mjs permissions --mode report",
39
48
  "format": "node ./src/cli.mjs format --write",
40
49
  "format:check": "node ./src/cli.mjs format --check",
41
50
  "lint:fs": "node ./src/cli.mjs lint --mode fail",
@@ -45,7 +54,7 @@
45
54
  "db:rollback": "node ./src/cli.mjs db:rollback --count 1",
46
55
  "deploy:node": "node ./src/cli.mjs deploy --target node",
47
56
  "deploy:vercel": "node ./src/cli.mjs deploy --target vercel",
48
- "deploy:cloudflare": "node ./src/cli.mjs deploy --target cloudflare",
57
+ "deploy:cloudflare": "node ./scripts/deploy-cloudflare-site.mjs",
49
58
  "worker": "node ./src/cli.mjs worker",
50
59
  "worker:replay-dead-letter": "node ./src/cli.mjs worker replay-dead-letter --limit 50",
51
60
  "style:generate": "node ./scripts/style-generate.mjs",
@@ -60,6 +69,17 @@
60
69
  "test:db-cli": "node ./scripts/test-db-cli.mjs",
61
70
  "test:fs-diag": "node ./scripts/test-fs-diagnostics.mjs",
62
71
  "test:roundtrip": "node ./scripts/test-roundtrip.mjs",
72
+ "test:compatibility-first-runtime": "node ./scripts/test-compatibility-first-runtime.mjs",
73
+ "test:authored-ts-in-fs": "node ./scripts/test-authored-ts-in-fs.mjs",
74
+ "test:js-ts-syntax-proof": "node ./scripts/test-js-ts-syntax-proof.mjs",
75
+ "test:fs-parity-corpus": "node ./scripts/test-fs-parity-corpus.mjs",
76
+ "test:conversion-rollback-manifest": "node ./scripts/test-conversion-rollback-manifest.mjs",
77
+ "test:toolchain-observability": "node ./scripts/test-toolchain-observability.mjs",
78
+ "test:runtime-permissions": "node ./scripts/test-runtime-permissions.mjs",
79
+ "test:benchmark-discipline": "node ./scripts/test-benchmark-discipline.mjs",
80
+ "test:regression-guard": "node ./scripts/test-regression-guard.mjs",
81
+ "test:ecosystem-compatibility-contract": "node ./scripts/test-ecosystem-compatibility-contract.mjs",
82
+ "test:release-discipline": "node ./scripts/test-release-discipline.mjs",
63
83
  "test:validation": "node ./scripts/test-validation.mjs",
64
84
  "test:webhook-storage": "node ./scripts/test-webhook-storage.mjs",
65
85
  "test:jobs": "node ./scripts/test-jobs.mjs",
@@ -109,7 +129,7 @@
109
129
  "kpi:track": "node ./scripts/kpi-track.mjs",
110
130
  "deploy:zero-downtime": "node ./scripts/zero-downtime-deploy.mjs",
111
131
  "merge:gate": "node ./scripts/release-merge-gate.mjs",
112
- "test:core": "npm run test:middleware && npm run test:auth && npm run test:db && npm run test:db-cli && npm run test:validation && npm run test:webhook-storage && npm run test:jobs && npm run test:plugins && npm run test:fs-diag && npm run test:metrics && npm run test:roundtrip && npm run test:sourcemap-fidelity && npm run test:cache-parity && npm run test:typecheck && npm run test:typecheck-depth && npm run test:format-lint && npm run test:style-rules && npm run test:style-primitives && npm run test:routes && npm run test:runtime-contract && npm run test:determinism && npm run test:parser-fuzz && npm run test:security-baseline && npm run test:deploy-adapters && npm run test:interop-matrix && npm run test:vscode-language && npm run test:runtime-context-rules && npm run test:runtime-scope-diagnostics && npm run test:v2:ambient-runtime && npm run test:v2:stdlib-methods && npm run test:v2:stdlib-matrix && npm run test:v2:dom-globals && npm run test:v2:dom-patterns && npm run test:v2:inference-corpus && npm run test:v2:inference-patterns && npm run test:v2:zero-js-app && npm run test:v2:zero-js-corpora && npm run test:conformance",
132
+ "test:core": "npm run test:middleware && npm run test:auth && npm run test:db && npm run test:db-cli && npm run test:validation && npm run test:webhook-storage && npm run test:jobs && npm run test:plugins && npm run test:fs-diag && npm run test:metrics && npm run test:roundtrip && npm run test:compatibility-first-runtime && npm run test:authored-ts-in-fs && npm run test:js-ts-syntax-proof && npm run test:fs-parity-corpus && npm run test:conversion-rollback-manifest && npm run test:toolchain-observability && npm run test:runtime-permissions && npm run test:benchmark-discipline && npm run test:regression-guard && npm run test:ecosystem-compatibility-contract && npm run test:release-discipline && npm run test:sourcemap-fidelity && npm run test:cache-parity && npm run test:typecheck && npm run test:typecheck-depth && npm run test:format-lint && npm run test:style-rules && npm run test:style-primitives && npm run test:routes && npm run test:runtime-contract && npm run test:determinism && npm run test:parser-fuzz && npm run test:security-baseline && npm run test:deploy-adapters && npm run test:interop-matrix && npm run test:vscode-language && npm run test:runtime-context-rules && npm run test:runtime-scope-diagnostics && npm run test:v2:ambient-runtime && npm run test:v2:stdlib-methods && npm run test:v2:stdlib-matrix && npm run test:v2:dom-globals && npm run test:v2:dom-patterns && npm run test:v2:inference-corpus && npm run test:v2:inference-patterns && npm run test:v2:zero-js-app && npm run test:v2:zero-js-corpora && npm run test:conformance",
113
133
  "qa:gate": "npm run repo:lock && npm run format:check && npm run lint:fs && npm run typecheck && npm run validate && npm run test:core && npm run smoke:dev && npm run smoke:start",
114
134
  "qa:all": "npm run repo:lock && npm run format:check && npm run lint:fs && npm run typecheck && npm run docs:index && npm run docs:api-ref && npm run validate && npm run test:core && npm run smoke:dev && npm run smoke:start && npm run bench:report && npm run benchmark:suite && npm run interop:report && npm run sbom:generate && npm run proof:publish && npm run backup:create && npm run backup:verify",
115
135
  "release:patch": "node ./scripts/release.mjs patch",
@@ -118,7 +138,8 @@
118
138
  "pack:check": "node ./scripts/prepare-npm-release.mjs --pack-dry-run",
119
139
  "hooks:install": "node ./scripts/install-hooks.mjs",
120
140
  "public:bundle": "node ./scripts/prepare-public-docs-bundle.mjs",
121
- "release:npm:prepare": "node ./scripts/prepare-npm-release.mjs"
141
+ "release:npm:prepare": "node ./scripts/prepare-npm-release.mjs",
142
+ "deploy:cloudflare:adapter": "node ./src/cli.mjs deploy --target cloudflare"
122
143
  },
123
144
  "engines": {
124
145
  "node": ">=20.0.0"