@solidjs/web 2.0.0-beta.20 → 2.0.0-beta.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,24 @@ import { ResponseEnvelope } from "../response.cjs";
2
2
  import { JSONCodecOptions } from "../serializer.cjs";
3
3
  import { RequestEvent } from "../server.cjs";
4
4
 
5
- export { FUNCTION_HEADER, INSTANCE_HEADER, decodeResponse } from "./shared.cjs";
5
+ export {
6
+ FUNCTION_HEADER,
7
+ INSTANCE_HEADER,
8
+ SINGLE_FLIGHT_HEADER,
9
+ decodeResponse,
10
+ getServerFunctionMetadata,
11
+ isServerFunction,
12
+ subscribeFlightData,
13
+ withMeta
14
+ } from "./shared.cjs";
15
+ export type {
16
+ FlightDataConsumer,
17
+ FlightDataContext,
18
+ ServerFunction,
19
+ ServerFunctionMetadata,
20
+ SingleFlightPayload
21
+ } from "./shared.cjs";
22
+ import { ServerFunction } from "./shared.cjs";
6
23
 
7
24
  /**
8
25
  * The request event a server function call runs under: the base
@@ -13,6 +30,58 @@ export interface ServerFunctionEvent extends RequestEvent {
13
30
  serverOnly?: boolean;
14
31
  }
15
32
 
33
+ /**
34
+ * What a server function call resolved to, as seen by the single-flight
35
+ * hook — enough context for any data-production strategy without core
36
+ * assuming one.
37
+ */
38
+ export interface ServerFunctionOutcome {
39
+ /** The build-stable id of the function that ran. */
40
+ id: string;
41
+ /**
42
+ * The value the caller will receive: the raw return for plain results,
43
+ * the unwrapped `value` for `ResponseEnvelope`s, `null` for body-less
44
+ * control-flow `Response`s (redirect/reload).
45
+ */
46
+ value: unknown;
47
+ /**
48
+ * The `Response` carrying the result's HTTP metadata, when there is one
49
+ * (from a returned/thrown `Response` or a `ResponseEnvelope`). Read
50
+ * `Location` here for redirect-with-data — the data should describe the
51
+ * destination route — and `X-Revalidate` for the invalidated keys.
52
+ * Undefined for plain values.
53
+ */
54
+ response: Response | undefined;
55
+ /**
56
+ * The original HTTP request, untouched: headers the client integration
57
+ * sent (referrer, custom route context) ride here for the hook to read —
58
+ * core assigns them no meaning.
59
+ */
60
+ request: Request;
61
+ /** Whether the result was thrown rather than returned. */
62
+ thrown: boolean;
63
+ }
64
+
65
+ /**
66
+ * The single-flight server hook: given the request event and the function's
67
+ * outcome, optionally produce a data payload (possibly async) to fold into
68
+ * the response alongside the return value. Data production is a black box
69
+ * to the protocol — render data-only, run route preloads, query a cache,
70
+ * whatever the integration chooses; the payload just has to be
71
+ * codec-serializable. Return undefined to send the response unchanged
72
+ * (byte-identical to a call without the hook).
73
+ *
74
+ * Runs after `transformResult`, only for scripted calls that sent
75
+ * `SINGLE_FLIGHT_HEADER` on the request, on returned results and thrown
76
+ * `Response`/`ResponseEnvelope` control-flow signals alike (plain thrown
77
+ * errors never collect). The handler owns the enveloping: contributed data
78
+ * ships as `{ value, data }` under the single-flight response header.
79
+ */
80
+ export type CollectFlightDataHook = (
81
+ event: ServerFunctionEvent,
82
+ outcome: ServerFunctionOutcome
83
+ ) => unknown | Promise<unknown>;
84
+
16
85
  /** Options for `configureServerFunctionsServer`. */
