edge-functions 1.0.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (111) hide show
  1. package/.eslintrc.json +60 -15
  2. package/.github/workflows/major.yml +1 -1
  3. package/.github/workflows/minor.yml +1 -2
  4. package/README.md +36 -0
  5. package/aliases.js +1 -1
  6. package/docs/overview.md +4 -3
  7. package/docs/presets.md +18 -6
  8. package/examples/angular-static/package.json +0 -1
  9. package/examples/simple-js-esm/main.js +13 -8
  10. package/examples/simple-js-esm-node/index.js +45 -0
  11. package/examples/simple-ts-esm/main.ts +24 -0
  12. package/examples/simple-ts-esm/messages.ts +11 -0
  13. package/examples/simple-ts-esm/package.json +9 -0
  14. package/examples/simple-ts-esm/tsconfig.json +109 -0
  15. package/examples/simple-ts-esm/yarn.lock +8 -0
  16. package/examples/vue-vite-static/README.md +29 -0
  17. package/examples/vue-vite-static/index.html +13 -0
  18. package/examples/vue-vite-static/package.json +18 -0
  19. package/examples/vue-vite-static/public/favicon.ico +0 -0
  20. package/examples/vue-vite-static/src/App.vue +85 -0
  21. package/examples/vue-vite-static/src/assets/base.css +73 -0
  22. package/examples/vue-vite-static/src/assets/logo.svg +1 -0
  23. package/examples/vue-vite-static/src/assets/main.css +35 -0
  24. package/examples/vue-vite-static/src/components/HelloWorld.vue +44 -0
  25. package/examples/vue-vite-static/src/components/TheWelcome.vue +86 -0
  26. package/examples/vue-vite-static/src/components/WelcomeItem.vue +86 -0
  27. package/examples/vue-vite-static/src/components/icons/IconCommunity.vue +7 -0
  28. package/examples/vue-vite-static/src/components/icons/IconDocumentation.vue +7 -0
  29. package/examples/vue-vite-static/src/components/icons/IconEcosystem.vue +7 -0
  30. package/examples/vue-vite-static/src/components/icons/IconSupport.vue +7 -0
  31. package/examples/vue-vite-static/src/components/icons/IconTooling.vue +19 -0
  32. package/examples/vue-vite-static/src/main.js +11 -0
  33. package/examples/vue-vite-static/src/router/index.js +23 -0
  34. package/examples/vue-vite-static/src/views/AboutView.vue +15 -0
  35. package/examples/vue-vite-static/src/views/HomeView.vue +9 -0
  36. package/examples/vue-vite-static/vite.config.js +16 -0
  37. package/jsconfig.json +1 -1
  38. package/lib/build/bundlers/esbuild/esbuild.config.js +12 -0
  39. package/lib/build/bundlers/esbuild/index.js +46 -0
  40. package/lib/build/bundlers/esbuild/plugins/node-polyfills/index.js +147 -0
  41. package/lib/build/bundlers/esbuild/plugins/node-polyfills/node-polyfills-paths.js +124 -0
  42. package/lib/build/bundlers/index.js +2 -2
  43. package/lib/build/bundlers/webpack/index.js +16 -7
  44. package/lib/build/bundlers/webpack/webpack.config.js +5 -25
  45. package/lib/build/dispatcher/dispatcher.js +116 -66
  46. package/lib/constants/framework-initializer.constants.js +51 -0
  47. package/lib/constants/index.js +4 -1
  48. package/lib/constants/messages/build.messages.js +2 -0
  49. package/lib/constants/messages/env.messages.js +5 -3
  50. package/lib/constants/messages/global.messages.js +4 -1
  51. package/lib/env/polyfills/FetchEvent.polyfills.js +13 -0
  52. package/lib/env/polyfills/fetch.polyfills.js +39 -0
  53. package/lib/env/polyfills/index.js +4 -0
  54. package/lib/env/runtime.env.js +57 -63
  55. package/lib/env/server.env.js +32 -76
  56. package/lib/main.js +234 -90
  57. package/lib/notations/namespaces.js +5 -0
  58. package/lib/platform/actions/core/auth.actions.js +1 -1
  59. package/lib/platform/actions/core/propagation.actions.js +8 -5
  60. package/lib/platform/actions/core/storage.actions.js +1 -1
  61. package/lib/platform/actions/function/showFunctionLogs.actions.js +71 -92
  62. package/lib/platform/edgehooks/debugRequest/debugRequest.hooks.js +28 -0
  63. package/lib/platform/edgehooks/debugRequest/index.js +3 -0
  64. package/lib/platform/edgehooks/index.js +4 -1
  65. package/lib/polyfills/FetchEvent.polyfills.js +13 -0
  66. package/lib/polyfills/fetch.polyfills.js +39 -0
  67. package/lib/polyfills/index.js +4 -0
  68. package/lib/presets/custom/angular/deliver/config.js +2 -4
  69. package/lib/presets/custom/angular/deliver/handler.js +12 -5
  70. package/lib/presets/custom/angular/deliver/prebuild.js +8 -11
  71. package/lib/presets/custom/astro/deliver/config.js +2 -4
  72. package/lib/presets/custom/astro/deliver/handler.js +14 -7
  73. package/lib/presets/custom/astro/deliver/prebuild.js +18 -21
  74. package/lib/presets/custom/hexo/deliver/config.js +2 -4
  75. package/lib/presets/custom/hexo/deliver/handler.js +13 -6
  76. package/lib/presets/custom/hexo/deliver/prebuild.js +18 -21
  77. package/lib/presets/custom/next/deliver/config.js +2 -6
  78. package/lib/presets/custom/next/deliver/handler.js +14 -7
  79. package/lib/presets/custom/next/deliver/prebuild.js +37 -40
  80. package/lib/presets/custom/react/deliver/config.js +2 -4
  81. package/lib/presets/custom/react/deliver/handler.js +10 -3
  82. package/lib/presets/custom/react/deliver/prebuild.js +4 -7
  83. package/lib/presets/custom/vue/deliver/config.js +2 -4
  84. package/lib/presets/custom/vue/deliver/handler.js +10 -3
  85. package/lib/presets/custom/vue/deliver/prebuild.js +66 -10
  86. package/lib/presets/default/html/deliver/config.js +1 -4
  87. package/lib/presets/default/html/deliver/handler.js +14 -7
  88. package/lib/presets/default/html/deliver/prebuild.js +3 -2
  89. package/lib/presets/default/javascript/compute/config.js +2 -5
  90. package/lib/presets/default/javascript/compute/handler.js +15 -4
  91. package/lib/presets/default/javascript/compute/prebuild.js +2 -1
  92. package/lib/presets/default/typescript/compute/config.js +10 -0
  93. package/lib/presets/default/typescript/compute/handler.js +16 -0
  94. package/lib/presets/default/typescript/compute/prebuild.js +7 -0
  95. package/lib/providers/azion/worker.js +6 -2
  96. package/lib/utils/exec/exec.utils.js +34 -23
  97. package/lib/utils/feedback/feedback.utils.js +6 -2
  98. package/lib/utils/getAbsoluteLibDirPath/getAbsoluteLibDirPath.utils.js +11 -2
  99. package/lib/utils/getVulcanBuildId/getVulcanBuildId.utils.js +1 -1
  100. package/lib/utils/index.js +4 -2
  101. package/lib/utils/overrideStaticOutputPath/overrideStaticOutputPath.utils.js +2 -2
  102. package/lib/utils/presets/index.js +3 -0
  103. package/lib/utils/presets/presets.utils.js +169 -0
  104. package/lib/utils/spinner/index.js +3 -0
  105. package/lib/utils/spinner/spinner.utils.js +32 -0
  106. package/package.json +17 -4
  107. package/examples/hexo-static/yarn.lock +0 -1625
  108. package/lib/build/polyfills/index.js +0 -0
  109. package/lib/utils/getPresetsList/getPresetsList.utils.js +0 -50
  110. package/lib/utils/getPresetsList/index.js +0 -3
  111. /package/lib/utils/{getPresetsList/getPresetsList.utils.test.js → presets/presets.utils.test.js} +0 -0
