Evan Reichard f308970531 chore(runtime): Add logrus logging framework with structured logging
Add logrus as the primary logging framework with dependency injection
pattern. All errors now use WithError() for context, and structured
logging uses camelCase field names. Tag runtime service with service
field for better log organization.

- Add logrus dependency
- Update Runtime to accept logger via dependency injection
- Add WithLogger() option for logger configuration
- Log errors with WithError() for context
- Log async function failures with service context
- Document logging practices in AGENTS.md
2026-01-29 21:32:00 -05:00
2026-01-29 21:31:49 -05:00
2026-01-29 21:31:49 -05:00

Poiesis

A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with qjs, with an extensible function system.

Project Structure

reichard.io/poiesis/
├── cmd/
│   └── poiesis/                    # CLI application entry point
│       └── main.go
├── internal/
│   ├── runtime/                    # Runtime management, transpilation, execution
│   │   ├── runtime.go              # Core runtime, transpilation, execution
│   │   └── runtime_test.go         # Runtime tests
│   ├── functions/                  # Function registration framework
│   │   ├── registry.go             # Registration system
│   │   ├── types.go                # Core interfaces and types
│   │   ├── typescript.go           # TypeScript definition generation
│   │   ├── collector.go            # Type collection utilities
│   │   └── typescript_test.go      # Type system tests
│   └── stdlib/                     # Standard library implementations
│       ├── fetch.go                # HTTP fetch implementation
│       └── fetch_test.go           # Fetch tests

Architecture

The project is cleanly separated into three packages:

  1. internal/runtime - Runtime management

    • TypeScript transpilation with esbuild
    • JavaScript execution with qjs
    • Automatic function registration and execution
  2. internal/functions - Generic function registration framework

    • Type-safe registration with generics
    • Bidirectional Go ↔ JavaScript type conversion
    • Automatic TypeScript declaration generation
  3. internal/stdlib - Standard library implementations

    • fetch - HTTP requests
    • Extensible for additional standard functions

Installation & Build

go build ./cmd/poiesis

CLI Options

  • [file] - Path to TypeScript file to execute (optional)
  • --print-types - Print TypeScript type declarations for all registered functions
  • --help - Show help information

Testing

go test ./...
golangci-lint run

Usage

poiesis execute [file]      # Run TypeScript file
poiesis types               # Print TypeScript type declarations
poiesis --help              # Show help

Function System

The function system allows you to easily expose Go functions to TypeScript/JavaScript.

Adding a Function

Just write a Go function and register it:

package mystdlib

import (
    "context"
    "reichard.io/poiesis/internal/functions"
)

type AddArgs struct {
    A int `json:"a"`
    B int `json:"b"`
}

func (a AddArgs) Validate() error {
    return nil
}

func Add(_ context.Context, args AddArgs) (int, error) {
    return args.A + args.B, nil
}

func init() {
    functions.RegisterFunction[AddArgs, int]("add", Add)
}

That's it! The framework automatically:

  • Converts JavaScript values to Go types
  • Handles errors (panics as JS errors)
  • Generates TypeScript definitions
  • Manages the qjs integration

Calling Convention

Important: There's an important difference between how functions are defined in Go versus how they're called in JavaScript:

  • Go side: The function receives a single argument struct containing all parameters
  • JavaScript side: The function is called with the struct fields as individual arguments (in the order they appear in the struct)
// Go: Single struct argument
type AddArgs struct {
    A int `json:"a"`
    B int `json:"b"`
}

func Add(_ context.Context, args AddArgs) (int, error) {
    return args.A + args.B, nil
}
// JavaScript: Individual arguments (not an object!)
const result = add(5, 10);  // NOT add({ a: 5, b: 10 })

The framework extracts the JSON tags from the struct fields and uses them to generate the correct TypeScript function signature.

Example

// TypeScript code - call with individual arguments matching struct fields
const response = fetch("https://httpbin.org/get");
console.log("OK:", response.ok);
console.log("Status:", response.status);
console.log("Body:", response.body);

Built-in Functions

  • fetch(options) - HTTP requests
    • options.input (string) - URL to fetch
    • options.init (object) - Optional init object with method, headers, body

Dependencies

  • github.com/evanw/esbuild/pkg/api - TypeScript transpilation
  • github.com/fastschema/qjs - JavaScript execution (QuickJS)
  • github.com/stretchr/testify/assert - Test assertions

Development

  • Test framework: Go's built-in testing package
  • Code style: Follow standard Go conventions
  • Linting: golangci-lint run - must pass before committing
  • TypeScript test files: Tests that require TypeScript files should create them inline using os.CreateTemp() instead of relying on external test files
Description
No description provided
Readme 107 KiB
Languages
Go 98.6%
Nix 1.4%