@scriptdb/vm 1.0.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 (3) hide show
  1. package/README.md +96 -0
  2. package/dist/index.js +111 -0
  3. package/package.json +40 -0
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # ScriptDB VM
2
+
3
+ Virtual Machine package for executing TypeScript and JavaScript code in a secure sandboxed environment.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @scriptdb/vm
9
+ # or
10
+ yarn add @scriptdb/vm
11
+ # or
12
+ bun add @scriptdb/vm
13
+ ```
14
+
15
+ ## Quick Start
16
+
17
+ ```typescript
18
+ import { VM } from '@scriptdb/vm';
19
+
20
+ // Create a VM instance
21
+ const vm = new VM();
22
+
23
+ // Execute TypeScript code
24
+ const result = await vm.run(`
25
+ const greet = (name: string) => \`Hello, \${name}!\`;
26
+ greet("World");
27
+ `);
28
+
29
+ console.log(result); // "Hello, World!"
30
+ ```
31
+
32
+ ## API Reference
33
+
34
+ ### Constructor
35
+
36
+ ```typescript
37
+ new VM(options?)
38
+ ```
39
+
40
+ Creates a new VM instance with the specified options.
41
+
42
+ #### Options
43
+
44
+ ```typescript
45
+ interface VMOptions {
46
+ language?: 'ts' | 'js'; // Code language (default: 'ts')
47
+ registerModules?: { [key: string]: any }; // Modules to register in the VM context
48
+ }
49
+ ```
50
+
51
+ ### Methods
52
+
53
+ #### run(code, options?)
54
+
55
+ Executes code in the VM context.
56
+
57
+ ```typescript
58
+ await vm.run(code: string, options?: vm.RunningCodeOptions | string): Promise<any>
59
+ ```
60
+
61
+ - `code` (string): The TypeScript or JavaScript code to execute
62
+ - `options` (optional): Execution options or filename
63
+
64
+ Returns a promise that resolves with the result of the code execution.
65
+
66
+ #### register(context)
67
+
68
+ Registers modules and values in the VM context.
69
+
70
+ ```typescript
71
+ vm.register(context: { [key: string]: any }): void
72
+ ```
73
+
74
+ - `context` (object): Object with keys that will be available in the VM context
75
+
76
+ ## Development
77
+
78
+ ```bash
79
+ # Install dependencies
80
+ bun install
81
+
82
+ # Run in development mode
83
+ bun run dev
84
+
85
+ # Build the package
86
+ bun run build:all
87
+
88
+ # Run tests
89
+ bun run test
90
+
91
+ # Run linting
92
+ bun run lint
93
+
94
+ # Type checking
95
+ bun run typecheck
96
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,111 @@
1
+ // @bun
2
+ // src/index.ts
3
+ var Bun = globalThis.Bun;
4
+ import vm from "vm";
5
+ import fs from "fs";
6
+ import path from "path";
7
+ import os from "os";
8
+
9
+ class VM {
10
+ transpiler;
11
+ ctx;
12
+ registerModules;
13
+ DATABASE_DIR = path.join(os.homedir(), ".scriptdb", "databases");
14
+ PLUGIN_DIR = path.join(os.homedir(), ".scriptdb", "plugins");
15
+ pkgPlugin = {};
16
+ constructor(options) {
17
+ if (!fs.existsSync(this.DATABASE_DIR)) {
18
+ fs.mkdirSync(this.DATABASE_DIR, { recursive: true });
19
+ }
20
+ if (!fs.existsSync(this.PLUGIN_DIR)) {
21
+ fs.mkdirSync(this.PLUGIN_DIR, { recursive: true });
22
+ }
23
+ const pkgPath = path.join(this.PLUGIN_DIR, "package.json");
24
+ if (fs.existsSync(pkgPath)) {
25
+ this.pkgPlugin = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
26
+ }
27
+ this.transpiler = new Bun.Transpiler({
28
+ loader: options?.language || "ts"
29
+ });
30
+ this.registerModules = options?.registerModules || {};
31
+ this.ctx = vm.createContext(this.registerModules);
32
+ }
33
+ register(context) {
34
+ this.registerModules = { ...this.registerModules, ...context };
35
+ this.ctx = vm.createContext(this.registerModules);
36
+ }
37
+ resolvePath(fileList, query) {
38
+ const aliases = { "@db": this.DATABASE_DIR };
39
+ let resolvedPath = query;
40
+ for (const [alias, target] of Object.entries(aliases)) {
41
+ if (resolvedPath.startsWith(alias + "/")) {
42
+ resolvedPath = resolvedPath.replace(alias, target);
43
+ break;
44
+ }
45
+ }
46
+ resolvedPath = path.normalize(resolvedPath);
47
+ return fileList.find((file) => {
48
+ const normalizedFile = path.normalize(file);
49
+ const fileWithoutExt = normalizedFile.replace(/\.[^/.]+$/, "");
50
+ return normalizedFile === resolvedPath || fileWithoutExt === resolvedPath || normalizedFile === resolvedPath + ".ts" || normalizedFile === resolvedPath + ".js";
51
+ });
52
+ }
53
+ async moduleLinker(specifier, referencingModule) {
54
+ const dbFiles = fs.readdirSync(this.DATABASE_DIR).filter((f) => f.endsWith(".ts")).map((f) => path.join(this.DATABASE_DIR, f));
55
+ const dbResult = this.resolvePath(dbFiles, specifier);
56
+ if (dbResult) {
57
+ try {
58
+ const actualModule = await import(dbResult);
59
+ const exportNames = Object.keys(actualModule);
60
+ return new vm.SyntheticModule(exportNames, function() {
61
+ exportNames.forEach((key) => {
62
+ this.setExport(key, actualModule[key]);
63
+ });
64
+ }, { identifier: specifier, context: referencingModule.context });
65
+ } catch (err) {
66
+ console.error(`Failed to load database module ${specifier}:`, err);
67
+ throw err;
68
+ }
69
+ }
70
+ const allowedPackages = Object.keys(this.pkgPlugin.dependencies || {});
71
+ if (allowedPackages.includes(specifier)) {
72
+ try {
73
+ const modulePath = path.join(this.PLUGIN_DIR, "node_modules", specifier);
74
+ const actualModule = await import(modulePath);
75
+ const exportNames = Object.keys(actualModule);
76
+ return new vm.SyntheticModule(exportNames, function() {
77
+ exportNames.forEach((key) => {
78
+ this.setExport(key, actualModule[key]);
79
+ });
80
+ }, { identifier: specifier, context: referencingModule.context });
81
+ } catch (err) {
82
+ console.error(`Failed to load plugin module ${specifier}:`, err);
83
+ throw err;
84
+ }
85
+ }
86
+ throw new Error(`Module ${specifier} is not allowed or not found.`);
87
+ }
88
+ async run(code, options) {
89
+ const logs = [];
90
+ const customConsole = ["log", "error", "warn", "info", "debug", "trace"].reduce((acc, type) => {
91
+ acc[type] = (...args) => logs.push({ type, args });
92
+ return acc;
93
+ }, {});
94
+ this.register({
95
+ console: customConsole
96
+ });
97
+ const js = this.transpiler.transformSync(code);
98
+ const mod = new vm.SourceTextModule(js, { context: this.ctx, identifier: path.join(this.PLUGIN_DIR, "virtual-entry.js") });
99
+ await mod.link(this.moduleLinker.bind(this));
100
+ await mod.evaluate();
101
+ return {
102
+ namespace: mod.namespace,
103
+ logs
104
+ };
105
+ }
106
+ }
107
+ var src_default = VM;
108
+ export {
109
+ src_default as default,
110
+ VM
111
+ };
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@scriptdb/vm",
3
+ "version": "1.0.0",
4
+ "description": "Virtual machine package for script database",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "dev": "bun --watch src/index.ts",
20
+ "build": "bun build src/index.ts --outdir dist --target bun --format esm --splitting",
21
+ "build:cjs": "bun build src/index.ts --outdir dist --target bun --format cjs --outfile dist/index.js",
22
+ "build:types": "tsc --emitDeclarationOnly --project tsconfig.build.json",
23
+ "build:all": "bun run build && bun run build:cjs && bun run build:types",
24
+ "test": "bun test",
25
+ "lint": "bun run lint:src",
26
+ "lint:src": "eslint src --ext .ts,.tsx",
27
+ "lint:fix": "eslint src --ext .ts,.tsx --fix",
28
+ "typecheck": "tsc --noEmit",
29
+ "clean": "rm -rf dist"
30
+ },
31
+ "devDependencies": {
32
+ "@types/bun": "^1.3.2",
33
+ "@types/node": "^20.0.0",
34
+ "@typescript-eslint/eslint-plugin": "^6.0.0",
35
+ "@typescript-eslint/parser": "^6.0.0",
36
+ "bun-types": "latest",
37
+ "eslint": "^8.0.0",
38
+ "typescript": "^5.0.0"
39
+ }
40
+ }