Skip to content

Commit

Permalink
434737e feat(server$): config argument, optional GET, ServerError (#6…
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrickJS committed May 10, 2024
1 parent 152398a commit 50ec079
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 41 deletions.
13 changes: 11 additions & 2 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,11 +534,20 @@ export declare type RouteNavigate = QRL<(path?: string, options?: {
export declare const RouterOutlet: Component<unknown>;

/** @public */
export declare const server$: <T extends ServerFunction>(first: T) => ServerQRL<T>;
export declare const server$: <T extends ServerFunction>(first: T, options?: ServerConfig | undefined) => ServerQRL<T>;

/** @public */
declare interface ServerConfig {
origin?: string;
method?: 'get' | 'post';
headers?: Record<string, string>;
fetchOptions?: any;
}

/** @public */
export declare type ServerFunction = {
(this: RequestEventBase, ...args: any[]): any;
options?: ServerConfig;
};

/**
Expand All @@ -550,7 +559,7 @@ export declare type ServerFunction = {
export declare type ServerQRL<T extends ServerFunction> = QRL<((abort: AbortSignal, ...args: Parameters<T>) => ReturnType<T>) | ((...args: Parameters<T>) => ReturnType<T>)>;

/** @public */
export declare const serverQrl: <T extends ServerFunction>(qrl: QRL<T>) => ServerQRL<T>;
export declare const serverQrl: <T extends ServerFunction>(qrl: QRL<T>, options?: ServerConfig) => ServerQRL<T>;

/** @public */
export declare const ServiceWorkerRegister: (props: {
Expand Down
43 changes: 30 additions & 13 deletions index.qwik.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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* () {
Expand All @@ -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) => {
Expand Down
43 changes: 30 additions & 13 deletions index.qwik.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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* () {
Expand All @@ -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) => {
Expand Down
30 changes: 27 additions & 3 deletions middleware/request-handler/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,21 @@ var request_handler_exports = {};
__export(request_handler_exports, {
AbortMessage: () => AbortMessage,
RedirectMessage: () => RedirectMessage,
ServerError: () => ServerError,
getErrorHtml: () => getErrorHtml,
mergeHeadersCookies: () => mergeHeadersCookies,
requestHandler: () => requestHandler
});
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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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") {
Expand All @@ -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;
Expand Down Expand Up @@ -1423,6 +1446,7 @@ async function loadRequestHandlers(qwikCityPlan, pathname, method, checkOrigin,
0 && (module.exports = {
AbortMessage,
RedirectMessage,
ServerError,
getErrorHtml,
mergeHeadersCookies,
requestHandler
Expand Down
7 changes: 7 additions & 0 deletions middleware/request-handler/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,13 @@ declare interface SendMethod {
(response: Response): AbortMessage;
}

/** @public */
export declare class ServerError<T = Record<any, any>> 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.
Expand Down
29 changes: 26 additions & 3 deletions middleware/request-handler/index.mjs
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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") {
Expand All @@ -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;
Expand Down Expand Up @@ -1382,6 +1404,7 @@ async function loadRequestHandlers(qwikCityPlan, pathname, method, checkOrigin,
export {
AbortMessage,
RedirectMessage,
ServerError,
getErrorHtml,
mergeHeadersCookies,
requestHandler
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
Loading

0 comments on commit 50ec079

Please sign in to comment.