Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
30b6002
Add TypeScript to mjs migration todo
sergey-shandar Aug 7, 2026
1110fc8
Clarify gradual TypeScript migration ordering
sergey-shandar Aug 7, 2026
8759de0
Make compiler compatibility migration depend on mjs migration
sergey-shandar Aug 7, 2026
7274aa4
Clarify JavaScript migration phase boundaries
sergey-shandar Aug 7, 2026
948a588
Require js tracking before compiler migration
sergey-shandar Aug 7, 2026
09c0928
Move TypeScript migration plan to top-level todo
sergey-shandar Aug 7, 2026
4a4c82e
Add authored f.js package support todo
sergey-shandar Aug 7, 2026
0a9f8e3
Update FunctionalScript extension migration contract
sergey-shandar Aug 7, 2026
d544798
Align mjs package support with stage-one migration
sergey-shandar Aug 7, 2026
3332cff
Align package roadmap with two-stage source migration
sergey-shandar Aug 7, 2026
1243cec
Add compiler migration package prerequisites
sergey-shandar Aug 7, 2026
32d0560
Remove old CI-scoped migration todo
sergey-shandar Aug 7, 2026
47edfca
Simplify mjs packaging around clean CI
sergey-shandar Aug 7, 2026
2c74e8c
Use clean CI package invariant
sergey-shandar Aug 7, 2026
a018676
Remove local cleanup assumptions for f.js
sergey-shandar Aug 7, 2026
f8bf61e
Clarify CI-only packaging during migration
sergey-shandar Aug 7, 2026
2a2e7b1
Align compiler docs with clean CI packaging
sergey-shandar Aug 7, 2026
ca6a2ec
Simplify mixed-source prepack plan
sergey-shandar Aug 7, 2026
7b2d820
Document inline two-pass prepack
sergey-shandar Aug 7, 2026
65d4195
Document declaration-only prepack after TypeScript migration
sergey-shandar Aug 7, 2026
7f1b091
Simplify prepack after TypeScript migration
sergey-shandar Aug 7, 2026
929bbc2
Simplify prepack at end of TypeScript migration
sergey-shandar Aug 7, 2026
e6affd2
Clarify declaration-only prepack after migration
sergey-shandar Aug 7, 2026
cdd4835
Clarify validated mixed-source prepack behavior
sergey-shandar Aug 7, 2026
d804f1e
Document validated two-pass package emission
sergey-shandar Aug 7, 2026
86ad21a
Plan JSDoc-compatible Effects variance
sergey-shandar Aug 7, 2026
24bd1c8
Account for JSDoc type-system blockers
sergey-shandar Aug 7, 2026
31ca6d1
Document JSDoc variance migration
sergey-shandar Aug 7, 2026
40d5e3f
Remove obsolete JSDoc variance blocker
sergey-shandar Aug 7, 2026
76cd2ce
Document JavaScript JSDoc type declarations
sergey-shandar Aug 7, 2026
df8ea12
Preserve AGENTS newline
sergey-shandar Aug 7, 2026
5e2fce8
Clarify JSDoc typedef examples
sergey-shandar Aug 7, 2026
93b4995
Clarify JSDoc variance wording
sergey-shandar Aug 7, 2026
b0dc45c
Keep JSDoc variance example concise
sergey-shandar Aug 7, 2026
7afeac4
Clarify proof migration and test prerequisite
sergey-shandar Aug 7, 2026
2edbfbb
Restore f.mjs test prerequisite
sergey-shandar Aug 7, 2026
89d68ff
Align f.mjs fixtures with stage-one proof migration
sergey-shandar Aug 7, 2026
a6cff6e
Allow proof.f.mjs during source migration
sergey-shandar Aug 7, 2026
283920e
Align roadmap with two-stage source migration
sergey-shandar Aug 7, 2026
d31900e
Allow proof.f.mjs during stage-one migration
sergey-shandar Aug 7, 2026
27ff0fc
Clarify authored mjs package scope
sergey-shandar Aug 7, 2026
1562ce0
Allow incidental authored mjs in packages
sergey-shandar Aug 7, 2026
5fb6df2
Track TypeScript-to-JSDoc hard cases
sergey-shandar Aug 7, 2026
383ef5d
Align stage-2 package prerequisite
sergey-shandar Aug 7, 2026
bd29a64
Align language extension roadmap
sergey-shandar Aug 7, 2026
b093a36
Align NanVM MVP extension roadmap
sergey-shandar Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 79 additions & 19 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,33 +158,38 @@ acceptable. If a line or branch genuinely cannot be reached, restructure the cod
so it isn't there rather than leaving it uncovered.

