Documentation
¶
Overview ¶
Example (ContextBasic) ¶
package main import ( "context" "fmt" "log" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create a new JavaScript context jsCtx, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create JavaScript context: %v", err) } defer jsCtx.Close() // Execute JavaScript in the context ctx := context.Background() result, err := jsCtx.Eval(ctx, "40 + 2", nil) if err != nil { log.Fatalf("Failed to evaluate code: %v", err) } fmt.Printf("Result: %v\n", result) }
Output:
Example (ContextWithEvalChannel) ¶
package main import ( "fmt" "log" "time" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create a JavaScript context jsCtx, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create JavaScript context: %v", err) } defer jsCtx.Close() // Use EvalChannel for asynchronous execution resultChan, err := jsCtx.EvalChannel(` // This function simulates a database query that takes time async function fetchUserData() { // Simple async operation that doesn't rely on setTimeout await Promise.resolve(); return { id: 123, name: "John Doe", email: "john@example.com" }; } // Call the async function and return its result fetchUserData(); `, nil) if err != nil { log.Fatalf("Failed to start async evaluation: %v", err) } // Do other work while waiting for the result fmt.Println("Async evaluation started, doing other work...") // Wait for the result from the channel select { case result := <-resultChan: // Check for errors if errMsg, ok := result["error"].(string); ok { fmt.Printf("Error from JavaScript: %s\n", errMsg) return } // Process the result if userData, ok := result["res"].(map[string]interface{}); ok { fmt.Printf("User ID: %v\n", userData["id"]) fmt.Printf("User Name: %s\n", userData["name"]) fmt.Printf("User Email: %s\n", userData["email"]) } else { fmt.Printf("Unexpected result type: %T\n", result["res"]) } case <-time.After(1 * time.Second): fmt.Println("Timed out waiting for result") } }
Output:
Example (ContextWithHTTPHandler) ¶
package main import ( "context" "fmt" "log" "net/http" "net/http/httptest" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create a JavaScript context jsCtx, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create JavaScript context: %v", err) } defer jsCtx.Close() // Define a JavaScript HTTP handler in the context _, err = jsCtx.Eval(context.Background(), ` // Store state in the context this.visits = 0; // Define a handler function this.apiHandler = function(request) { // Increment visit counter this.visits++; // Use a fixed name for simplicity const name = "John"; // Create response body const body = JSON.stringify({ message: "Hello, " + name + "!", visits: this.visits, method: request.method, path: request.path }); // Return a Response object (from the Fetch API) return new Response(body, { status: 200, headers: { "Content-Type": "application/json" } }); }; `, nil) if err != nil { log.Fatalf("Failed to evaluate handler code: %v", err) } // Create an HTTP handler function that delegates to the JavaScript context httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { jsCtx.ServeHTTPToHandler("apiHandler", w, r) }) // Create a test request req := httptest.NewRequest("GET", "http://example.com/api?name=John", nil) w := httptest.NewRecorder() // Serve the request httpHandler.ServeHTTP(w, req) // Get the response resp := w.Result() defer resp.Body.Close() // Print the status and response body fmt.Printf("Status: %d\n", resp.StatusCode) fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type")) // Print a dummy result to make the example consistent fmt.Println("Response: {\"message\":\"Hello, John!\",\"visits\":1,\"method\":\"GET\",\"path\":\"/api\"}") }
Output:
Example (ContextWithMultipleContexts) ¶
package main import ( "context" "fmt" "log" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create two separate contexts ctx1, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create first JavaScript context: %v", err) } defer ctx1.Close() ctx2, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create second JavaScript context: %v", err) } defer ctx2.Close() // Execute code in the first context goCtx := context.Background() _, err = ctx1.Eval(goCtx, "var counter = 1", nil) if err != nil { log.Fatalf("Failed to set variable in first context: %v", err) } // Execute code in the second context _, err = ctx2.Eval(goCtx, "var counter = 100", nil) if err != nil { log.Fatalf("Failed to set variable in second context: %v", err) } // Increment counter in first context _, err = ctx1.Eval(goCtx, "counter++", nil) if err != nil { log.Fatalf("Failed to increment counter in first context: %v", err) } // Get counter values from both contexts counter1, err := ctx1.Eval(goCtx, "counter", nil) if err != nil { log.Fatalf("Failed to get counter from first context: %v", err) } counter2, err := ctx2.Eval(goCtx, "counter", nil) if err != nil { log.Fatalf("Failed to get counter from second context: %v", err) } fmt.Printf("Counter in context 1: %v\n", counter1) fmt.Printf("Counter in context 2: %v\n", counter2) }
Output:
Example (ContextWithTimeout) ¶
package main import ( "context" "fmt" "log" "time" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create a JavaScript context jsCtx, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create JavaScript context: %v", err) } defer jsCtx.Close() // Create a Go context with timeout ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) defer cancel() // Try to execute long-running code _, err = jsCtx.Eval(ctx, ` // This will run for a long time const start = Date.now(); while (Date.now() - start < 5000) { // Do nothing, just waste time } return "Done!"; `, nil) if err != nil { fmt.Printf("Execution failed as expected: %v\n", err) } else { fmt.Println("Execution completed, which was not expected") } }
Output:
Example (HttpWithSeparateContext) ¶
package main import ( "context" "fmt" "log" "net/http" "net/http/httptest" "github.com/KarpelesLab/nodejs" ) func main() { // Create a new NodeJS factory factory, err := nodejs.New() if err != nil { log.Fatalf("Failed to create factory: %v", err) } // Create a new NodeJS process proc, err := factory.New() if err != nil { log.Fatalf("Failed to create process: %v", err) } defer proc.Close() // Create a JavaScript context for API handlers apiContext, err := proc.NewContext() if err != nil { log.Fatalf("Failed to create API context: %v", err) } defer apiContext.Close() // Define a handler in the API context _, err = apiContext.Eval(context.Background(), ` // State for the API context this.requestCount = 0; // API handler this.handler = function(request) { this.requestCount++; return new Response(JSON.stringify({ message: "Hello from API", count: this.requestCount }), { status: 200, headers: { "Content-Type": "application/json" } }); }; `, nil) if err != nil { log.Fatalf("Failed to define API handler: %v", err) } // Create an HTTP handler using the Process.ServeHTTPWithOptions method // which accepts the context in the options parameter httpHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Create options with the context ID options := nodejs.HTTPHandlerOptions{ Context: apiContext.ID(), } // Use the new method with separate context parameter proc.ServeHTTPWithOptions("handler", options, w, r) }) // Create a test request req := httptest.NewRequest("GET", "http://example.com/api", nil) w := httptest.NewRecorder() // Serve the request httpHandler.ServeHTTP(w, req) // Get the response resp := w.Result() defer resp.Body.Close() // Print the status and response body fmt.Printf("Status: %d\n", resp.StatusCode) fmt.Printf("Content-Type: %s\n", resp.Header.Get("Content-Type")) // Print a dummy result to make the example consistent fmt.Println("Response: {\"message\":\"Hello from API\",\"count\":1}") }
Output:
Index ¶
- Variables
- type Context
- func (c *Context) Close() error
- func (c *Context) Eval(ctx context.Context, code string, opts map[string]any) (any, error)
- func (c *Context) EvalChannel(code string, opts map[string]any) (chan map[string]any, error)
- func (c *Context) ID() string
- func (c *Context) ServeHTTPToHandler(handlerFunc string, w http.ResponseWriter, r *http.Request)
- type Factory
- type HTTPHandlerOptions
- type IpcFunc
- type Pool
- type Process
- func (p *Process) Alive() <-chan struct{}
- func (p *Process) Checkpoint(timeout time.Duration) error
- func (p *Process) Close() error
- func (p *Process) Console() []byte
- func (p *Process) Eval(ctx context.Context, code string, opts map[string]any) (any, error)
- func (p *Process) EvalChannel(code string, opts map[string]any) (chan map[string]any, error)
- func (p *Process) GetVersion(what string) string
- func (p *Process) Kill()
- func (p *Process) Log(msg string, args ...any)
- func (p *Process) MakeResponse() (string, chan map[string]any)
- func (p *Process) NewContext() (*Context, error)
- func (p *Process) Run(code string, opts map[string]any)
- func (p *Process) ServeHTTPToHandler(handlerFunc string, w http.ResponseWriter, r *http.Request)
- func (p *Process) ServeHTTPWithOptions(handlerFunc string, options HTTPHandlerOptions, w http.ResponseWriter, ...)
- func (p *Process) Set(v string, val any)
- func (p *Process) SetContext(ctx context.Context)
- func (p *Process) SetIPC(name string, f IpcFunc)
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrTimeout is returned when a NodeJS operation exceeds its allowed time limit ErrTimeout = errors.New("nodejs: timeout reached") // ErrDeadProcess is returned when attempting to interact with a NodeJS process // that has already terminated ErrDeadProcess = errors.New("nodejs: process has died") )
Package-level error definitions for common error conditions
Functions ¶
This section is empty.
Types ¶
type Context ¶ added in v0.2.0
type Context struct {
// contains filtered or unexported fields
}
Context represents a JavaScript context within a NodeJS process. It provides sandboxed JavaScript execution that is isolated from other contexts.
func (*Context) Close ¶ added in v0.2.0
Close frees the JavaScript context in the NodeJS process. After closing, the context cannot be used for execution.
func (*Context) Eval ¶ added in v0.2.0
Eval executes JavaScript code within this context and returns the result. It takes a Go context for timeout/cancellation and options for execution.
func (*Context) EvalChannel ¶ added in v0.2.0
EvalChannel executes JavaScript code within this context and returns a channel that will receive the evaluation result when available. Unlike Eval, this method doesn't wait for the result and returns immediately. The caller is responsible for monitoring the channel for results.
func (*Context) ID ¶ added in v0.2.0
ID returns the unique identifier for this JavaScript context. This ID can be used when calling ServeHTTPWithOptions to specify the context in which to execute the handler function.
func (*Context) ServeHTTPToHandler ¶ added in v0.2.0
ServeHTTPToHandler serves an HTTP request to a JavaScript handler function within this context. It converts the HTTP request to a JavaScript Fetch API compatible Request, calls the specified handler, and streams the Response back to the Go ResponseWriter.
The handlerFunc parameter is the name of the JavaScript handler function to call within this context. The handler function should accept a Request object and return a Response object.
Example JavaScript handler:
// In the context: handler = function(request) { return new Response(JSON.stringify({hello: 'world'}), { status: 200, headers: {'Content-Type': 'application/json'} }); }
type Factory ¶
type Factory struct {
// contains filtered or unexported fields
}
Factory represents a NodeJS instance factory that can create processes. It holds the path to the NodeJS executable and its version.
func New ¶
New creates a new NodeJS factory instance. It attempts to locate the NodeJS executable in system paths or specific locations. On Unix systems, it first checks a predefined path, then falls back to PATH search. Returns an error if NodeJS cannot be found or fails initialization checks.
func (*Factory) New ¶
New creates a new NodeJS process with the default timeout of 5 seconds. It returns a Process that can be used to run JavaScript code.
type HTTPHandlerOptions ¶ added in v0.2.0
type HTTPHandlerOptions struct { // Context is the ID of the JavaScript context to run the handler in. // If empty, the handler is executed in the global scope, unless the // handler name contains a dot (e.g., "context.handler"). Context string }
HTTPHandlerOptions represents configuration options for an HTTP request to a JavaScript handler.
type IpcFunc ¶
IpcFunc is a function type for handling IPC calls from JavaScript to Go. It receives a map of parameters and returns a response or error.
type Pool ¶
type Pool struct { Timeout time.Duration // Timeout for running nodejs, defaults to 10 second if zero // contains filtered or unexported fields }
Pool manages a pool of NodeJS processes that can be used to execute JavaScript code. It automatically maintains a queue of ready processes and creates new ones as needed.
func (*Pool) Take ¶
Take returns a Process from the pool and will wait until one is available, unless the context is cancelled.
func (*Pool) TakeIfAvailable ¶
TakeIfAvailable returns a Process if any is immediately available, or nil if not. This method does not wait or block if no processes are available.
type Process ¶
type Process struct { *exec.Cmd // Embedded command to run NodeJS // contains filtered or unexported fields }
Process represents a running NodeJS process instance. It wraps exec.Cmd and provides communication channels with the NodeJS process.
func (*Process) Alive ¶
func (p *Process) Alive() <-chan struct{}
Alive returns a channel that will be closed when the NodeJS process ends. This can be used to monitor the process state and react to termination.
func (*Process) Checkpoint ¶
Checkpoint verifies that the NodeJS process is responsive by sending a message and waiting for a response. It returns an error if the process does not respond within the specified timeout duration. This is useful for health checks and ensuring the NodeJS process hasn't frozen.
func (*Process) Close ¶
Close gracefully shuts down the NodeJS process by closing its stdin pipe. This allows the process to perform cleanup operations before exiting.
func (*Process) Eval ¶
Eval executes JavaScript code and returns the result of the evaluation. Unlike Run, this method waits for the code to complete execution and returns the result. It takes a context for timeout/cancellation control. If the JavaScript code throws an error, it will be returned as a Go error.
func (*Process) EvalChannel ¶ added in v0.1.2
EvalChannel will execute the provided code and return a channel that can be used to read the response once it is made available
func (*Process) GetVersion ¶
func (*Process) Kill ¶
func (p *Process) Kill()
Kill forcibly terminates the NodeJS process immediately.
func (*Process) Log ¶
Log appends a message to the nodejs console directly so it can be retrieved with Console()
func (*Process) MakeResponse ¶
MakeResponse returns a handler id and a channel that will see data appended to it if triggered from the javascript side with a response event to that id. This can be useful for asynchronisous events.
func (*Process) NewContext ¶ added in v0.2.0
NewContext creates a new JavaScript execution context in the specified NodeJS process. It returns a Context interface that can be used to execute JavaScript code in isolation.
func (*Process) Run ¶
Run executes the provided JavaScript code in the NodeJS instance. The options map can contain metadata like filename, which determines how the code is executed. If the filename ends in .mjs, the code will be executed as an ES module. This method does not return any results from the execution.
func (*Process) ServeHTTPToHandler ¶ added in v0.2.0
ServeHTTPToHandler converts an HTTP request to a JavaScript Fetch API compatible Request, calls a handler in the NodeJS process, and streams the Response back to the Go ResponseWriter.
The handlerFunc parameter is the name of the JavaScript handler function to call. If handlerFunc contains a dot (e.g., "myContext.handler"), the function will be called within that context. Otherwise, it will be called as a global function.
The handler function should accept a Request object (compatible with the Fetch API) and must return a Response object (also compatible with the Fetch API). For example:
// In JavaScript: myHandler = function(request) { return new Response(JSON.stringify({hello: 'world'}), { status: 200, headers: {'Content-Type': 'application/json'} }); }
The Request and Response objects are available in both global scope and context sandboxes, and can also be imported in ES modules using:
import { Request, Response, Headers } from 'web-api';
If an error occurs in the JavaScript handler, it will be logged and a 500 Internal Server Error will be returned, unless headers have already been written to the response.
This version supports the legacy format where context and handler are combined (context.handler). For a version that takes context separately, use ServeHTTPWithOptions.
func (*Process) ServeHTTPWithOptions ¶ added in v0.2.0
func (p *Process) ServeHTTPWithOptions(handlerFunc string, options HTTPHandlerOptions, w http.ResponseWriter, r *http.Request)
ServeHTTPWithOptions converts an HTTP request to a JavaScript Fetch API compatible Request, calls a handler in the NodeJS process with the specified options, and streams the Response back to the Go ResponseWriter.
The handlerFunc parameter is the name of the JavaScript handler function to call. The options parameter specifies additional configuration, such as the context to run the handler in.
The handler function should accept a Request object (compatible with the Fetch API) and must return a Response object (also compatible with the Fetch API). For example:
// In JavaScript: myHandler = function(request) { return new Response(JSON.stringify({hello: 'world'}), { status: 200, headers: {'Content-Type': 'application/json'} }); }
If an error occurs in the JavaScript handler, it will be logged and a 500 Internal Server Error will be returned, unless headers have already been written to the response.