Move public types from .f.mjs JSDoc into authored types.ts - #1483
Conversation
Deploying with
|
| Status | Name | Latest Commit | Preview URL | Updated (UTC) |
|---|---|---|---|---|
| ✅ Deployment successful! View logs |
functionalscript | 6949188 | Commit Preview URL Branch Preview URL |
Aug 11 2026, 12:59 AM |
types.ts: fjs/asserts, fjs/types/function
types.ts: fjs/asserts, fjs/types/function.f.mjs JSDoc into authored types.ts
o2alexanderfedin
left a comment
There was a problem hiding this comment.
Approving. Verified locally at head 4e05808 against merge-base f59e949.
What I checked
Public type surface is unchanged. This is the claim that matters for a 166-file type move, so I checked it mechanically rather than by reading: built main and ts in separate worktrees, ran npm run prepack in both, extracted every export type declaration from every emitted .d.ts/.d.mts, and diffed the two sets keyed by type name (so a move between files is not a difference).
Result — 0 public types added, 0 removed, 2 textually changed:
Cmp2— whitespace only ([A, B]vs[ A, B ]).SupportedRecord—typeof booleanbecametypeof booleanTag, from the unavoidableimport type { boolean as booleanTag }infjs/asn.1/types.ts. Same underlying export, local alias only.
The 14 declarations that left the emitted surface are all _-prefixed (_Index, _Tuple, _Revision, _ItemThunk, _X0..2, _FirstBranch3/5, _FirstLeaf1/2, _PathItem3/5, _ItemArray) — exactly the "needed only by a public type" bucket in your table, which is meant to become non-exported. Intentional, and consistent with the _-prefix convention.
No runtime cycle from fjs/asn.1. The types.ts ↔ module.f.mjs edge is type-only in both directions and erases: every one of the 33 emitted types.js files is export {}; (plus its @module comment) and nothing else.
No re-export shims. grep for export … from './types.ts' across .f.mjs/.f.ts returns nothing, per §5.2.
Tests. 2356 pass / 0 fail on this branch — identical to main's authored count (2356), so no coverage was dropped. npx tsc clean. npm run prepack emits declarations and JavaScript for every new types.ts.
(One note on that number, in case it trips anyone up later: after prepack the count reads 4526 on main vs 4512 here. The gap is entirely fjs/types/string/proof.f.ts → proof.f.mjs — 14 tests that used to run twice, once authored and once from the emitted proof.f.js duplicate. Nothing was lost.)
The packaging follow-up is a non-issue for TypeScript consumers
The description leaves this open, so I tested it rather than leaving it open: npm packed the tarball and consumed it from a separate nodenext + strict project.
types.ts is indeed not in files, but types.d.ts and types.js are both emitted and shipped, and TypeScript resolves the './types.ts' specifier in the shipped declarations to types.d.ts. All three of these type-check correctly against the installed package:
import { vec } from 'functionalscript/fjs/types/bit_vec/module.f.mjs' // Vec resolves through module.f.d.mts
import type { Vec } from 'functionalscript/fjs/types/bit_vec/types.ts' // authored path — resolves
import type { Vec } from 'functionalscript/fjs/types/bit_vec/types.js' // emitted path — resolvesEach one yields a real Vec, not any — I confirmed by forcing Type 'Vec' is not assignable to type 'string' in all three cases, since a silent pass would have been the signature of an unresolved import. So fjs/ci/todo/f-mjs-package-support.md stays open on its own merits, but widening the pilot to 31 modules does not make anything worse, and nothing here ships broken.
One thing to fix, non-blocking
The description no longer matches the branch. It says "124 files changed overall, net −792 lines"; the branch is 166 files, net +88. It also says fjs/types/phantom "still needs its rename" and that "remaining .f.ts implementations are untouched" — but this branch renames phantom/module.f.ts → types.ts and migrates types/ordered_map and types/string (module and proof) from .f.ts to .f.mjs.
Those extra migrations are fine and I verified them — the ordered_map/proof.f.mjs translation in 4e05808 preserves every annotation faithfully (as const → /** @type {const} */(…), the OrderedMap<number> binding, both number parameter types) and drops no assertions. The issue is only that a reviewer reading the summary would not know they are in scope. Worth a refresh of the body before merge, since the summary is what ends up describing this in history.
CI is green across all platforms and runtimes.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c6e64bd1e0
ℹ️ 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".
| /** | ||
| * Compile-time-only check: a type resolves only if it is exactly `true`. | ||
| * Used to assert type-level properties without any runtime cost, e.g. | ||
| * `type _ = Assert<Equal<A, B>>`. | ||
| */ |
There was a problem hiding this comment.
Add module headers to the new type modules
Add the required module-level JSDoc header before this declaration; the current comment documents Assert rather than the module and omits @module. The same omission exists in the newly added fjs/types/function/types.ts and fjs/types/ordered_map/types.ts, leaving all three type modules without the repository's required module documentation.
AGENTS.md reference: AGENTS.md:L252-L265
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3fc9579. Added @module headers to all three: fjs/asserts/types.ts ("Type-level assertion helpers."), fjs/types/function/types.ts ("Types for function composition."), fjs/types/ordered_map/types.ts ("Types for the ordered map data structure."). The existing Assert doc comment stays where it is, now below the module header.
Checked the rest of the set while I was in there — the other 30 types.ts files already had headers, so these three were the only gaps.
| * @example | ||
| * | ||
| * ```js | ||
| * import { join, concat, repeat, cmp } from './module.f.ts' |
There was a problem hiding this comment.
Update the migrated string-module import example
After this module was renamed to module.f.mjs, copying the example still imports ./module.f.ts, which no longer exists anywhere in fjs/types/string; users following the module documentation therefore get a module-not-found error. Change the example specifier to ./module.f.mjs.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3fc9579 — the example now imports ./module.f.mjs.
Two more instances of the same stale specifier exist outside this PR's diff, both self-referencing examples in modules migrated before this branch:
fjs/types/result/module.f.mjs:9—from './module.f.ts', and it also importstype Result, which now lives in./types.tsfjs/types/bigint/module.f.mjs:9—from './module.f.ts'
Neither file is touched by this PR, so I left them alone rather than widening the diff. (fjs/types/nibble_set/module.f.mjs:6 also links to ../byte_set/module.f.ts, but that one is correct — byte_set has not been migrated.)
Add the required `@module` header to the three `types.ts` files that were missing it, and fix the `fjs/types/string` example to import `./module.f.mjs` instead of the deleted `./module.f.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3fc9579fa7
ℹ️ 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".
| import type { Tag0, Tag1, Const, Or, String as RttiString, Struct, Tuple, Type, ConstObject } from '../module.f.ts' | ||
| import type { Assert } from '../../../asserts/module.f.mjs' | ||
| import type { phantomKey } from '../../phantom/module.f.ts' | ||
| import type { Assert } from '../../../asserts/types.ts' |
There was a problem hiding this comment.
Update the Phantom import in the recursive-schema example
When users follow the recursive-schema guidance in this module, the prose on line 100 and the example on line 104 still direct them to fjs/types/phantom/module.f.ts, even though this change renames that file to types.ts and a repo-wide search confirms the old module no longer exists. Update both references to the new path so the documented example does not fail with a module-not-found error.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 0fe245c — both the prose on line 100 and the example on line 104 now point at the renamed types.ts.
One extra change beyond what you flagged: the example specifier was '../types/phantom/module.f.ts', which was wrong in the path as well as the filename — from fjs/types/rtti/ts/ that resolves to fjs/types/rtti/types/phantom/. It is now '../../phantom/types.ts', matching this module's own import type { phantomKey } on line 14, so the example is copy-pasteable from where it is documented.
npx tsc clean, tests 0 fail.
Point the recursive-schema prose and example at `fjs/types/phantom/types.ts`, renamed from `module.f.ts` in this branch. The example specifier now matches the module's own import, `../../phantom/types.ts`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
o2alexanderfedin
left a comment
There was a problem hiding this comment.
Re-reviewed the three commits since my approval (4e05808..0fe245c). npx tsc clean, 2356/2356 pass, CI green, and my earlier approval still stands — the type surface is untouched by these.
Two of the three are good stale-doc catches. rtti/ts/module.f.ts also fixes a path that was wrong before the rename, not just after it: '../types/phantom/module.f.ts' resolved from fjs/types/rtti/ts/ to fjs/types/rtti/types/phantom/…, which never existed. '../../phantom/types.ts' is right.
The @module header regression from #1475 is back, in 18 of the 33 types.ts files
3fc9579 adds @module headers to asserts, function, and ordered_map. The first two get a blank line after the header block; ordered_map does not, and its header is dropped from the emitted JavaScript:
$ cat fjs/types/ordered_map/types.js
export {}; # header gone
$ cat fjs/asserts/types.js
/**
* Type-level assertion helpers.
*
* @module
*/
export {}; # header keptSame cause as #1475: with no blank line, TypeScript treats the block as the leading comment of the import that follows, and the comment is elided along with the type-only import. I confirmed it is the blank line and nothing else by adding exactly one to ordered_map/types.ts and re-emitting — the header comes back.
Swept all 33 types.ts files. The correlation is 33/33, no exceptions:
Emitted types.js |
Line after the header block | Count |
|---|---|---|
| header kept | blank line | 15 |
| header lost | import … immediately |
18 |
The 18 losing it: asn.1, basen, common/monoid, crypto/pow, crypto/secp, crypto/sha2, text, text/utf8, types/array, types/bigint, types/bit_vec, types/btree/find, types/btree/types, types/list, types/ordered_map, types/prime_field, types/range_map, types/sorted_list.
On impact, so this is weighted fairly: the .d.ts keeps the header in every case, so tooling and editors are unaffected — this is only the shipped types.js, which is export {}; and that nobody imports for behavior. It is a cosmetic defect, not a correctness one, and I would not hold the PR for it. But §4 asks every module to carry the header, all 33 files do carry it in source, and which ones survive emit is currently decided by whether a blank line happens to be there. Fixing it is one blank line per file, it is the same fix already made once, and doing it here keeps the whole set consistent from the start rather than leaving 18 to be found later.
Minor
fjs/types/ordered_map/types.ts uses double quotes for its import specifier; the other 17 types.ts files with imports all use single quotes.
Separate the `@module` header from the following `import` with a blank line in the 18 `types.ts` files that lacked one. Without it TypeScript treats the header as the import's leading comment and elides both, dropping the header from the emitted `types.js` — and from `types.d.ts` too when every import is elided, as in `fjs/types/array`. Same cause and same fix as #1475. Also switch `fjs/types/ordered_map/types.ts` to single-quoted import specifiers, matching the other 17 `types.ts` files with imports. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Both addressed in
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0fe245c667
ℹ️ 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".
| * Used to assert type-level properties without any runtime cost, e.g. | ||
| * `type _ = Assert<Equal<A, B>>`. | ||
| */ | ||
| export type Assert<T extends true> = T |
There was a problem hiding this comment.
Point type-proof imports at the new type modules
When a contributor follows the mandatory type-level proof guidance in AGENTS.md, it still directs them to import Assert from fjs/asserts/module.f.mjs and Equal from fjs/types/ts/module.f.mjs, but this change removes both types from those modules. Update that guidance to reference the new types.ts files; otherwise new proofs written according to the repository instructions fail type checking.
AGENTS.md reference: AGENTS.md:L211-L217
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 6949188. AGENTS.md §3.4 now points at fjs/asserts/types.ts and fjs/types/ts/types.ts.
| /** | ||
| * Represents a result that can be either successful or failed. | ||
| */ | ||
| export type Result<T, E> = Ok<T> | Error<E> |
There was a problem hiding this comment.
Update issue designs to use the new type paths
When existing issues are picked up, several checked-in designs now prescribe invalid type imports: fjs/effects/todo/fold-stream-combinator.md:69 imports Result from the runtime module, fjs/types/todo/bit-set-factory.md:57 locates Range there, and fjs/fsm/todo/scan-as-map.md:42 locates Entry in range_map/module.f.mjs. Those exports move to types.ts in this commit, so these designs should be updated as part of the path sweep before they guide future implementation.
AGENTS.md reference: AGENTS.md:L337-L339
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 6949188, plus four more of the same kind.
All three you named:
fjs/effects/todo/fold-stream-combinator.md:69—Result->'../../types/result/types.ts'fjs/types/todo/bit-set-factory.md:57—Range->fjs/types/range/types.tsfjs/fsm/todo/scan-as-map.md:42—Entry->fjs/types/range_map/types.ts:11, and theRangeMapArrayline ref:58->:19
I widened the sweep to catch the rest. Cross-referencing every export type name across the 33 types.ts files against every markdown mention of a module.f.mjs path turned up four more docs asserting a moved type still lives in the runtime module:
fjs/asn.1/todo/65z-asn1-tag-codec-table.md:85—SupportedRecord->fjs/asn.1/types.ts:45-51fjs/crypto/secp/todo/init-named-coefficients.md:8and:106—InitandPoint2D->fjs/crypto/secp/types.ts. This one also quotedInitas a JSDoc@typedefblock, which is no longer how it is written; the quoted definition is now the TypeScript form.fjs/types/list/todo/simplify-list-type.md:8—List->fjs/types/list/types.tsfjs/crypto/vdf/todo/iterate-combinator.md:54—Unary->fjs/types/bigint/types.ts
The remaining module.f.mjs mentions the cross-reference flagged are true positives for the name but not for the claim — they point at runtime values that legitimately stayed put (BitOrder's implementation, sorted_list's ReduceOp constructors, secp's mul, monoid's repeat), so I left them.
One thing outside the type move, found while editing init-named-coefficients.md: every line number that doc cites into fjs/crypto/secp/module.f.mjs was stale by exactly 35 lines — curve at :72 is really :37, the five curve literals at :146/:181/:195/:211/:227 are really :111/:146/:160/:176/:192, and so on. That drift predates this branch and came from secp's own .f.ts -> .f.mjs migration, but I was already rewriting those lines and did not want to leave known-wrong numbers next to freshly corrected ones, so they are fixed in the same commit.
npx tsc clean, 2356/2356 authored tests pass.
Point documentation at the `types.ts` files the types now live in: - `AGENTS.md` §3.4 — `Assert` and `Equal` for type-level proofs. - `fjs/effects/todo/fold-stream-combinator.md` — `Result`. - `fjs/types/todo/bit-set-factory.md` — `Range`. - `fjs/fsm/todo/scan-as-map.md` — `Entry` / `RangeMapArray`. - `fjs/asn.1/todo/65z-asn1-tag-codec-table.md` — `SupportedRecord`. - `fjs/crypto/secp/todo/init-named-coefficients.md` — `Init` / `Point2D`, including the quoted definition, now TypeScript rather than a JSDoc typedef. - `fjs/types/list/todo/simplify-list-type.md` — `List`. - `fjs/crypto/vdf/todo/iterate-combinator.md` — `Unary`. `init-named-coefficients.md` also carried line numbers into `fjs/crypto/secp/module.f.mjs` that were 35 lines stale from that module's earlier migration; corrected while sweeping the file. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Removing ParseContext, djsResult, and Step from the emitted module.f.d.mts is the specifier-level break #1483 and #1503 record for exactly this kind of move — this repository's own stdio proof took the same break in this diff. The entry gains the prefix and the "importers must use the types.ts specifier" clause in the house wording. Also make rows 2 and 5 of the measurement table position-independent per the review's non-blocking note: a detached block lands after the bare types in the reproductions, before them when it leads the file; emitted bare is the invariant. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkqgqTaffDpYFQEJydpqHF
Summary
Applies the authored
types.tsconvention introduced by#1481 across
every migrated
.f.mjsmodule: each module's public type-level API moves outof JSDoc
@typedefinto a siblingtypes.ts, written as ordinary TypeScript,and every consumer now names that real source path.
types.tsfiles, covering all 31 modules that carried publictypedefs (two —
fjs/assertsandfjs/types/function— landed as the pilotearlier in this branch).
fjs/types/option/module.f.mjsis deleted. It was a runtime-emptydeclaration-only module; it becomes
fjs/types/option/types.tsoutright ratherthan keeping a JavaScript file with no runtime content.
fjs/types/phantom/module.f.tsis renamed totypes.ts, andfjs/types/ordered_mapandfjs/types/stringare migrated from.f.tsto.f.mjs— module and proof both. Seethe section below.
specifier rewrites; the new
types.tsfiles and the JSDoc annotations in thetwo migrated implementations account for the small net gain.
What moved, and what didn't
The split follows the rule in
AGENTS.md§6.2 and the "Separate type-only APIsinto
types.ts" section oftodo/migrate-typescript-to-mjs.md:types.ts, asexport type_-prefixed, needed only by a public typetypes.ts, as a non-exportedtype_-prefixed, needed by both a public type and the implementationtypes.ts, exported but still_-prefixed_-prefixed, implementation-local onlymodule.f.mjs@typedef {typeof x extends …} T)Four modules therefore get no
types.ts:fjs/types/btree/remove,fjs/types/btree/set,fjs/types/number, andfjs/fscdeclare nothing but_-prefixed implementation aliases. Splitting those would publish private typesas a public type module and add indirection for no reader — the migration doc
explicitly asks not to split mechanically. Their import specifiers are updated
like everyone else's.
Why this is worth doing now
These types were already the public contract; they were merely spelled in
JSDoc. Three concrete costs of leaving them there:
Cmp2,StateScan, orBitOrderbefore and after — conditional types, variance, and inner genericsread far better as TypeScript, and several carried
@templateclauses purelyas ceremony.
.f.tsmodules had to reach into.f.mjsfor types. That is thetype-only edge the migration wants gone before more implementations move to
JavaScript, not after.
writes
'./types.ts'once and it stays correct throughmodule.f.ts → module.f.mjs → module.f.js.Notable cases
fjs/asn.1—SupportedRecorddiscriminates ontypeof boolean,typeof integer, … which are runtime constants inmodule.f.mjs. Itstypes.tstherefore takes a type-only import back from./module.f.mjs. Thecycle is type-only in both directions (JSDoc
@importone way,import typethe other) and erases completely, so there is no runtime cycle.
fjs/types/bit_vec—_NormOpand_UnpackConcatare used by the publicBitOrderand by the implementation, so they are exported fromtypes.tswhile keeping the
_prefix that marks them private._Base,_ListToVecState, and_ListToVecOpare implementation-only and stayed.fjs/types/array— theAssert<Equal<KeyOf<…>>>compile-time assertionsmoved with
KeyOfintotypes.ts, where the facts they assert now live.Also here: phantom rename and two implementation migrations
Three sweep tasks from
todo/migrate-typescript-to-mjs.mdland in this branch aswell, since the
types.tsconvention is what unblocked them:fjs/types/phantom/module.f.ts→fjs/types/phantom/types.ts— a purerename, 100% similarity, no content change. The module was always type-only: its
public
Phantomusesdeclare const phantomKey: unique symbol, which JSDoccannot express, so it stays authored TypeScript and now says so by its name. No
runtime phantom value is introduced.
fjs/types/ordered_map—module.f.ts→module.f.mjs, withEntryandOrderedMapsplit intotypes.tsper the rule above, andproof.f.ts→proof.f.mjs.fjs/types/string—module.f.ts→module.f.mjsandproof.f.ts→proof.f.mjs. It exports no types, so it gets notypes.ts. The twoexplanatory comments —
joinis not a monoid fold becausejoinOp(sep)has noidentity,
concatis one — became JSDoc blocks attached to their declarationsrather than free-floating
//comments.Both proof translations keep every annotation instead of falling back to
inference:
as constbecomes/** @type {const} */(…),let m: OrderedMap<number>keeps its type through a
@typetag, and the twonumberparameters of thesetReducecallback keep theirs as inline/** @type {number} */tags. Noassertion was dropped.
Breaking change
Moving an exported type breaks importers even though nothing changes at runtime.
Per §8.4 every importer in this repository is updated in the same PR and no
re-export shims are left behind — a module does not re-export its
types.tssibling, since two spellings for one type is exactly what §5.2 says not to keep.
Known follow-up (not introduced here)
Emitted declarations reference the authored source path, e.g.
import type { Func } from '../../../types/function/types.ts'inmodule.f.d.ts, andtypes.tsis not in the package'sfileslist. This ispre-existing behavior from the pilot commit rather than something this change
introduces, and it is exactly the open item tracked in
fjs/ci/todo/f-mjs-package-support.md("verify emitted declarations referencepackage paths that actually exist and determine whether generated
types.jsisrequired for portable consumers"). Widening the pilot makes it apply to 31
modules instead of 2; it does not change the question. Left for that issue
rather than folded in here.
The sweep in
todo/migrate-typescript-to-mjs.mdis not finished: 166.f.tsfiles remain, and the checklist entry for the phantom rename is still unticked
even though this branch performs it.
Validation
npx tsc— clean.npm start test— 2356 tests pass, 0 fail.npm run prepack— declaration and JavaScript emit succeed for every newtypes.ts.npm run update— lockfiles and CI workflow refreshed.