diff --git a/packages/cli/src/lib/api-route-worker.js b/packages/cli/src/lib/api-route-worker.js new file mode 100644 index 000000000..071bb4ea4 --- /dev/null +++ b/packages/cli/src/lib/api-route-worker.js @@ -0,0 +1,43 @@ +// https://github.com/nodejs/modules/issues/307#issuecomment-858729422 +import { parentPort } from 'worker_threads'; + +// based on https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript +async function responseAsObject (response) { + if (!response instanceof Response) { + throw Object.assign( + new Error(), + { name: 'TypeError', message: 'Argument must be a Response object' } + ); + } + response = response.clone(); + + function stringifiableObject (obj) { + const filtered = {}; + for (const key in obj) { + if (['boolean', 'number', 'string'].includes(typeof obj[key]) || obj[key] === null) { + filtered[key] = obj[key]; + } + } + return filtered; + } + + return { + ...stringifiableObject(response), + headers: Object.fromEntries(response.headers), + // signal: stringifiableObject(request.signal), + body: await response.text() + }; +} + +async function executeRouteModule({ href, request }) { + // console.log('WORKER', { href, message, request }) + const { handler } = await import(href); + const response = await handler(request); + + // console.log('WORKER', { response }) + parentPort.postMessage(await responseAsObject(response)); +} + +parentPort.on('message', async (task) => { + await executeRouteModule(task); +}); \ No newline at end of file diff --git a/packages/cli/src/plugins/resource/plugin-api-routes.js b/packages/cli/src/plugins/resource/plugin-api-routes.js index d3da4e302..4a1e8234a 100644 --- a/packages/cli/src/plugins/resource/plugin-api-routes.js +++ b/packages/cli/src/plugins/resource/plugin-api-routes.js @@ -4,6 +4,35 @@ * */ import { ResourceInterface } from '../../lib/resource-interface.js'; +import { Worker } from 'worker_threads'; + +// https://stackoverflow.com/questions/57447685/how-can-i-convert-a-request-object-into-a-stringifiable-object-in-javascript +function requestAsObject (request) { + if (!request instanceof Request) { + throw Object.assign( + new Error(), + { name: 'TypeError', message: 'Argument must be a Request object' } + ); + } + request = request.clone(); + + function stringifiableObject (obj) { + const filtered = {}; + for (const key in obj) { + if (['boolean', 'number', 'string'].includes(typeof obj[key]) || obj[key] === null) { + filtered[key] = obj[key]; + } + } + return filtered; + } + + return { + ...stringifiableObject(request), + headers: Object.fromEntries(request.headers), + signal: stringifiableObject(request.signal) + // bodyText: await request.text(), // requires function to be async + }; +} class ApiRoutesResource extends ResourceInterface { constructor(compilation, options) { @@ -19,17 +48,46 @@ class ApiRoutesResource extends ResourceInterface { async serve(url, request) { const api = this.compilation.manifest.apis.get(url.pathname); const apiUrl = new URL(`.${api.path}`, this.compilation.context.userWorkspace); - // https://github.com/nodejs/modules/issues/307#issuecomment-1165387383 - const href = process.env.__GWD_COMMAND__ === 'develop' // eslint-disable-line no-underscore-dangle - ? `${apiUrl.href}?t=${Date.now()}` - : apiUrl.href; - - const { handler } = await import(href); - const req = new Request(new URL(`${request.url.origin}${url}`), { + const href = apiUrl.href; + const req = new Request(new URL(url), { ...request }); - return await handler(req); + // console.log({ req }); + + // TODO does this ever run in anything but development mode? + if (process.env.__GWD_COMMAND__ === 'develop') { // eslint-disable-line no-underscore-dangle + const workerUrl = new URL('../../lib/api-route-worker.js', import.meta.url); + + const response = await new Promise((resolve, reject) => { + const worker = new Worker(workerUrl); + const req = requestAsObject(request); + + worker.on('message', (result) => { + // console.log('RESULT =====>', { result }); + resolve(result); + }); + worker.on('error', reject); + worker.on('exit', (code) => { + if (code !== 0) { + reject(new Error(`Worker stopped with exit code ${code}`)); + } + }); + + // console.log('OUT', { reqUnrevied }); + + worker.postMessage({ href, request: req }); + }); + + // console.log('SUCCESS', {response}) + return new Response(response.body, { + ...response + }); + } else { + const { handler } = await import(href); + + return await handler(req); + } } }