Skip to content

RFC 10 follow-up: expose declaration metadata to invocation hooks #3058

Closed as not planned

Description

@birkskyum

Problem

The server-function metadata channel is available to client transport policy through prepareRequest(init, { id, meta }). The server-side wrapInvocation context currently exposes id, args, event, request, and direct, but not declaration metadata.

This leaves external middleware and policy packages without a stable way to select per-function behavior. They must either repeat policy inside every function body or key global policy by generated function ids. The registered HTTP implementation is the raw function, so getServerFunctionMetadata(getServerFunction(id)) does not recover metadata attached to the server reference.

Proposed scope

Make declaration metadata available during server invocation, for both HTTP dispatch and direct SSR calls. For example:

wrapInvocation(run, {
  id,
  meta,
  args,
  event,
  request,
  direct
});

This may require server-side metadata bookkeeping keyed by function id. The metadata should remain local declaration data and should not be serialized over the wire.

Why core

This does not ask core to implement middleware chains, authentication, or validation. It completes the mechanism that lets external packages build those policies while preserving direct-call and HTTP-dispatch parity.

Related specification: RFC 10.

Activity

  1. ryansolid commented on Aug 25, 2026

    @ryansolid
    Member

    Implemented by #3059 — wrapInvocation now receives declaration meta on both HTTP dispatch and direct SSR calls, without serializing it over the wire. On next for the next rc.

  2. ryansolid commented on Aug 25, 2026

    @ryansolid
    Member

    Reopened: #3059 was merged without proper design review and has been reverted (2c90ae6). See the discussion there — the direction we'd take is registration-scoped metadata (declared where dispatch structurally finds it) rather than harvesting call-path reference decorations into an id-keyed map.

  3. birkskyum commented on Aug 26, 2026

    @birkskyum
    MemberAuthor

    Following up with a concrete consumer after the #3059 revert: TanStack Start does not necessarily require a new registration-metadata API.

    A TanStack-style layer can keep its fluent semantics:

    createServerFn()
      .middleware([requireUser])
      .handler(handler);

    Its compiler can lower the server side conceptually to a composed implementation registered with Solid:

    const implementation = applyMiddleware(handler, [requireUser]);
    registerServerReference(id, implementation, name);

    HTTP dispatch and direct SSR would then execute the same wrapped function. The client side can target Solid's createServerReference while retaining TanStack's ergonomic API.

    That keeps the boundary clear:

    • Solid owns function identity, registration, transport and dispatch.
    • TanStack owns middleware, validation and typed context.
    • Solid does not recognize createServerFn or middleware declarations.
    • withMeta remains call-path transport metadata.

    Keeping a small semantic compiler is not the same as keeping a separate protocol. TanStack can compile its higher-level API to Solid's runtime ABI without requiring Solid to understand those semantics.

    This narrows the remaining question for #3058: is there an independent consumer that needs declarative registration metadata to be inspected centrally through wrapInvocation, rather than composing policy into the raw registered implementation? The TanStack procedure example alone does not establish that requirement.

    The useful next step is therefore an adapter prototype targeting Solid's registration ABI. If it exposes something that cannot be expressed by registering the composed implementation, that concrete failure can define the smaller core mechanism.

  4. ryansolid commented on Aug 28, 2026

    @ryansolid
    Member

    Agreed with your narrowing, and closing on it — with the reopen condition explicit.

    The standing rule ("mechanism waits for a concrete consumer") is doing the work here, and it has a good track record this cycle: invoke (#3057) landed cleanly because it waited for the data-layer cancellation case to concretize, and just this week we cut speculative invocation-channel forwarding from the router primitives for the mirror-image reason — semantics with no consumer are load-bearing the day they ship and revisable never. Registration-scoped metadata is in the same spot: the #3059 revert already sketched the shape that survives the design objection (attached at registerServerReference time, where dispatch structurally can't miss it — making getServerFunctionMetadata(getServerFunction(id)) just work, with wrapInvocation's context unchanged), but the shape without a consumer is exactly what we don't ship.

    Your TanStack lowering is the sanctioned answer, and it's worth stating that it isn't a workaround: registering the composed implementation IS the model. Export-value registration exists precisely so policy composes into the thing dispatch executes, identically on HTTP and in-process SSR. A layer with its own compiler targeting registerServerReference(id, applyMiddleware(handler, mw)) is using the ABI as designed — Solid owns identity/transport/dispatch, the layer owns its semantics, and core recognizes nothing.

    For the adapter prototype, here's what a genuine composition-inexpressible would look like — the two places a composed implementation is structurally too late or too opaque:

    • Pre-decode policy. The composed function runs after argument deserialization. Per-function body-size caps, content-type gates, or rate limits that must reject before parsing the payload need dispatch-visible metadata — the handler has to read policy off the id before it touches the body.
    • Central introspection. Enumerating registered functions with their policy (manifests, generated docs, route tables). Composition hides policy inside closures; only registration-scoped data can be walked.

    If the prototype (or any real consumer) hits either of those — or a third we haven't named — that concrete failure reopens this and defines the mechanism, and most of its design is already written down in the #3059 revert comment. Until then the registered implementation is the composition point, and withMeta stays what it is: call-path transport metadata for the paths that actually go through the reference.

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