The implementation and proof extensions are independent during the incremental
`.f.mjs` migration:
TypeScript-to-JavaScript migration:

| Implementation | Proof | When |
|---|---|---|
| `module.f.ts` | `proof.f.ts` | Default. |
| `module.f.mjs` | `proof.f.ts` | A module migrated to `.f.mjs` keeps its TypeScript proof, which may still import `.f.ts` test helpers such as `fjs/asserts/module.f.ts`. |
| `module.f.mjs` | `proof.f.mjs` | Once the proof's own syntax and relative FunctionalScript dependencies are compiler-ready. |
| `module.f.ts` | `proof.f.ts` | Both files are still authored TypeScript. |
| `module.f.mjs` | `proof.f.ts` | The implementation has migrated, while the proof temporarily remains TypeScript. |
| `module.f.mjs` | `proof.f.mjs` | The proof is valid JavaScript/JSDoc and all of its authored FunctionalScript runtime/type dependencies outside the migration group are already `.f.mjs`. Compiler readiness is not required. |

Renaming an implementation to `.f.mjs` therefore never requires renaming its
proof, and never removes it from proof discovery or from Node and Deno coverage:
`shouldLoad` in [`fjs/dev/module.f.ts`](./fjs/dev/module.f.ts) matches both
authored extensions, and both `npm run cov` and `deno task cov` include
`module.f.ts` and `module.f.mjs`. Ordinary (non-FunctionalScript) `.mjs` files
stay opt-in through the `proof.mjs` filename convention.
proof in the same change, and never removes it from proof discovery or from Node
and Deno coverage: `shouldLoad` in [`fjs/dev/module.f.ts`](./fjs/dev/module.f.ts)
matches both authored extensions, and both `npm run cov` and `deno task cov`
include `module.f.ts` and `module.f.mjs`. Ordinary (non-FunctionalScript) `.mjs`
files stay opt-in through the `proof.mjs` filename convention. Stage 1 still
ends with no authored `.f.ts`, so every remaining `proof.f.ts` must eventually
migrate to `proof.f.mjs`.

A `proof.f.mjs` is authored `.f.mjs` like any other, so it must satisfy the same
dependency-closure rule as any other migrated file — its relative imports and
type references may target `.f.mjs` modules only. That rule is what makes
`proof.f.ts` the default layout for a migrated module: a TypeScript proof can
keep using the existing `.f.ts` test helpers. See
source dependency-closure rule as any other migrated file: its relative runtime
imports and JSDoc type references may target `.f.mjs` modules only. This is a
JavaScript/JSDoc and dependency-readiness rule, not a FunctionalScript compiler
feature gate. A proof that still imports an unmigrated helper such as
`fjs/asserts/module.f.ts` remains `proof.f.ts` until that helper migrates. See
[`fjs/fsc/README.md`](./fjs/fsc/README.md) for the migration order and the
module-import policy it implies.

### 3.3 Use `assert` / `assertEq`, never a hand-written `if`/`throw`

Assert results in `proof` code with `assert`/`assertEq` from
`fjs/asserts/module.f.ts`, not a hand-written `if (cond) { throw ... }`.
Assert results in `proof` code with `assert`/`assertEq` from the current authored
`fjs/asserts/module.f.*` source, not a hand-written `if (cond) { throw ... }`.
During Stage 1 that helper is `.f.ts` until it migrates to `.f.mjs`; a
`proof.f.mjs` that needs it must wait for that dependency migration.

