Code is written in typescript. Typing for this code is specific, and is not extended from any existing typing. Every machine has initialization and cleanup functions, plus callback functions that can be called from external sources.
Callbacks can be called by API, and their returned value will be returned as HTTP response.
All callback functions must return RouteResult
instances to provide data with proper HTTP status code.
The machine uses the @david.uhlir/vm-machine
library which provides a secure VM environment with specific libraries.
External callbacks can be called via HTTP request using any method (GET, POST, PATCH, ...). The result of this function will be returned as the request response. Each call has it's request (MachineScopeContext). Libraries like blob or fetch needs to have this context provided.
GET http://serverurl.com/v1/machine/{machineId}/{machineEnvironment}/call/{callbackName}
note: machineId is real machine ID not it's name, and environment can be development
, sandbox
or production
To restrict HTTP methods, add a precheck to your callback:
async function setValue(req: ProcessedRequest) { if (req.method !== 'POST') { return new RouteResult({}, 405); } // Your logic here }
The machine code must define functions using the new API format:
// Bootstrap function for initialization bootstrap(async function() { // Initialization code console.log('Machine started'); }); // Cleanup function for disposal onDispose(async function() { // Cleanup code console.log('Machine stopped'); }); // Expose callable functions route('POST', 'setValue', async function(req: ProcessedRequest) { await storage.setValue('value', req.body.data); return new RouteResult({ success: true }, 200); }); route('POST', 'getValue', async function(req: ProcessedRequest) { const data = await storage.getValue('value'); return new RouteResult({ data }, 200); });
The VM provides these built-in classes for working with data:
Represents a file or binary data stored in the blob storage:
class BlobReference { reference: string // Internal reference to blob storage size: number // Size in bytes mimeType: string // MIME type (e.g., 'image/jpeg') name?: string // Optional filename }
For creating multipart/form-data requests:
class FormData { constructor(data?: Record<string, any>) append(name: string, value: any): void } // Usage: const formData = new FormData(); formData.append('name', 'John Doe'); formData.append('file', blobReference);
All callback functions must return RouteResult instances:
class RouteResult { constructor(data: any, status?: number, headers?: Record<string, string>) } // Usage: return new RouteResult({ success: true }, 200); return new RouteResult({ error: 'Not found' }, 404); return new RouteResult(blobReference, 200, { 'Content-Type': 'image/jpeg' });
Fetch automatically converts responses based on content-type. When responses contain binary data or form-data, they are automatically converted to BlobReference or FormData instances.
In outside of exposed functions, you use the machineContext
variable directly:
const result = await fetch(machineContext, 'http://localhost/uploadFile', { ... });
Note: keep in mind, machineContext will not automaticaly remove blobs after function call, you need to manage it yourself.
HTTP requests with machine protocol support:
fetch(callContext: MachineScopeContext, url: string, options?: { method?: string, headers?: { [key: string]: string }, body?: string | FormData | UrlEncodedFormObject }): Promise<{ status: number, body: any | BlobReference | FormData, // Automatically converted based on response type headers: { [key: string]: string } }>
For multipart/form-data requests (file uploads), use the FormData
class:
// Create form data with files and fields const formData = new FormData(); formData.append('name', 'John Doe'); formData.append('email', 'john@example.com'); formData.append('avatar', blobReference); // BlobReference object const response = await fetch(callContext, 'https://api.example.com/upload', { method: 'POST', body: formData // Automatically sets Content-Type: multipart/form-data }); // Response body will be automatically converted: // - Binary responses → BlobReference instances // - Form-data responses → FormData instances // - JSON responses → parsed objects
For URL-encoded forms, use createUrlEncodedForm
:
const urlEncodedData = createUrlEncodedForm({ username: 'john', password: 'secret' }); await fetch(callContext, 'https://api.example.com/login', { method: 'POST', body: urlEncodedData // Automatically sets Content-Type: application/x-www-form-urlencoded });
When receiving files in HTTP callbacks or fetch responses, they are represented as BlobReference instances. These objects are created automatically by the system when processing file uploads or binary data:
// Example usage in callback route('POST', 'uploadFile', async function(req: ProcessedRequest) { // req.body.files contains BlobReference instances for uploaded files const uploadedFile = req.body.files.document; // BlobReference instance console.log(`Received file: ${uploadedFile.name}`); console.log(`Size: ${uploadedFile.size} bytes`); console.log(`Type: ${uploadedFile.mimeType}`); // Forward the file to another service using FormData const formData = new FormData(); formData.append('file', uploadedFile); formData.append('metadata', 'processed'); const result = await fetch(req.context, 'https://storage.example.com/save', { method: 'POST', method: 'POST', body: formData }); return new RouteResult({ uploaded: true, result: result.body, fileReference: uploadedFile }); });
Fetch supports localhost in requests to request machien itself (including test code)
It can be used for targeting machine itself by calling http://localhost/callbackName
or
you can call to another (but hosted on same server) machine http://hosted-machines/${machineId}/${machineEnvironment}/callbackName
// Call this machine await fetch(callContext, 'http://localhost/callbackName', { method: 'POST', body: JSON.stringify({ data: 'value' }) }); // Call another machine by ID await fetch(callContext, 'http://hosted-machines/1234/development/callbackName', { method: 'GET' });
Available helper functions for data manipulation:
createUrlEncodedForm(data: Record<string, any>): UrlEncodedFormObject // Creates URL-encoded form data for fetch requests removeBlob(blobReference: BlobReference): Promise<void> // Removes blob data from storage // Note: FormData is now a class, create with: new FormData()
These are injected based on machine configuration:
environment: string // Machine environment (development, production, etc.) variables: { [key: string]: any } // Configuration variables
storage.getValue(key: string): Promise<any> // get value by key storage.setValue(key: string, value: any): Promise<void> // set value by key storage.getAll(): Promise<{ [key: string]: any }> // get all stored values storage.removeValue(key: string): Promise<void> // remove value by key getSecret(secretName: string): Promise<string> // get secret value set by administration
note: Returned value from storage.getValue
is exactly the same you set by calling storage.setValue
. There is no id in structure.
// String encoding/decoding (UTF-8) base64Encode(data: string): string // encode UTF-8 string to base64 base64Decode(base64: string): string // decode base64 to UTF-8 string // Browser-compatible binary functions btoa(data: string): string // encode binary string to base64 (like browser btoa) atob(base64: string): string // decode base64 to binary string (like browser atob) // Blob to base64 conversion and vice versa blobToBase64(callContext: MachineScopeContext, blobReference: BlobReference): Promise<string> // convert blob data to base64 base64ToBlob(callContext: MachineScopeContext, base64Data: string, mimeType?: string, name?: string): Promise<BlobReference> // convert base64 to blob
All callback functions must return RouteResult instances:
// RouteResult constructor new RouteResult(data: any, status: number = 200, headers?: Record<string, string>) // Examples: return new RouteResult({ success: true }); // 200 OK return new RouteResult({ error: 'Not found' }, 404); // 404 Not Found return new RouteResult(blobReference, 200, { // File download 'Content-Type': 'image/jpeg', 'Content-Disposition': 'attachment; filename="image.jpg"' });
note: machineId is real machine ID not it's name, and environment can be development
, sandbox
or production
Storage is a simple key-value storage saved locally in database. This storage is persistent and isolated for each machine. Use the provided storage functions to interact with it:
// Store data await storage.setValue('key', { some: 'data' }); // Retrieve data const data = await storage.getValue('key'); // Get all stored data const allData = await storage.getAll(); // Remove data await storage.removeValue('key');
Store received value:
// Bootstrap initialization bootstrap(async function() { console.log('Store received value example'); }); // Setup cleanup onDispose(async function() { console.log('Stopped'); }); // Expose callable functions route('POST', 'setValue', async function(req: ProcessedRequest) { await storage.setValue('value', req.body.data); return new RouteResult({ success: true }, 200); }); route('GET', 'getValue', async function(req: ProcessedRequest) { const data = await storage.getValue('value'); return new RouteResult({ data }, 200); });
Callback with multiple methods
// Expose callable functions route(['GET', 'POST'], 'home', async function(req: ProcessedRequest) { if (req.body) { await storage.setValue('value', req.body.data); } return new RouteResult({ success: true }, 200); });
Callback with params methods
// Expose callable functions route(['POST'], 'entity/:name', async function(req: ProcessedRequest) { if (req.body) { const entities = await storage.getValue('entities') || {} await storage.setValue('entities', { ...entities, [req.params.name]: req.body.value, }); } return new RouteResult({ success: true }, 200); });
Returning html data as frontend page
route(['GET', 'POST'], 'home', async function serveUi(source, req: ProcessedRequest) { const value = await storage.getValue('name') const html = `<!DOCTYPE html> <html lang="cs"> <head> <meta charset="UTF-8"> <title>My form</title> </head> <body> <h1>Hello ${value}</h1> <form action="" method="post" enctype="multipart/form-data"> <label for="name">Name:</label> <input type="text" id="name" name="name" required /> <br /><br /> <input type="submit" value="Send"> </form> </body> </html> ` return new RouteResult(html, 200, { ['Content-Type']: 'text/html; charset=utf-8', }) })
Using cron to schedule task:
async function scheduled() { console.log('Still running', Date.now()) } bootstrap(async () => { cron.addJob('regenerateSchemas', '* * * * *', async () => { scheduled() }) })
Using sharp to modify image:
route('GET', 'image', async function(req: ProcessedRequest) { const result = await fetch(req.context, variables.imageUrl) if (result.body instanceof BlobReference) { const sharpReference = await sharp(req.context, result.body) const grayscaleBlob = await sharpReference.grayscale().toBlob() return new RouteResult(grayscaleBlob, 200) } return new RouteResult('Unable to resolve image', 500) })
Base64 functions with blobs:
// Encode/decode UTF-8 text const encoded = base64Encode('Hello World!'); // "SGVsbG8gV29ybGQh" const decoded = base64Decode(encoded); // "Hello World!" // Convert uploaded file to base64 route('POST', 'processFile', async function(req: ProcessedRequest) { const uploadedFile = req.body.files.document; const base64Data = await blobToBase64(req.context, uploadedFile); // Now you can embed the file in JSON or send it to APIs that expect base64 return new RouteResult({ filename: uploadedFile.name, size: uploadedFile.size, data: base64Data }); }); // Convert base64 back to blob (e.g., from API response) route('POST', 'saveBase64File', async function(req: ProcessedRequest) { const { base64Data, filename, mimeType } = req.body; // Convert base64 to blob const blobRef = await base64ToBlob(req.context, base64Data, mimeType, filename); // Store the blob reference await storage.setValue('convertedFile', blobRef); return new RouteResult({ success: true, blobReference: blobRef.reference, size: blobRef.size }); });
Basic authorization:
route(['GET'], 'endpoint', async function(req: ProcessedRequest) { return basicAuthorization( req, 'Please authorize', [{ username: 'test', password: 'test' }], async (req: ProcessedRequest, username: string) => { const data = { count: 123, property: 'Hello world', } return new RouteResult(data, 200, { ['Content-Type']: 'application/json', }) }, ) })
Receiving url encoded data:
route(['GET'], 'endpoint', async function(req: ProcessedRequest) { const body = req.body as URLEncodedFormObject<{ username: string; password: string}> console.log(body.data.username, body.data.password) }) SqlLite: ```typescript bootstrap(async () => { // Count log entries const count = await database.query(`SELECT COUNT(*) as total FROM logs;`); console.log('Total log entries:', count[0]?.total); });
© 2025 Routzie Routzie.com