17
86
  export interface ServerFunctionsServerConfig {
18
87
  /**
@@ -23,6 +92,13 @@ export interface ServerFunctionsServerConfig {
23
92
  * an established request scope parks on the global.
24
93
  */
25
94
  provideEvent?: <T>(event: ServerFunctionEvent, fn: () => T) => T;
95
+ /**
96
+ * The single-flight hook: produces the data payload folded into
97
+ * responses of calls that opted in (see `CollectFlightDataHook`).
98
+ * Registered once by the integration that owns data production (a
99
+ * router); per-handler `collectFlightData` options override it.
100
+ */
101
+ collectFlightData?: CollectFlightDataHook;
26
102
  /**
27
103
  * Endpoint the HTTP handler is mounted on, used for the `url` of SSR'd
28
104
  * references (e.g. form actions) — must match the client configuration.
@@ -42,7 +118,8 @@ export interface ServerFunctionsServerConfig {
42
118
  /**
43
119
  * Configures the server runtime. Call once at server startup, before
44
120
  * handling requests. Only needed when deviating from the defaults (custom
45
- * endpoint, codec plugins, or an explicit event provider).
121
+ * endpoint, codec plugins, an explicit event provider, or a single-flight
122
+ * hook).
46
123
  */
47
124
  export function configureServerFunctionsServer(config?: ServerFunctionsServerConfig): void;
48
125
 
@@ -57,6 +134,12 @@ export function configureServerFunctionsServer(config?: ServerFunctionsServerCon
57
134
  export interface ServerFunctionReference<T extends any[] = any[], R = any> {
58
135
  id: string;
59
136
  fn: (...args: T) => R;
137
+ /**
138
+ * The function's source name, emitted by development builds only —
139
+ * `createServerReference` seeds the metadata channel with it.
140
+ * @internal
141
+ */
142
+ name?: string;
60
143
  }
61
144
 
62
145
  /**
@@ -82,12 +165,15 @@ export function getServerFunction<T extends any[], R>(id: string): (...args: T)
82
165
  * Compiler ABI — emitted by compiled `"use server"` server output for
83
166
  * every server function: registers `fn` for HTTP dispatch under its
84
167
  * build-stable id and returns the reference the server-side
85
- * `createServerReference` consumes. Not meant for hand-written code.
168
+ * `createServerReference` consumes. Development builds pass the function's
169
+ * source name as the trailing argument (dev-only metadata; never emitted in
170
+ * production). Not meant for hand-written code.
86
171
  * @internal
87
172
  */
88
173
  export function registerServerReference<T extends any[], R>(
89
174
  id: string,
90
- fn: (...args: T) => R
175
+ fn: (...args: T) => R,
176
+ name?: string
91
177
  ): ServerFunctionReference<T, R>;
92
178
 
93
179
  /**
@@ -102,6 +188,29 @@ export function createServerReference<T extends any[], R>(
102
188
  reference: ServerFunctionReference<T, R>
103
189
  ): (...args: T) => R;
104
190
 
191
+ /**
192
+ * Declares a server function callable over HTTP GET. The server half is
193
+ * identity-flavored — SSR calls stay in-process — but it brands the
194
+ * declaration on the reference's metadata channel
195
+ * (`getServerFunctionMetadata(fn)?.method === "GET"`) and records the
196
+ * declared method for the function's id so `handleServerFunctionRequest`
197
+ * enforces it: GET-declared functions accept GET requests (and only GET),
198
+ * everything else answers 405.
199
+ *
200
+ * Wrap the reference at its declaration; the compiler round-trips the call
201
+ * in both builds:
202
+ *
203
+ * ```ts
204
+ * export const getUser = GET(async (id: string) => {
205
+ * "use server";
206
+ * return db.users.find(id);
207
+ * });
208
+ * ```
209
+ */
210
+ export function GET<A extends readonly any[], R>(
211
+ fn: (...args: A) => R
212
+ ): ServerFunction<A, Awaited<R>>;
213
+
105
214
  /** Identity of the currently executing server function. */
106
215
  export interface ServerFunctionMeta {
107
216
  id: string;
@@ -133,17 +242,26 @@ export interface HandleServerFunctionOptions {
133
242
  provideEvent?<T>(event: ServerFunctionEvent, fn: () => T): T;
134
243
  /**
135
244
  * Observes or replaces the function's result before encoding — the
136
- * extension point for policies like single-flight payloads. Runs for
137
- * returned and thrown results alike (`context.thrown` distinguishes);
138
- * `context.instance` is null for no-JS calls. Return the result
139
- * unchanged to pass through, or a `ResponseEnvelope` (exposed through
140
- * the core entry) to send HTTP metadata plus a structured payload.
245
+ * extension point for response metadata policies (headers, statuses,
246
+ * substituted results). Runs for returned and thrown results alike
247
+ * (`context.thrown` distinguishes); `context.instance` is null for no-JS
248
+ * calls. Return the result unchanged to pass through, or a
249
+ * `ResponseEnvelope` (exposed through the core entry) to send HTTP
250
+ * metadata plus a structured payload. Runs before `collectFlightData`,
251
+ * so the flight hook sees the transformed outcome — use
252
+ * `collectFlightData`, not this, to fold data into the response.
141
253
  */
142
254
  transformResult?(
143
255
  event: ServerFunctionEvent,
144
256
  result: unknown,
145
257
  context: { instance: string | null; request: Request; thrown?: boolean }
146
258
  ): unknown | ResponseEnvelope | Promise<unknown | ResponseEnvelope>;
259
+ /**
260
+ * Overrides the configured single-flight hook for this handler — same
261
+ * contract as the `collectFlightData` config option (see
262
+ * `CollectFlightDataHook`).
263
+ */
264
+ collectFlightData?: CollectFlightDataHook;
147
265
  /**
148
266
  * Builds the response for calls made without the client runtime (no
149
267
  * instance header — no-JS form posts, direct HTTP) — the extension
@@ -164,12 +282,13 @@ export interface HandleServerFunctionOptions {
164
282
 
165
283
  /**
166
284
  * Web-standard HTTP handler for server function calls: resolves the
167
- * function id from the request, decodes arguments, runs the function under
168
- * a request-event scope, and encodes the result (forwarding
169
- * redirect/revalidation metadata through headers). Mount it on the endpoint
170
- * the client transport targets (default `/_server`); platform adapters
171
- * (h3, express, ...) convert their request shape to a web `Request` around
172
- * it.
285
+ * function id from the request, enforces the declared method (405 when the
286
+ * request method contradicts a `GET` declaration or uses GET without
287
+ * one), decodes arguments, runs the function under a request-event scope,
288
+ * and encodes the result (forwarding redirect/revalidation metadata
289
+ * through headers). Mount it on the endpoint the client transport targets
290
+ * (default `/_server`); platform adapters (h3, express, ...) convert their
291
+ * request shape to a web `Request` around it.
173
292
  *
174
293
  * @example
175
294
  * ```ts
@@ -38,6 +38,177 @@ export const FUNCTION_HEADER: string;
38
38
  */
39
39
  export const INSTANCE_HEADER: string;
40
40
 
41
+ /**
42
+ * Header driving the single-flight protocol on both legs
43
+ * (`"X-Single-Flight"`). On the request it opts the call into data
44
+ * collection — the transport sends it automatically on non-GET calls while
45
+ * a flight-data consumer is subscribed (subscribing IS the opt-in). On the
46
+ * response it marks a body carrying the standardized `SingleFlightPayload`.
47
+ * How the data is produced (a data-only render, running route preloads,
48
+ * anything else) and what it means is entirely the integration's business —
49
+ * the protocol only standardizes the wire shape and the delivery.
50
+ */
51
+ export const SINGLE_FLIGHT_HEADER: string;
52
+
53
+ /**
54
+ * The standardized body of a single-flight response (a response tagged with
55
+ * `SINGLE_FLIGHT_HEADER`): the function's return `value` plus the
56
+ * integration-produced `data` payload, folded into one round trip by the
57
+ * HTTP handler. Integrations decoding passthrough responses themselves (no
58
+ * registered consumer) see this shape from `decodeResponse`. The top level
59
+ * is reserved for the protocol — integration payload lives entirely under
60
+ * `data`, which can be any codec-serializable value.
61
+ */
62
+ export interface SingleFlightPayload<T = unknown, D = unknown> {
63
+ /** The server function's return (or thrown) value. */
64
+ value: T;
65
+ /** The integration-produced data payload. */
66
+ data: D;
67
+ }
68
+
69
+ /**
70
+ * Envelope context delivered alongside single-flight data: the transport
71
+ * response, whose headers carry the integration metadata (`Location` for
72
+ * redirect-with-data, `X-Revalidate` keys) and status. The body is already
73
+ * consumed — read `data` and `value` from the delivery, not from here.
74
+ */
75
+ export interface FlightDataContext {
76
+ /** The HTTP response the data arrived on (metadata only). */
77
+ response: Response;
78
+ }
79
+
80
+ /**
81
+ * Consumer receiving single-flight data on the client: `data` is the
82
+ * integration-produced payload (opaque to the protocol), `context` carries
83
+ * the envelope metadata. Async consumers are awaited before the function
84
+ * value is returned to the caller, so caches are seeded first.
85
+ */
86
+ export type FlightDataConsumer<D = unknown> = (
87
+ data: D,
88
+ context: FlightDataContext
89
+ ) => void | Promise<void>;
90
+
91
+ /**
92
+ * Registers the consumer the client transport delivers single-flight data
93
+ * to. Subscribing is the single-flight opt-in: while a consumer is
94
+ * registered the transport sends the request-leg `SINGLE_FLIGHT_HEADER` on
95
+ * non-GET calls (GET reads stay plain and cacheable), asking the server's
96
+ * collection hook to fold data into the response. When a single-flight
97
+ * response arrives, the transport decodes the standardized
98
+ * `{ value, data }` payload, delivers `data` (with the response as
99
+ * envelope context — redirect location, revalidation keys), and returns
100
+ * `value` to the caller as if the call were plain. What to do with the
101
+ * data (seed caches, navigate, ...) is entirely the consumer's business.
102
+ * One active consumer at a time — a later registration replaces the
103
+ * current one; returns an unsubscribe function. With no consumer
104
+ * registered, no header is sent and the server does no collection work;
105
+ * responses an integration opted in manually still pass through to the
106
+ * caller whole, exactly like other integration responses.
107
+ */
108
+ export function subscribeFlightData<D = unknown>(consumer: FlightDataConsumer<D>): () => void;
109
+
110
+ /**
111
+ * The currently registered single-flight consumer.
112
+ *
113
+ * Transport building block; not meant for hand-written code.
114
+ * @internal
115
+ */
116
+ export function getFlightDataConsumer(): FlightDataConsumer | undefined;
117
+
118
+ /**
119
+ * The public contract of a server function reference — what a `"use
120
+ * server"` import is at runtime on either side: an async callable plus its
121
+ * build-stable identity.
122
+ */
123
+ export interface ServerFunction<A extends readonly any[] = any[], T = any> {
124
+ (...args: A): Promise<T>;
125
+ /** The build-stable function id (stable across the client and server builds). */
126
+ readonly id: string;
127
+ /** URL invoking this function directly over HTTP (form `action`s, raw fetches). */
128
+ readonly url: string;
129
+ }
130
+
131
+ /**
132
+ * Declaration-static metadata attached to a server function reference
133
+ * through declaration wrappers (`GET`, `withMeta`). Read it with
134
+ * `getServerFunctionMetadata`; routers and integrations detect capability
135
+ * from here instead of property sniffing, and `prepareRequest` receives it
136
+ * as `context.meta`. Write through `withMeta` — later writes shallow-merge
137
+ * over earlier ones.
138
+ */
139
+ export interface ServerFunctionMetadata {
140
+ /** The declared HTTP method. Undeclared references call over POST. */
141
+ readonly method?: "GET" | "POST";
142
+ /**
143
+ * A human-readable label for the function, seeded by development builds
144
+ * from the compiled function's source name (dev tooling — inspectors,
145
+ * logs). Dev-only: production builds emit no name. Not unique and not an
146
+ * identity key — use `id` for identity. Seeded as a default: an explicit
147
+ * `withMeta` write wins.
148
+ */
149
+ readonly name?: string;
150
+ /** User-declared transport metadata attached with `withMeta`. */
151
+ readonly [key: string]: unknown;
152
+ }
153
+
154
+ /**
155
+ * Reads a server function reference's declaration metadata — e.g.
156
+ * `getServerFunctionMetadata(fn)?.method === "GET"` detects a `GET(fn)`
157
+ * declaration. Returns undefined when `fn` is not a server function
158
+ * reference; plain references carry an empty metadata object. Works on
159
+ * client proxies and server-side references alike, across duplicated
160
+ * module instances (registered-symbol brand).
161
+ */
162
+ export function getServerFunctionMetadata(fn: unknown): ServerFunctionMetadata | undefined;
163
+
164
+ /**
165
+ * Whether `fn` is a server function reference (a client proxy or a
166
+ * server-side registered callable). Detection is structural — a
167
+ * registered-symbol metadata brand — so it holds across duplicated module
168
+ * instances and both sides of the directive boundary.
169
+ */
170
+ export function isServerFunction(fn: unknown): fn is ServerFunction;
171
+
172
+ /**
173
+ * Attaches user-declared transport metadata to a server function reference
174
+ * (client proxy or server-registered callable) and returns the reference.
175
+ * Writes ride the same channel `GET` uses: later writes shallow-merge over
176
+ * earlier ones, and `getServerFunctionMetadata(fn)` reads the merged bag —
177
+ * so `withMeta` composes with `GET` in either order
178
+ * (`GET(withMeta(fn, meta))` ≡ `withMeta(GET(fn), meta)`).
179
+ *
180
+ * The pattern is declare-on-function, react-in-hook: metadata declared
181
+ * here reaches `prepareRequest` as `context.meta`, letting session-dynamic
182
+ * transport policy key on declarations instead of comparing function ids:
183
+ *
184
+ * ```ts
185
+ * export const chargeCard = withMeta(async (amount: number) => {
186
+ * "use server";
187
+ * // ...
188
+ * }, { requiresAuth: true });
189
+ *
190
+ * configureServerFunctionsClient({
191
+ * prepareRequest(init, { meta }) {
192
+ * if (meta?.requiresAuth) {
193
+ * return {
194
+ * ...init,
195
+ * headers: { ...init.headers, Authorization: `Bearer ${session.token()}` }
196
+ * };
197
+ * }
198
+ * return init;
199
+ * }
200
+ * });
201
+ * ```
202
+ */
203
+ export function withMeta<F extends (...args: any[]) => any>(fn: F, meta: ServerFunctionMetadata): F;
204
+
205
+ /**
206
+ * The registered symbol branding server function references with their
207
+ * declaration metadata. Use the typed accessors instead.
208
+ * @internal
209
+ */
210
+ export const SERVER_FUNCTION_METADATA: unique symbol;
211
+
41
212
  /**
42
213
  * Header carrying the body format tag (a `BodyFormat` value) —
43
214
  * `"X-Server-Function-Format"`.