Skip to content

v0.2.14

Choose a tag to compare

@github-actions github-actions released this 01 Sep 15:37
· 66 commits to main since this release

Fold the exported VERSION with the package version

Collapsing 0.2.17 back onto 0.2.14 moved package.json and left src/index.ts
exporting the version that no longer exists. The export-map test exists for
exactly this and caught it.

Release 0.2.14

0.2.14 was never published — npm stops at the version before it — so the work
that followed folds into it rather than incrementing past it. The tags for
0.2.15,0.2.16,0.2.17 existed with no npm release and no GitHub release behind them.

Turn on noUnusedLocals/noUnusedParameters, and say what is checked

tsconfig named tests in include AND in exclude, and exclude wins, so
nothing under tests/ was ever typechecked while the config claimed otherwise.
include now says src.

Behind that exclusion are 220 type errors across 39 files, and — as in helix —
test-side declare module augmentations that leak into src when the two share
one program. Checking them needs a separate tsconfig and its own pass.

The StaticMiddleware test added earlier today is fixed here rather than left
for that pass: its fake StreamBackend was missing two members and its
RawRequest/RouteInfo literals did not match the real shapes.

Key the container on the token, stream static files, generate the MIME table

Container: Fold's stores are Map<string | symbol | AbstractConstructor, …>, so
a token is its own key. Deriving a string key instead cost what identity buys:
Symbol.for('cache') became "Symbol(cache)" and collided with that plain string,
a unique Symbol() had to be refused outright, and two @service classes sharing
a name shared a singleton cache entry.

StaticMiddleware: readFileSync on the event loop, with .mp4/.zip/.pdf among the
allowed extensions — one large asset was every other request's latency, and
each request allocated the file again. Streamed through response.stream(), and
opened with O_NOFOLLOW so the last component cannot be swapped for a symlink
between the realpath check and the read. Its ETag is now quoted and weak
(RFC 9110 §8.8.3) and If-None-Match is parsed as a list compared weakly (§13.1.2).

mime.ts: the hand-written table knew the extensions someone thought of, so
.yaml, .sql, .opus and .xhtml fell through and went out as the content type
verbatim. Generated from mime-db instead, with a test proving all 1239
extensions resolve as mime-types resolves them. The +json charset rule was
invented: mime-db gives fhir+json a charset and ld+json none.

MultipartFile.move: overwrite:false checked existence and then wrote, so two
concurrent moves both succeeded and the second replaced the first. O_EXCL now
decides. Upstream's message is kept word for word.

SseStream: a keepalive write that rejected was an unhandled rejection on a
detached timer — one dead socket ending every other client's stream.

Release 0.2.17

Branch on the second signal instead of returning after exit

A return after process.exit is dead in production and only mattered
because the suite replaces process.exit so it does not kill the runner — code
shaped by its test, with a line that reads as a mistake to anyone who has not
read the comment explaining why it is not one.

The two paths are mutually exclusive, so they are a branch. Nothing falls
through under a stubbed exit either, and there is no dead line to explain.

Release 0.2.16

Compute entity tags and content types here

Two dependencies for work the ecosystem already did. etag is fifteen lines
of hashing, and @c9up/archive has kept its own MIME table all along — so the
framework was pulling a package to do what a sibling already does by hand.

Both formats are reproduced exactly, and that is the whole constraint. An ETag
is a cache key: a different shape invalidates every cached response in flight
the day it ships. contentType keeps its charset rules, keeps a charset the
caller set, and still answers false for what it cannot resolve so the
response falls back to the raw input rather than writing content-type: false.

Checked both against the packages they replace before removing them — byte for
byte on the ETags, and identical on thirty-seven content types, the one
difference being ico, where the IANA spelling won.

Release 0.2.15

Key a class token by the class, not by its name

token.name made two different classes called Service one binding — the
second registration silently replaced the first — and made the class Service
indistinguishable from the string "Service". AdonisJS keys on the
constructor, so a class, a string and a symbol are three tokens even when they
read alike.

A weak map assigns each class a key that carries its name, for error messages
and for inspect, and a counter that makes two same-named classes distinct.
The map is shared across containers, because per-container numbering would give
one class two keys in a parent and its child, and the child would never find
what the parent bound.

Checked the ecosystem first for the migration this could force — a binding
registered by class and resolved by the string of its name. There is none, and
the twelve packages that consume the container all pass unchanged.

Keep the escalation return, which a test relies on

process.exit does not return in production, so the line after it is dead
there — but the suite replaces process.exit so it does not kill the runner,
and without the return a second signal would fall through and start a second
shutdown.

Keep MultipartFile.type as upstream has it, and narrow beside it

Closing type to a union was a deviation the rule does not allow: nothing
about NAPI forced it — the filtering happens in TypeScript, after #mimeParts —
and the protection can be had without touching the contract. It also broke a
public type in a patch, so valid AdonisJS code stopped compiling and
x-foo/bar came back as undefined where upstream hands back the segment.

type is string | undefined again, raw segment and all. registeredType is
the narrowed member: against a closed union === 'image/png' is a compile
error instead of a comparison that always fails. The two disagree only where
the type is outside the registered set, and nowhere else.

Container introspection followed the wrong order. resolve() follows an alias
before it looks at a binding, but the listing kept the binding and skipped the
alias — so a token with both was reported as transient while resolving it
handed back the alias target. Inspect showed a definition nobody gets. The
alias wins now, names what it points at, and says which binding it shadows.

Name the MultipartFile.type deviation

AdonisJS types it type?: string. Closing it to the registered top-level set
is deliberate — a string is what let file.type === 'image/png' compile — and
it costs one thing worth stating rather than leaving for a reader to discover:
a malformed top-level type reports undefined here where upstream hands back the
raw segment. mime still carries it.

Release 0.2.14

List what the container holds, and close MultipartFile.type

inspect --help promised a section for services and rendered none, because
nothing could enumerate them — has() answers about one token you already know
the name of, which is no help when the question is what is in there. The
container lists its bindings now, with the kind beside each. Not every
@Inject()-decorated class: one is discovered when it is resolved and nothing
registers it before that, so no complete list of those exists. The help says
bindings, which is what it shows.

MultipartFile.type is a closed union of the top-level types IANA registers, so
file.type === 'image/png' is a compile error instead of a comparison that
always fails — the exact trap its own comment used to warn about, now refused
by the compiler. The runtime agrees with the type rather than asserting past
it: a top-level type outside the set reports undefined, and mime still
carries whatever was read, so nothing is lost.


Changes since v0.2.13.