@@ -16,6 +16,8 @@ const build = {
16
16
  },
17
17
  error: {
18
18
  vulcan_build_failed: 'Vulcan build failed.',
19
+ invalid_preset: 'Invalid build preset. Run "vulcan preset ls" to view available presets.',
20
+ invalid_preset_mode: (mode, preset) => `Mode '${mode}' does not exists in preset '${preset}'. Try 'deliver' or 'compute'.`,
19
21
  },
20
22
 
21
23
  };
@@ -19,18 +19,20 @@ const env = {
19
19
  unknown_error: 'An error occurred while executing the script',
20
20
  fetch_event_missing: 'No fetch event handler was defined',
21
21
  fetch_event_unknown_error: 'An error occurred while handling the fetch event:',
22
+ fetch_event_remove_listener: 'Unable to remove event listener.',
23
+ fetch_event_type: (type) => `Unsupported event type: ${type}`,
22
24
  undefined_response: 'No response was defined',
23
25
  },
24
26
  },
25
27
  server: {
26
28
  success: {
27
- server_running: (port) => `Server running on port ${port}`,
29
+ server_running: (port) => `Function running on port ${port}`,
28
30
  },
29
31
  info: {
30
- code_change_detect: 'Code change detected. Restarting server...',
32
+ code_change_detect: 'Code change detected. Restarting...',
31
33
  },
32
34
  errors: {
33
- load_worker_failed: 'Failed to load code:',
35
+ load_worker_failed: (path) => `Failed load worker: ${path}`,
34
36
  },
35
37
  },
