Socket
Socket
Sign inDemoInstall

@edge-runtime/vm

Package Overview
Dependencies
Maintainers
1
Versions
101
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@edge-runtime/vm - npm Package Compare versions

Comparing version 2.1.2 to 2.2.0-beta.0

23

dist/edge-vm.d.ts
import type * as EdgePrimitives from '@edge-runtime/primitives';
import type { VMContext, VMOptions } from './vm';
import { VM } from './vm';
export interface EdgeVMOptions<T> {
import type { DispatchFetch } from './types';
import { VM, type VMContext, type VMOptions } from './vm';
export interface EdgeVMOptions<T extends EdgeContext> {
/**

@@ -16,2 +16,8 @@ * Provide code generation options to the Node.js VM.

/**
* Code to be evaluated as when the Edge Runtime is created. This is handy
* to run code directly instead of first creating the runtime and then
* evaluating.
*/
initialCode?: string;
/**
* Provides an initial map to the require cache.

@@ -22,7 +28,4 @@ * If none is given, it will be initialized to an empty map.

}
/**
* An implementation of a VM that pre-loads on its context Edge Primitives.
* The context can be extended from its constructor.
*/
export declare class EdgeVM<T extends EdgeContext> extends VM<T> {
export declare class EdgeVM<T extends EdgeContext = EdgeContext> extends VM<T> {
readonly dispatchFetch: DispatchFetch;
constructor(options?: EdgeVMOptions<T>);

@@ -38,7 +41,3 @@ }

btoa: typeof EdgePrimitives.btoa;
Cache: typeof EdgePrimitives.Cache;
caches: typeof EdgePrimitives.caches;
CacheStorage: typeof EdgePrimitives.CacheStorage;
console: typeof EdgePrimitives.console;
createCaches: typeof EdgePrimitives.createCaches;
crypto: typeof EdgePrimitives.crypto;

@@ -45,0 +44,0 @@ Crypto: typeof EdgePrimitives.Crypto;

"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.EdgeVM = void 0;
const buffer_1 = require("buffer");
const require_1 = require("./require");
const vm_1 = require("./vm");
const vm_2 = require("vm");
const vm_1 = require("vm");
const vm_2 = require("./vm");
const streamsImpl = __importStar(require("@edge-runtime/primitives/streams"));
const urlImpl = __importStar(require("@edge-runtime/primitives/url"));
const cryptoImpl = __importStar(require("@edge-runtime/primitives/crypto"));
const eventsImpl = __importStar(require("@edge-runtime/primitives/events"));
/**
* An implementation of a VM that pre-loads on its context Edge Primitives.
* The context can be extended from its constructor.
* Store handlers that the user defined from code so that we can invoke them
* from the Node.js realm.
*/
class EdgeVM extends vm_1.VM {
constructor(options = {}) {
let unhandledRejectionHandlers;
let uncaughtExceptionHandlers;
class EdgeVM extends vm_2.VM {
constructor(options) {
super({
...options,
extend: (context) => {
return options.extend
return (options === null || options === void 0 ? void 0 : options.extend)
? options.extend(addPrimitives(context))

@@ -22,5 +50,198 @@ : addPrimitives(context);

});
Object.defineProperty(this.context, '__onUnhandledRejectionHandlers', {
set: registerUnhandledRejectionHandlers,
configurable: false,
enumerable: false,
});
Object.defineProperty(this, '__rejectionHandlers', {
get: () => unhandledRejectionHandlers,
configurable: false,
enumerable: false,
});
Object.defineProperty(this.context, '__onErrorHandlers', {
set: registerUncaughtExceptionHandlers,
configurable: false,
enumerable: false,
});
Object.defineProperty(this, '__errorHandlers', {
get: () => uncaughtExceptionHandlers,
configurable: false,
enumerable: false,
});
this.evaluate(getDefineEventListenersCode());
this.dispatchFetch = this.evaluate(getDispatchFetchCode());
for (const item of constructorsToPatchInstanceOf) {
patchInstanceOf(item, this.context);
}
if (options === null || options === void 0 ? void 0 : options.initialCode) {
this.evaluate(options.initialCode);
}
}
}
exports.EdgeVM = EdgeVM;
/**
* A list of constructors that need to be patched to make sure that the
* `instanceof` operator works as expected from within the vm context,
* even when passing it objects that were created in the Node.js realm.
*
* Example: the return value from `new TextEncoder().encode("hello")` is a
* Uint8Array. If `TextEncoder` is coming from the Node.js realm, then the
* following will be false, which doesn't fit the expectation of the user:
* ```ts
* new TextEncoder().encode("hello") instanceof Uint8Array
* ```
*
* This is because the `Uint8Array` in the `vm` context is not the same
* as the one in the Node.js realm.
*
* Patching the constructors in the `vm` is done by the {@link patchInstanceOf}
* function, and this is the list of constructors that need to be patched.
*/
const constructorsToPatchInstanceOf = [
'Object',
'Array',
'RegExp',
'Uint8Array',
'ArrayBuffer',
'Error',
'SyntaxError',
'TypeError',
];
function patchInstanceOf(item, ctx) {
// @ts-ignore
ctx[Symbol.for(`node:${item}`)] = eval(item);
return (0, vm_1.runInContext)(`
globalThis.${item} = new Proxy(${item}, {
get(target, prop, receiver) {
if (prop === Symbol.hasInstance && receiver === globalThis.${item}) {
const nodeTarget = globalThis[Symbol.for('node:${item}')];
if (nodeTarget) {
return function(instance) {
return instance instanceof target || instance instanceof nodeTarget;
};
} else {
throw new Error('node target must exist')
}
}
return Reflect.get(target, prop, receiver);
}
})
`, ctx);
}
/**
* Register system-level handlers to make sure that we report to the user
* whenever there is an unhandled rejection or exception before the process crashes.
* Do it on demand so we don't swallow rejections/errors for no reason.
*/
function registerUnhandledRejectionHandlers(handlers) {
if (!unhandledRejectionHandlers) {
process.on('unhandledRejection', function invokeRejectionHandlers(reason, promise) {
unhandledRejectionHandlers.forEach((handler) => handler({ reason, promise }));
});
}
unhandledRejectionHandlers = handlers;
}
function registerUncaughtExceptionHandlers(handlers) {
if (!uncaughtExceptionHandlers) {
process.on('uncaughtException', function invokeErrorHandlers(error) {
uncaughtExceptionHandlers.forEach((handler) => handler(error));
});
}
uncaughtExceptionHandlers = handlers;
}
/**
* Generates polyfills for addEventListener and removeEventListener. It keeps
* all listeners in hidden property __listeners. It will also call a hook
* `__onUnhandledRejectionHandler` and `__onErrorHandler` when unhandled rejection
* events are added or removed and prevent from having more than one FetchEvent
* handler.
*/
function getDefineEventListenersCode() {
return `
Object.defineProperty(self, '__listeners', {
configurable: false,
enumerable: false,
value: {},
writable: true,
})
function __conditionallyUpdatesHandlerList(eventType) {
if (eventType === 'unhandledrejection') {
self.__onUnhandledRejectionHandlers = self.__listeners[eventType];
} else if (eventType === 'error') {
self.__onErrorHandlers = self.__listeners[eventType];
}
}
function addEventListener(type, handler) {
const eventType = type.toLowerCase();
if (eventType === 'fetch' && self.__listeners.fetch) {
throw new TypeError('You can register just one "fetch" event listener');
}
self.__listeners[eventType] = self.__listeners[eventType] || [];
self.__listeners[eventType].push(handler);
__conditionallyUpdatesHandlerList(eventType);
}
function removeEventListener(type, handler) {
const eventType = type.toLowerCase();
if (self.__listeners[eventType]) {
self.__listeners[eventType] = self.__listeners[eventType].filter(item => {
return item !== handler;
});
if (self.__listeners[eventType].length === 0) {
delete self.__listeners[eventType];
}
}
__conditionallyUpdatesHandlerList(eventType);
}
`;
}
/**
* Generates the code to dispatch a FetchEvent invoking the handlers defined
* for such events. In case there is no event handler defined it will throw
* an error.
*/
function getDispatchFetchCode() {
return `(async function dispatchFetch(input, init) {
const request = new Request(input, init);
const event = new FetchEvent(request);
if (!self.__listeners.fetch) {
throw new Error("No fetch event listeners found");
}
const getResponse = ({ response, error }) => {
if (error || !response || !(response instanceof Response)) {
console.error(error ? error.toString() : 'The event listener did not respond')
response = new Response(null, {
statusText: 'Internal Server Error',
status: 500
})
}
response.waitUntil = () => Promise.all(event.awaiting);
if (response.status < 300 || response.status >= 400 ) {
response.headers.delete('content-encoding');
response.headers.delete('transform-encoding');
response.headers.delete('content-length');
}
return response;
}
try {
await self.__listeners.fetch[0].call(event, event)
} catch (error) {
return getResponse({ error })
}
return Promise.resolve(event.response)
.then(response => getResponse({ response }))
.catch(error => getResponse({ error }))
})`;
}
function addPrimitives(context) {

@@ -38,25 +259,32 @@ defineProperty(context, 'self', { enumerable: true, value: context });

exports: (0, require_1.requireWithCache)({
path: require.resolve('@edge-runtime/primitives/console'),
context,
path: require.resolve('@edge-runtime/primitives/console'),
scopedContext: { console },
}),
nonenumerable: ['console'],
});
const encodings = (0, require_1.requireWithCache)({
context,
path: require.resolve('@edge-runtime/primitives/encoding'),
scopedContext: { Buffer: buffer_1.Buffer, global: {} },
const atob = (str) => Buffer.from(str, 'base64').toString('binary');
const btoa = (str) => Buffer.from(str, 'binary').toString('base64');
// Events
defineProperties(context, {
exports: eventsImpl,
nonenumerable: [
'Event',
'EventTarget',
'FetchEvent',
'PromiseRejectionEvent',
],
});
// Encoding APIs
defineProperties(context, {
exports: encodings,
exports: { atob, btoa, TextEncoder, TextDecoder },
nonenumerable: ['atob', 'btoa', 'TextEncoder', 'TextDecoder'],
});
const streams = (0, require_1.requireWithCache)({
path: require.resolve('@edge-runtime/primitives/streams'),
const textEncodingStreamImpl = (0, require_1.requireWithFakeGlobalScope)({
context,
path: require.resolve('@edge-runtime/primitives/text-encoding-streams'),
scopedContext: streamsImpl,
});
// Streams
defineProperties(context, {
exports: streams,
exports: { ...streamsImpl, ...textEncodingStreamImpl },
nonenumerable: [

@@ -73,9 +301,10 @@ 'ReadableStream',

});
const abort = (0, require_1.requireWithCache)({
// AbortController
const abortControllerImpl = (0, require_1.requireWithFakeGlobalScope)({
path: require.resolve('@edge-runtime/primitives/abort-controller'),
context,
path: require.resolve('@edge-runtime/primitives/abort-controller'),
scopedContext: eventsImpl,
});
// AbortController
defineProperties(context, {
exports: abort,
exports: abortControllerImpl,
nonenumerable: ['AbortController', 'AbortSignal', 'DOMException'],

@@ -85,56 +314,42 @@ });

defineProperties(context, {
exports: (0, require_1.requireWithCache)({
cache: new Map([['punycode', { exports: require('punycode') }]]),
context,
path: require.resolve('@edge-runtime/primitives/url'),
scopedContext: {
TextEncoder: encodings.TextEncoder,
TextDecoder: encodings.TextDecoder,
},
}),
exports: urlImpl,
nonenumerable: ['URL', 'URLSearchParams', 'URLPattern'],
});
const blob = (0, require_1.requireWithCache)({
context,
path: require.resolve('@edge-runtime/primitives/blob'),
});
// Blob
defineProperties(context, {
exports: blob,
exports: (0, require_1.requireWithFakeGlobalScope)({
context,
path: require.resolve('@edge-runtime/primitives/blob'),
scopedContext: streamsImpl,
}),
nonenumerable: ['Blob'],
});
const webFetch = (0, require_1.requireWithCache)({
context,
cache: new Map([
['abort-controller', { exports: abort }],
['assert', { exports: require('assert') }],
['buffer', { exports: require('buffer') }],
['events', { exports: require('events') }],
['http', { exports: require('http') }],
['net', { exports: require('net') }],
['perf_hooks', { exports: require('perf_hooks') }],
['querystring', { exports: require('querystring') }],
['stream', { exports: require('stream') }],
['tls', { exports: require('tls') }],
['util', { exports: require('util') }],
['zlib', { exports: require('zlib') }],
[
require.resolve('@edge-runtime/primitives/streams'),
{ exports: streams },
],
[require.resolve('@edge-runtime/primitives/blob'), { exports: blob }],
]),
path: require.resolve('@edge-runtime/primitives/fetch'),
scopedContext: {
Uint8Array: createUint8ArrayForContext(context),
Buffer: buffer_1.Buffer,
global: {},
queueMicrotask,
setImmediate,
clearImmediate,
},
// Structured Clone
defineProperties(context, {
exports: (0, require_1.requireWithFakeGlobalScope)({
path: require.resolve('@edge-runtime/primitives/structured-clone'),
context,
scopedContext: streamsImpl,
}),
nonenumerable: ['structuredClone'],
});
// Fetch APIs
defineProperties(context, {
exports: webFetch,
exports: (0, require_1.requireWithFakeGlobalScope)({
context,
cache: new Map([
['abort-controller', { exports: abortControllerImpl }],
['streams', { exports: streamsImpl }],
]),
path: require.resolve('@edge-runtime/primitives/fetch'),
scopedContext: {
...streamsImpl,
...urlImpl,
structuredClone: context.structuredClone,
...eventsImpl,
AbortController: context.AbortController,
DOMException: context.DOMException,
AbortSignal: context.AbortSignal,
},
}),
nonenumerable: [

@@ -147,58 +362,11 @@ 'fetch',

'Response',
'WebSocket',
],
});
// Cache
defineProperties(context, {
exports: (0, require_1.requireWithCache)({
cache: new Map([
[
require.resolve('@edge-runtime/primitives/fetch'),
{ exports: webFetch },
],
]),
context,
path: require.resolve('@edge-runtime/primitives/cache'),
scopedContext: { global: {} },
}),
enumerable: ['caches'],
nonenumerable: ['Cache', 'CacheStorage'],
});
// Crypto
defineProperties(context, {
exports: (0, require_1.requireWithCache)({
context,
cache: new Map([
['crypto', { exports: require('crypto') }],
['process', { exports: require('process') }],
]),
path: require.resolve('@edge-runtime/primitives/crypto'),
scopedContext: {
Buffer: buffer_1.Buffer,
Uint8Array: createUint8ArrayForContext(context),
},
}),
exports: cryptoImpl,
enumerable: ['crypto'],
nonenumerable: ['Crypto', 'CryptoKey', 'SubtleCrypto'],
});
// Events
defineProperties(context, {
exports: (0, require_1.requireWithCache)({
context,
path: require.resolve('@edge-runtime/primitives/events'),
}),
nonenumerable: [
'Event',
'EventTarget',
'FetchEvent',
'PromiseRejectionEvent',
],
});
// Structured Clone
defineProperties(context, {
exports: (0, require_1.requireWithCache)({
context,
path: require.resolve('@edge-runtime/primitives/structured-clone'),
}),
nonenumerable: ['structuredClone'],
});
return context;

@@ -235,19 +403,2 @@ }

}
function createUint8ArrayForContext(context) {
return new Proxy((0, vm_2.runInContext)('Uint8Array', context), {
// on every construction (new Uint8Array(...))
construct(target, args) {
// construct it
const value = new target(...args);
// if this is not a buffer
if (!(args[0] instanceof buffer_1.Buffer)) {
// return what we just constructed
return value;
}
// if it is a buffer, then we spread the binary data into an array,
// and build the Uint8Array from that
return new target([...value]);
},
});
}
//# sourceMappingURL=edge-vm.js.map
/// <reference types="node" />
import type { Dictionary } from './types';
import type { Context } from 'vm';

@@ -11,3 +10,3 @@ /**

context: Context;
requireCache: Map<string, Dictionary>;
requireCache: Map<string, Record<string | number, any>>;
dependencies: Array<{

@@ -28,1 +27,8 @@ mapExports: {

}): any;
export declare function requireWithFakeGlobalScope(params: {
context: Context;
cache?: Map<string, any>;
path: string;
references?: Set<string>;
scopedContext: Record<string, any>;
}): {};
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.requireWithCache = exports.createRequire = exports.requireDependencies = void 0;
exports.requireWithFakeGlobalScope = exports.requireWithCache = exports.createRequire = exports.requireDependencies = void 0;
const fs_1 = require("fs");
const vm_1 = require("vm");
const path_1 = require("path");
const module_1 = __importDefault(require("module"));
/**

@@ -57,2 +61,27 @@ * Allows to require a series of dependencies provided by their path

exports.requireWithCache = requireWithCache;
function requireWithFakeGlobalScope(params) {
const resolved = require.resolve(params.path);
const getModuleCode = `(function(module,exports,require,__dirname,__filename,globalThis,${Object.keys(params.scopedContext).join(',')}) {${(0, fs_1.readFileSync)(resolved, 'utf-8')}\n})`;
const module = {
exports: {},
loaded: false,
id: resolved,
};
const moduleRequire = (module_1.default.createRequire || module_1.default.createRequireFromPath)(resolved);
function throwingRequire(path) {
var _a;
if (path.startsWith('./')) {
const moduleName = path.replace(/^\.\//, '');
if (!((_a = params.cache) === null || _a === void 0 ? void 0 : _a.has(moduleName))) {
throw new Error(`Cannot find module '${moduleName}'`);
}
return params.cache.get(moduleName).exports;
}
return moduleRequire(path);
}
throwingRequire.resolve = moduleRequire.resolve.bind(moduleRequire);
eval(getModuleCode)(module, module.exports, throwingRequire, (0, path_1.dirname)(resolved), resolved, params.context, ...Object.values(params.scopedContext));
return module.exports;
}
exports.requireWithFakeGlobalScope = requireWithFakeGlobalScope;
//# sourceMappingURL=require.js.map

@@ -1,7 +0,11 @@

/**
* Just any type of object indexed by string or numbers where the value can
* be anything or the provided generic.
*/
export interface Dictionary<T = any> {
[key: string | number]: T;
export interface DispatchFetch {
(input: string, init?: RequestInit): Promise<Response & {
waitUntil: () => Promise<any>;
}>;
}
export interface RejectionHandler {
(reason?: {} | null, promise?: Promise<any>): void;
}
export interface ErrorHandler {
(error?: {} | null): void;
}
/// <reference types="node" />
import type { CreateContextOptions } from 'vm';
import type { Dictionary } from './types';
export interface VMOptions<T> {

@@ -19,3 +18,3 @@ /**

*/
requireCache?: Map<string, Dictionary>;
requireCache?: Map<string, Record<string | number, any>>;
}

@@ -27,5 +26,5 @@ /**

*/
export declare class VM<T extends Dictionary> {
export declare class VM<T extends Record<string | number, any>> {
private readonly requireFn;
readonly requireCache: Map<string, Dictionary>;
readonly requireCache: Map<string, Record<string | number, any>>;
readonly context: VMContext & T;

@@ -41,3 +40,3 @@ constructor(options?: VMOptions<T>);

*/
require<T extends Dictionary = any>(filepath: string): T;
require<T extends Record<string | number, any> = any>(filepath: string): T;
/**

@@ -48,3 +47,3 @@ * Same as `require` but it will copy each of the exports in the context

*/
requireInContext<T extends Dictionary = any>(filepath: string): void;
requireInContext<T extends Record<string | number, any> = any>(filepath: string): void;
/**

@@ -51,0 +50,0 @@ * Same as `requireInContext` but allows to pass the code instead of a

@@ -5,3 +5,3 @@ {

"homepage": "https://edge-runtime.vercel.app/packages/vm",
"version": "2.1.2",
"version": "2.2.0-beta.0",
"main": "dist/index.js",

@@ -26,5 +26,2 @@ "repository": {

],
"dependencies": {
"@edge-runtime/primitives": "2.1.2"
},
"engines": {

@@ -41,2 +38,13 @@ "node": ">=14"

"types": "dist/index.d.ts",
"dependencies": {
"@edge-runtime/primitives": "2.2.0-beta.0"
},
"devDependencies": {
"@types/multer": "1.4.7",
"@types/test-listen": "1.1.0",
"@types/ws": "8.5.3",
"multer": "1.4.5-lts.1",
"test-listen": "1.1.0",
"ws": "8.13.0"
},
"scripts": {

@@ -43,0 +51,0 @@ "build": "tsc --project ./tsconfig.prod.json",

<div align="center">
<br>
<img src="https://edge-runtime.vercel.app/og-image.png" alt="edge-runtime logo">
<img src="https://user-images.githubusercontent.com/2096101/235130063-e561514e-1f66-4ff6-9034-70dbf7ca3260.png#gh-dark-mode-only">
<img src="https://user-images.githubusercontent.com/2096101/235127419-ac6fe609-d0cd-4339-a593-c48305a83823.png#gh-light-mode-only">
<br>

@@ -5,0 +6,0 @@ <br>

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc