diff --git a/index.d.ts b/index.d.ts index edee4ec..a8c37f3 100644 --- a/index.d.ts +++ b/index.d.ts @@ -534,11 +534,20 @@ export declare type RouteNavigate = QRL<(path?: string, options?: { export declare const RouterOutlet: Component; /** @public */ -export declare const server$: (first: T) => ServerQRL; +export declare const server$: (first: T, options?: ServerConfig | undefined) => ServerQRL; + +/** @public */ +declare interface ServerConfig { + origin?: string; + method?: 'get' | 'post'; + headers?: Record; + fetchOptions?: any; +} /** @public */ export declare type ServerFunction = { (this: RequestEventBase, ...args: any[]): any; + options?: ServerConfig; }; /** @@ -550,7 +559,7 @@ export declare type ServerFunction = { export declare type ServerQRL = QRL<((abort: AbortSignal, ...args: Parameters) => ReturnType) | ((...args: Parameters) => ReturnType)>; /** @public */ -export declare const serverQrl: (qrl: QRL) => ServerQRL; +export declare const serverQrl: (qrl: QRL, options?: ServerConfig) => ServerQRL; /** @public */ export declare const ServiceWorkerRegister: (props: { diff --git a/index.qwik.cjs b/index.qwik.cjs index 3cf0d10..04c8f98 100644 --- a/index.qwik.cjs +++ b/index.qwik.cjs @@ -231,6 +231,7 @@ const CLIENT_DATA_CACHE = /* @__PURE__ */ new Map(); const PREFETCHED_NAVIGATE_PATHS = /* @__PURE__ */ new Set(); const QACTION_KEY = "qaction"; const QFN_KEY = "qfunc"; +const QDATA_KEY = "qdata"; const toPath = (url) => url.pathname + url.search + url.hash; const toUrl = (url, baseUrl) => new URL(url, baseUrl.href); const isSameOrigin = (a, b) => a.origin === b.origin; @@ -1270,15 +1271,19 @@ const zodQrl = (qrl) => { return void 0; }; const zod$ = /* @__PURE__ */ qwik.implicit$FirstArg(zodQrl); -const serverQrl = (qrl) => { +const serverQrl = (qrl, options) => { if (build.isServer) { const captured = qrl.getCaptured(); if (captured && captured.length > 0 && !qwik._getContextElement()) throw new Error("For security reasons, we cannot serialize QRLs that capture lexical scope."); } - function stuff() { + const method = options?.method?.toUpperCase?.() || "POST"; + const headers = options?.headers || {}; + const origin = options?.origin || ""; + const fetchOptions = options?.fetchOptions || {}; + function rpc() { return /* @__PURE__ */ qwik.inlinedQrl(async function(...args) { - const [qrl2] = qwik.useLexicalScope(); + const [fetchOptions2, headers2, method2, origin2, qrl2] = qwik.useLexicalScope(); const signal = args.length > 0 && args[0] instanceof AbortSignal ? args.shift() : void 0; if (build.isServer) { let requestEvent = globalThis.qcAsyncRequestStore?.getStore(); @@ -1303,19 +1308,27 @@ const serverQrl = (qrl) => { return arg; }); const hash = qrl2.getHash(); - const res = await fetch(`?${QFN_KEY}=${hash}`, { - method: "POST", + let query = ""; + const config = { + ...fetchOptions2, + method: method2, headers: { + ...headers2, "Content-Type": "application/qwik-json", // Required so we don't call accidentally "X-QRL": hash }, - signal, - body: await qwik._serializeData([ - qrl2, - ...filtered - ], false) - }); + signal + }; + const body = await qwik._serializeData([ + qrl2, + ...filtered + ], false); + if (method2 === "GET") + query += `&${QDATA_KEY}=${encodeURIComponent(body)}`; + else + config.body = body; + const res = await fetch(`${origin2}?${QFN_KEY}=${hash}${query}`, config); const contentType = res.headers.get("Content-Type"); if (res.ok && contentType === "text/qwik-json-stream" && res.body) return async function* () { @@ -1335,11 +1348,15 @@ const serverQrl = (qrl) => { return obj; } } - }, "serverQrl_stuff_wOIPfiQ04l4", [ + }, "serverQrl_rpc_SGytLJ8uq8I", [ + fetchOptions, + headers, + method, + origin, qrl ]); } - return stuff(); + return rpc(); }; const server$ = /* @__PURE__ */ qwik.implicit$FirstArg(serverQrl); const getValidators = (rest, qrl) => { diff --git a/index.qwik.mjs b/index.qwik.mjs index 079a840..a7beaf2 100644 --- a/index.qwik.mjs +++ b/index.qwik.mjs @@ -213,6 +213,7 @@ const CLIENT_DATA_CACHE = /* @__PURE__ */ new Map(); const PREFETCHED_NAVIGATE_PATHS = /* @__PURE__ */ new Set(); const QACTION_KEY = "qaction"; const QFN_KEY = "qfunc"; +const QDATA_KEY = "qdata"; const toPath = (url) => url.pathname + url.search + url.hash; const toUrl = (url, baseUrl) => new URL(url, baseUrl.href); const isSameOrigin = (a, b) => a.origin === b.origin; @@ -1252,15 +1253,19 @@ const zodQrl = (qrl) => { return void 0; }; const zod$ = /* @__PURE__ */ implicit$FirstArg(zodQrl); -const serverQrl = (qrl) => { +const serverQrl = (qrl, options) => { if (isServer) { const captured = qrl.getCaptured(); if (captured && captured.length > 0 && !_getContextElement()) throw new Error("For security reasons, we cannot serialize QRLs that capture lexical scope."); } - function stuff() { + const method = options?.method?.toUpperCase?.() || "POST"; + const headers = options?.headers || {}; + const origin = options?.origin || ""; + const fetchOptions = options?.fetchOptions || {}; + function rpc() { return /* @__PURE__ */ inlinedQrl(async function(...args) { - const [qrl2] = useLexicalScope(); + const [fetchOptions2, headers2, method2, origin2, qrl2] = useLexicalScope(); const signal = args.length > 0 && args[0] instanceof AbortSignal ? args.shift() : void 0; if (isServer) { let requestEvent = globalThis.qcAsyncRequestStore?.getStore(); @@ -1285,19 +1290,27 @@ const serverQrl = (qrl) => { return arg; }); const hash = qrl2.getHash(); - const res = await fetch(`?${QFN_KEY}=${hash}`, { - method: "POST", + let query = ""; + const config = { + ...fetchOptions2, + method: method2, headers: { + ...headers2, "Content-Type": "application/qwik-json", // Required so we don't call accidentally "X-QRL": hash }, - signal, - body: await _serializeData([ - qrl2, - ...filtered - ], false) - }); + signal + }; + const body = await _serializeData([ + qrl2, + ...filtered + ], false); + if (method2 === "GET") + query += `&${QDATA_KEY}=${encodeURIComponent(body)}`; + else + config.body = body; + const res = await fetch(`${origin2}?${QFN_KEY}=${hash}${query}`, config); const contentType = res.headers.get("Content-Type"); if (res.ok && contentType === "text/qwik-json-stream" && res.body) return async function* () { @@ -1317,11 +1330,15 @@ const serverQrl = (qrl) => { return obj; } } - }, "serverQrl_stuff_wOIPfiQ04l4", [ + }, "serverQrl_rpc_SGytLJ8uq8I", [ + fetchOptions, + headers, + method, + origin, qrl ]); } - return stuff(); + return rpc(); }; const server$ = /* @__PURE__ */ implicit$FirstArg(serverQrl); const getValidators = (rest, qrl) => { diff --git a/middleware/request-handler/index.cjs b/middleware/request-handler/index.cjs index f9d3c23..27d5b3d 100644 --- a/middleware/request-handler/index.cjs +++ b/middleware/request-handler/index.cjs @@ -32,6 +32,7 @@ var request_handler_exports = {}; __export(request_handler_exports, { AbortMessage: () => AbortMessage, RedirectMessage: () => RedirectMessage, + ServerError: () => ServerError, getErrorHtml: () => getErrorHtml, mergeHeadersCookies: () => mergeHeadersCookies, requestHandler: () => requestHandler @@ -39,6 +40,13 @@ __export(request_handler_exports, { module.exports = __toCommonJS(request_handler_exports); // packages/qwik-city/middleware/request-handler/error-handler.ts +var ServerError = class extends Error { + constructor(status, data) { + super(); + this.status = status; + this.data = data; + } +}; var ErrorResponse = class extends Error { constructor(status, message) { super(message); @@ -252,6 +260,7 @@ var RedirectMessage = class extends AbortMessage { var MODULE_CACHE = /* @__PURE__ */ new WeakMap(); var QACTION_KEY = "qaction"; var QFN_KEY = "qfunc"; +var QDATA_KEY = "qdata"; // packages/qwik-city/middleware/request-handler/response-page.ts function getQwikCityServerData(requestEv) { @@ -318,7 +327,7 @@ var resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderH requestHandlers.unshift(csrfCheckMiddleware); } if (isPageRoute) { - if (method === "POST") { + if (method === "POST" || method === "GET") { requestHandlers.push(pureServerFunction); } requestHandlers.push(fixTrailingSlash); @@ -535,6 +544,11 @@ async function pureServerFunction(ev) { result = await qrl.apply(ev, args); } } catch (err) { + if (err instanceof ServerError) { + ev.headers.set("Content-Type", "application/qwik-json"); + ev.send(err.status, await qwikSerializer._serializeData(err.data, true)); + return; + } ev.headers.set("Content-Type", "application/qwik-json"); ev.send(500, await qwikSerializer._serializeData(err, true)); return; @@ -1029,7 +1043,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, manif if (requestData !== void 0) { return requestData; } - return requestData = parseRequest(requestEv.request, sharedMap, qwikSerializer); + return requestData = parseRequest(requestEv, sharedMap, qwikSerializer); }, json: (statusCode, data) => { headers.set("Content-Type", "application/json; charset=utf-8"); @@ -1073,7 +1087,7 @@ function getRequestMode(requestEv) { return requestEv[RequestEvMode]; } var ABORT_INDEX = Number.MAX_SAFE_INTEGER; -var parseRequest = async (request, sharedMap, qwikSerializer) => { +var parseRequest = async ({ request, method, query }, sharedMap, qwikSerializer) => { var _a2; const type = ((_a2 = request.headers.get("content-type")) == null ? void 0 : _a2.split(/[;,]/, 1)[0].trim()) ?? ""; if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") { @@ -1084,6 +1098,15 @@ var parseRequest = async (request, sharedMap, qwikSerializer) => { const data = await request.json(); return data; } else if (type === "application/qwik-json") { + if (method === "GET" && query.has(QDATA_KEY)) { + const data = query.get(QDATA_KEY); + if (data) { + try { + return qwikSerializer._deserializeData(decodeURIComponent(data)); + } catch (err) { + } + } + } return qwikSerializer._deserializeData(await request.text()); } return void 0; @@ -1423,6 +1446,7 @@ async function loadRequestHandlers(qwikCityPlan, pathname, method, checkOrigin, 0 && (module.exports = { AbortMessage, RedirectMessage, + ServerError, getErrorHtml, mergeHeadersCookies, requestHandler diff --git a/middleware/request-handler/index.d.ts b/middleware/request-handler/index.d.ts index 631e264..00007ba 100644 --- a/middleware/request-handler/index.d.ts +++ b/middleware/request-handler/index.d.ts @@ -584,6 +584,13 @@ declare interface SendMethod { (response: Response): AbortMessage; } +/** @public */ +export declare class ServerError> extends Error { + status: number; + data: T; + constructor(status: number, data: T); +} + /** * HTTP Server Error Status Codes Status codes in the 5xx range indicate that the server encountered * an error or was unable to fulfill the request due to unexpected conditions. diff --git a/middleware/request-handler/index.mjs b/middleware/request-handler/index.mjs index fe5d135..27dcce3 100644 --- a/middleware/request-handler/index.mjs +++ b/middleware/request-handler/index.mjs @@ -1,4 +1,11 @@ // packages/qwik-city/middleware/request-handler/error-handler.ts +var ServerError = class extends Error { + constructor(status, data) { + super(); + this.status = status; + this.data = data; + } +}; var ErrorResponse = class extends Error { constructor(status, message) { super(message); @@ -212,6 +219,7 @@ var RedirectMessage = class extends AbortMessage { var MODULE_CACHE = /* @__PURE__ */ new WeakMap(); var QACTION_KEY = "qaction"; var QFN_KEY = "qfunc"; +var QDATA_KEY = "qdata"; // packages/qwik-city/middleware/request-handler/response-page.ts function getQwikCityServerData(requestEv) { @@ -278,7 +286,7 @@ var resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderH requestHandlers.unshift(csrfCheckMiddleware); } if (isPageRoute) { - if (method === "POST") { + if (method === "POST" || method === "GET") { requestHandlers.push(pureServerFunction); } requestHandlers.push(fixTrailingSlash); @@ -495,6 +503,11 @@ async function pureServerFunction(ev) { result = await qrl.apply(ev, args); } } catch (err) { + if (err instanceof ServerError) { + ev.headers.set("Content-Type", "application/qwik-json"); + ev.send(err.status, await qwikSerializer._serializeData(err.data, true)); + return; + } ev.headers.set("Content-Type", "application/qwik-json"); ev.send(500, await qwikSerializer._serializeData(err, true)); return; @@ -989,7 +1002,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, manif if (requestData !== void 0) { return requestData; } - return requestData = parseRequest(requestEv.request, sharedMap, qwikSerializer); + return requestData = parseRequest(requestEv, sharedMap, qwikSerializer); }, json: (statusCode, data) => { headers.set("Content-Type", "application/json; charset=utf-8"); @@ -1033,7 +1046,7 @@ function getRequestMode(requestEv) { return requestEv[RequestEvMode]; } var ABORT_INDEX = Number.MAX_SAFE_INTEGER; -var parseRequest = async (request, sharedMap, qwikSerializer) => { +var parseRequest = async ({ request, method, query }, sharedMap, qwikSerializer) => { var _a2; const type = ((_a2 = request.headers.get("content-type")) == null ? void 0 : _a2.split(/[;,]/, 1)[0].trim()) ?? ""; if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") { @@ -1044,6 +1057,15 @@ var parseRequest = async (request, sharedMap, qwikSerializer) => { const data = await request.json(); return data; } else if (type === "application/qwik-json") { + if (method === "GET" && query.has(QDATA_KEY)) { + const data = query.get(QDATA_KEY); + if (data) { + try { + return qwikSerializer._deserializeData(decodeURIComponent(data)); + } catch (err) { + } + } + } return qwikSerializer._deserializeData(await request.text()); } return void 0; @@ -1382,6 +1404,7 @@ async function loadRequestHandlers(qwikCityPlan, pathname, method, checkOrigin, export { AbortMessage, RedirectMessage, + ServerError, getErrorHtml, mergeHeadersCookies, requestHandler diff --git a/package.json b/package.json index 4561607..a59dc7a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@builder.io/qwik-city", "description": "The meta-framework for Qwik.", - "version": "1.5.4-dev20240510131703", + "version": "1.5.4-dev20240510190535", "bugs": "https://github.com/QwikDev/qwik/issues", "dependencies": { "@mdx-js/mdx": "^3.0.1", diff --git a/vite/index.cjs b/vite/index.cjs index 905cbc3..f24b43c 100644 --- a/vite/index.cjs +++ b/vite/index.cjs @@ -24588,6 +24588,13 @@ var Cookie = class { REQ_COOKIE, _a = RES_COOKIE, _b = LIVE_COOKIE; // packages/qwik-city/middleware/request-handler/error-handler.ts +var ServerError = class extends Error { + constructor(status, data) { + super(); + this.status = status; + this.data = data; + } +}; var ErrorResponse = class extends Error { constructor(status, message) { super(message); @@ -24659,6 +24666,7 @@ var RedirectMessage = class extends AbortMessage { // packages/qwik-city/runtime/src/constants.ts var QACTION_KEY = "qaction"; var QFN_KEY = "qfunc"; +var QDATA_KEY = "qdata"; // packages/qwik-city/middleware/request-handler/response-page.ts function getQwikCityServerData(requestEv) { @@ -24725,7 +24733,7 @@ var resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderH requestHandlers.unshift(csrfCheckMiddleware); } if (isPageRoute) { - if (method === "POST") { + if (method === "POST" || method === "GET") { requestHandlers.push(pureServerFunction); } requestHandlers.push(fixTrailingSlash); @@ -24942,6 +24950,11 @@ async function pureServerFunction(ev) { result = await qrl.apply(ev, args); } } catch (err) { + if (err instanceof ServerError) { + ev.headers.set("Content-Type", "application/qwik-json"); + ev.send(err.status, await qwikSerializer._serializeData(err.data, true)); + return; + } ev.headers.set("Content-Type", "application/qwik-json"); ev.send(500, await qwikSerializer._serializeData(err, true)); return; @@ -25385,7 +25398,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, manif if (requestData !== void 0) { return requestData; } - return requestData = parseRequest(requestEv.request, sharedMap, qwikSerializer); + return requestData = parseRequest(requestEv, sharedMap, qwikSerializer); }, json: (statusCode, data) => { headers.set("Content-Type", "application/json; charset=utf-8"); @@ -25429,7 +25442,7 @@ function getRequestMode(requestEv) { return requestEv[RequestEvMode]; } var ABORT_INDEX = Number.MAX_SAFE_INTEGER; -var parseRequest = async (request, sharedMap, qwikSerializer) => { +var parseRequest = async ({ request, method, query }, sharedMap, qwikSerializer) => { var _a2; const type = ((_a2 = request.headers.get("content-type")) == null ? void 0 : _a2.split(/[;,]/, 1)[0].trim()) ?? ""; if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") { @@ -25440,6 +25453,15 @@ var parseRequest = async (request, sharedMap, qwikSerializer) => { const data = await request.json(); return data; } else if (type === "application/qwik-json") { + if (method === "GET" && query.has(QDATA_KEY)) { + const data = query.get(QDATA_KEY); + if (data) { + try { + return qwikSerializer._deserializeData(decodeURIComponent(data)); + } catch (err) { + } + } + } return qwikSerializer._deserializeData(await request.text()); } return void 0; diff --git a/vite/index.mjs b/vite/index.mjs index 09618e8..cf79ad8 100644 --- a/vite/index.mjs +++ b/vite/index.mjs @@ -24578,6 +24578,13 @@ var Cookie = class { REQ_COOKIE, _a = RES_COOKIE, _b = LIVE_COOKIE; // packages/qwik-city/middleware/request-handler/error-handler.ts +var ServerError = class extends Error { + constructor(status, data) { + super(); + this.status = status; + this.data = data; + } +}; var ErrorResponse = class extends Error { constructor(status, message) { super(message); @@ -24649,6 +24656,7 @@ var RedirectMessage = class extends AbortMessage { // packages/qwik-city/runtime/src/constants.ts var QACTION_KEY = "qaction"; var QFN_KEY = "qfunc"; +var QDATA_KEY = "qdata"; // packages/qwik-city/middleware/request-handler/response-page.ts function getQwikCityServerData(requestEv) { @@ -24715,7 +24723,7 @@ var resolveRequestHandlers = (serverPlugins, route, method, checkOrigin, renderH requestHandlers.unshift(csrfCheckMiddleware); } if (isPageRoute) { - if (method === "POST") { + if (method === "POST" || method === "GET") { requestHandlers.push(pureServerFunction); } requestHandlers.push(fixTrailingSlash); @@ -24932,6 +24940,11 @@ async function pureServerFunction(ev) { result = await qrl.apply(ev, args); } } catch (err) { + if (err instanceof ServerError) { + ev.headers.set("Content-Type", "application/qwik-json"); + ev.send(err.status, await qwikSerializer._serializeData(err.data, true)); + return; + } ev.headers.set("Content-Type", "application/qwik-json"); ev.send(500, await qwikSerializer._serializeData(err, true)); return; @@ -25375,7 +25388,7 @@ function createRequestEvent(serverRequestEv, loadedRoute, requestHandlers, manif if (requestData !== void 0) { return requestData; } - return requestData = parseRequest(requestEv.request, sharedMap, qwikSerializer); + return requestData = parseRequest(requestEv, sharedMap, qwikSerializer); }, json: (statusCode, data) => { headers.set("Content-Type", "application/json; charset=utf-8"); @@ -25419,7 +25432,7 @@ function getRequestMode(requestEv) { return requestEv[RequestEvMode]; } var ABORT_INDEX = Number.MAX_SAFE_INTEGER; -var parseRequest = async (request, sharedMap, qwikSerializer) => { +var parseRequest = async ({ request, method, query }, sharedMap, qwikSerializer) => { var _a2; const type = ((_a2 = request.headers.get("content-type")) == null ? void 0 : _a2.split(/[;,]/, 1)[0].trim()) ?? ""; if (type === "application/x-www-form-urlencoded" || type === "multipart/form-data") { @@ -25430,6 +25443,15 @@ var parseRequest = async (request, sharedMap, qwikSerializer) => { const data = await request.json(); return data; } else if (type === "application/qwik-json") { + if (method === "GET" && query.has(QDATA_KEY)) { + const data = query.get(QDATA_KEY); + if (data) { + try { + return qwikSerializer._deserializeData(decodeURIComponent(data)); + } catch (err) { + } + } + } return qwikSerializer._deserializeData(await request.text()); } return void 0;