diff --git a/packages/qwik-city/middleware/request-handler/request-event.ts b/packages/qwik-city/middleware/request-handler/request-event.ts index 66d56857125..b1e2684e984 100644 --- a/packages/qwik-city/middleware/request-handler/request-event.ts +++ b/packages/qwik-city/middleware/request-handler/request-event.ts @@ -331,13 +331,15 @@ const parseRequest = async ( const data = query.get(QDATA_KEY); if (data) { try { - return qwikSerializer._deserialize(decodeURIComponent(data)) as JSONValue | undefined; + const deserializedData = qwikSerializer._deserialize(decodeURIComponent(data)); + return [deserializedData] as JSONValue | undefined; } catch (err) { // } } } - return qwikSerializer._deserialize(await request.text()) as JSONValue | undefined; + const deserializedData = qwikSerializer._deserialize(await request.text()); + return [deserializedData] as JSONValue | undefined; } return undefined; }; diff --git a/packages/qwik-city/middleware/request-handler/resolve-request-handlers.ts b/packages/qwik-city/middleware/request-handler/resolve-request-handlers.ts index f6a7a177556..38dd607e057 100644 --- a/packages/qwik-city/middleware/request-handler/resolve-request-handlers.ts +++ b/packages/qwik-city/middleware/request-handler/resolve-request-handlers.ts @@ -295,7 +295,7 @@ async function pureServerFunction(ev: RequestEvent) { ev.exit(); const isDev = getRequestMode(ev) === 'dev'; const qwikSerializer = (ev as RequestEventInternal)[RequestEvQwikSerializer]; - const data = await ev.parseBody(); + const [data] = (await ev.parseBody()) as unknown[]; if (Array.isArray(data)) { const [qrl, ...args] = data; if (isQrl(qrl) && qrl.getHash() === fn) { @@ -311,11 +311,11 @@ async function pureServerFunction(ev: RequestEvent) { } catch (err) { if (err instanceof ServerError) { ev.headers.set('Content-Type', 'application/qwik-json'); - ev.send(err.status, await qwikSerializer._serialize(err.data)); + ev.send(err.status, await qwikSerializer._serialize([err.data])); return; } ev.headers.set('Content-Type', 'application/qwik-json'); - ev.send(500, await qwikSerializer._serialize(err)); + ev.send(500, await qwikSerializer._serialize([err])); return; } if (isAsyncIterator(result)) { @@ -326,7 +326,7 @@ async function pureServerFunction(ev: RequestEvent) { if (isDev) { verifySerializable(qwikSerializer, item, qrl); } - const message = await qwikSerializer._serialize(item); + const message = await qwikSerializer._serialize([item]); if (ev.signal.aborted) { break; } @@ -336,7 +336,7 @@ async function pureServerFunction(ev: RequestEvent) { } else { verifySerializable(qwikSerializer, result, qrl); ev.headers.set('Content-Type', 'application/qwik-json'); - const message = await qwikSerializer._serialize(result); + const message = await qwikSerializer._serialize([result]); ev.send(200, message); } return; @@ -542,7 +542,7 @@ export async function renderQData(requestEv: RequestEvent) { const writer = requestEv.getWritableStream().getWriter(); const qwikSerializer = (requestEv as RequestEventInternal)[RequestEvQwikSerializer]; // write just the page json data to the response body - const data = await qwikSerializer._serialize(qData); + const data = await qwikSerializer._serialize([qData]); writer.write(encoder.encode(data)); requestEv.sharedMap.set('qData', qData); diff --git a/packages/qwik-city/runtime/src/server-functions.ts b/packages/qwik-city/runtime/src/server-functions.ts index dd7adcec8e9..f647a6d48b2 100644 --- a/packages/qwik-city/runtime/src/server-functions.ts +++ b/packages/qwik-city/runtime/src/server-functions.ts @@ -243,7 +243,10 @@ export const zodQrl = (( return z.object(obj); } }); - const data = inputData ?? (await ev.parseBody()); + let data = inputData; + if (!data) { + data = await ev.parseBody(); + } const result = await (await schema).safeParseAsync(data); if (result.success) { return result; @@ -376,7 +379,7 @@ export const serverQrl = ( })(); } else if (contentType === 'application/qwik-json') { const str = await res.text(); - const obj = _deserialize(str, ctxElm ?? document.documentElement); + const [obj] = _deserialize(str, ctxElm ?? document.documentElement); if (res.status === 500) { throw obj; } @@ -456,7 +459,8 @@ const deserializeStream = async function* ( const lines = buffer.split(/\n/); buffer = lines.pop()!; for (const line of lines) { - yield _deserialize(line, ctxElm); + const [deserializedData] = _deserialize(line, ctxElm); + yield deserializedData; } } } finally { diff --git a/packages/qwik-city/runtime/src/use-endpoint.ts b/packages/qwik-city/runtime/src/use-endpoint.ts index 5364c803dfd..67746fd71d1 100644 --- a/packages/qwik-city/runtime/src/use-endpoint.ts +++ b/packages/qwik-city/runtime/src/use-endpoint.ts @@ -42,7 +42,7 @@ export const loadClientData = async ( if ((rsp.headers.get('content-type') || '').includes('json')) { // we are safe we are reading a q-data.json return rsp.text().then((text) => { - const clientData = _deserialize(text, element) as ClientPageData | null; + const [clientData] = _deserialize(text, element) as [ClientPageData]; if (!clientData) { location.href = url.href; return; diff --git a/packages/qwik-city/static/worker-thread.ts b/packages/qwik-city/static/worker-thread.ts index 80b34a56af7..d5b3cd66398 100644 --- a/packages/qwik-city/static/worker-thread.ts +++ b/packages/qwik-city/static/worker-thread.ts @@ -189,7 +189,7 @@ async function workerRender( }; }); - const serialized = await _serialize(qData); + const serialized = await _serialize([qData]); dataWriter.write(serialized); writePromises.push( diff --git a/packages/qwik/src/core/api.md b/packages/qwik/src/core/api.md index 9e4288a4ae1..6a4327716d4 100644 --- a/packages/qwik/src/core/api.md +++ b/packages/qwik/src/core/api.md @@ -220,7 +220,7 @@ export interface DelHTMLAttributes extends Attrs<'del', T> { } // @internal (undocumented) -export function _deserialize(rawStateData: string, element?: unknown): unknown[] | unknown; +export function _deserialize(rawStateData: string | null, element?: unknown): unknown[]; // @internal (undocumented) export const _deserializeData: (data: string, element?: unknown) => any; @@ -1047,7 +1047,7 @@ export interface SelectHTMLAttributes extends Attrs<'select', } // @internal (undocumented) -export function _serialize(data: unknown): Promise; +export function _serialize(data: unknown[]): Promise; // @internal (undocumented) export const _serializeData: (data: any, pureQRL?: boolean) => Promise; diff --git a/packages/qwik/src/core/v2/shared/shared-serialization.ts b/packages/qwik/src/core/v2/shared/shared-serialization.ts index ae957754d4c..e66ac6975c3 100644 --- a/packages/qwik/src/core/v2/shared/shared-serialization.ts +++ b/packages/qwik/src/core/v2/shared/shared-serialization.ts @@ -43,7 +43,7 @@ import { getOrCreateProxy, isStore } from '../../state/store'; import { Task, type ResourceReturnInternal } from '../../use/use-task'; import { throwErrorAndStop } from '../../util/log'; import { isPromise } from '../../util/promises'; -import type { ValueOrPromise } from '../../util/types'; +import { isSerializableObject, type ValueOrPromise } from '../../util/types'; import { getDomContainer, type DomContainer } from '../client/dom-container'; import { vnode_getNode, vnode_isVNode, vnode_locate } from '../client/vnode'; import type { SymbolToChunkResolver } from '../ssr/ssr-types'; @@ -1243,7 +1243,7 @@ export function qrlToString( } /** @internal */ -export async function _serialize(data: unknown): Promise { +export async function _serialize(data: unknown[]): Promise { const serializationContext = createSerializationContext( null, new WeakMap(), @@ -1251,9 +1251,7 @@ export async function _serialize(data: unknown): Promise { () => {} ); - const roots = Array.isArray(data) ? data : [data]; - - for (const root of roots) { + for (const root of data) { serializationContext.$addRoot$(root); } await serializationContext.$breakCircularDepsAndAwaitPromises$(); @@ -1262,10 +1260,13 @@ export async function _serialize(data: unknown): Promise { } /** @internal */ -export function _deserialize(rawStateData: string, element?: unknown): unknown[] | unknown { +export function _deserialize(rawStateData: string | null, element?: unknown): unknown[] { + if (rawStateData == null) { + return []; + } const stateData = JSON.parse(rawStateData); if (!Array.isArray(stateData)) { - return null; + return []; } let container: DomContainer | undefined = undefined; @@ -1281,7 +1282,7 @@ export function _deserialize(rawStateData: string, element?: unknown): unknown[] function deserializeData( stateData: unknown[], - serializedData: string, + serializedData: unknown, container?: DeserializeContainer ) { let typeCode: number; @@ -1301,6 +1302,41 @@ function deserializeData( } } return propValue; + } else if (serializedData && typeof serializedData === 'object') { + if (Array.isArray(serializedData)) { + return deserializeArray(stateData, serializedData, container); + } else { + return deserializeObject(stateData, serializedData, container); + } + } + return serializedData; +} + +function deserializeObject( + stateData: unknown[], + serializedData: object, + container?: DeserializeContainer +) { + if (!isSerializableObject(serializedData)) { + return serializedData; + } + for (const key in serializedData) { + if (Object.prototype.hasOwnProperty.call(serializedData, key)) { + const value = serializedData[key]; + serializedData[key] = deserializeData(stateData, value, container); + } + } + return serializedData; +} + +function deserializeArray( + stateData: unknown[], + serializedData: Array, + container?: DeserializeContainer +) { + for (let i = 0; i < serializedData.length; i++) { + const value = serializedData[i]; + serializedData[i] = deserializeData(stateData, value, container); } return serializedData; }