Skip to content

Pre-1.0 public-API decisions from review (router.use, useAction types, buildEnv, plugin name, ...) #569

Closed

Description

@ivogt

Summary

Pre-1.0 public-API decisions surfaced by the multi-agent review. Each is a product/owner call (signature, naming, or surface change), so they were deferred from the stabilization PR. Userland before/after below per the PR-description guide. Best resolved before first stable external adoption (CLAUDE.md API Hygiene).

1. router.use() — Middleware alias + pattern-param overload

Rango.use() is typed with MiddlewareFn (the curated re-export surfaces the friendlier Middleware alias by name), and .use('/users/:id', mw) has no pattern-generic overload, so ctx.params is untyped.

// today: ctx.params is Record<string,string> (untyped keys)
router.use("/users/:id", (ctx) => { ctx.params.id /* string, but not known-present */ })
// proposed: pattern-generic overload -> ctx.params.id known

Decision: add the typed pattern overload + rename to Middleware in the public signature, or document the gap as intentional.

2. useAction() return type is @internal

useAction()'s return type (TrackedActionState/ActionLifecycleState) carries @internal — may change without notice, yet it is the public hook's return type — consumers can't name it.

const action = useAction(addToCart);
//    ^? type is reachable only as an @internal alias — can't be written in a consumer signature

Decision: promote/alias a stable public return type from ./client.

3. useRouter().prefetch() can't opt into source-scoped prefetch

<Link prefetchKey=":source"> exists, but the imperative router.prefetch(url) has no options arg.

router.prefetch("/product/1");                 // today
router.prefetch("/product/1", { key: ":source" }); // proposed parity with <Link>

4. buildEnv: "auto" permitted on the Node preset at the type level

BuildEnvOption allows "auto" on RangoNodeOptions, but "auto" (wrangler getPlatformProxy()) is Cloudflare-only; misuse is caught only at build time. Decision: move buildEnv onto per-preset interfaces (CF with "auto", Node without).

5. Plugin name vite-plugin-poke breaks the @rangojs/router:* convention

The public poke() plugin's name field is vite-plugin-poke; consumers keying on plugin name for ordering/filtering won't match the @rangojs/router:* family. Decision: rename before 1.0.

6. ./build entrypoint re-exports pipeline internals

./build exposes realm-crossing internals (TrieNode/TrieLeaf, etc.) indistinguishable from intended public API. Decision: move internals to an internal subpath or @internal-tag them. (generateManifestCode is documented public API — keep it.)

7. createScanFilter/ScanFilter is half-wired

DiscoveryState.scanFilter is permanently undefined and createScanFilter is never invoked by the Vite plugin, while the public createScanFilter/ScanFilter/findTsFiles API IS used by the CLI/build layer. Decision: complete the feature (wire it into discovery) or trim the internal seam.

8. mergeHandlerUse skips mount-site allow-list validation

The explicit-only / merged-explicit branches return explicitUse() output unvalidated, so a structurally-valid-but-prohibited item (e.g. middleware() inside a parallel slot) can slip through.

parallel({ "@modal": () => [middleware(auth)] }) // prohibited, but not currently rejected on the explicit path

Decision: validate there too (newly throws; breaks a referential-identity test that must be updated) or document the asymmetry.

9. profile-registry is mostly dead after removing cache("profileName")

With the build-breaking cache("profileName") overload removed (shipped), the global _profiles registry / getCacheProfile() reader is largely orphaned (still used by "use cache: <profile>" resolution + setCacheProfiles() from router.ts). Decision: audit whether the global registry is still needed or can be trimmed.

10. return redirect() from a server action forces as any

The package's own example actions cast return redirect() to any. Ergonomics gap in the action return type.

"use server";
export async function checkout() { return redirect("/done") as any; } // the `as any` shouldn't be needed

Activity

  1. reopened this on Jun 14, 2026
  2. ivogt commented on Jun 14, 2026

    @ivogt
    CollaboratorAuthor

    All 10 decisions resolved (shipped in #573 + #575). Resolution map:

    # Decision Outcome PR
    1 router.use(pattern, mw) typing pattern-generic overload via ExtractParams (bare form unchanged) #573
    2 useAction() return type exported stable ActionState/ActionLifecycleState; TrackedActionState stays internal #573
    3 imperative prefetch scope prefetch(url, { key: ":source" }) (parity with <Link>) #573
    4 buildEnv: "auto" moved to per-preset: CF allows "auto", Node rejects at type level #573
    5 poke plugin name renamed vite-plugin-poke → @rangojs/router:poke #573
    6 ./build internals dropped buildRouteTrie/buildPerRouterTrie/collectFallbackClientRefs/TrieNode/TrieLeaf from the barrel; discovery imports them via direct source path (realm-safe). Pinned by public-surface.test.ts #575
    7 scanFilter completed: public discovery: { include, exclude } compiled into state.scanFilter #573
    8 mergeHandlerUse validation gap documented the explicit-branch asymmetry (no behavior change) #573
    9 profile registry trimmed dead _profiles/setCacheProfiles/getCacheProfile; kept request-scoped resolveCacheProfiles #575
    10 return redirect() in actions prefer throw redirect(); return-form test actions throw; skill guidance strengthened #575

    Closing as completed.

  3. reopened this on Jun 14, 2026
  4. ivogt commented on Jun 14, 2026

    @ivogt
    CollaboratorAuthor

    Re-closing: all 10 decisions verified complete on main — see the resolution map above (#573 + #575). The earlier close was deliberate, not accidental.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions