Skip to content

Plan TypeScript to mjs migration - #1449

Merged
sergey-shandar merged 46 commits into
mainfrom
agent/migrate-ts-to-mjs
Aug 7, 2026
Merged

Plan TypeScript to mjs migration#1449
sergey-shandar merged 46 commits into
mainfrom
agent/migrate-ts-to-mjs

Conversation

@sergey-shandar

@sergey-shandar sergey-shandar commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Summary

Plan the repository migration in two separate stages:

  1. migrate authored .ts / .f.ts gradually to .mjs / .f.mjs with JSDoc, independently of FunctionalScript parser support;
  2. only after authored TypeScript is gone and authored .f.js package/tooling support exists, migrate compiler-supported .f.mjs modules to .f.js.

Stage 1 proceeds dependency-first: migrate files that do not depend on authored TypeScript, then their callers. Before the first real repository .f.ts -> .f.mjs conversion, complete authored .mjs package support and then the .f.mjs runtime test/coverage fixture task that is blocked by it. The fixture proves an actual module.f.mjs works with normal proof execution and Node/Deno coverage before production source starts moving.

Proof migration is part of Stage 1 and is compiler-independent too. A migrated module.f.mjs may temporarily keep proof.f.ts, while proof.f.mjs is allowed as soon as the proof is valid JavaScript/JSDoc and its authored FunctionalScript dependencies are already .f.mjs. Current FunctionalScript compiler support is not a gate; Stage 1 still ends with no authored .ts / .f.ts, including proofs.

TypeScript-only type syntax must be translated without weakening the public type contract. TypeScript 7 supports variance annotations in JSDoc type aliases, so out O extends Operation becomes @template {Operation} out O on the corresponding @typedef. AGENTS.md documents named types, generic constraints, and in / out variance.

Known TypeScript-to-JSDoc hard cases do not block unrelated Stage-1 work. Record them explicitly and postpone only the affected migration group until a focused design exists. The first known case is fjs/types/phantom/module.f.ts, whose type-only declare const ...: unique symbol contract cannot be translated mechanically without deciding its runtime/declaration representation.

