commit ab802a8af1a5fa9b9087c0c26ff228f1ed8b6e48 Author: Peter Stockings Date: Sat Jul 26 11:55:35 2025 +1000 Initial setup diff --git a/.buildpacks b/.buildpacks new file mode 100644 index 0000000..40eedd1 --- /dev/null +++ b/.buildpacks @@ -0,0 +1 @@ +https://github.com/dokku/buildpack-deno diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..dc4aff4 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: deno run --allow-net --allow-read deno_server.ts \ No newline at end of file diff --git a/deno.jsonc b/deno.jsonc new file mode 100644 index 0000000..f2a3d5c --- /dev/null +++ b/deno.jsonc @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "lib": ["deno.window", "deno.worker"] + }, + "lint": { + "files": { + "include": ["./*.ts"] + }, + "rules": { + "tags": ["recommended"], + "exclude": ["no-explicit-any"] + } + }, + "fmt": { + "files": { + "include": ["./*.ts"] + }, + "options": { + "useTabs": false, + "lineWidth": 80, + "indentWidth": 2, + "singleQuote": false + } + }, + "tasks": { + "start": "deno run --allow-net --allow-read --unstable-worker-options deno_server.ts" + } +} \ No newline at end of file diff --git a/deno.lock b/deno.lock new file mode 100644 index 0000000..80dacf2 --- /dev/null +++ b/deno.lock @@ -0,0 +1,96 @@ +{ + "version": "5", + "specifiers": { + "jsr:@b-fuze/deno-dom@*": "0.1.52" + }, + "jsr": { + "@b-fuze/deno-dom@0.1.52": { + "integrity": "ddd89594d2bd795cfa2c4dc5f93a94694a8a75b8c17c6b909e8c86157cc90b65" + } + }, + "redirects": { + "https://deno.land/x/deno_dom/deno-dom-wasm.ts": "https://deno.land/x/deno_dom@v0.1.53/deno-dom-wasm.ts", + "https://esm.sh/lodash": "https://esm.sh/lodash@4.17.21" + }, + "remote": { + "https://deno.land/std@0.140.0/async/abortable.ts": "87aa7230be8360c24ad437212311c9e8d4328854baec27b4c7abb26e85515c06", + "https://deno.land/std@0.140.0/async/deadline.ts": "48ac998d7564969f3e6ec6b6f9bf0217ebd00239b1b2292feba61272d5dd58d0", + "https://deno.land/std@0.140.0/async/debounce.ts": "564273ef242bcfcda19a439132f940db8694173abffc159ea34f07d18fc42620", + "https://deno.land/std@0.140.0/async/deferred.ts": "bc18e28108252c9f67dfca2bbc4587c3cbf3aeb6e155f8c864ca8ecff992b98a", + "https://deno.land/std@0.140.0/async/delay.ts": "cbbdf1c87d1aed8edc7bae13592fb3e27e3106e0748f089c263390d4f49e5f6c", + "https://deno.land/std@0.140.0/async/mod.ts": "6e42e275b44367361a81842dd1a789c55ab206d7c8a877d7163ab5c460625be6", + "https://deno.land/std@0.140.0/async/mux_async_iterator.ts": "f4d1d259b0c694d381770ddaaa4b799a94843eba80c17f4a2ec2949168e52d1e", + "https://deno.land/std@0.140.0/async/pool.ts": "97b0dd27c69544e374df857a40902e74e39532f226005543eabacb551e277082", + "https://deno.land/std@0.140.0/async/tee.ts": "1341feb1f5b1a96f8628d0f8fc07d8c43d3813423f18a63bf1b4785568d21b1f", + "https://deno.land/std@0.140.0/http/server.ts": "3da75405704bebcf212a55966a68a489f7e094ba52b5d38f181fe0ef8461a55d", + "https://deno.land/std@0.201.0/assert/assert.ts": "9a97dad6d98c238938e7540736b826440ad8c1c1e54430ca4c4e623e585607ee", + "https://deno.land/std@0.201.0/assert/assertion_error.ts": "4d0bde9b374dfbcbe8ac23f54f567b77024fb67dbb1906a852d67fe050d42f56", + "https://deno.land/std@0.201.0/async/delay.ts": "a6142eb44cdd856b645086af2b811b1fcce08ec06bb7d50969e6a872ee9b8659", + "https://deno.land/std@0.201.0/http/server.ts": "1b2403b3c544c0624ad23e8ca4e05877e65380d9e0d75d04957432d65c3d5f41", + "https://deno.land/std@0.201.0/path/_basename.ts": "057d420c9049821f983f784fd87fa73ac471901fb628920b67972b0f44319343", + "https://deno.land/std@0.201.0/path/_constants.ts": "e49961f6f4f48039c0dfed3c3f93e963ca3d92791c9d478ac5b43183413136e0", + "https://deno.land/std@0.201.0/path/_dirname.ts": "355e297236b2218600aee7a5301b937204c62e12da9db4b0b044993d9e658395", + "https://deno.land/std@0.201.0/path/_extname.ts": "eaaa5aae1acf1f03254d681bd6a8ce42a9cb5b7ff2213a9d4740e8ab31283664", + "https://deno.land/std@0.201.0/path/_format.ts": "4a99270d6810f082e614309164fad75d6f1a483b68eed97c830a506cc589f8b4", + "https://deno.land/std@0.201.0/path/_from_file_url.ts": "6eadfae2e6f63ad9ee46b26db4a1b16583055c0392acedfb50ed2fc694b6f581", + "https://deno.land/std@0.201.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", + "https://deno.land/std@0.201.0/path/_is_absolute.ts": "05dac10b5e93c63198b92e3687baa2be178df5321c527dc555266c0f4f51558c", + "https://deno.land/std@0.201.0/path/_join.ts": "815f5e85b042285175b1492dd5781240ce126c23bd97bad6b8211fe7129c538e", + "https://deno.land/std@0.201.0/path/_normalize.ts": "a19ec8706b2707f9dd974662a5cd89fad438e62ab1857e08b314a8eb49a34d81", + "https://deno.land/std@0.201.0/path/_os.ts": "d932f56d41e4f6a6093d56044e29ce637f8dcc43c5a90af43504a889cf1775e3", + "https://deno.land/std@0.201.0/path/_parse.ts": "0f9b0ff43682dd9964eb1c4398610c4e165d8db9d3ac9d594220217adf480cfa", + "https://deno.land/std@0.201.0/path/_relative.ts": "27bdeffb5311a47d85be26d37ad1969979359f7636c5cd9fcf05dcd0d5099dc5", + "https://deno.land/std@0.201.0/path/_resolve.ts": "7a3616f1093735ed327e758313b79c3c04ea921808ca5f19ddf240cb68d0adf6", + "https://deno.land/std@0.201.0/path/_to_file_url.ts": "a141e4a525303e1a3a0c0571fd024552b5f3553a2af7d75d1ff3a503dcbb66d8", + "https://deno.land/std@0.201.0/path/_to_namespaced_path.ts": "0d5f4caa2ed98ef7a8786286df6af804b50e38859ae897b5b5b4c8c5930a75c8", + "https://deno.land/std@0.201.0/path/_util.ts": "4e191b1bac6b3bf0c31aab42e5ca2e01a86ab5a0d2e08b75acf8585047a86221", + "https://deno.land/std@0.201.0/path/basename.ts": "bdfa5a624c6a45564dc6758ef2077f2822978a6dbe77b0a3514f7d1f81362930", + "https://deno.land/std@0.201.0/path/common.ts": "ee7505ab01fd22de3963b64e46cff31f40de34f9f8de1fff6a1bd2fe79380000", + "https://deno.land/std@0.201.0/path/dirname.ts": "b6533f4ee4174a526dec50c279534df5345836dfdc15318400b08c62a62a39dd", + "https://deno.land/std@0.201.0/path/extname.ts": "62c4b376300795342fe1e4746c0de518b4dc9c4b0b4617bfee62a2973a9555cf", + "https://deno.land/std@0.201.0/path/format.ts": "110270b238514dd68455a4c54956215a1aff7e37e22e4427b7771cefe1920aa5", + "https://deno.land/std@0.201.0/path/from_file_url.ts": "9f5cb58d58be14c775ec2e57fc70029ac8b17ed3bd7fe93e475b07280adde0ac", + "https://deno.land/std@0.201.0/path/glob.ts": "593e2c3573883225c25c5a21aaa8e9382a696b8e175ea20a3b6a1471ad17aaed", + "https://deno.land/std@0.201.0/path/is_absolute.ts": "0b92eb35a0a8780e9f16f16bb23655b67dace6a8e0d92d42039e518ee38103c1", + "https://deno.land/std@0.201.0/path/join.ts": "31c5419f23d91655b08ec7aec403f4e4cd1a63d39e28f6e42642ea207c2734f8", + "https://deno.land/std@0.201.0/path/mod.ts": "6e1efb0b13121463aedb53ea51dabf5639a3172ab58c89900bbb72b486872532", + "https://deno.land/std@0.201.0/path/normalize.ts": "6ea523e0040979dd7ae2f1be5bf2083941881a252554c0f32566a18b03021955", + "https://deno.land/std@0.201.0/path/parse.ts": "be8de342bb9e1924d78dc4d93c45215c152db7bf738ec32475560424b119b394", + "https://deno.land/std@0.201.0/path/posix.ts": "0a1c1952d132323a88736d03e92bd236f3ed5f9f079e5823fae07c8d978ee61b", + "https://deno.land/std@0.201.0/path/relative.ts": "8bedac226afd360afc45d451a6c29fabceaf32978526bcb38e0c852661f66c61", + "https://deno.land/std@0.201.0/path/resolve.ts": "133161e4949fc97f9ca67988d51376b0f5eef8968a6372325ab84d39d30b80dc", + "https://deno.land/std@0.201.0/path/separator.ts": "40a3e9a4ad10bef23bc2cd6c610291b6c502a06237c2c4cd034a15ca78dedc1f", + "https://deno.land/std@0.201.0/path/to_file_url.ts": "00e6322373dd51ad109956b775e4e72e5f9fa68ce2c6b04e4af2a6eed3825d31", + "https://deno.land/std@0.201.0/path/to_namespaced_path.ts": "1b1db3055c343ab389901adfbda34e82b7386bcd1c744d54f9c1496ee0fd0c3d", + "https://deno.land/std@0.201.0/path/win32.ts": "8b3f80ef7a462511d5e8020ff490edcaa0a0d118f1b1e9da50e2916bdd73f9dd", + "https://deno.land/x/deno_dom@v0.1.53/build/deno-wasm/deno-wasm.js": "567c1baaf7d3c381cb9c35937403cbaa83bdaa2ae2f4bf59230648c4e3349a0d", + "https://deno.land/x/deno_dom@v0.1.53/build/deno-wasm/deno-wasm_bg-wasm.js": "989a0f81b2270eb172bcaf48de4a98da2a3a3bbcf5df334e64602bc8712aec2e", + "https://deno.land/x/deno_dom@v0.1.53/build/deno-wasm/deno-wasm_bg.wasm": "05645f46bbdd0fb2a0f5ff759885f553919af1586c3a93fdd29393a6c2920728", + "https://deno.land/x/deno_dom@v0.1.53/build/deno-wasm/env.js": "17da784c11b192c591dbf7df21ec6f65f63f45ca9a713e4ca100f3c886b4023f", + "https://deno.land/x/deno_dom@v0.1.53/build/deno-wasm/wbg.js": "b5b6de12bc010fa23438c8a8276067b9044a405da516d0bbe4ae61b7c6d61a22", + "https://deno.land/x/deno_dom@v0.1.53/deno-dom-wasm.ts": "4f8ede24992eb4b3426c3930a511d9f33e4db8fe81ca8d1c63ed12f0a76ac3a4", + "https://deno.land/x/deno_dom@v0.1.53/src/api.ts": "0ff5790f0a3eeecb4e00b7d8fbfa319b165962cf6d0182a65ba90f158d74f7d7", + "https://deno.land/x/deno_dom@v0.1.53/src/constructor-lock.ts": "0e7b297e8b9cf921a3b0d3a692ec5fb462c5afc47ec554292e20090b9e16b40a", + "https://deno.land/x/deno_dom@v0.1.53/src/deserialize.ts": "514953418b7ae558ed7361ad9be21013f46cba2f58bd7f4acc90cf1e89f9c8cf", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/document-fragment.ts": "0b915d094830d43b330dc2fb8012b990f2c815773c6cdcd4a9fdff99fe47412e", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/document.ts": "ad584ac4ce6dce03f0ff6ef4b7db86fd598f9c7824da1387f7f2acd7d6948e4a", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/dom-parser.ts": "784ee0e766d4a01e14420f328053fd3a0016c6b40ee442edc3ae80f5d9777927", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/element.ts": "7d330192fbfd406fb67ab7a3387576fe35fec129b7c52c2ea38615144fa5b12e", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/elements/html-template-element.ts": "1707dfb4cbb145f3bcb94426d7cdedbaa336620d0afed30e99f50fe87ba24a98", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/html-collection.ts": "dcf328e883877f7748d3e20fb6319e739f297a6e24f4b00ec5b1a2f390cfa3c6", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/node-list.ts": "be9793475d82539da8b97a17b6b5538cc723538c10cc5820a23e5e4b2248845d", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/node.ts": "53ada9e4b2ae21f10f5941ff257ed4585920ae392020544648f349c05d15d30c", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/custom-api.ts": "852696bd58e534bc41bd3be9e2250b60b67cd95fd28ed16b1deff1d548531a71", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/nwsapi-types.ts": "c43b36c36acc5d32caabaa54fda8c9d239b2b0fcbce9a28efb93c84aa1021698", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/nwsapi.js": "985d7d8fc1eabbb88946b47a1c44c1b2d4aa79ff23c21424219f1528fa27a2ff", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/selectors.ts": "83eab57be2290fb48e3130533448c93c6c61239f2a2f3b85f1917f80ca0fdc75", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/sizzle-types.ts": "78149e2502409989ce861ed636b813b059e16bc267bb543e7c2b26ef43e4798b", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/selectors/sizzle.js": "c3aed60c1045a106d8e546ac2f85cc82e65f62d9af2f8f515210b9212286682a", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/string-cache.ts": "8e935804f7bac244cc70cec90a28c9f6d30fea14c61c2c4ea48fca274376d786", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/utils-types.ts": "96db30e3e4a75b194201bb9fa30988215da7f91b380fca6a5143e51ece2a8436", + "https://deno.land/x/deno_dom@v0.1.53/src/dom/utils.ts": "bc429635e9204051ba1ecc1b212031b5ee7c6bcd95120c91bef696804aa67e74", + "https://deno.land/x/deno_dom@v0.1.53/src/parser.ts": "e06b2300d693e6ae7564e53dfa5c9a9e97fdb8c044c39c52c8b93b5d60860be3", + "https://esm.sh/lodash@4.17.21": "c2f90ffd948b7a30f054986888bdc2667824115fa48ad583ca8b3a579ca4a5a8", + "https://esm.sh/lodash@4.17.21/denonext/lodash.mjs": "9d2a44e584d91008f61f974c6d0a32bf9afb1563761e60c366af0a293e8c759b" + } +} diff --git a/deno_server.ts b/deno_server.ts new file mode 100644 index 0000000..c9c1e2c --- /dev/null +++ b/deno_server.ts @@ -0,0 +1,73 @@ +// isolator/deno_server.ts +import { serve } from "https://deno.land/std@0.201.0/http/server.ts"; + +const TIMEOUT_MS = 5_000; +const States = Object.freeze({ + TIMEOUT: "TIMEOUT", +}); + +async function handler(req: Request): Promise { + if (req.method !== "POST" || new URL(req.url).pathname !== "/execute") { + return new Response("Not Found", { status: 404 }); + } + + try { + const body = await req.json(); + const { code = "", request = {}, environment = {}, name } = body; + + const worker = new Worker(new URL("./worker.ts", import.meta.url).href, { + type: "module", + // The `deno` option is now stable, but your editor might show a TS error + // if it's not configured for Deno. This will run correctly with Deno. + deno: { + namespace: true, // recommended for workers + permissions: "inherit", + }, + }); + + const executionPromise = new Promise((resolve, reject) => { + worker.onmessage = (e) => { + resolve(e.data); + worker.terminate(); + }; + worker.onerror = (e) => { + reject(new Error(`Worker error: ${e.message}`)); + worker.terminate(); + }; + }); + + const timeoutPromise = new Promise((_, reject) => + setTimeout(() => reject(new Error("Timeout")), TIMEOUT_MS) + ); + + worker.postMessage({ code, request, environment, name }); + + const startTime = performance.now(); + try { + const result = await Promise.race([executionPromise, timeoutPromise]); + return new Response(JSON.stringify(result), { + headers: { "Content-Type": "application/json" }, + }); + } catch (err) { + worker.terminate(); + const executionTime = performance.now() - startTime; + const payload = { + status: States.TIMEOUT, + result: err.message, + logs: [], + environment: environment, + execution_time: executionTime, + }; + return new Response(JSON.stringify(payload), { + status: 500, + headers: { "Content-Type": "application/json" }, + }); + } + } catch (e) { + return new Response(`Bad Request: ${e.message}`, { status: 400 }); + } +} + +console.log("⚡ Deno server ready on :8000"); + +await serve(handler, { port: 8000 }); diff --git a/worker.ts b/worker.ts new file mode 100644 index 0000000..b77800e --- /dev/null +++ b/worker.ts @@ -0,0 +1,93 @@ +// isolator/worker.ts +const States = Object.freeze({ + SUCCESS: "SUCCESS", + NOT_A_FUNCTION: "NOT_A_FUNCTION", + SCRIPT_ERROR: "ERROR", +}); + +self.onmessage = async (e) => { + const { code, request, environment } = e.data; + const logs: any[] = []; + const startTime = performance.now(); + + // Temporarily override console.log and console.error to capture logs + const oldConsoleLog = console.log; + const oldConsoleError = console.error; + console.log = (...args) => { + logs.push(args); + oldConsoleLog.apply(console, args); + }; + console.error = (...args) => { + logs.push(args); + oldConsoleError.apply(console, args); + }; + + try { + // Make the environment object and response helpers available globally. + (self as any).environment = environment; + (self as any).Response = (body = "", headers = {}, status = 200) => ({ + body, + status, + headers, + }); + (self as any).JsonResponse = (body = {}, headers = {}, status = 200) => + (self as any).Response( + JSON.stringify(body), + { "Content-Type": "application/json", ...headers }, + status + ); + (self as any).HtmlResponse = (body = "", headers = {}, status = 200) => + (self as any).Response( + body, + { "Content-Type": "text/html", ...headers }, + status + ); + (self as any).TextResponse = (body = "", headers = {}, status = 200) => + (self as any).Response( + body, + { "Content-Type": "text/plain", ...headers }, + status + ); + + // Use a data URL to import the user's code as an ES module. + const dataUrl = `data:text/javascript;base64,${btoa( + unescape(encodeURIComponent(code)) + )}`; + const userModule = await import(dataUrl); + + if (typeof userModule.default !== "function") { + throw new Error( + "Module does not have a default export or the export is not a function." + ); + } + + const userFn = userModule.default; + const result = await userFn(request); + const executionTime = performance.now() - startTime; + + self.postMessage({ + status: States.SUCCESS, + result, + logs, + environment: (self as any).environment, // Return the mutated environment. + execution_time: executionTime, + }); + } catch (err) { + const executionTime = performance.now() - startTime; + self.postMessage({ + status: States.SCRIPT_ERROR, + result: { + name: err.name, + message: err.message, + stack: err.stack, + }, + logs, + environment, + execution_time: executionTime, + }); + } finally { + // Restore original console functions + console.log = oldConsoleLog; + console.error = oldConsoleError; + } +};