36
38
  };
@@ -6,9 +6,12 @@ Global messages object.
6
6
  */
7
7
  const global = {
8
8
  success: {},
9
- info: {},
9
+ info: {
10
+ name_required: 'A name is required.',
11
+ },
10
12
  errors: {
11
13
  unknown_error: 'An error occurred.',
14
+ invalid_choice: 'Invalid choice.',
12
15
  folder_creation_failed: (folder) => `An error occurred while creating the ${folder} folder.`,
13
16
  write_file_failed: (file) => `An error occurred while writing the ${file} file.`,
14
17
  file_doesnt_exist: (file) => `An error occurred while reading the ${file} file.`,
@@ -0,0 +1,13 @@
1
+ import primitives from '@edge-runtime/primitives';
2
+ import { feedback } from '#utils';
3
+
4
+ class FetchEventPolyfill extends primitives.FetchEvent {
5
+ constructor(request) {
6
+ super(request);
7
+ this.console = {
8
+ log: (log) => feedback.server.log(log),
9
+ };
10
+ }
11
+ }
12
+
13
+ export default FetchEventPolyfill;
@@ -0,0 +1,39 @@
1
+ import { join } from 'path';
2
+ import { readFileSync } from 'fs';
3
+ import mime from 'mime-types';
4
+ import { EdgeRuntime } from 'edge-runtime';
5
+
6
+ /**
7
+ * A custom fetch implementation that adds an additional path to the URL if it starts with 'file://'.
8
+ * This function is used to simulate the local edge environment. When a 'file://' request is made,
9
+ * it behaves as if the request is made from within the edge itself. In this case, an additional
10
+ * '.edge/storage' folder is appended to the URL to represent the edge environment.
11
+ * @param {EdgeRuntime} context - VMContext
12
+ * @param {URL} url - The URL to fetch.
13
+ * @param {object} [options] - The fetch options.
14
+ * @returns {Promise<Response>} A Promise that resolves to the Response object.
15
+ */
16
+ async function fetchPolyfill(context, url, options) {
17
+ const {
18
+ URL, Headers, Response,
19
+ } = context;
20
+
21
+ const urlOBJ = new URL(url);
22
+ if (urlOBJ.href.startsWith('file://')) {
23
+ // url pathname = /VERSION_ID/filePath
24
+ const file = url.pathname.slice(15);
25
+ const filePath = join(process.cwd(), '.edge', 'storage', file);
26
+ const fileContent = readFileSync(filePath);
27
+ const contentType = mime.lookup(filePath) || 'application/octet-stream';
28
+
29
+ const headers = new Headers();
30
+ headers.append('Content-Type', contentType);
31
+
32
+ const response = new Response(fileContent, { headers, ...options });
33
+ return response;
34
+ }
35
+
36
+ return fetch(url, options);
37
+ }
38
+
39
+ export default fetchPolyfill;
@@ -0,0 +1,4 @@
1
+ import fetchPolyfill from './fetch.polyfills.js';
2
+ import FetchEventPolyfill from './FetchEvent.polyfills.js';
3
+
4
+ export { fetchPolyfill, FetchEventPolyfill };
@@ -1,69 +1,63 @@
1
- import { NodeVM } from 'vm2';
2
- import { RuntimeApis, Messages } from '#constants';
3
- import { feedback, debug } from '#utils';
1
+ import { EdgeRuntime } from 'edge-runtime';
2
+
3
+ import { fetchPolyfill, FetchEventPolyfill } from './polyfills/index.js';
4
4
 
5
5
  /**
6
- * Execute the specified code with the provided event object.
6
+ * Executes the specified JavaScript code within a sandbox environment,
7
+ * simulating the behavior of edges that use isolates.
7
8
  * @param {string} code - The JavaScript code to be executed.
8
- * @param {object} event - The event object containing the request and other properties.
9
- * @returns {Promise<object>} A Promise that resolves with the Response object.
9
+ * @returns {EdgeRuntime} An instance of the 'EdgeRuntime' class that represents
10
+ * the sandboxed environment where the code will be executed.
11
+ *
12
+ * This function allows you to run JavaScript code within a sandboxed environment,
13
+ * similar to how it would behave on edges that use isolates. It uses the 'EdgeRuntime'
14
+ * class provided by the 'edge-runtime' library to create the sandboxed environment.
15
+ *
16
+ * Example usage:
17
+ * ```
18
+ * const code = `
19
+ * addEventListener('fetch', event => {
20
+ * const { searchParams } = new URL(event.request.url)
21
+ * const url = searchParams.get('url')
22
+ * return event.respondWith(fetch(url))
23
+ * })`;
24
+ *
25
+ * const isolate = new runtime(code);
26
+ *
27
+ * const response = await isolate.dispatchFetch('http://localhost:3000');
28
+ *
29
+ * // If your code logic performs asynchronous tasks, you should await them.
30
+ * // https://developer.mozilla.org/en-US/docs/Web/API/ExtendableEvent/waitUntil
31
+ * await response.waitUntil();
32
+ *
33
+ * // `response` is a Web standard, you can use any of its methods
34
+ * console.log(response.status);
35
+ * ```
10
36
  */
11
- async function run(code, event) {
12
- let fetchEventHandler = null;
13
- let respondWithPromise = null;
14
-
15
- const vm = new NodeVM({
16
- console: 'inherit',
17
- sandbox: {
18
- Headers,
19
- ...event,
20
- Response,
21
- addEventListener: (type, handler) => {
22
- if (type !== 'fetch') {
23
- throw new Error(`Unsupported event type: ${type}`);
24
- }
25
- fetchEventHandler = handler;
26
- },
27
- },
28
- require: {
29
- external: true,
30
- builtin: RuntimeApis,
31
- },
32
- });
33
-
34
- try {
35
- vm.run(code);
36
- } catch (error) {
37
- debug.error(error);
38
- feedback.error(Messages.env.runtime.errors.unknown_error);
39
- throw error;
40
- }
41
-
42
- if (!fetchEventHandler) {
43
- throw new Error(Messages.env.runtime.errors.fetch_event_missing);
44
- }
45
-
46
- let response;
47
- try {
48
- const fetchEvent = {
49
- request: event.request,
50
- respondWith: (responsePromise) => {
51
- respondWithPromise = responsePromise;
52
- },
53
- };
54
- fetchEventHandler(fetchEvent);
55
- response = respondWithPromise;
56
- } catch (error) {
57
- debug.error(error);
58
- feedback.error(Messages.env.runtime.errors.fetch_event_unknown_error);
59
- throw error;
60
- }
61
-
62
- if (!response) {
63
- throw new Error(Messages.env.runtime.errors.undefined_response);
64
- }
65
-
66
- return response;
37
+ function runtime(code) {
38
+ const extend = (context) => {
39
+ context.fetch = (url, options) => fetchPolyfill(context, url, options);
40
+ context.FetchEvent = FetchEventPolyfill;
41
+ context.FirewallEvent = {}; // TODO: Firewall Event
42
+ /*
43
+ * According to the Vercel documentation at https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime#unsupported-apis,
44
+ * the default runtime doesn't support `eval`.
45
+ * However, in our Runtime environment (Cells Runtime/Azion)
46
+ * we've enabled and support its functionality.
47
+ */
48
+ // eslint-disable-next-line no-eval
49
+ context.eval = eval;
50
+
51
+ /* ==== Cells Runtime/Azion does not have this interface ==== */
52
+ context.File = undefined;
53
+ context.WebSocket = undefined;
54
+ /* ========================================================== */
55
+
56
+ return context;
57
+ };
58
+
59
+ const edgeRuntime = new EdgeRuntime({ extend, initialCode: code });
60
+ return edgeRuntime;
67
61
  }
68
62
 
69
- export default run;
63
+ export default runtime;
@@ -1,90 +1,46 @@
1
- import http from 'http';
2
- import chokidar from 'chokidar';
3
-
4
- import { debug, feedback, readWorkerFile } from '#utils';
1
+ import { debug, readWorkerFile, feedback } from '#utils';
5
2
  import { Messages } from '#constants';
6
- import run from './runtime.env.js';
3
+ import { runServer } from 'edge-runtime';
4
+ import runtime from './runtime.env.js';
7
5
 
8
6
  /**
9
- * Create a new HTTP server.
10
- * @param {string} filePath - The path to the file containing the code.
11
- * @returns {http.Server} The created server.
7
+ * Read the worker code from the file.
8
+ * @param {string} workerPath - The path to the file containing the code.
9
+ * @returns {Promise<string>} A promise that resolves to the worker code.
12
10
  */
13
- function createServer(filePath) {
14
- return http.createServer(async (request, response) => {
15
- let code;
16
-
17
- try {
18
- // Load the code to be executed on the server for each request
19
- code = await readWorkerFile(filePath);
20
- } catch (error) {
21
- debug.error(error);
22
- response.statusCode = 500;
23
- response.setHeader('Content-Type', 'text/plain');
24
- response.end(JSON.stringify({ error: error.message }));
25
- return;
26
- }
27
-
28
- try {
29
- const responseBody = await run(code, { request, response });
30
-
31
- if (responseBody instanceof Response) {
32
- responseBody.headers.forEach((value, name) => {
33
- response.setHeader(name, value);
34
- });
35
-
36
- const body = await responseBody.text();
37
- response.statusCode = responseBody.status;
38
- response.end(body);
39
- } else {
40
- response.statusCode = 200;
41
- response.setHeader('Content-Type', 'text/plain');
42
- response.end(responseBody);
43
- }
44
- } catch (error) {
45
- let errorMessage = error.message;
46
- if (error.message.includes('is not defined')) {
47
- errorMessage = `${error.message} in Edge runtime.`;
48
- }
49
-
50
- error.message = errorMessage;
51
- debug.error(error);
52
- feedback.error(Messages.env.server.errors.load_worker_failed);
53
- response.statusCode = 500;
54
- response.setHeader('Content-Type', 'text/plain');
55
- response.end(JSON.stringify({ error: { message: errorMessage, status: 500 } }));
56
- }
57
- });
11
+ async function readWorkerCode(workerPath) {
12
+ try {
13
+ const worker = await readWorkerFile(workerPath);
14
+ return worker;
15
+ } catch (error) {
16
+ debug.error(error);
17
+ feedback.server.error(
18
+ Messages.env.server.errors.load_worker_failed(workerPath),
19
+ );
20
+ throw error;
21
+ }
58
22
  }
59
23
 
60
24
  /**
61
25
  * Start the HTTP server with the specified port and file path to load the code from.
62
- * @param {string} filePath - The path to the file containing the code.
26
+ * @param {string} workerPath - The path to the file containing the worker code.
63
27
  * @param {number} port - The port to listen on.
64
- * @returns {http.Server} The created server.
65
28
  */
66
- async function startServer(filePath, port) {
67
- let server = createServer(filePath);
68
-
69
- // Use chokidar to watch for changes in the file
70
- const watcher = chokidar.watch(filePath);
71
-
72
- watcher.on('change', async () => {
73
- feedback.info(Messages.env.server.info.code_change_detect);
74
- // Close the current server
75
- server.close();
76
- // And create a new one
77
- server = createServer(filePath);
78
- server.listen(port, () => {
79
- feedback.success(Messages.env.server.success.server_running(port));
29
+ async function startServer(workerPath, port) {
30
+ try {
31
+ const workerCode = await readWorkerCode(workerPath);
32
+ const execution = runtime(workerCode);
33
+ const server = await runServer({
34
+ port,
35
+ host: 'localhost',
36
+ runtime: execution,
80
37
  });
81
- });
82
-
83
- server.listen(port, () => {
84
- feedback.success(Messages.env.server.success.server_running(port));
85
- });
86
-
87
- return server;
38
+ feedback.server.success(
39
+ Messages.env.server.success.server_running(server.url),
40
+ );
41
+ } catch (error) {
42
+ debug.error(error);
43
+ }
88
44
  }
89
45
 
90
46
  export default startServer;