@solidjs/web 2.0.0-beta.27 → 2.0.0-beta.29

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.
Files changed (43) hide show
  1. package/dist/dev.cjs +40 -2
  2. package/dist/dev.js +39 -4
  3. package/dist/server.cjs +113 -37
  4. package/dist/server.js +112 -39
  5. package/dist/web.cjs +40 -2
  6. package/dist/web.js +39 -4
  7. package/frames/dist/client.cjs +370 -209
  8. package/frames/dist/client.dev.cjs +370 -210
  9. package/frames/dist/client.dev.js +371 -211
  10. package/frames/dist/client.js +371 -210
  11. package/frames/dist/server.cjs +373 -74
  12. package/frames/dist/server.js +373 -75
  13. package/package.json +3 -3
  14. package/server-functions/dist/client.cjs +100 -3
  15. package/server-functions/dist/client.js +92 -4
  16. package/server-functions/dist/server.cjs +85 -17
  17. package/server-functions/dist/server.js +83 -17
  18. package/types/client.d.ts +15 -0
  19. package/types/core.d.ts +1 -1
  20. package/types/frames/client.d.ts +8 -5
  21. package/types/frames/frame-client.d.ts +17 -0
  22. package/types/frames/frame-sink.d.ts +29 -6
  23. package/types/frames/frame-transport.d.ts +76 -12
  24. package/types/frames/server.d.ts +29 -1
  25. package/types/index.d.ts +74 -0
  26. package/types/server-functions/client.d.ts +25 -0
  27. package/types/server-functions/server.d.ts +105 -18
  28. package/types/server-functions/shared.d.ts +22 -0
  29. package/types/server-mock.d.ts +6 -2
  30. package/types/server.d.ts +39 -1
  31. package/types-cjs/client.d.cts +15 -0
  32. package/types-cjs/core.d.cts +1 -1
  33. package/types-cjs/frames/client.d.cts +8 -5
  34. package/types-cjs/frames/frame-client.d.cts +17 -0
  35. package/types-cjs/frames/frame-sink.d.cts +29 -6
  36. package/types-cjs/frames/frame-transport.d.cts +76 -12
  37. package/types-cjs/frames/server.d.cts +29 -1
  38. package/types-cjs/index.d.cts +74 -0
  39. package/types-cjs/server-functions/client.d.cts +25 -0
  40. package/types-cjs/server-functions/server.d.cts +105 -18
  41. package/types-cjs/server-functions/shared.d.cts +22 -0
  42. package/types-cjs/server-mock.d.cts +6 -2
  43. package/types-cjs/server.d.cts +39 -1
@@ -6,6 +6,7 @@ const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
6
6
  function isResponseEnvelope(value) {
7
7
  return !!(value && typeof value === "object" && value[ENVELOPE]);
8
8
  }
9
+ const REVALIDATE_HEADER = "X-Revalidate";
9
10
 
10
11
  Feature.AggregateError | Feature.BigIntTypedArray;
11
12
  const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
@@ -341,6 +342,18 @@ async function decodeResponse(response, codecOptions) {
341
342
  if (!response.body) return undefined;
342
343
  return await extractBody(response, codecOptions === undefined ? codecConfig.codec : codecOptions);
343
344
  }
345
+ async function decodeResponsePayload(response, codecOptions) {
346
+ const decoded = await decodeResponse(response, codecOptions);
347
+ if (decoded !== undefined && response.headers.has(SINGLE_FLIGHT_HEADER)) {
348
+ return {
349
+ value: decoded.value,
350
+ flightData: decoded.data
351
+ };
352
+ }
353
+ return {
354
+ value: decoded
355
+ };
356
+ }
344
357
 
345
358
  function encodeInputValue(value) {
346
359
  if (value instanceof FormData) return {
@@ -395,6 +408,7 @@ const config = {
395
408
  provideEvent: undefined,
396
409
  collectFlightData: undefined,
397
410
  transformResult: undefined,
411
+ transformFlightResult: undefined,
398
412
  transformDirectResult: undefined,
399
413
  handleNoJS: undefined,
400
414
  endpoint: "/_server"
@@ -403,6 +417,7 @@ function configureServerFunctionsServer({
403
417
  provideEvent,
404
418
  collectFlightData,
405
419
  transformResult,
420
+ transformFlightResult,
406
421
  transformDirectResult,
407
422
  handleNoJS,
408
423
  endpoint,
@@ -411,6 +426,7 @@ function configureServerFunctionsServer({
411
426
  if (provideEvent !== undefined) config.provideEvent = provideEvent;
412
427
  if (collectFlightData !== undefined) config.collectFlightData = collectFlightData;
413
428
  if (transformResult !== undefined) config.transformResult = transformResult;
429
+ if (transformFlightResult !== undefined) config.transformFlightResult = transformFlightResult;
414
430
  if (transformDirectResult !== undefined) config.transformDirectResult = transformDirectResult;
415
431
  if (handleNoJS !== undefined) config.handleNoJS = handleNoJS;
416
432
  if (endpoint !== undefined) config.endpoint = endpoint;
@@ -424,6 +440,7 @@ function provideEvent(event, fn) {
424
440
  }
425
441
  const REGISTRATIONS = new Map();
426
442
  const METHODS = new Map();
443
+ const INVOCATIONS = new WeakMap();
427
444
  function registerServerFunction(id, callback) {
428
445
  REGISTRATIONS.set(id, callback);
429
446
  return callback;
@@ -467,9 +484,9 @@ function createServerReference({
467
484
  const evt = {
468
485
  ...ogEvt
469
486
  };
470
- evt.locals.serverFunctionMeta = {
487
+ INVOCATIONS.set(evt, {
471
488
  id
472
- };
489
+ });
473
490
  evt.serverOnly = true;
474
491
  const result = provideEvent(evt, () => {
475
492
  return fn.apply(thisArg, args);
@@ -478,11 +495,13 @@ function createServerReference({
478
495
  if (transform && result && typeof result.then === "function") {
479
496
  return result.then(value => transform(value, {
480
497
  id,
498
+ args,
481
499
  event: evt
482
500
  }));
483
501
  }
484
502
  return transform ? transform(result, {
485
503
  id,
504
+ args,
486
505
  event: evt
487
506
  }) : result;
488
507
  }
@@ -497,9 +516,11 @@ function GET(fn) {
497
516
  method: "GET"
498
517
  });
499
518
  }
500
- function getServerFunctionMeta() {
501
- const event = getRequestEvent();
502
- return event && event.locals.serverFunctionMeta;
519
+ function getServerFunctionInvocation() {
520
+ return getEventServerFunctionInvocation(getRequestEvent());
521
+ }
522
+ function getEventServerFunctionInvocation(event) {
523
+ return event && INVOCATIONS.get(event);
503
524
  }
504
525
  function resolveFunctionId(request, url) {
505
526
  const reference = request.headers.get(FUNCTION_HEADER);
@@ -529,15 +550,49 @@ async function parseArguments(request, url, instance, codec) {
529
550
  }
530
551
  return parsed;
531
552
  }
532
- async function foldFlightData(hook, event, headers, outcome) {
553
+ async function foldFlightData(hook, event, headers, outcome, context = {}) {
554
+ if (outcome.value instanceof Response && outcome.value.body) return outcome.value;
555
+ digestOutcome(event, outcome);
533
556
  const data = await hook(event, outcome);
534
557
  if (data === undefined) return outcome.value;
535
558
  headers.set(SINGLE_FLIGHT_HEADER, "true");
559
+ if (context.transformFlightResult) {
560
+ const transformed = await context.transformFlightResult(event, {
561
+ value: outcome.value,
562
+ data
563
+ }, context);
564
+ if (transformed !== undefined) {
565
+ for (const cookie of headers.getSetCookie()) transformed.headers.append("Set-Cookie", cookie);
566
+ headers.forEach((value, key) => {
567
+ if (key !== "set-cookie" && !transformed.headers.has(key)) {
568
+ transformed.headers.set(key, value);
569
+ }
570
+ });
571
+ return transformed;
572
+ }
573
+ }
536
574
  return {
537
575
  value: outcome.value,
538
576
  data
539
577
  };
540
578
  }
579
+ function digestOutcome(event, outcome) {
580
+ const {
581
+ request,
582
+ response
583
+ } = outcome;
584
+ outcome.revalidateKeys = response?.headers.get(REVALIDATE_HEADER)?.split(",");
585
+ outcome.foldedHeaders = foldSetCookies(request.headers, [...(event.response?.headers?.getSetCookie() ?? []), ...(response?.headers?.getSetCookie() ?? [])]);
586
+ try {
587
+ const referrer = request.headers.get("referer");
588
+ if (referrer) {
589
+ const location = response?.headers.get("Location");
590
+ const target = location ? new URL(location, request.url) : new URL(referrer);
591
+ if (target.origin === new URL(request.url).origin) outcome.targetUrl = target.toString();
592
+ }
593
+ } catch {
594
+ }
595
+ }
541
596
  function parseSetCookie(setCookie) {
542
597
  const [pair, ...attributes] = setCookie.split(";");
543
598
  const eq = pair.indexOf("=");
@@ -676,22 +731,29 @@ async function handleServerFunctionRequest(request, options = {}) {
676
731
  const provide = options.provideEvent || provideEvent;
677
732
  const flightHook = options.collectFlightData !== undefined ? options.collectFlightData : config.collectFlightData;
678
733
  const transformResult = options.transformResult !== undefined ? options.transformResult : config.transformResult;
734
+ const transformFlightResult = options.transformFlightResult !== undefined ? options.transformFlightResult : config.transformFlightResult;
679
735
  const handleNoJS = options.handleNoJS !== undefined ? options.handleNoJS : config.handleNoJS !== undefined ? config.handleNoJS : isFormPost(request) ? defaultNoJSHandler || (defaultNoJSHandler = createNoJSHandler()) : undefined;
680
736
  const collectsFlight = !!(flightHook && instance && request.headers.has(SINGLE_FLIGHT_HEADER));
681
737
  const parsed = await parseArguments(request, url, instance, codec);
738
+ const flightContext = {
739
+ id: functionId,
740
+ args: parsed,
741
+ instance,
742
+ request,
743
+ collectsFlight,
744
+ codec,
745
+ transformFlightResult
746
+ };
682
747
  const headers = new Headers();
683
748
  try {
684
749
  let result = await provide(event, async () => {
685
- event.locals.serverFunctionMeta = {
750
+ INVOCATIONS.set(event, {
686
751
  id: functionId
687
- };
752
+ });
688
753
  return serverFunction(...parsed);
689
754
  });
690
755
  if (transformResult) {
691
- result = await transformResult(event, result, {
692
- instance,
693
- request
694
- });
756
+ result = await transformResult(event, result, flightContext);
695
757
  }
696
758
  let status = 200;
697
759
  let metadata;
@@ -733,7 +795,8 @@ async function handleServerFunctionRequest(request, options = {}) {
733
795
  response: metadata,
734
796
  request,
735
797
  thrown: false
736
- });
798
+ }, flightContext);
799
+ if (result instanceof Response && result.headers.has("X-Content-Raw")) return result;
737
800
  }
738
801
  if (!instance) {
739
802
  if (handleNoJS) return handleNoJS(result, request, parsed);
@@ -745,8 +808,7 @@ async function handleServerFunctionRequest(request, options = {}) {
745
808
  if (x instanceof Response || isResponseEnvelope(x)) {
746
809
  if (transformResult) {
747
810
  x = await transformResult(event, x, {
748
- instance,
749
- request,
811
+ ...flightContext,
750
812
  thrown: true
751
813
  });
752
814
  }
@@ -784,7 +846,11 @@ async function handleServerFunctionRequest(request, options = {}) {
784
846
  response: metadata,
785
847
  request,
786
848
  thrown: true
787
- });
849
+ }, flightContext);
850
+ if (x instanceof Response && x.headers.has("X-Content-Raw")) {
851
+ x.headers.set(ERROR_HEADER, "true");
852
+ return x;
853
+ }
788
854
  }
789
855
  headers.set(ERROR_HEADER, "true");
790
856
  if (!instance) {
@@ -806,4 +872,4 @@ async function handleServerFunctionRequest(request, options = {}) {
806
872
  }
807
873
  }
808
874
 
809
- export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getServerFunction, getServerFunctionMeta, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
875
+ export { ERROR_HEADER, FLASH_COOKIE, FUNCTION_HEADER, GET, INSTANCE_HEADER, SINGLE_FLIGHT_HEADER, clearFlashCookie, configureServerFunctionsServer, createNoJSHandler, createServerReference, decodeErrorHeaderValue, decodeFlashCookie, decodeResponse, decodeResponsePayload, encodeErrorHeaderValue, encodeFlashCookie, foldSetCookies, getEventServerFunctionInvocation, getServerFunction, getServerFunctionInvocation, getServerFunctionMetadata, handleServerFunctionRequest, hasFlashCookie, isServerFunction, registerServerFunction, registerServerReference, subscribeFlightData, withMeta };
package/types/client.d.ts CHANGED
@@ -148,6 +148,21 @@ export function generateHydrationScript(options?: {
148
148
  eventNames?: string[];
149
149
  }): string;
150
150
  export function Assets(props: { children?: JSX.Element }): JSX.Element;
151
+ /**
152
+ * See the server entry's `ResponseStub` — the shape of the mutable response
153
+ * head integrations expose as `event.response` via module augmentation.
154
+ */
155
+ export interface ResponseStub {
156
+ status?: number;
157
+ statusText?: string;
158
+ headers: Headers;
159
+ /**
160
+ * Set by the integration once the response head has been derived/sent
161
+ * from this stub (status/headers can no longer change); consumers must
162
+ * treat later writes and cleanup-time retractions as no-ops.
163
+ */
164
+ committed?: boolean;
165
+ }
151
166
  export interface RequestEvent {
152
167
  request: Request;
153
168
  locals: Record<string | number | symbol, any>;
package/types/core.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export { getOwner, runWithOwner, createComponent, createRoot as root, sharedConfig, untrack, merge as mergeProps, flatten, ssrHandleError, ssrScope, NoHydration, Hydration } from "solid-js";
1
+ export { getOwner, runWithOwner, createComponent, createRoot as root, sharedConfig, untrack, merge as mergeProps, flatten, ssrHandleError, ssrScope, NoHydration, Hydration, runInServerComponentScope } from "solid-js";
2
2
  export declare const effect: (fn: any, effectFn: any, options: any) => void;
3
3
  export declare const memo: (fn: any) => import("solid-js").SourceAccessor<any>;
4
4
  export declare const runWithHydrationScope: (id: any, fn: any) => unknown;
@@ -1,14 +1,17 @@
1
1
  export { createFrame, createFrameHost, createFrameElement, FRAME_APPLIED_EVENT } from "./frame-client.js";
2
2
  export { FRAME_STREAM_HEADER, applyFrameResponse, isFrameStreamResponse, createServerComponentHandler } from "./frame-transport.js";
3
3
  export { createJSONDataTable } from "./serializer.js";
4
+ export type { Slot } from "./server.js";
4
5
  export declare function getFrameHost(): any;
5
6
  /**
6
7
  * Installs the server-component transport policy on the server-function
7
- * client: boundary identity derives from the reactive owner captured at
8
- * each call site (`getOwner`), so distinct `dynamic()` sources get
9
- * independent boundaries with nothing declared, refetches from the same
10
- * source resolve to the identical component, and ownerless calls fall back
11
- * to one boundary per function id.
8
+ * client: boundary identity is the call's intrinsic (function, arguments)
9
+ * address per-args, exactly like the query cache, so a cached component
10
+ * always mounts the boundary showing the call it was cached for. Repeat
11
+ * calls for the same args resolve the identical component (refetches morph
12
+ * in place, cache hits pass `dynamic`'s equals-gate); a source switching
13
+ * args swaps boundaries, re-materialized instantly from the host's
14
+ * retained state.
12
15
  *
13
16
  * Call once in the client entry (an explicit call — the package is
14
17
  * `sideEffects: false`, so a bare import would be tree-shaken away);
@@ -71,6 +71,14 @@ export interface SlotContext {
71
71
  * re-call displaced (e.g. `{$frame}` region ranges) is dropped.
72
72
  */
73
73
  adopted?: boolean;
74
+ /**
75
+ * Whether this occurrence is a render-prop CALL (the producer placed it
76
+ * with arguments — possibly empty — via a slot record) as opposed to a
77
+ * direct-insert position. Consumers cannot tell from the resolved props
78
+ * alone: an argless render prop and a direct insert both arrive as `{}`,
79
+ * but one is a function to invoke and the other a value to place.
80
+ */
81
+ invoked?: boolean;
74
82
  /**
75
83
  * Register cleanup for when this occurrence's range is removed from the
76
84
  * server content, or the owning frame is disposed.
@@ -83,6 +91,15 @@ export interface SlotContext {
83
91
  * in place (zero DOM mutation).
84
92
  */
85
93
  existing: ChildNode[];
94
+ /**
95
+ * The range's own marker comments, when the occurrence has a placed range.
96
+ * A framework binding whose slot content is reactive at the top level (a
97
+ * boundary accessor, changing route children) owns the interior instead of
98
+ * returning nodes: bind before `end` with the framework's insert primitive
99
+ * and return `undefined` — the frame leaves the range alone (server morphs
100
+ * already protect slot ranges).
101
+ */
102
+ range?: { start: Comment; end: Comment };
86
103
  }
87
104
 
88
105
  /**
@@ -126,16 +126,39 @@ export function createDocumentSlotProps(
126
126
  * as `configureServerFunctionsServer({ transformDirectResult })` and a
127
127
  * direct (same-process) server-function result that is a function comes back
128
128
  * as an inline-renderable server component (frame markers + document
129
- * slot props). Non-function results pass through.
129
+ * slot props), branded with its function id and the call's wire address.
130
+ * Non-function results pass through.
130
131
  */
131
- export function frameTransformDirectResult<T>(value: T, options: { id: string }): T;
132
+ export function frameTransformDirectResult<T>(
133
+ value: T,
134
+ options: { id: string; args?: unknown[] }
135
+ ): T;
132
136
 
133
137
  /**
134
- * Seroval plugin for the hydration serializer: writes an inline server
135
- * component as a stable per-function-id placeholder reference
136
- * (`self._$SC.r(id)`) instead of meeting an unserializable function.
138
+ * The frame half of single-flight, as a `transformFlightResult` policy for
139
+ * `handleServerFunctionRequest`: when part of what a mutation invalidated is
140
+ * markup (a component-valued flight-data entry), the frame stream carries
141
+ * the whole payload — each component's content as a region addressed by its
142
+ * call, the `{ value, data }` envelope as `outcome` chunks with the
143
+ * component entries serialized as flight references. Returns `undefined`
144
+ * when nothing invalidated is markup (the response stays the plain
145
+ * single-flight envelope).
137
146
  */
138
- export const ServerComponentPlugin: unknown;
147
+ export function frameTransformFlightResult(
148
+ event: unknown,
149
+ outcome: { value: unknown; data: unknown },
150
+ context?: unknown
151
+ ): Promise<Response | undefined>;
152
+
153
+ // The brands and the codec plugin live with the transport (client bundles
154
+ // resolve flight references against the live registry); re-exported here for
155
+ // server integrations importing the document-SSR surface.
156
+ export {
157
+ SERVER_COMPONENT,
158
+ SERVER_COMPONENT_ADDRESS,
159
+ SERVER_COMPONENT_SOURCE,
160
+ ServerComponentPlugin
161
+ } from "./frame-transport.js";
139
162
 
140
163
  /**
141
164
  * Inline bootstrap for the document shell: installs the `self._$SC`
@@ -1,4 +1,11 @@
1
1
  import { FrameChunk, FrameHost } from "./frame-client.js";
2
+ import { JSONCodecOptions } from "./serializer.js";
3
+
4
+ // Structural mirror of server-functions/shared.js's FlightDataConsumer:
5
+ // this file may only reference siblings that ship with it when integrations
6
+ // copy the frames declaration set (solid-web's types build), and the
7
+ // server-functions declarations are copied to a different root.
8
+ type FlightConsumer = (data: unknown, context: { response: Response }) => void | Promise<void>;
2
9
 
3
10
  /**
4
11
  * Header tagging a Response as a frame stream; its value is the producing
@@ -23,9 +30,24 @@ export interface ApplyFrameResponseOptions {
23
30
  * Restamp every chunk of the response with this version (one response IS
24
31
  * one version). Versions belong to the client too: the producer cannot
25
32
  * know how many streams a boundary has consumed, so pass the Nth-response
26
- * counter to make policy A's stale-guard real across navigations.
33
+ * counter to make policy A's stale-guard real across navigations. A
34
+ * single-flight response addresses several boundaries, each with its own
35
+ * history — pass a function and it is called once per frame in the
36
+ * response.
37
+ */
38
+ version?: number | ((frameId: string) => number);
39
+ /**
40
+ * Remap any frame id other than the response's own root onto a local one
41
+ * — how a consumer resolves the addresses a single-flight response uses
42
+ * for the regions it refreshed.
27
43
  */
28
- version?: number;
44
+ route?(id: string): string;
45
+ /**
46
+ * Receives the payload text of each `outcome` chunk — the response-scoped
47
+ * single-flight envelope, the caller's result rather than anything the
48
+ * host renders.
49
+ */
50
+ onOutcome?(payload: string): void;
29
51
  }
30
52
 
31
53
  /**
@@ -48,6 +70,31 @@ export function applyFrameResponse(
48
70
  options?: ApplyFrameResponseOptions
49
71
  ): Promise<string>;
50
72
 
73
+ /** Brands an inline-rendered server component with its function id. */
74
+ export const SERVER_COMPONENT: unique symbol;
75
+
76
+ /** The unwrapped server component behind an inline-render wrap. */
77
+ export const SERVER_COMPONENT_SOURCE: unique symbol;
78
+
79
+ /** The call's wire address (`frameAddress`), for regions to be emitted under. */
80
+ export const SERVER_COMPONENT_ADDRESS: unique symbol;
81
+
82
+ /**
83
+ * Seroval plugin for a server component crossing a serialization boundary:
84
+ * a branded component serializes as a REFERENCE — a per-function document
85
+ * placeholder in the hydration serializer, a live-registry lookup by call
86
+ * address in the JSON codec (single-flight envelopes) — its markup never
87
+ * rides as data.
88
+ */
89
+ export const ServerComponentPlugin: unknown;
90
+
91
+ /**
92
+ * The codec options for a single-flight envelope: `codec` plus
93
+ * `ServerComponentPlugin` (deduped by tag). Injected by the protocol on both
94
+ * legs; exported for integrations composing their own flight carriers.
95
+ */
96
+ export function flightCodec(codec?: JSONCodecOptions): JSONCodecOptions;
97
+
51
98
  /** Options for `createServerComponentHandler`. */
52
99
  export interface ServerComponentHandlerOptions<C = unknown> {
53
100
  host: FrameHost;
@@ -57,12 +104,6 @@ export interface ServerComponentHandlerOptions<C = unknown> {
57
104
  * own frame instance under the boundary id (multi-mount fans out).
58
105
  */
59
106
  component(frameId: string): C;
60
- /**
61
- * Runs synchronously at each server-function call site (before any
62
- * await); its return is the call's ambient identity — e.g. Solid's
63
- * `getOwner`. Calls sharing a captured context share one boundary.
64
- */
65
- capture?(info: { id: string; meta: unknown }): unknown;
66
107
  /**
67
108
  * A new response is about to stream into a boundary: rotate
68
109
  * response-scoped state (codec data tables) here. `version` is the
@@ -83,6 +124,18 @@ export interface ServerComponentHandlerOptions<C = unknown> {
83
124
  * never observes a pending beat.
84
125
  */
85
126
  intercept?(info: { id: string; meta: unknown; args: unknown[] }): C | undefined;
127
+ /**
128
+ * Reads the registered single-flight consumer at delivery time. The
129
+ * consumer is module state in the server-function client's SHARED
130
+ * instance; pass a getter reading that instance when your bundling gives
131
+ * this module a private copy. Defaults to the local copy's reader.
132
+ */
133
+ consumer?(): FlightConsumer | undefined;
134
+ /**
135
+ * Reads the configured codec options at decode time — same instance-
136
+ * identity contract as `consumer`. Defaults to the local copy's reader.
137
+ */
138
+ codec?(): JSONCodecOptions | undefined;
86
139
  }
87
140
 
88
141
  /**
@@ -92,15 +145,26 @@ export interface ServerComponentHandlerOptions<C = unknown> {
92
145
  * (Solid's `dynamic`) never remounts across refetches — the response streams
93
146
  * into the boundary underneath as the only observable effect.
94
147
  *
95
- * Boundary identity is derived, never declared: contexts captured per call
96
- * key a WeakMap of boundaries (dying with their call sites); ownerless calls
97
- * fall back to one boundary per function id.
148
+ * Boundary identity is derived, never declared: every call keys by its
149
+ * intrinsic (function, arguments) address the query cache's per-args rule,
150
+ * so cached components and boundaries stay one-to-one. Same-args calls
151
+ * resolve the identical component and morph in place; an args switch swaps
152
+ * boundaries, re-materialized from the host's retained state.
98
153
  */
99
154
  export function createServerComponentHandler<C>(options: ServerComponentHandlerOptions<C>): {
100
- capture?(info: { id: string; meta: unknown }): unknown;
101
155
  intercept?(info: { id: string; meta: unknown; args: unknown[] }): C | undefined;
102
156
  handle(
103
157
  response: Response,
104
158
  ctx: { id: string; meta: unknown; args: unknown[]; context: unknown }
105
159
  ): C | undefined;
160
+ /**
161
+ * Declares that the document is showing a call: hydration-data references
162
+ * carry their call's address (`_$SC.r(id, address)`) but never travel
163
+ * through the transport, so the integration forwards those records here —
164
+ * they are how a post-load call for the same (function, arguments) finds
165
+ * its way back to the adopted boundary. `component` must be the exact
166
+ * reference the integration's cache holds for the call (the per-function
167
+ * placeholder), or readers' equals-gates fail into remounts.
168
+ */
169
+ showing(address: string, functionId: string, component: C): void;
106
170
  };
@@ -1,2 +1,30 @@
1
- export { renderToFrameStream, renderServerComponent, serverComponentResponse, frameTransformResult, createFrameSink, frameTransformDirectResult, ServerComponentPlugin, SERVER_COMPONENT_BOOTSTRAP } from "./frame-sink.js";
1
+ import type { Element as SolidElement } from "solid-js";
2
+ /**
3
+ * A client position in a server component: a prop the server renders (as JSX
4
+ * or by calling it) where client-owned markup belongs. `P` is the client
5
+ * component's own props, so a server component can reference the client
6
+ * component's type directly instead of restating it.
7
+ *
8
+ * Arguments are classified by VALUE, not by name — any prop may carry any of
9
+ * these:
10
+ *
11
+ * - primitives ride the chunk;
12
+ * - server JSX streams as a nested region (html once, never data);
13
+ * - anything else serializes as a data record.
14
+ *
15
+ * Async server JSX in an argument needs its own boundary: the region is
16
+ * emitted as one finished string, so a bare async read has no fallback to
17
+ * show and no fragment to reveal into.
18
+ *
19
+ * `$key` names the occurrence so client state follows an entity across
20
+ * responses rather than being positional — the slot-level analogue of `For`'s
21
+ * `keyed`, for when references can't carry identity because every response
22
+ * re-creates everything. It is occurrence identity, not client data: it is
23
+ * stripped before the client component sees its props. Positional identity is
24
+ * the right default; `$key` matters when a live list reorders.
25
+ */
26
+ export type Slot<P = {}> = (props: P & {
27
+ $key?: string | number;
28
+ }) => SolidElement;
29
+ export { renderToFrameStream, renderServerComponent, serverComponentResponse, frameTransformResult, frameTransformFlightResult, createFrameSink, frameTransformDirectResult, ServerComponentPlugin, SERVER_COMPONENT_BOOTSTRAP } from "./frame-sink.js";
2
30
  export { FRAME_STREAM_HEADER, isFrameStreamResponse } from "./frame-transport.js";
package/types/index.d.ts CHANGED
@@ -176,3 +176,77 @@ export declare function dynamic<T extends ValidComponent>(source: () => T | Prom
176
176
  * @description https://docs.solidjs.com/reference/components/dynamic
177
177
  */
178
178
  export declare function Dynamic<T extends ValidComponent>(props: DynamicProps<T>): JSX.Element;
179
+ /**
180
+ * Wraps a dynamically imported component so it renders only in the browser.
181
+ * The server renders `props.fallback` (and nothing else); the client shows
182
+ * the fallback until the import resolves and the tree has mounted, then
183
+ * swaps the real component in.
184
+ *
185
+ * Unlike `lazy()`, this avoids Suspense entirely and never server-renders
186
+ * the wrapped component — only the fallback — so the component participates
187
+ * in no hydration asset manifest and its code is guaranteed to never run on
188
+ * the server (safe for browser-only libraries touching `window`, DOM
189
+ * measurement, etc.). The mount gate keeps hydration safe: during hydration
190
+ * the fallback is rendered exactly as the server did, and the swap happens
191
+ * only after settle, so there is no mismatch.
192
+ *
193
+ * By default the import starts as soon as `clientOnly` is called (module
194
+ * load); pass `{ lazy: true }` to defer the import to the component's first
195
+ * render.
196
+ *
197
+ * @example
198
+ * ```tsx
199
+ * const Chart = clientOnly(() => import("./Chart.jsx"));
200
+ * // <Chart fallback={<div>Loading chart…</div>} data={data()} />
201
+ * ```
202
+ */
203
+ export declare function clientOnly<T extends Component<any>>(fn: () => Promise<{
204
+ default: T;
205
+ }>, options?: {
206
+ lazy?: boolean;
207
+ }): Component<ComponentProps<T> & {
208
+ fallback?: JSX.Element;
209
+ }>;
210
+ /**
211
+ * Declares the HTTP response status (and optional status text) for the
212
+ * lifetime of the current reactive scope during SSR — call it bare in a
213
+ * component or reactive-scope body where the status is decided (a 404
214
+ * route, an error fallback). Client build: a no-op — the response head was
215
+ * sent long ago.
216
+ *
217
+ * Naming note — this is a scope-tied *declaration*, not a mutation: "while
218
+ * this reactive scope is live, the response has this status." Solid
219
+ * reserves `set*` verbs for event-time mutation; like
220
+ * `createSignal`/`onCleanup` this is called in scope bodies and un-declares
221
+ * on scope disposal.
222
+ *
223
+ * Retraction semantics (server): the write snapshots the previous
224
+ * `event.response` status at write time and restores it when the owning
225
+ * scope is disposed — so a boundary that errored, declared a status, and
226
+ * then recovered retracts its write instead of stomping a status a
227
+ * surviving part of the tree legitimately set. Once the integration marks
228
+ * the response head `committed` (head derived/sent), writes and
229
+ * retractions are no-ops.
230
+ */
231
+ export declare function httpStatus(_code: number, _text?: string): void;
232
+ /**
233
+ * Declares an HTTP response header (or with `append`, appends to one) for
234
+ * the lifetime of the current reactive scope during SSR — call it bare in a
235
+ * component or reactive-scope body. Client build: a no-op — the response
236
+ * head was sent long ago.
237
+ *
238
+ * Naming note — this is a scope-tied *declaration*, not a mutation: "while
239
+ * this reactive scope is live, the response has this header." Solid
240
+ * reserves `set*` verbs for event-time mutation; like
241
+ * `createSignal`/`onCleanup` this is called in scope bodies and un-declares
242
+ * on scope disposal.
243
+ *
244
+ * Retraction semantics (server): the header's prior value is snapshotted at
245
+ * write time and restored when the owning scope is disposed (deleted if
246
+ * there was none) — a boundary that errors or recovers retracts its writes.
247
+ * Once the integration marks the response head `committed` (head
248
+ * derived/sent), writes and retractions are no-ops.
249
+ */
250
+ export declare function httpHeader(_name: string, _value: string, _options?: {
251
+ append?: boolean;
252
+ }): void;
@@ -2,21 +2,29 @@ import { JSONCodecOptions } from "../serializer.js";
2
2
  import { ServerFunction, ServerFunctionMetadata } from "./shared.js";
3
3
 
4
4
  export {
5
+ ChunkReader,
5
6
  ERROR_HEADER,
6
7
  FLASH_COOKIE,
7
8
  FUNCTION_HEADER,
8
9
  INSTANCE_HEADER,
9
10
  SINGLE_FLIGHT_HEADER,
10
11
  clearFlashCookie,
12
+ createChunk,
11
13
  decodeErrorHeaderValue,
12
14
  decodeResponse,
15
+ decodeResponsePayload,
16
+ deserializeStream,
13
17
  encodeErrorHeaderValue,
18
+ frameAddress,
19
+ getFlightDataConsumer,
14
20
  getServerFunctionMetadata,
21
+ getServerFunctionsCodec,
15
22
  hasFlashCookie,
16
23
  isServerFunction,
17
24
  subscribeFlightData,
18
25
  withMeta
19
26
  } from "./shared.js";
27
+ export { REVALIDATE_HEADER } from "../response.js";
20
28
  export type {
21
29
  FlightDataConsumer,
22
30
  FlightDataContext,
@@ -173,3 +181,20 @@ export function createServerReference(id: string, name?: string, base?: string):
173
181
  * @internal
174
182
  */
175
183
  export function registerServerReference(): never;
184
+
185
+ /**
186
+ * Identity of the currently executing server function call — see the
187
+ * server entry. Named here so isomorphic code can import the type from
188
+ * either entry.
189
+ */
190
+ export interface ServerFunctionInvocation {
191
+ id: string;
192
+ }
193
+
194
+ /**
195
+ * Client no-op mirror of the server entry's accessor: there is never a
196
+ * server function call in flight on the client, so this always returns
197
+ * undefined. Present so `"use server"` modules that import it stay
198
+ * import-stable in client builds before dead-code elimination.
199
+ */
200
+ export function getServerFunctionInvocation(): ServerFunctionInvocation | undefined;