@solidjs/web 2.0.0-beta.32 → 2.0.0-beta.34

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 (52) hide show
  1. package/dist/dev.cjs +58 -16
  2. package/dist/dev.js +54 -17
  3. package/dist/server.cjs +714 -75
  4. package/dist/server.js +711 -78
  5. package/dist/web.cjs +58 -16
  6. package/dist/web.js +54 -17
  7. package/frames/dist/client.cjs +181 -64
  8. package/frames/dist/client.dev.cjs +185 -64
  9. package/frames/dist/client.dev.js +183 -62
  10. package/frames/dist/client.js +179 -62
  11. package/frames/dist/server.cjs +972 -136
  12. package/frames/dist/server.js +974 -138
  13. package/package.json +17 -6
  14. package/serialization/decode/package.json +20 -0
  15. package/serialization/dist/decode.cjs +110 -0
  16. package/serialization/dist/decode.js +104 -0
  17. package/serialization/dist/serialization.cjs +98 -43
  18. package/serialization/dist/serialization.js +99 -44
  19. package/serialization/types/index.d.ts +18 -160
  20. package/serialization/types/serializer-decode.d.ts +182 -0
  21. package/serialization/types-cjs/index.d.cts +18 -160
  22. package/serialization/types-cjs/serializer-decode.d.cts +182 -0
  23. package/server-functions/dist/client.cjs +101 -105
  24. package/server-functions/dist/client.js +101 -105
  25. package/server-functions/dist/server.cjs +131 -107
  26. package/server-functions/dist/server.dev.cjs +131 -107
  27. package/server-functions/dist/server.dev.js +132 -108
  28. package/server-functions/dist/server.js +132 -108
  29. package/types/client.d.ts +23 -1
  30. package/types/cookies.d.ts +93 -0
  31. package/types/core.d.ts +1 -1
  32. package/types/frames/frame-client.d.ts +26 -0
  33. package/types/frames/frame-transport.d.ts +1 -1
  34. package/types/frames/serializer.d.ts +18 -160
  35. package/types/serializer-decode.d.ts +182 -0
  36. package/types/serializer.d.ts +18 -160
  37. package/types/server-functions/client.d.ts +1 -1
  38. package/types/server-functions/server.d.ts +1 -1
  39. package/types/server-functions/shared.d.ts +57 -1
  40. package/types/server.d.ts +21 -1
  41. package/types-cjs/client.d.cts +23 -1
  42. package/types-cjs/cookies.d.cts +93 -0
  43. package/types-cjs/core.d.cts +1 -1
  44. package/types-cjs/frames/frame-client.d.cts +26 -0
  45. package/types-cjs/frames/frame-transport.d.cts +1 -1
  46. package/types-cjs/frames/serializer.d.cts +18 -160
  47. package/types-cjs/serializer-decode.d.cts +182 -0
  48. package/types-cjs/serializer.d.cts +18 -160
  49. package/types-cjs/server-functions/client.d.cts +1 -1
  50. package/types-cjs/server-functions/server.d.cts +1 -1
  51. package/types-cjs/server-functions/shared.d.cts +57 -1
  52. package/types-cjs/server.d.cts +21 -1
@@ -1,5 +1,3 @@
1
- import { fromCrossJSON, Feature, toCrossJSONStream } from 'seroval';
2
- import { AbortSignalPlugin, CustomEventPlugin, DOMExceptionPlugin, EventPlugin, FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin } from 'seroval-plugins/web';
3
1
  import { sharedConfig } from 'solid-js';
4
2
 
5
3
  const ENVELOPE = Symbol.for("solid.ResponseEnvelope");
@@ -12,51 +10,70 @@ function isSafeError(value) {
12
10
  }
13
11
  const REVALIDATE_HEADER = "X-Revalidate";
14
12
 
