Files
poiesis/AGENTS.md
2026-01-29 21:31:49 -05:00

4.5 KiB

Poiesis

Module Name

reichard.io/poiesis

Overview

Go tool that transpiles TypeScript to JavaScript using esbuild API and executes it with github.com/fastschema/qjs. Features a flexible builtin system for exposing Go functions to TypeScript with support for both synchronous and asynchronous (Promise-based) operations.

Build & Test

go build ./cmd/poiesis
go test ./...
golangci-lint run

Project Structure

reichard.io/poiesis/
├── cmd/poiesis/              # CLI entry point
│   └── main.go
├── internal/
│   ├── runtime/              # Runtime management, transpilation, execution
│   │   ├── runtime.go
│   │   ├── runtime_test.go
│   │   └── options.go
│   ├── functions/            # Function registration framework
│   │   ├── registry.go
│   │   ├── types.go
│   │   ├── typescript_test.go
│   │   └── functions_test.go
│   ├── tsconvert/            # Go-to-TypeScript type conversion utilities
│   │   ├── convert.go
│   │   ├── types.go
│   │   └── convert_test.go
│   └── stdlib/               # Standard library implementations
│       ├── fetch.go
│       └── fetch_test.go

Key Packages

  • reichard.io/poiesis/internal/runtime - Runtime management, TypeScript transpilation, JavaScript execution, per-runtime type management
  • reichard.io/poiesis/internal/functions - Generic function registration framework (sync/async wrappers, automatic JS/Go conversion via JSON)
  • reichard.io/poiesis/internal/tsconvert - Go-to-TypeScript type conversion utilities and type declaration generation
  • reichard.io/poiesis/internal/stdlib - Standard library implementations (fetch)

Function System

Registration

Two types of functions:

  • Sync: RegisterFunction[T, R](name, fn) - executes synchronously, returns value
  • Async: RegisterAsyncFunction[T, R](name, fn) - runs in goroutine, returns Promise

Requirements

  • Args must be a struct implementing Args interface with Validate() error method
  • Use JSON tags for TypeScript type definitions
  • Async functions automatically generate Promise<R> return types

Calling Convention

Important: Functions have different calling conventions on Go vs JavaScript sides:

  • Go side: Function receives a single argument struct with all parameters
  • JavaScript side: Function is called with individual arguments matching the struct fields (in field order)

Fields are ordered by their position in the struct, with the generated TypeScript signature using those field names as argument names.

Example

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
}

// Register sync function
functions.RegisterFunction[AddArgs, int]("add", Add)

// Register async function
functions.RegisterAsyncFunction[FetchArgs, *FetchResult]("fetch", Fetch)

Testing Patterns

  • Test framework: Go's built-in testing package
  • Assertions: github.com/stretchr/testify/assert and require
  • Linting: golangci-lint run - must pass before committing
  • Test organization: Test files use _test.go suffix, test functions prefixed with Test
  • TypeScript test files: Tests that require TypeScript files should create them inline using os.CreateTemp() instead of relying on external test files

Dependencies

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

Code Conventions

  • Handle all return values from external functions (enforced by golangci-lint)
  • Use os package instead of deprecated io/ioutil
  • Error logging uses _, _ = fmt.Fprintf(stderr, ...) pattern
  • Package structure follows standard Go project layout with internal packages

Comment Style

Code blocks (even within functions) should be separated with title-cased comments describing what the block does:

// Create Runtime
r := &Runtime{opts: qjs.Option{Context: ctx}}

// Create QuickJS Context
rt, err := qjs.New(r.opts)

// Populate Globals
if err := r.populateGlobals(); err != nil {
    return nil, err
}

For more complex blocks, use a hyphen to add elaboration:

// Does Thing - We do this here because we need to do xyz...