After the last authored .ts / .f.ts file is removed, simplify prepack to declaration-only emission, remove the TypeScript-to-JavaScript emit path, remove obsolete generated .js from the transition working tree, and remove the blanket **/*.js ignore so .js becomes authorable and trackable again.

Package-build invariant

Package and publish jobs run in CI from a clean checkout; normal development should type-check and test without generating package artifacts. Stage-1 packaging therefore does not need generated-output cleanup or legacy-output tracking across .ts -> .mjs renames.

Keep the two TypeScript emission passes inline in the NPM lifecycle instead of exposing separately callable emit:* scripts while TypeScript source remains:

"prepack": "tsc --noEmit false --emitDeclarationOnly && tsc --noEmit false --declaration false"

The declaration pass runs first for .ts + .mjs. With the generated declarations present, the second TypeScript invocation resolves .d.mts for authored .mjs modules and emits the remaining TypeScript runtime JavaScript without overwriting authored .mjs.

This exact setup is already validated by PR #1451: allowJs / checkJs are enabled, benchmark.mjs remains authored source, and the Node 26 CI job completes npm pack successfully with the same two-pass prepack.

benchmark.mjs may remain in the packed archive during the transition. It exposes no documented public API, so special-casing it out of the package is unnecessary; removing it later is independent cleanup.

As soon as no authored .ts / .f.ts remains, remove the second pass:

"prepack": "tsc --noEmit false --emitDeclarationOnly"

Review follow-up

  • Moved the cross-cutting migration plan to top-level todo/migrate-typescript-to-mjs.md and used the repository-standard Problem / Proposal structure.
  • Updated the authoritative fjs/fsc/README.md extension contract and package roadmap atomically: .f.mjs is stage-1 FunctionalScript-intent JavaScript, not a compiler-compatibility marker.
  • Reworked authored .mjs package support as the stage-1 prerequisite.
  • Restored fjs/emergent_testing/todo/f-mjs-test-and-coverage.md as the second prerequisite before the first real repository .f.ts -> .f.mjs conversion.
  • Simplified that test prerequisite to a synthetic module.f.mjs + proof.f.ts fixture, avoiding a circular dependency on migrating assertion helpers merely to prove the first Stage-1 runtime/coverage layout.
  • Updated AGENTS.md and CONTRIBUTING.md so proof.f.mjs is allowed during Stage 1 based on JavaScript/JSDoc and dependency readiness, not current compiler support.
  • Updated todo/plan/roadmap.md to the two-stage contract: Stage 1 removes TypeScript into .f.mjs; Stage 2 uses authored .f.js as the compiler-compatibility marker.
  • Updated todo/lang/README.md and nanvm-lib/todo/mvp-roadmap.md to the same two-stage extension contract, removing the remaining language/MVP guidance that gated .f.ts -> .f.mjs on parser support.
  • Added a focused P1 f-js-package-support.md stage-2 prerequisite covering direct .f.js type checking, declaration emission, NPM packing, and clean-consumer runtime/type tests.
  • Updated todo/fjs-nanvm-integration.md so repository compiler-compatibility migration is blocked by both stage 1 and authored .f.js package support before any .f.mjs -> .f.js rename.
  • Removed cleanup/repeated-pack requirements from the Stage-2 prerequisite too: authored .f.js validation runs from a clean CI checkout and requires checking, declarations, packing, and clean-consumer tests, not cleanup safety.
  • Removed the prior cleanup/repeated-pack requirements after clarifying that packaging happens only in clean CI checkouts; stale ignored outputs from developer machines cannot leak into package jobs.
  • Simplified stage-1 emission to one inline two-pass prepack; no user-facing emit:* scripts are needed.
  • Documented the end-of-stage-1 simplification: once all .ts / .f.ts files are gone, prepack becomes declaration-only.
  • Addressed the runtime-emit concern with repository evidence from PR Allow .mjs files as sources. #1451 instead of adding an unnecessary second TypeScript configuration.
  • Documented TypeScript 7 JSDoc type declarations in AGENTS.md, including constrained generics and in / out variance on @typedef type parameters.
  • Removed the temporary Effects variance blocker because the existing Cont<out O, T> / Do<out O, T> contract can be represented directly in JSDoc.
  • Removed the requirement to exclude incidental non-public authored .mjs files from NPM; benchmark.mjs may stay packed until it is removed later.
  • Added a hard-case policy: identify constructs such as Phantom's unique symbol, defer the concrete redesign, and block only the affected migration group.

Validation

Documentation-only change. The exact mixed .ts + .mjs two-pass package command is exercised by PR #1451, whose Node 26 CI job completes npm pack successfully. The .f.mjs runtime proof/coverage fixture remains an explicit prerequisite to implement before the first real repository source conversion.

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
functionalscript b093a36 Commit Preview URL

Branch Preview URL
Aug 07 2026, 08:46 PM

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 30b60026c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/migrate-typescript-to-mjs.md Outdated
Comment thread fjs/ci/todo/migrate-typescript-to-mjs.md Outdated
Comment thread fjs/ci/todo/migrate-typescript-to-mjs.md Outdated
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 948a58883c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/migrate-typescript-to-mjs.md Outdated
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 32d05600af

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/ci/todo/f-mjs-package-support.md Outdated
@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d31900e33f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread todo/migrate-typescript-to-mjs.md

@o2alexanderfedin o2alexanderfedin left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving. Documentation and planning only, CI green. Rather than take the technical claims on faith, I checked the load-bearing one — the JSDoc variance support used to justify removing the Effects variance blocker — against the pinned TypeScript 7.0.2.

The variance claim holds, on all three specifics

  • @template out T on a @typedef is genuinely enforced, not merely parsed. A deliberately wrong annotation (out T on a structurally contravariant alias) produces the identical diagnostic in .mjs as the .ts control: TS2636: Type 'Sink<sub-T>' is not assignable to type 'Sink<super-T>' as implied by variance annotation. This was the claim most worth testing, since "TypeScript accepts the syntax" and "TypeScript checks it" are very different guarantees.
  • The constrained form @template {Operation} out O is accepted, as documented.
  • Variance on an ordinary function's @template is rejected: TS1274: 'out' modifier can only appear on a type parameter of a class, interface or type alias — exactly what AGENTS.md now says.

I also wrote a faithful JSDoc translation of the real Operation / Pr / Cont / Do / Effect from fjs/effects/module.f.ts. It type-checks clean and still permits the widening the annotation exists for (Effect<ReadOp>Effect<ReadOp | WriteOp>). So the Cont<out O, T> / Do<out O, T> contract really is representable in JSDoc and dropping the blocker is justified.

One observation, not blocking

out isn't currently load-bearing even in the TypeScript source: removing it from both Cont and Do on main leaves npx tsc clean. That matches the comment at fjs/effects/module.f.ts:149, which presents it as an asserted covariance TypeScript cannot derive through Pr — so it's a deliberate forward-looking annotation and keeping it through the migration is right. Just worth knowing that if the translation ever drops an out by accident, the type-checker will not be the thing that catches it. The plan's "public assignability is not weakened" checklist item is therefore a manual review step, not a tsc step.

Cross-PR note

The packaging prerequisite this plan retains — "Non-package .mjs files must remain excluded from the packed archive" — is contradicted by #1451 as it currently stands, which packs fjs/types/bigint/benchmark.mjs. Details in my comment there. The plan is right; the implementation needs a one-line fix.

The two-stage split (drop TypeScript into .f.mjs first, treat .f.js as the compiler-compatibility marker only in stage 2) reads as the correct decomposition, and keeping the .f.mjs runtime/coverage fixture as a hard prerequisite before the first real source conversion is the right ordering.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fb6df2182

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread fjs/fsc/README.md
Comment on lines +11 to +12
| `.f.mjs` | Authored FunctionalScript-intent ESM JavaScript with JSDoc types. It may use FunctionalScript features the current parser/compiler does not support yet. |
| `.f.js` | During stage 1, generated JavaScript emitted from `.f.ts` and never authored. After stage 1 and authored-`.f.js` package support are complete, authored FunctionalScript that the current parser/compiler must accept. |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reconcile the remaining extension roadmaps

This new authoritative meaning still conflicts with todo/lang/README.md:20-25 and nanvm-lib/todo/mvp-roadmap.md:293-297,344-348, which continue to say that .f.mjs marks compiler-supported modules and that .f.ts moves only as parser support grows. Fresh evidence beyond the previously addressed todo/plan/roadmap.md is that these two repository-wide language/MVP documents still prescribe the old migration, so contributors following them will incorrectly gate Stage 1 on compiler coverage; update them to the two-stage contract atomically.

AGENTS.md reference: AGENTS.md:L322-L329

Useful? React with 👍 / 👎.

Comment thread todo/fjs-nanvm-integration.md Outdated
Before stage 2 renames any repository source, complete the focused
[`f-js-package-support.md`](../fjs/ci/todo/f-js-package-support.md) prerequisite.
A standalone authored `.f.js` must be directly included in TypeScript checking,
receive a generated `.d.ts`, survive cleanup, be included in the packed NPM

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Remove cleanup from the stage-2 prerequisite

The referenced f-js-package-support.md:44-48,80-83 explicitly says Stage 2 runs from clean CI and requires no generated-output cleanup or repeated-pack safety, but this integration plan newly requires authored .f.js to “survive cleanup” and repeats “cleanup safety” at line 111. Fresh evidence after the earlier cleanup rebuttal is this direct contradiction inside the revised Stage-2 design; an implementer following this checklist could reintroduce the rejected cleanup work, so align the prerequisite with the focused issue.

AGENTS.md reference: AGENTS.md:L322-L329

Useful? React with 👍 / 👎.

@sergey-shandar

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@sergey-shandar
sergey-shandar marked this pull request as ready for review August 7, 2026 20:48
@sergey-shandar
sergey-shandar added this pull request to the merge queue Aug 7, 2026
Merged via the queue into main with commit 18af44b Aug 7, 2026
19 checks passed
This was referenced Aug 8, 2026
@sergey-shandar
sergey-shandar deleted the agent/migrate-ts-to-mjs branch August 8, 2026 21:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants