Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
65b7d86
docs(plan): front-ends (U7/PR3) plan
systemfsoftware-maker Oct 12, 2026
925b4cc
refactor(api-extractor): make the package isolatedDeclarations-clean …
systemfsoftware-maker Oct 12, 2026
2721cc1
perf(profile): per-phase front-end profile on the real corpus (U2)
systemfsoftware-maker Oct 12, 2026
056c36c
feat(front-end): emitter port and the oxc isolated-declarations adapt…
systemfsoftware-maker Oct 12, 2026
9a9abf4
feat(front-end): model builder, analysis-message resolver, TypeScript…
systemfsoftware-maker Oct 12, 2026
d4d55d3
refactor(front-end): simplify the front-end slice (ce-simplify-code)
systemfsoftware-maker Oct 12, 2026
fd4ad93
docs(front-end): second phase profile with build and resolve phases, …
systemfsoftware-maker Oct 12, 2026
549b472
fix(front-end): apply the operator-accepted review dispositions (U10)
systemfsoftware-maker Oct 12, 2026
1f13a4a
docs(solutions): the compiler fallback inherits the package tsconfig'…
systemfsoftware-maker Oct 12, 2026
a2fe1eb
docs(solutions): state the single-emit-policy invariant only as far a…
systemfsoftware-maker Oct 12, 2026
3162d19
fix(front-end): keep both emitters inside the analysed package (revie…
systemfsoftware-maker Oct 13, 2026
72ebed4
fix(front-end): write numbers by value so both routes build one model
systemfsoftware-maker Oct 13, 2026
7b597e4
fix(front-end): match api-extractor's release-tag notes on both routes
systemfsoftware-maker Oct 13, 2026
2b2e595
Merge remote-tracking branch 'origin/main' into feat/front-ends
systemfsoftware-maker Oct 13, 2026
77e0365
Merge remote-tracking branch 'origin/main' into feat/front-ends
systemfsoftware-maker Oct 13, 2026
2c13063
test(front-end): keep only refusal checks no consumer can observe (U11)
systemfsoftware-maker Oct 13, 2026
fb70da8
docs(plan): rule every open front-end finding (U12)
systemfsoftware-maker Oct 13, 2026
25d0ef9
fix(front-end): synthetic diagnostics carry no TypeScript code (U12 C4)
systemfsoftware-maker Oct 13, 2026
5a862ac
fix(front-end): keep local alias names, optional tuple marks and sett…
systemfsoftware-maker Oct 13, 2026
55eb06e
fix(front-end): scope type parameters to their declaration in the res…
systemfsoftware-maker Oct 13, 2026
fa1f695
fix(front-end): read .cts entries and expand subpath pattern exports …
systemfsoftware-maker Oct 13, 2026
3e81954
fix(front-end): decline what the fast path cannot prove (U12)
systemfsoftware-maker Oct 13, 2026
7520eae
fix(front-end): read compiler diagnostics from the TypeScript 7 API, …
systemfsoftware-maker Oct 13, 2026
1ba9588
fix(front-end): carry the package's root files and type roots into th…
systemfsoftware-maker Oct 13, 2026
8efc442
docs(front-end): say both routes give one model apart from the produc…
systemfsoftware-maker Oct 13, 2026
35c33f4
docs(plan): correct D1 and D2 to what the adapters do; record delta r…
systemfsoftware-maker Oct 13, 2026
e5ec09d
docs(solutions): point the emit-option learning at the 1834 plan
systemfsoftware-maker Oct 13, 2026
c093a03
fix(front-end): skip null and array export targets; pattern exports r…
systemfsoftware-maker Oct 13, 2026
c4202d9
fix(front-end): the fallback ignores the package's isolatedDeclaratio…
systemfsoftware-maker Oct 13, 2026
432b608
fix(front-end): resolve types through the package's own node_modules …
systemfsoftware-maker Oct 13, 2026
d77e2dd
fix(front-end): a mapped-type key or an infer variable shadows a decl…
systemfsoftware-maker Oct 13, 2026
a4ed6fd
docs(front-end): rule the e5ec09d review (U13); third phase profile; …
systemfsoftware-maker Oct 13, 2026
5831e97
docs(solutions): point the emit-option learning at the 1956 plan
systemfsoftware-maker Oct 13, 2026
18c81e0
fix(front-end): mirror a package installed under node_modules without…
systemfsoftware-maker Oct 13, 2026
a98496e
docs(front-end): rule the U13 delta review; record the node_modules m…
systemfsoftware-maker Oct 13, 2026
a740661
docs(plan): rule every finding of the a98496e review and plan the mai…
systemfsoftware-maker Oct 13, 2026
44f2be0
fix(front-end): read a hand-written declaration file a source imports…
systemfsoftware-maker Oct 13, 2026
e882d6d
fix(front-end): read release tags with the TSDoc parser upstream uses…
systemfsoftware-maker Oct 13, 2026
a0d13d1
fix(front-end): refuse an undecodable manifest with ManifestRejected;…
systemfsoftware-maker Oct 13, 2026
f91e8c3
fix(front-end): model an anonymous default and follow tsc's implicit …
systemfsoftware-maker Oct 13, 2026
932ae50
Merge remote-tracking branch 'origin/main' into feat/front-ends
systemfsoftware-maker Oct 13, 2026
1109e48
fix(front-end): keep a hand-written .d.ts entry as its own declaratio…
systemfsoftware-maker Oct 13, 2026
5dd424b
docs(front-end): say when a CompilerRejected carries tsc's printed ou…
systemfsoftware-maker Oct 13, 2026
611a024
refactor(front-end): one shared-declaration rule and one absent-file …
systemfsoftware-maker Oct 13, 2026
4fec99b
fix(front-end): arrow text, literal renames and distinct names for hi…
systemfsoftware-maker Oct 13, 2026
eca9cee
test(front-end): pin a two-entry model and a caller-chosen source con…
systemfsoftware-maker Oct 13, 2026
34206fd
fix(front-end): a diagnostic is Reported or a Note, not a record whos…
systemfsoftware-maker Oct 13, 2026
47bf1c3
test(front-end): a refused package's diagnostic keeps tsc's elaborati…
systemfsoftware-maker Oct 13, 2026
425c81c
docs(plan): rule every finding of the 932ae50 review (U15)
systemfsoftware-maker Oct 13, 2026
8f4617a
fix(front-end): merged namespaces and enums keep every member; a qual…
systemfsoftware-maker Oct 13, 2026
95956df
docs(front-end): list every UnsupportedDeclaration cause (U15)
systemfsoftware-maker Oct 13, 2026
f98127b
fix(front-end): the compiler route reads a hand-written entry, not ts…
systemfsoftware-maker Oct 13, 2026
dfd896d
test(front-end): pin refusals, literal types, pattern edges and per-e…
systemfsoftware-maker Oct 13, 2026
99b7495
fix(front-end): a pattern export inserts the matched file name litera…
systemfsoftware-maker Oct 13, 2026
a6300c7
docs(plan): rule every finding of the 425c81c review (U16)
systemfsoftware-maker Oct 13, 2026
2dd44cb
fix(front-end): an infer name reads as the name its conditional binds…
systemfsoftware-maker Oct 13, 2026
68502ef
test(front-end): a bare-path or absent exports names no source entry …
systemfsoftware-maker Oct 13, 2026
0e1d692
fix(front-end): merge namespace bodies at any depth and refuse a repe…
systemfsoftware-maker Oct 13, 2026
460f429
docs(plan): rule every finding of the a6300c7 review (U17)
systemfsoftware-maker Oct 13, 2026
e0790d6
fix(front-end): a namespace exported inside a namespace body stays an…
systemfsoftware-maker Oct 13, 2026
fef7fe8
fix(front-end): a pattern walk enters each real directory once (U18)
systemfsoftware-maker Oct 13, 2026
1f3d00c
fix(front-end): a namespace body's own names shadow the file's; impor…
systemfsoftware-maker Oct 13, 2026
3ff23ef
docs(plan): rule every finding of the 460f429 review (U18)
systemfsoftware-maker Oct 13, 2026
9b04f01
fix(front-end): a pattern walk stops only on a cycle and reads names …
systemfsoftware-maker Oct 14, 2026
8881471
fix(front-end): only a namespace body's type names shadow a type refe…
systemfsoftware-maker Oct 14, 2026
681efe3
docs(plan): rule every finding of the 3ff23ef review (U19)
systemfsoftware-maker Oct 14, 2026
24e9b67
fix(front-end): a pattern walk lists at most 4096 directories (U20)
systemfsoftware-maker Oct 14, 2026
4ba7034
fix(front-end): scope, private constructors and index-signature names…
systemfsoftware-maker Oct 14, 2026
9e220fc
fix(front-end): a namespace body's names shadow imports in the resolv…
systemfsoftware-maker Oct 14, 2026
abb3022
fix(front-end): typeof and class heritage read a namespace body's val…
systemfsoftware-maker Oct 14, 2026
3016e54
refactor(front-end): a pattern walk lists directories in readdir orde…
systemfsoftware-maker Oct 14, 2026
43a8ee4
refactor(front-end): one declaration classifier and one entry shape (…
systemfsoftware-maker Oct 14, 2026
db5c70a
test(front-end): namespaces get their own feature; the fast path is p…
systemfsoftware-maker Oct 14, 2026
e8df9af
fix(front-end): a pattern walk skips node_modules and lists at most 1…
systemfsoftware-maker Oct 14, 2026
5d32c3f
docs(plan): rule every finding of the 681efe3 review (U20)
systemfsoftware-maker Oct 14, 2026
e5db940
fix(front-end): an export naming no declaration is refused, not dropp…
systemfsoftware-maker Oct 14, 2026
688e85d
fix(front-end): pattern exports follow Node's winning key; the walk s…
systemfsoftware-maker Oct 14, 2026
aea9874
fix(front-end): the resolver scopes a merged namespace with every blo…
systemfsoftware-maker Oct 14, 2026
c9fc383
test(front-end): a merged namespace's class and enum read in both spa…
systemfsoftware-maker Oct 14, 2026
9854ba8
docs(plan): rule every finding of the 5d32c3f review (U21)
systemfsoftware-maker Oct 14, 2026
b88de2f
refactor(front-end): the resolver builds each namespace path once (U22)
systemfsoftware-maker Oct 14, 2026
5c6c2d1
fix(front-end): an unresolved export is refused naming the file that …
systemfsoftware-maker Oct 14, 2026
eec0927
test(front-end): pin class members, undecomposed type text and a miss…
systemfsoftware-maker Oct 14, 2026
42a57e7
docs(front-end): the fallback needs TypeScript 7's native compiler pa…
systemfsoftware-maker Oct 14, 2026
1e61902
fix(front-end): a class member's name carries protected and abstract …
systemfsoftware-maker Oct 14, 2026
ea58038
docs(plan): rule every finding of the 9854ba8 review (U22)
systemfsoftware-maker Oct 14, 2026
4ce0980
test(front-end): an abstract property, an abstract accessor and a pri…
systemfsoftware-maker Oct 14, 2026
a40e552
docs(api-extractor): restore KeywordName's doc comment (U23)
systemfsoftware-maker Oct 14, 2026
8c1bc4e
fix(front-end): an external re-export reached through export * is ref…
systemfsoftware-maker Oct 14, 2026
69173f8
test(front-end): a public getter with a protected setter models as tw…
systemfsoftware-maker Oct 14, 2026
fa6bca2
docs(plan): rule every finding of the ea58038 review (U23)
systemfsoftware-maker Oct 14, 2026
d08f648
fix(front-end): an external re-export is refused naming the file that…
systemfsoftware-maker Oct 14, 2026
84ef8d2
test(api-extractor): a decoded verdict carries the family brand (U24)
systemfsoftware-maker Oct 14, 2026
ecbf999
docs(front-end): the source condition's value is a path (U24)
systemfsoftware-maker Oct 14, 2026
c25bf9a
docs(plan): rule every finding of the fa6bca2 review (U24)
systemfsoftware-maker Oct 14, 2026
5054d04
refactor(front-end): the resolver reads type parameter names through …
systemfsoftware-maker Oct 14, 2026
f701dc7
test(front-end): a missing entry source is refused by both routes (U25)
systemfsoftware-maker Oct 14, 2026
9ae4420
docs(front-end): importing the package needs oxc's native bindings (U25)
systemfsoftware-maker Oct 14, 2026
ce351e3
refactor(front-end): the printer's keyword table is indexed by the mo…
systemfsoftware-maker Oct 14, 2026
b22c2b5
test(front-end): an import whose .ts candidate is a directory reads i…
systemfsoftware-maker Oct 14, 2026
ccd067e
docs(front-end): a pattern target with two stars names no entry (U25)
systemfsoftware-maker Oct 14, 2026
3deadf9
docs(plan): rule every finding of the c25bf9a review (U25)
systemfsoftware-maker Oct 14, 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
7 changes: 7 additions & 0 deletions .changeset/old-cobras-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@systemfsoftware/api-extractor": minor
---

Add the `FrontEnd` namespace. It reads a package and emits its declarations two ways behind one emitter port: on the isolated-declarations fast path (oxc-transform), or through the TypeScript 7 compiler when a package's exports are not annotated. One syntactic builder turns those declarations into the versioned API model, and a syntactic resolver reports five api-extractor analysis messages. Both routes produce the same model apart from its producer stamp, which names the emitter that ran.

The package now installs `typescript` 7, `oxc-parser`, `oxc-transform` and `@microsoft/tsdoc` as runtime dependencies, which the front-ends use.
110 changes: 109 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ The design and its requirements live in
| Part | State |
|---|---|
| Versioned model, canonical serialiser, diff, rule corpus and verdict | in this repository |
| Front-ends (isolated-declarations fast path, compiler fallback) | next |
| Front-ends (isolated-declarations fast path, TypeScript 7 compiler fallback, model builder, analysis messages) | in this repository |
| `.api.md` report with a differential test against `@microsoft/api-extractor` 7.59.1 | planned |
| Benchmark, CLI, changeset-bump check | planned |
| systemfsoftware and stryker-js-effect packages from the flake (`.sfs-deps`) | in this repository |
Expand Down Expand Up @@ -48,6 +48,114 @@ For example, a function returning `Effect<User, NotFound>` that now returns
is minor `E_WIDENED_IN_INPUT`. Removing a tagged error from a returned error channel is
major `E_TAG_REMOVED`, because a caller's `catchTag` for that tag stops compiling.

## Front-ends

```ts
import { NodeServices } from '@effect/platform-node'
import { Effect, Layer } from 'effect'
import { ApiModel, FrontEnd } from '@systemfsoftware/api-extractor'

const emitters = Layer.mergeAll(FrontEnd.isolatedEmitterLayer, FrontEnd.compilerEmitterLayer).pipe(
Layer.provideMerge(NodeServices.layer),
)

const analysis = await Effect.runPromise(
FrontEnd.readPackage({ packageRoot: 'packages/my-lib' }).pipe(Effect.flatMap(FrontEnd.analyse), Effect.provide(emitters)),
)
process.stdout.write(ApiModel.serialise(analysis.model))
```

`readPackage` takes each export whose `@systemfsoftware/source` condition names a
`.ts`, `.tsx`, `.mts` or `.cts` file, or a hand-written `.d.ts`, `.d.mts` or `.d.cts`
file, as an entry point; `readPackage({ packageRoot,
sourceCondition })` reads another condition instead, for example `types` for a package
whose sources are not under the family's condition. A subpath pattern (`"./*"`, one
star) expands to one entry per matching source under Node's `exports` rules: an explicit
subpath wins over a pattern, and an export path belongs to the pattern key Node picks for
it, so it names no entry when that key's target has no source or is `null`. A key or a
target with two stars names no entry. The condition is read where the family writes it,
directly
under an export path, and its value is the source's path; a target that nests it under
another condition, lists targets in an array, or gives the condition anything but a path
names no entry. A package with no such export is refused with `NoSourceEntry`.
A package whose `package.json` does not decode — invalid JSON, or a manifest the schema
refuses — is refused with `ManifestRejected`, naming the package root. So is a pattern
export whose walk lists more than 1024 directories, which symlinked aliases of a
directory can reach by fanning out. The walk enters only directories the pattern's
prefix can lead to, never a symlink back to an ancestor, and never a `node_modules`
directory no export name could pass through.
A `package.json`
that is missing or cannot be read fails with the file system's `PlatformError`, as every
operation here does on an I/O fault.
`analyse` first emits declarations with `oxc-transform`'s isolated-declarations emitter,
which never type-checks. If any source an entry point reaches needs the checker (an
unannotated export, TS9xxx), or an entry cannot be proven from its own file (its source is
missing, lies outside the package, or shares a declaration path with another entry, or an
import names no file), the package takes the fallback. This package's own TypeScript 7
`tsc` runs as a process on a config that extends the package's tsconfig with a fixed emit
policy (`FrontEnd.compilerEmitPolicy`), and `route` is `Fallback` with the diagnostics that
sent it there. A `tsc` that fails refuses the package with `CompilerRejected`, whose
diagnostics come from the TypeScript 7 API. Only when the API reports no error (a write
failure, say) does the refusal carry one diagnostic with `tsc`'s exit code and printed
output, which a package's own file names and import specifiers can spell, so a consumer
treats that message as untrusted text. A `Reported` diagnostic is one the compiler
reported, carrying its `TSxxxx` code; a `Note` is a refusal reason no compiler reported, so
it carries no code. Either names a `file` only when that file is inside the package. A
refusal of an entry point names the entry's recorded source, which may lie outside the
package. The fallback runs TypeScript 7's native compiler, which the `typescript` package
installs for the host platform as an optional dependency. Installed without optional
dependencies, or on a platform TypeScript 7 does not ship for, every package that needs
the fallback is refused with `CompilerRejected`, whose one diagnostic carries `tsc`'s
message naming the missing platform package. `oxc-parser` and `oxc-transform` install
their native bindings the same way, but load them when this package is imported, so
without them the import itself fails with `Cannot find native binding`.
Both routes meet at declaration text: one syntactic builder makes the model and one
resolver reports `ae-forgotten-export`, `ae-missing-release-tag`,
`ae-internal-missing-underscore`, `ae-incompatible-release-tags` and
`ae-internal-mixed-release-tag`, so both routes give the same model and messages, apart
from the producer stamp (`producer.declarations`), which names the emitter that ran
(`FrontEnd.OXC_TRANSFORM_PRODUCER` or `FrontEnd.TYPESCRIPT_PRODUCER`).

The builder refuses a package with `UnsupportedDeclaration`, naming the file and the
construct, when its declarations contain one of these constructs, which model schema
version 2 cannot hold. The compiler fallback refuses the same way when a declaration it
emitted imports a relative path that names no declaration file inside the package. This
is the whole list:

- a destructuring declaration or parameter, a variable without a type or with an
initialiser that is not a literal, `import x = require(...)`, `export as namespace`
and `export default` of an expression
- an entry point that re-exports a name from another package (`export { it } from 'vitest'`).
The whole package is refused, not just that export.
- an export list naming a name that no declaration holds (`export { present, Missing }` with
no `Missing`), which happens only with declaration text written by hand
- `declare global`, module augmentation (`declare module 'vitest'`) and a dotted
namespace (`declare namespace A.B`)
- `export =`
- an import type that names a module without a member (`typeof import('./x.js')`)
- an enum member whose declared value is computed rather than a literal, for example a
hand-written `.d.ts` with `Write = 1 << 1`, and an enum member declared twice across
merged declarations of one enum. Both emitters write constant members as literals and
`tsc` refuses a repeated member, so these happen only with declaration text written by hand.
- an index signature without a parameter, a computed member or method name other than a
literal or a dotted name (`[Symbol.iterator]` is kept), an enum member named by neither
an identifier nor a string, a heritage clause that is not a name (`extends
mixin(Base)`), a type node the printer has no form for, and text that does not parse as
a declaration file

One construct is not refused: `export * from 'effect'`, or `export *` from any other
package, adds no items to the model. The names it re-exports are missing from the model,
and a verdict does not see them change.

The fast-path emitter was chosen from a per-phase profile on systemfsoftware's public
packages, [`docs/profiles/2026-10-12-front-end-phases.md`](docs/profiles/2026-10-12-front-end-phases.md).
To reproduce it, check out systemfsoftware at the commit the profile names, install it
with `pnpm install --frozen-lockfile --ignore-scripts`, and run:

```sh
nix develop --command pnpm --filter api-extractor-profile profile <systemfsoftware checkout> ../../docs/profiles/<date>-front-end-phases
```

## Development

The `@systemfsoftware/*` packages install from tarballs that this repository's flake
Expand Down
Loading
Loading