15
- Feature.AggregateError | Feature.BigIntTypedArray;
16
- const serializeOnlyDisabledFeatures = () => process.env.NODE_ENV === "development" ? 0 : Feature.ErrorPrototypeStack;
17
- const DEFAULT_WEB_PLUGINS = Object.freeze([AbortSignalPlugin,
18
- CustomEventPlugin, DOMExceptionPlugin, EventPlugin,
19
- FormDataPlugin, HeadersPlugin, ReadableStreamPlugin, RequestPlugin, ResponsePlugin, URLSearchParamsPlugin, URLPlugin]);
20
- function resolveSerializerPlugins(customPlugins) {
21
- return customPlugins ? [...customPlugins, ...DEFAULT_WEB_PLUGINS] : [...DEFAULT_WEB_PLUGINS];
22
- }
23
- const JSON_CODEC_DISABLED_FEATURES = Feature.RegExp;
24
- const JSON_CODEC_DEPTH_LIMIT = 64;
25
- function resolveCodecOptions({
26
- plugins,
27
- disabledFeatures,
28
- depthLimit
29
- } = {}) {
30
- return {
31
- plugins: resolveSerializerPlugins(plugins),
32
- disabledFeatures: disabledFeatures === undefined ? JSON_CODEC_DISABLED_FEATURES : disabledFeatures,
33
- depthLimit: depthLimit === undefined ? JSON_CODEC_DEPTH_LIMIT : depthLimit
34
- };
13
+ const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
14
+ function getServerFunctionMetadata(fn) {
15
+ if (typeof fn !== "function") return undefined;
16
+ return fn[SERVER_FUNCTION_METADATA] || undefined;
35
17
  }
36
- function serializeJSON(value, {
37
- onParse,
38
- onDone,
39
- onError,
40
- ...codecOptions
41
- }) {
42
- const resolved = resolveCodecOptions(codecOptions);
43
- return toCrossJSONStream(value, {
44
- onParse,
45
- onDone,
46
- onError,
47
- ...resolved,
48
- disabledFeatures: resolved.disabledFeatures | serializeOnlyDisabledFeatures()
49
- });
18
+ function isServerFunction(fn) {
19
+ return typeof fn === "function" && !!fn[SERVER_FUNCTION_METADATA];
50
20
  }
51
- function createJSONDeserializer(options) {
52
- const refs = new Map();
53
- const resolved = resolveCodecOptions(options);
54
- return function deserializeJSONChunk(node) {
55
- return fromCrossJSON(node, {
56
- refs,
57
- ...resolved
58
- });
59
- };
21
+ function withMeta(fn, meta) {
22
+ const metadata = getServerFunctionMetadata(fn);
23
+ if (!metadata) {
24
+ throw new Error("withMeta expects a server function reference");
25
+ }
26
+ Object.assign(metadata, meta);
27
+ return fn;
28
+ }
29
+ const SERVER_FUNCTION_RPC = Symbol.for("solid.ServerFunctionRPC");
30
+ function provideServerFunctionRPC(rpc) {
31
+ globalThis[SERVER_FUNCTION_RPC] || (globalThis[SERVER_FUNCTION_RPC] = rpc);
32
+ }
33
+
34
+ function parseCookieHeader(header) {
35
+ const cookies = {};
36
+ if (!header) return cookies;
37
+ for (const part of header.split(";")) {
38
+ const eq = part.indexOf("=");
39
+ if (eq < 0) continue;
40
+ const name = decodeSafe(part.slice(0, eq).trim());
41
+ let value = part.slice(eq + 1).trim();
42
+ if (value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
43
+ value = value.slice(1, -1);
44
+ }
45
+ cookies[name] = decodeSafe(value);
46
+ }
47
+ return cookies;
48
+ }
49
+ function decodeSafe(text) {
50
+ try {
51
+ return decodeURIComponent(text);
52
+ } catch {
53
+ return text;
54
+ }
55
+ }
56
+ function serializeCookie(name, value, options = {}) {
57
+ let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
58
+ cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
59
+ if (options.domain) cookie += `; Domain=${options.domain}`;
60
+ if (options.maxAge !== undefined) cookie += `; Max-Age=${Math.trunc(options.maxAge)}`;
61
+ if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
62
+ if (options.httpOnly) cookie += "; HttpOnly";
63
+ if (options.secure) cookie += "; Secure";
64
+ if (options.sameSite) {
65
+ const sameSite = options.sameSite.toLowerCase();
66
+ cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
67
+ }
68
+ return cookie;
69
+ }
70
+ const FLASH_COOKIE = "flash";
71
+ const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
72
+ function hasFlashCookie(cookieHeader) {
73
+ return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
74
+ }
75
+ function clearFlashCookie() {
76
+ return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
60
77
  }
61
78
 
62
79
  const codecConfig = {
@@ -72,22 +89,6 @@ function subscribeFlightData(consumer) {
72
89
  return () => {
73
90
  };
74
91
  }
75
- const SERVER_FUNCTION_METADATA = Symbol.for("solid.ServerFunctionMetadata");
76
- function getServerFunctionMetadata(fn) {
77
- if (typeof fn !== "function") return undefined;
78
- return fn[SERVER_FUNCTION_METADATA] || undefined;
79
- }
80
- function isServerFunction(fn) {
81
- return typeof fn === "function" && !!fn[SERVER_FUNCTION_METADATA];
82
- }
83
- function withMeta(fn, meta) {
84
- const metadata = getServerFunctionMetadata(fn);
85
- if (!metadata) {
86
- throw new Error("withMeta expects a server function reference");
87
- }
88
- Object.assign(metadata, meta);
89
- return fn;
90
- }
91
92
  const FUNCTION_HEADER = "X-Server-Function-Id";
92
93
  const ERROR_HEADER = "X-Server-Function-Error";
93
94
  const ERROR_HEADER_MARKER = "=?1?";
@@ -118,14 +119,6 @@ const INSTANCE_HEADER = "X-Server-Function-Instance";
118
119
  const BODY_FORMAT_HEADER = "X-Server-Function-Format";
119
120
  const SINGLE_FLIGHT_HEADER = "X-Single-Flight";
120
121
  const FILE_FORM_KEY = "__server_function_file__";
121
- const FLASH_COOKIE = "flash";
122
- const FLASH_MATCHER = new RegExp(`(?:^|;\\s*)${FLASH_COOKIE}=([^;]+)`);
123
- function hasFlashCookie(cookieHeader) {
124
- return !!cookieHeader && FLASH_MATCHER.test(cookieHeader);
125
- }
126
- function clearFlashCookie() {
127
- return `${FLASH_COOKIE}=; Max-Age=0; Path=/`;
128
- }
129
122
  const BodyFormat = {
130
123
  Serialized: "0",
131
124
  String: "1",
@@ -137,6 +130,38 @@ const BodyFormat = {
137
130
  Uint8Array: "7",
138
131
  Json: "8"
139
132
  };
133
+ const JSON_SAFE_DEPTH_LIMIT = 10000;
134
+ const EXIT = {};
135
+ function isJSONSafe(value) {
136
+ const stack = [value];
137
+ const ancestors = new Set();
138
+ while (stack.length) {
139
+ const v = stack.pop();
140
+ if (v === EXIT) {
141
+ ancestors.delete(stack.pop());
142
+ continue;
143
+ }
144
+ if (v === null) continue;
145
+ const t = typeof v;
146
+ if (t === "string" || t === "boolean") continue;
147
+ if (t === "number") {
148
+ if (!Number.isFinite(v)) return false;
149
+ continue;
150
+ }
151
+ if (t !== "object") return false;
152
+ if (ancestors.has(v) || ancestors.size >= JSON_SAFE_DEPTH_LIMIT) return false;
153
+ ancestors.add(v);
154
+ stack.push(v, EXIT);
155
+ if (Array.isArray(v)) {
156
+ for (let i = 0; i < v.length; i++) stack.push(v[i]);
157
+ } else {
158
+ const proto = Object.getPrototypeOf(v);
159
+ if (proto !== Object.prototype && proto !== null) return false;
160
+ for (const k in v) stack.push(v[k]);
161
+ }
162
+ }
163
+ return true;
164
+ }
140
165
  function getHeadersAndBody(body) {
141
166
  switch (true) {
142
167
  case typeof body === "string":
@@ -296,7 +321,10 @@ class ChunkReader {
296
321
  }
297
322
  function serializeStream(value, codecOptions) {
298
323
  return new ReadableStream({
299
- start(controller) {
324
+ async start(controller) {
325
+ const {
326
+ serializeJSON
327
+ } = await import('@solidjs/web/serialization');
300
328
  serializeJSON(value, {
301
329
  ...codecOptions,
302
330
  onParse(node) {
@@ -319,6 +347,9 @@ async function deserializeStream(source, codecOptions) {
319
347
  const reader = new ChunkReader(source.body);
320
348
  const result = await reader.next();
321
349
  if (!result.done) {
350
+ const {
351
+ createJSONDeserializer
352
+ } = await import('@solidjs/web/serialization/decode');
322
353
  const deserializeChunk = createJSONDeserializer(codecOptions);
323
354
  function interpretChunk(chunk) {
324
355
  return deserializeChunk(JSON.parse(chunk));
@@ -348,43 +379,6 @@ async function decodeResponsePayload(response, codecOptions) {
348
379
  };
349
380
  }
350
381
 
351
- function parseCookieHeader(header) {
352
- const cookies = {};
353
- if (!header) return cookies;
354
- for (const part of header.split(";")) {
355
- const eq = part.indexOf("=");
356
- if (eq < 0) continue;
357
- const name = decodeSafe(part.slice(0, eq).trim());
358
- let value = part.slice(eq + 1).trim();
359
- if (value.length > 1 && value[0] === '"' && value[value.length - 1] === '"') {
360
- value = value.slice(1, -1);
361
- }
362
- cookies[name] = decodeSafe(value);
363
- }
364
- return cookies;
365
- }
366
- function decodeSafe(text) {
367
- try {
368
- return decodeURIComponent(text);
369
- } catch {
370
- return text;
371
- }
372
- }
373
- function serializeCookie(name, value, options = {}) {
374
- let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
375
- cookie += `; Path=${options.path === undefined ? "/" : options.path}`;
376
- if (options.domain) cookie += `; Domain=${options.domain}`;
377
- if (options.maxAge !== undefined) cookie += `; Max-Age=${Math.trunc(options.maxAge)}`;
378
- if (options.expires) cookie += `; Expires=${options.expires.toUTCString()}`;
379
- if (options.httpOnly) cookie += "; HttpOnly";
380
- if (options.secure) cookie += "; Secure";
381
- if (options.sameSite) {
382
- const sameSite = options.sameSite.toLowerCase();
383
- cookie += `; SameSite=${sameSite === "none" ? "None" : sameSite === "strict" ? "Strict" : "Lax"}`;
384
- }
385
- return cookie;
386
- }
387
-
388
382
  const RequestContext = Symbol.for("solid.RequestContext");
389
383
  function getRequestEvent() {
390
384
  return globalThis[RequestContext] ? globalThis[RequestContext].getStore() || sharedConfig.context && sharedConfig.context.event || console.warn("RequestEvent is missing. This is most likely due to accessing `getRequestEvent` non-managed async scope in a partially polyfilled environment. Try moving it above all `await` calls.") : undefined;
@@ -548,7 +542,17 @@ function provideEvent(event, fn) {
548
542
  const REGISTRATIONS = new Map();
549
543
  const METHODS = new Map();
550
544
  const INVOCATIONS = new WeakMap();
545
+ let rpcProvided = false;
546
+ function provideRPC() {
547
+ if (rpcProvided) return;
548
+ rpcProvided = true;
549
+ provideServerFunctionRPC({
550
+ GET,
551
+ decodeResponse
552
+ });
553
+ }
551
554
  function registerServerFunction(id, callback) {
555
+ provideRPC();
552
556
  REGISTRATIONS.set(id, callback);
553
557
  return callback;
554
558
  }
@@ -573,6 +577,7 @@ function createServerReference({
573
577
  name
574
578
  }) {
575
579
  if (typeof fn !== "function") throw new Error("Export from a 'use server' module must be a function");
580
+ provideRPC();
576
581
  const metadata = name === undefined ? {} : {
577
582
  name
578
583
  };
@@ -684,7 +689,9 @@ async function foldFlightData(hook, event, headers, outcome, context = {}) {
684
689
  return transformed;
685
690
  }
686
691
  }
687
- return {
692
+ return outcome.value === undefined ? {
693
+ data
694
+ } : {
688
695
  value: outcome.value,
689
696
  data
690
697
  };
@@ -816,6 +823,23 @@ function encodeResult(value, headers, status, codec) {
816
823
  headers
817
824
  });
818
825
  }
826
+ if (value === undefined) {
827
+ return new Response(null, {
828
+ status,
829
+ headers
830
+ });
831
+ }
832
+ try {
833
+ if (isJSONSafe(value)) {
834
+ headers.set(BODY_FORMAT_HEADER, BodyFormat.Json);
835
+ headers.set("Content-Type", "application/json");
836
+ return new Response(JSON.stringify(value), {
837
+ status,
838
+ headers
839
+ });
840
+ }
841
+ } catch {
842
+ }
819
843
  const response = serializedResponse(value, headers, codec);
820
844
  return status === 200 ? response : new Response(response.body, {
821
845
  status,
package/types/client.d.ts CHANGED
@@ -289,7 +289,7 @@ export interface ResponseStub {
289
289
  * share ONE interface identity and a single augmentation reaches every
290
290
  * `locals`, whichever entry typed the event.
291
291
  */
292
- export type { RequestEventLocals } from "./server.js";
292
+ export { RequestEventLocals } from "./server.js";
293
293
  export interface RequestEvent {
294
294
  request: Request;
295
295
  locals: RequestEventLocals;
@@ -311,5 +311,27 @@ export function getRequestEvent(): RequestEvent | undefined;
311
311
  */
312
312
  export { parseCookieHeader, serializeCookie } from "./cookies.js";
313
313
  export type { CookieOptions } from "./cookies.js";
314
+ /**
315
+ * The flash cookie's isomorphic half (name/detection/clearing — cookie
316
+ * utilities living beside the cookie codec) and the codec-free
317
+ * server-function layer (reference detection + the late-bound RPC seam).
318
+ * On the core entries so integrations consuming them eagerly (routers)
319
+ * never import the server-functions entry — whose client half is the
320
+ * transport + codec — from their eager graph. Declared through
321
+ * server-functions/shared.d.ts, the declaration home published-types
322
+ * layouts ship.
323
+ */
324
+ export {
325
+ clearFlashCookie,
326
+ getServerFunctionMetadata,
327
+ getServerFunctionRPC,
328
+ hasFlashCookie,
329
+ isServerFunction
330
+ } from "./server-functions/shared.js";
331
+ export type {
332
+ ServerFunction,
333
+ ServerFunctionMetadata,
334
+ ServerFunctionRPC
335
+ } from "./server-functions/shared.js";
314
336
  /** Hydration-walk primitive; not for hand-written code. @internal */
315
337
  export function runHydrationEvents(): void;
@@ -0,0 +1,93 @@
1
+ // Cookie wire format: the platform-gap primitives, and ALL of core's
2
+ // cookie surface — core owns the exchange (the request's headers in, the
3
+ // response stub's headers out) and the codec, nothing ambient. Blessed
4
+ // patterns:
5
+ //
6
+ // parseCookieHeader(event.request.headers.get("cookie"))
7
+ // event.response.headers.append("set-cookie", serializeCookie(name, value, options))
8
+ //
9
+ // Dependency-free and isomorphic (exported from both entries — real
10
+ // implementation, never a stub); integrity/confidentiality layers
11
+ // (sessions) belong to the caller, on top of these primitives.
12
+
13
+ /**
14
+ * Attributes for a `Set-Cookie` header, mirroring RFC 6265. `path`
15
+ * defaults to `/`; nothing else is defaulted.
16
+ */
17
+ export interface CookieOptions {
18
+ /** Cookie `Path` attribute. Defaults to `/`. */
19
+ path?: string;
20
+ /** Cookie `Domain` attribute. Emitted only when provided. */
21
+ domain?: string;
22
+ /** Cookie `Max-Age` attribute, in seconds (truncated to an integer). */
23
+ maxAge?: number;
24
+ /** Cookie `Expires` attribute. */
25
+ expires?: Date;
26
+ /** Emit the `HttpOnly` attribute. */
27
+ httpOnly?: boolean;
28
+ /** Emit the `Secure` attribute. */
29
+ secure?: boolean;
30
+ /** Cookie `SameSite` attribute, any case. */
31
+ sameSite?: "lax" | "strict" | "none" | "Lax" | "Strict" | "None";
32
+ }
33
+
34
+ /**
35
+ * Parses a `Cookie` request header into a name → value map. Names and
36
+ * values are `decodeURIComponent`-decoded (falling back to the raw text
37
+ * when decoding throws); a quoted value keeps its content. `null`/empty
38
+ * input parses to an empty map.
39
+ *
40
+ * The read half of the platform gap — the blessed request-cookie read is
41
+ * `parseCookieHeader(event.request.headers.get("cookie"))`.
42
+ */
43
+ export function parseCookieHeader(header: string | null | undefined): Record<string, string>;
44
+
45
+ /**
46
+ * Serializes a cookie to a `Set-Cookie` header value. The name and value
47
+ * are `encodeURIComponent`-encoded (the parser decodes symmetrically);
48
+ * `path` defaults to `/` and every other attribute is emitted exactly
49
+ * when the caller asked for it.
50
+ *
51
+ * The write half of the platform gap — the blessed response-cookie write
52
+ * is `event.response.headers.append("set-cookie", serializeCookie(name,
53
+ * value, options))`, which every head materialization path carries to the
54
+ * wire entry-by-entry.
55
+ */
56
+ export function serializeCookie(name: string, value: string, options?: CookieOptions): string;
57
+
58
+ /**
59
+ * Name of the cookie carrying the outcome of a server function call made
60
+ * without the client runtime (`"flash"`). A no-JS form post has no way to
61
+ * receive a value — the browser follows the redirect and renders the next
62
+ * page — so the handler stashes the outcome here for the render after it
63
+ * to pick up, which is how a form submitted without JavaScript still shows
64
+ * its result.
65
+ *
66
+ * The name, detection and clearing are cookie utilities and isomorphic
67
+ * (integrations read the cookie from code that also ships to the browser);
68
+ * the codec that fills and decodes it is server-only and lives behind the
69
+ * server-functions server entry.
70
+ */
71
+ export const FLASH_COOKIE: string;
72
+
73
+ /**
74
+ * Whether a Cookie header carries a flash cookie, readable or not. Cheap
75
+ * enough to call on every render so the clear can be queued before the
76
+ * response headers flush.
77
+ */
78
+ export function hasFlashCookie(cookieHeader: string | null): boolean;
79
+
80
+ /**
81
+ * The `Set-Cookie` value clearing the flash cookie. The outcome is
82
+ * one-shot: append this as soon as the cookie is detected, whether or not
83
+ * it decodes, so a stale outcome cannot resurface on a later request.
84
+ */
85
+ export function clearFlashCookie(): string;
86
+
87
+ /**
88
+ * The raw encoded flash payload out of a Cookie header, if present — the
89
+ * codec's own accessor.
90
+ *
91
+ * @internal
92
+ */
93
+ export function matchFlashCookie(cookieHeader: string | null): string | undefined;
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, runInServerComponentScope } from "solid-js";
1
+ export { getOwner, runWithOwner, createComponent, createRoot as root, sharedConfig, untrack, merge as mergeProps, flatten, ssrHandleError, ssrScope, NoHydration, Hydration, runInServerComponentScope, creationStamp, inServerComponentScope } 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;
@@ -186,6 +186,10 @@ export interface FrameHost {
186
186
  serialize(value: unknown): { $ref: string };
187
187
  /** `frameId` is the resolving frame's id — route to its stream's table. */
188
188
  resolve(ref: { $ref: string }, frameId?: string): unknown;
189
+ /** See FrameHostOptions.revive. */
190
+ revive?(value: unknown): unknown;
191
+ /** See FrameHostOptions.isContainer. */
192
+ isContainer?(value: unknown): boolean;
189
193
  }
190
194
 
191
195
  /**
@@ -219,6 +223,28 @@ export interface FrameHostOptions {
219
223
  * `applyData: c => table.apply(c)` (see `createJSONDataTable`).
220
224
  */
221
225
  applyData?(chunk: Extract<FrameChunk, { type: "data" }>): void;
226
+ /**
227
+ * A lazily-loaded deserializer's load, awaited by the transport before it
228
+ * delivers a `data` chunk — `applyData`/`resolve` can assume the codec is
229
+ * resident once data has arrived. Keeps codec weight out of the eager
230
+ * client graph for responses that never carry serialized data.
231
+ */
232
+ prepareData?(): Promise<unknown>;
233
+ /**
234
+ * Revive protocol markers inside LITERAL slot args (values that are
235
+ * neither `{$ref}` nor `{$frame}`) at arg-resolution time. Document-face
236
+ * container traces ride this way — inline in the record, revived by the
237
+ * integration (`reviveContainerTraces`) into live local containers.
238
+ */
239
+ revive?(value: unknown): unknown;
240
+ /**
241
+ * Whether a resolved arg value is a LIVE CONTAINER (a materialized trace —
242
+ * see `isMaterializedContainer`). The record-dedupe compare must know: a
243
+ * pending container's property reads throw not-ready, so async probes and
244
+ * serialization compares would detonate it. Containers compare by
245
+ * identity only.
246
+ */
247
+ isContainer?(value: unknown): boolean;
222
248
  }
223
249
 
224
250
  /** @experimental */
@@ -3,7 +3,7 @@
3
3
  // shapes and the wire format may change between prereleases (RFC 11).
4
4
  // Every export in this module is @experimental.
5
5
  import { FrameChunk, FrameHost } from "./frame-client.js";
6
- import { JSONCodecOptions } from "./serializer.js";
6
+ import { JSONCodecOptions } from "./serializer-decode.js";
7
7
 
8
8
  // Structural mirror of server-functions/shared.js's FlightDataConsumer:
9
9
  // this file may only reference siblings that ship with it when integrations