Repository navigation
v0.1.18
Write down that roles are ANY and permissions are ALL
Reformat what the strictness pass reflowed
Two files-worth of blank lines and one long call the formatter wraps
differently now that a helper sits above them. CI resolves biome from a
caret range and installs a newer one than the lockfile pins.
Turn on noUncheckedIndexedAccess
It was not missing here — it was explicitly false, in sixteen of the
seventeen tsconfigs. eon alone had it on, which is why nobody had seen
what it finds.
It stays a named deviation from upstream: @adonisjs/tsconfig sets
strictNullChecks and noImplicitAny but not this one. We keep it because
turning it on is what caught an as asserting a possibly-absent regex
group was a known value — the exact shape the flag exists to find. Doing
better than upstream is kept and written down, not reverted to parity.
Every site is restated rather than silenced: no !, no cast, no ?? 0
standing in for a branch that cannot happen. A reversed copy read by
value where an index walked a callback list backwards, the winner of a
scan kept as the value it found rather than its position, destructuring
where a length check was doing the proving, and an explicit break where a
loop condition already bounds the read.
Say what container.make() returns for the tokens this package binds
ream declares ContainerBindings open on purpose: it registers its own
entries and expects each package to contribute the ones it owns — its
comment on the interface names auth (warden), logger (spectrum) and db
(atlas) as exactly this. None of them did, and every other package that
binds a string token was in the same state, so container.make('cache'),
make('mail'), make('hash') and the rest all answered unknown and
every call site had to assert a type it could not prove.
Loaded from the barrel AND from the provider, the second of which is where
AdonisJS puts its own (providers/redis_provider.ts carries the
declare module for redis, database_provider.ts for lucid.db).
Verified live rather than assumed: a declare module naming a specifier
that does not resolve is silently inert, so renaming the member has to
break the compile. It does.
Take the transport key in the shape upstream gives it
transport is { driver, channel? }, and null for no bus — the shape
@boringnode/transmit declares (TransmitConfig.transport). The driver
on its own, with transportChannel beside it, is the flat form this
config had before and still resolves; the block's channel wins when both
are written, since it is the one attached to the transport it configures.
Also removes a branch that could not fire: #checkDispatchAuth read
#guards.guard, which useGuards never writes — it folds the singular
into guards — so the normalisation had two homes and one of them was
dead. And writes down why roles are any-of while permissions are all-of:
it is the rule the HTTP middleware, the RPC router and the GraphQL guard
all apply, so a hub agrees with the route next to it.
Release 0.1.18
Stop trusting a token, a body, or a prototype level
Seven defects, most of them on the half of a connection's life nothing
tested: after the client is gone, or when the request is not the one that
opened it.
An authorizer that awaits — a database lookup is the usual one — could
resolve after its socket closed. Writing the subscription then put the uid
back into the channel index for a client that no longer existed, and nothing
could take it out again: dropping a client walks that client's own channel
set. The entry stayed for the life of the process, counted, returned and
walked on every broadcast; on a reconnect it attached the old subscription
to the new connection, which had never asked for that channel.
subscribe and unsubscribe took their uid and channel off a JSON body and
believed the signature. A channel that arrived as an object reached the
pattern matcher and threw channel.split is not a function out of the
route: a 500 and a stack trace for a body anyone can post. Both arguments
are unknown now, and the relay decides.
SignalR negotiate is an unauthenticated POST that wrote a token entry, and
only a stream closing ever removed one. A caller that never opened a stream
left it forever. Tokens now expire unclaimed and are capped, the way
SignalR's own server disposes a connection whose transport never attaches.
Holding a connectionToken was enough to invoke as the connection's identity
— and it travels in a query string, so it reaches access logs and proxies.
Both hub routes now apply the ownership rule the relay already applies to
its own uid. A stale stream's close no longer tears down the connection
that replaced it, and re-registering a client id no longer hands the new
connection the groups the old one had joined.
The handler allowlist read one prototype level, so a hub extending another
hub answered its inherited handlers with E_RELAY_UNKNOWN_EVENT. It walks
the chain down to Hub.prototype now, which is where the allowlist's actual
purpose starts.
Parity with @adonisjs/transmit 3.0.2 and @boringnode/transmit 0.4.0, read
from the tarballs: pingInterval was missing entirely, and an idle SSE stream
is one nginx closes at sixty seconds. An absent payload is carried as null,
as upstream does, so it does not mean three different things depending on
where the listener runs. allowUnauthorizedChannels is a named deviation —
upstream allows a channel no authorizer covers; we refuse it — and the
comment claiming it was upstream's default is corrected.
Also: an invocation carrying an invocationId is always completed, a failed
handshake closes the connection, a Close from the client closes the stream,
one client's throwing transport no longer silences the rest of its group,
and the README no longer says hubs have no server transport when they do.
Three rejections that escaped, one of them out of the constructor
Promise.resolve(transport.subscribe(...)) calls subscribe FIRST, so a
transport that threw synchronously — a bad URL, a client built without a
connection — came straight out of new Relay(...) and never reached the catch
the comment promised. Called inside the async function now, as quasar's
dispatch already is.
A connected frame that fails leaves the client without the uid it needs to
subscribe or unsubscribe, so it is dropped rather than kept as a silent tenant
of the client map; unhandled, that rejection also ended the process. The
SignalR hub send had the same shape.
Both are pinned by tests that fail against the previous source.
Changes since v0.1.17.