You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
@btravstack/testing's tapped is the last compile-time gate in this repo that
rides a conditional rest tuple — the shape #66 removed from start precisely
because an arity error never prints a type. #82 documented it honestly rather
than fixing it; this is the fix.
Tapping a port the module does not export (measured, scratch file in packages/testing/src, npx tsc -p tsconfig.json --noEmit):
error TS2554: Expected 4 arguments, but got 2.
That is the whole message. Neither NOT EXPORTED nor the offending port
reaches the reader. Both are in the parameter's type and answer one slot at a
time, which is the technique /reference/di/entry-points#the-gate teaches:
error TS2345: Argument of type '0' is not assignable to parameter of type '"NOT EXPORTED"'.
error TS2345: Argument of type 'number' is not assignable to parameter of type 'Secret'.
Secret is the port — reachable in two hand-spelled steps, printed in none.
The fix, and the one thing that has to be measured
start moved its marker onto the module parameter and the sentence started
printing, because a parameter type is something TypeScript prints and an arity
count is not. The same move applies here, with one open question: which
parameter carries it.
ports reads best — the mistake is the ports array, and a sentence like "NOT EXPORTED — the module exports no port named Secret" lands on the
argument that is wrong.
module is the closer analogue to start, and the safer inference bet.
The risk is stated in packages/core/src/start.ts:139: a conditional type in an
inference-bearing position can defer that parameter's inference and collapse a
type parameter to unknown. For start it was measured not to — X still
infers from the Module<X, …> half of the intersection. Here P is const P extends readonly AnyPort[], inferred from ports, and a const type
parameter under an intersection is not obviously the same case. Measure both
placements before choosing; do not reason it out.
Whether the port's name can reach the sentence is a second measurement. @btravstack/http's UNDECLARED KEY gets its key in via a template literal
because its mapped type is keyed by K (#82, ac12553). tapped has an Exclude<...> of port instances rather than a key, so the same trick may not
be available — if it is not, "NOT EXPORTED — this module exports no such port"
printed in full is still strictly better than an arity count.
What moves with it
packages/testing/src/tapped.ts — the gate, and its TSDoc, which currently
says the gate "refuses a port module does not export, at this call site".
docs/reference/testing.md — the #the-tap-gate-an-arity-error section, in
full: its anchor is its claim, so the anchor changes too, and every inbound
link with it (docs/how-to/test-an-application.md is one).
docs/explanation/compile-time-wiring.md — "Three gate mechanisms live in
this repo that a composing application meets… A fourth lives in the test
harness". If tapped stops being a rest tuple, the fourth stops being an
arity gate and that whole passage is restated.
A changeset. @btravstack/testing only; the nine move as one anyway.
Not in scope
di's own gate on Module.scoped / build / forkScope stays a rest tuple, and
that is a decision, not an oversight — docs/explanation/compile-time-wiring.md
argues it: the arity form points the blame at the call rather than deep inside a
generic instantiation, and di's missing: N is a set of ports a reader can
read off the signature, where the kernel's is one of three fixed sentences. tapped's is a single sentence plus one port, which is the kernel's shape, not
di's.
Found by the whole-branch review on #82, which caught that #66's sweep had
stopped at three mechanisms when there were four.
@btravstack/testing'stappedis the last compile-time gate in this repo thatrides a conditional rest tuple — the shape #66 removed from
startpreciselybecause an arity error never prints a type. #82 documented it honestly rather
than fixing it; this is the fix.
What it prints today
packages/testing/src/tapped.ts:34-36:Tapping a port the module does not export (measured, scratch file in
packages/testing/src,npx tsc -p tsconfig.json --noEmit):That is the whole message. Neither
NOT EXPORTEDnor the offending portreaches the reader. Both are in the parameter's type and answer one slot at a
time, which is the technique
/reference/di/entry-points#the-gateteaches:Secretis the port — reachable in two hand-spelled steps, printed in none.The fix, and the one thing that has to be measured
startmoved its marker onto themoduleparameter and the sentence startedprinting, because a parameter type is something TypeScript prints and an arity
count is not. The same move applies here, with one open question: which
parameter carries it.
portsreads best — the mistake is the ports array, and a sentence like"NOT EXPORTED — the module exports no port named Secret"lands on theargument that is wrong.
moduleis the closer analogue tostart, and the safer inference bet.The risk is stated in
packages/core/src/start.ts:139: a conditional type in aninference-bearing position can defer that parameter's inference and collapse a
type parameter to
unknown. Forstartit was measured not to —Xstillinfers from the
Module<X, …>half of the intersection. HerePisconst P extends readonly AnyPort[], inferred fromports, and aconsttypeparameter under an intersection is not obviously the same case. Measure both
placements before choosing; do not reason it out.
Whether the port's name can reach the sentence is a second measurement.
@btravstack/http'sUNDECLARED KEYgets its key in via a template literalbecause its mapped type is keyed by
K(#82,ac12553).tappedhas anExclude<...>of port instances rather than a key, so the same trick may notbe available — if it is not,
"NOT EXPORTED — this module exports no such port"printed in full is still strictly better than an arity count.
What moves with it
packages/testing/src/tapped.ts— the gate, and its TSDoc, which currentlysays the gate "refuses a port
moduledoes not export, at this call site".docs/reference/testing.md— the#the-tap-gate-an-arity-errorsection, infull: its anchor is its claim, so the anchor changes too, and every inbound
link with it (
docs/how-to/test-an-application.mdis one).docs/explanation/compile-time-wiring.md— "Three gate mechanisms live inthis repo that a composing application meets… A fourth lives in the test
harness". If
tappedstops being a rest tuple, the fourth stops being anarity gate and that whole passage is restated.
*.test-d.tspinning the new arm.packages/testinghas no type tests andno
tsconfig.test-d.jsontoday —packages/temporalwas in exactly thisposition before Keyed dependency records for Provider — positional deps are the highest-frequency DX wart #65 and gained both, so there is a worked precedent.
@btravstack/testingonly; the nine move as one anyway.Not in scope
di's own gate on
Module.scoped/build/forkScopestays a rest tuple, andthat is a decision, not an oversight —
docs/explanation/compile-time-wiring.mdargues it: the arity form points the blame at the call rather than deep inside a
generic instantiation, and di's
missing: Nis a set of ports a reader canread off the signature, where the kernel's is one of three fixed sentences.
tapped's is a single sentence plus one port, which is the kernel's shape, notdi's.
Found by the whole-branch review on #82, which caught that #66's sweep had
stopped at three mechanisms when there were four.