Skip to main content

Phase 4. ApiSurface JSON

FieldValue
MEPMEP-72 §Phases
StatusNOT STARTED
Tracking issue(pending)
Tracking PR(pending)
Commit(pending)

Gate

TestPhase4ApiSurface in package3/typescript/apisurface/phase04_test.go: subtests schema_version, parse_function, parse_class, parse_interface, parse_type_alias, parse_namespace, parse_generic, parse_promise, parse_async_iterable, parse_union, parse_intersection, parse_mapped, parse_conditional, roundtrip_corpus. The first asserts the parser rejects an unknown schema-version. The next 12 each parse a representative ApiSurface JSON fragment and assert the resulting ApiSurface Go struct contains the expected entries. The last round-trips all 24 fixture-corpus packages: ingest produces JSON, parser produces ApiSurface, re-serialiser produces JSON identical to the ingest output (byte-for-byte, modulo canonical-JSON normalisation).

Lowering decisions

The schema is versioned: "schema-version": "1" (an integer string). The parser refuses unknown versions; the ingest helper writes the version it was built against.

The schema has six top-level sections:

{
"schema-version": "1",
"package": {
"name": "zod",
"version": "3.22.4",
"module-shape": "esm",
"exports-map-resolved": "./dist/index.mjs"
},
"imports": [
{"specifier": "node:crypto", "kind": "builtin"},
{"specifier": "fast-deep-equal", "kind": "external", "version": "3.1.3"}
],
"items": [...],
"types": {...},
"skipped": [...],
"capabilities": ["net", "fs"]
}

The items array is ordered (insertion order from the export walker; matches .d.ts declaration order in practice). Each item carries {kind, name, signature, type-id, jsdoc?, deprecated?} where signature is a string formatted by the helper ((x: T) => Promise<U>) and type-id is a stable integer pointer into types.

The types map is a dictionary from type-id to a normalised representation. The map is closed: every type the helper emits resolves to one of {scalar, array, tuple, record, object, union, intersection, function, generic, promise, async-iterable, conditional, mapped, opaque, skip}. The opaque kind covers references to types declared outside the consumed package (Node built-ins, browser DOM types, etc.); these become extern type declarations on the Mochi side.

The skipped array records every export the helper could not lower, with {name, reason, location}. The phase-5 type-mapping table reads this array verbatim and propagates the SkipReports into Mochi's SkipReport mechanism.

The capabilities array records which node:* built-ins the package imports (transitively visible via the imports list); phase 9 propagates this into the lockfile and phase 17 cross-checks against the edge-runtime allowed list.

The parser uses Go's encoding/json with strict mode (d.DisallowUnknownFields()) so a forward-incompatible schema version is caught early. Unknown items in the kind discriminator produce a clear error rather than silently dropping.

Files changed

FilePurpose
package3/typescript/apisurface/apisurface.goApiSurface, Package, Import, Item, Type, Skip plus the closed type discriminator
package3/typescript/apisurface/parser.goParse([]byte) (*ApiSurface, error)
package3/typescript/apisurface/serialiser.goSerialise(*ApiSurface) ([]byte, error) (canonical-JSON for lockfile-cache key stability)
package3/typescript/apisurface/phase04_test.goTestPhase4ApiSurface sentinel
package3/typescript/apisurface/testdata/*.jsonper-fixture canonical ApiSurface JSON (committed as golden)

Test set

  • TestPhase4ApiSurface/schema_version
  • TestPhase4ApiSurface/parse_function
  • TestPhase4ApiSurface/parse_class
  • TestPhase4ApiSurface/parse_interface
  • TestPhase4ApiSurface/parse_type_alias
  • TestPhase4ApiSurface/parse_namespace
  • TestPhase4ApiSurface/parse_generic
  • TestPhase4ApiSurface/parse_promise
  • TestPhase4ApiSurface/parse_async_iterable
  • TestPhase4ApiSurface/parse_union
  • TestPhase4ApiSurface/parse_intersection
  • TestPhase4ApiSurface/parse_mapped
  • TestPhase4ApiSurface/parse_conditional
  • TestPhase4ApiSurface/roundtrip_corpus

Cross-references