A local `if`/`throw` in a test is itself a new branch for the coverage tool to
track, and its failure side is normally never exercised (the test is expected to
Expand Down Expand Up @@ -429,6 +434,53 @@ cases remain explicit and independently testable.

### 6.2 Types

#### JavaScript/JSDoc type declarations

Authored `.mjs` / `.f.mjs` files must remain valid JavaScript. Put named and
generic static types in JSDoc rather than TypeScript syntax, and preserve the
same public assignability and declaration-emission behavior when translating a
`.ts` / `.f.ts` file.

Use `@typedef` for a named type and `@template` for its type parameters. A
constraint goes in braces before the parameter name:

```js
/**
* @template {Operation} O
* @template T
* @typedef {(_: Pr<O, O[0]>[1]) => Effect<O, T>} Cont
*/
```

TypeScript 7 also supports variance modifiers on JSDoc type-alias parameters.
Translate TypeScript `in` / `out` directly on `@template` instead of dropping
the variance annotation. For example:

```ts
export type Cont<out O extends Operation, T> =
(_: Pr<O, O[0]>[1]) => Effect<O, T>
```

becomes:

```js
/**
* @template {Operation} out O
* @template T
* @typedef {(_: Pr<O, O[0]>[1]) => Effect<O, T>} Cont
*/
```

The supported forms are `@template out T`, `@template in T`, and constrained
forms such as `@template {Operation} out O`. Variance modifiers belong to type
parameters of a JSDoc type alias (`@typedef`); do not put `in` / `out` on an
ordinary function's `@template`, where TypeScript rejects them.

When translating a public type, verify both normal type checking and emitted
`.d.ts` / `.d.mts` declarations. The JSDoc spelling may differ, but the public
type contract must not become weaker just because the source moved to
JavaScript.

#### Prefer inference

Let TypeScript infer the type of private constants, local variables, and return
Expand Down Expand Up @@ -803,9 +855,17 @@ anywhere else as the rule being broken.

### 6.5 FunctionalScript module rules

- Only import other `.f.ts` files from FunctionalScript modules. Avoid references
to built-in or external Node modules such as `node:path` in `.f.ts` files.
- No `try`/`catch` — see [§3.5](#35-never-use-trycatch-test-throwing-with-the-throw-key).
During Stage 1 of the TypeScript-to-JavaScript migration, relative authored
FunctionalScript dependencies follow the asymmetric source rule:

- `.f.ts` may import `.f.ts` or already migrated `.f.mjs`;
- `.f.mjs` may import only `.f.mjs` authored FunctionalScript dependencies;
- compiler support does not gate an `.f.ts` / `proof.f.ts` -> `.f.mjs` /
`proof.f.mjs` rename; JavaScript/JSDoc validity and dependency closure do.

Avoid references to built-in or external Node modules such as `node:path` in
FunctionalScript source. No `try`/`catch` — see
[§3.5](#35-never-use-trycatch-test-throwing-with-the-throw-key).

### 6.6 Formatting

Expand Down Expand Up @@ -942,4 +1002,4 @@ Only add CHANGELOG entries for code changes — PRs that only touch `todo/`,
- Releasing is its own commit: the version lives in `package.json` (`"version"`)
— `deno.json` holds tasks and formatting only. When it's bumped, create a new
`## X.Y.Z` section in `CHANGELOG.md` immediately after `## Unreleased` and move
all entries from `## Unreleased` into it, leaving `## Unreleased` empty.
all entries from `## Unreleased` into it, leaving `## Unreleased` empty.
9 changes: 6 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,12 @@ Bun, and published-CLI equivalents are listed in
[AGENTS.md §1.4](./AGENTS.md#14-ways-to-run-the-functionalscript-test-suite).

New `.f.ts` and `.f.mjs` modules need a co-located proof with 100% proof
coverage — see [AGENTS.md §3](./AGENTS.md#3-testing-and-proof-coverage). A
`module.f.mjs` may keep a `proof.f.ts` during the incremental `.f.mjs`
migration, or use a `proof.f.mjs` once that proof is itself compiler-ready.
coverage — see [AGENTS.md §3](./AGENTS.md#3-testing-and-proof-coverage). During
the TypeScript-to-JavaScript migration, a `module.f.mjs` may keep a
`proof.f.ts`, or the proof may migrate to `proof.f.mjs` as soon as the proof
itself is valid JavaScript/JSDoc and its authored FunctionalScript dependencies
are already `.f.mjs`. Current FunctionalScript compiler support is not required
for that proof rename.

### Updating dependencies

Expand Down
100 changes: 100 additions & 0 deletions fjs/ci/todo/f-js-package-support.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
## Package support for authored `.f.js`

**Priority:** P1
**Status:** blocked
**Blocked by:** [`todo/migrate-typescript-to-mjs.md`](../../../todo/migrate-typescript-to-mjs.md)

### Problem

After authored TypeScript is removed, `.f.js` will become the marker for
FunctionalScript source accepted by the current parser/compiler. The existing
package design, however, is built around authored `.ts` / `.mjs` roots and does
not yet guarantee that a standalone authored `.f.js` module is directly checked,
gets a generated `.d.ts`, is included in the NPM package, or type-checks from a
clean consumer.

Compiler compatibility alone is therefore not enough to rename `.f.mjs` to
`.f.js`. Without explicit package/tooling support, a compiler-ready module could
ship without the declaration and validation guarantees expected from the rest of
the package.

This work cannot begin until
[`migrate-typescript-to-mjs.md`](../../../todo/migrate-typescript-to-mjs.md)
finishes: while `.f.ts` exists, TypeScript can generate `.f.js`, and the
repository intentionally ignores `**/*.js`.

### Proposal

After stage 1 removes authored TypeScript, TypeScript-to-JavaScript emission, and
the blanket `.js` ignore, make authored `.f.js` a first-class checked and
packable source extension before the first compiler-compatibility rename.

The stage-2 invariant is:

```text
source.f.mjs -> source.f.js + source.f.d.ts
```

The rename from `.f.mjs` to `.f.js` means the source is accepted by the current
FunctionalScript compiler. TypeScript still checks the authored JavaScript via
`allowJs` / `checkJs` and emits its declaration.

Validation must include `.f.js` as an explicit root/source pattern, not merely
rely on another `.mjs` module importing it. Declaration emission must likewise
cover standalone `.f.js` modules and produce `.d.ts` files for package
consumers.

Package and publish jobs continue to run in CI from a clean checkout. Stage 2
therefore does not need generated-output cleanup or repeated local-pack safety;
it only needs to make sure authored `.f.js` is included as source and never
mistaken for generated TypeScript output.

NPM packaging must include the authored `.f.js` runtime and its `.d.ts`
declaration, and clean-consumer tests must verify runtime and type resolution.

Do not add package-time import or declaration-specifier rewriting. A migrated
`.f.js` group must remain dependency-closed according to the compiler migration
rules in [`todo/fjs-nanvm-integration.md`](../../../todo/fjs-nanvm-integration.md).

### Tasks

- [ ] After stage 1, include authored `.f.js` directly in the root TypeScript
checked source set with `allowJs` / `checkJs`.
- [ ] Ensure declaration emission produces `.d.ts` for standalone authored
`.f.js` modules.
- [ ] Verify NPM package rules include authored `.f.js` and its `.d.ts`.
- [ ] Add an authored `.f.js` package fixture that is not reachable only through
an `.mjs` root, proving direct source discovery.
- [ ] Verify the fixture is type-checked in the repository.
- [ ] Verify the clean CI package build contains the authored `.f.js` and its
generated declaration.
- [ ] Verify a clean consumer can import the `.f.js` runtime and type-check
against its generated `.d.ts` without repository source files.
- [ ] Update package/contributor documentation for the stage-2 authored `.f.js`
meaning.

### Acceptance criteria

- A standalone authored `.f.js` is directly included in repository TypeScript
checking.
- Declaration emission produces a corresponding `.d.ts`.
- The packed NPM artifact contains the authored `.f.js` and all declarations
required by its public/transitive type graph.
- Package/publish runs start from a clean CI checkout; no local generated-output
cleanup or repeated-pack guarantee is required.
- A clean consumer can execute/import the `.f.js` runtime and type-check it.
- No staging tree or package-time runtime/declaration specifier rewrite is
required.
- The first `.f.mjs` -> `.f.js` compiler-compatibility rename is **blocked by**
completion of this task.

### Related

- [`todo/migrate-typescript-to-mjs.md`](../../../todo/migrate-typescript-to-mjs.md)
— stage-1 prerequisite that removes TypeScript and makes `.js` authorable.
- [`f-mjs-package-support.md`](./f-mjs-package-support.md) — stage-1 authored
`.mjs` package support.
- [`publishing-packages.md`](./publishing-packages.md) — broader package plan.
- [`fjs/fsc/README.md`](../../fsc/README.md) — extension contract.
- [`todo/fjs-nanvm-integration.md`](../../../todo/fjs-nanvm-integration.md) —
compiler-compatibility migration blocked by this package prerequisite.
Loading
Loading