feat: add http gateway (#3)

This commit is contained in:
Morten Olsen
2024-01-12 21:10:48 +01:00
committed by GitHub
parent 9c5249956e
commit 1115ce2fb3
22 changed files with 397 additions and 74 deletions

View File

@@ -17,12 +17,12 @@
}
},
"devDependencies": {
"@morten-olsen/mini-loader": "workspace:^",
"@morten-olsen/mini-loader-configs": "workspace:^",
"@types/node": "^20.10.8",
"typescript": "^5.3.3"
},
"dependencies": {
"@morten-olsen/mini-loader": "workspace:^",
"eventemitter3": "^5.0.1",
"nanoid": "^5.0.4"
},
@@ -31,4 +31,4 @@
"type": "git",
"url": "https://github.com/morten-olsen/mini-loader"
}
}
}

View File

@@ -1,17 +1,5 @@
import { Worker } from 'worker_threads';
import os from 'os';
import { EventEmitter } from 'eventemitter3';
import { Event } from '@morten-olsen/mini-loader';
import { join } from 'path';
import { createServer } from 'http';
import { nanoid } from 'nanoid';
import { chmod, mkdir, rm, writeFile } from 'fs/promises';
type RunEvents = {
message: (event: Event) => void;
error: (error: Error) => void;
exit: () => void;
};
import { setup } from './setup/setup.js';
type RunOptions = {
script: string;
@@ -20,44 +8,17 @@ type RunOptions = {
};
const run = async ({ script, input, secrets }: RunOptions) => {
const dataDir = join(os.tmpdir(), 'mini-loader', nanoid());
await mkdir(dataDir, { recursive: true });
await chmod(dataDir, 0o700);
const hostSocket = join(dataDir, 'host');
const server = createServer();
const inputLocation = join(dataDir, 'input');
const info = await setup({ script, input, secrets });
if (input) {
await writeFile(inputLocation, input);
}
const emitter = new EventEmitter<RunEvents>();
server.on('connection', (socket) => {
socket.on('data', (data) => {
const message = JSON.parse(data.toString());
emitter.emit('message', message);
});
});
server.listen(hostSocket);
const worker = new Worker(script, {
eval: true,
const worker = new Worker(info.scriptLocation, {
stdin: false,
stdout: false,
stderr: false,
env: {
HOST_SOCKET: hostSocket,
SECRETS: JSON.stringify(secrets),
INPUT_PATH: inputLocation,
},
workerData: {
input,
},
env: info.env,
});
worker.stdout?.on('data', (data) => {
emitter.emit('message', {
info.emitter.emit('message', {
type: 'log',
payload: {
severity: 'info',
@@ -67,7 +28,7 @@ const run = async ({ script, input, secrets }: RunOptions) => {
});
worker.stderr?.on('data', (data) => {
emitter.emit('message', {
info.emitter.emit('message', {
type: 'log',
payload: {
severity: 'error',
@@ -78,20 +39,24 @@ const run = async ({ script, input, secrets }: RunOptions) => {
const promise = new Promise<void>((resolve, reject) => {
worker.on('exit', async () => {
server.close();
await rm(dataDir, { recursive: true, force: true });
await info.teardown();
resolve();
});
worker.on('error', async (error) => {
server.close();
reject(error);
});
});
return {
emitter,
...info,
teardown: async () => {
worker.terminate();
},
promise,
};
};
type RunInfo = Awaited<ReturnType<typeof run>>;
export type { RunInfo };
export { run };

View File

@@ -0,0 +1,71 @@
import { join } from 'path';
import os from 'os';
import { nanoid } from 'nanoid';
import { chmod, mkdir, rm, writeFile } from 'fs/promises';
import { createServer } from 'net';
import { EventEmitter } from 'eventemitter3';
type SetupOptions = {
input?: Buffer | string;
script: string;
secrets?: Record<string, string>;
};
type RunEvents = {
message: (event: any) => void;
error: (error: Error) => void;
exit: () => void;
};
const setup = async (options: SetupOptions) => {
const { input, script, secrets } = options;
const emitter = new EventEmitter<RunEvents>();
const dataDir = join(os.tmpdir(), 'mini-loader', nanoid());
await mkdir(dataDir, { recursive: true });
await chmod(dataDir, 0o700);
const hostSocket = join(dataDir, 'host');
const httpGatewaySocket = join(dataDir, 'socket');
const server = createServer();
const inputLocation = join(dataDir, 'input');
const scriptLocation = join(dataDir, 'script.js');
if (input) {
await writeFile(inputLocation, input);
}
await writeFile(scriptLocation, script);
const env = {
HOST_SOCKET: hostSocket,
SECRETS: JSON.stringify(secrets || {}),
INPUT_PATH: inputLocation,
HTTP_GATEWAY_PATH: httpGatewaySocket,
};
const teardown = async () => {
server.close();
await rm(dataDir, { recursive: true, force: true });
};
server.on('connection', (socket) => {
socket.on('data', (data) => {
const message = JSON.parse(data.toString());
emitter.emit('message', message);
});
});
server.listen(hostSocket);
return {
env,
emitter,
teardown,
httpGatewaySocket,
scriptLocation,
hostSocket,
};
};
type Setup = Awaited<ReturnType<typeof setup>>;
export type { Setup };
export { setup };