initial commit

This commit is contained in:
2026-01-27 09:55:09 -05:00
commit ccbe9cd7bf
18 changed files with 2210 additions and 0 deletions

180
internal/runtime/runtime.go Normal file
View File

@@ -0,0 +1,180 @@
package runtime
import (
"context"
"fmt"
"os"
"strings"
"github.com/evanw/esbuild/pkg/api"
"github.com/fastschema/qjs"
"reichard.io/poiesis/internal/functions"
_ "reichard.io/poiesis/internal/stdlib"
)
type Runtime struct {
ctx *qjs.Context
opts qjs.Option
funcs map[string]functions.Function
typeDecls map[string]string
}
func New(ctx context.Context, opts ...RuntimeOption) (*Runtime, error) {
// Create Runtime
r := &Runtime{opts: qjs.Option{Context: ctx}}
for _, opt := range opts {
opt(r)
}
// Create QuickJS Context
rt, err := qjs.New(r.opts)
if err != nil {
return nil, err
}
r.ctx = rt.Context()
// Populate Globals
if err := r.populateGlobals(); err != nil {
return nil, err
}
return r, nil
}
func (r *Runtime) populateGlobals() error {
// Initialize Maps
r.funcs = make(map[string]functions.Function)
r.typeDecls = make(map[string]string)
// Load Requested Functions
allFuncs := functions.GetRegisteredFunctions()
for name, fn := range allFuncs {
r.funcs[name] = fn
if err := r.addFunctionTypes(fn); err != nil {
return fmt.Errorf("failed to add types for function %s: %w", name, err)
}
}
// Register Functions with QuickJS
for name, fn := range r.funcs {
if fn.IsAsync() {
r.ctx.SetAsyncFunc(name, func(this *qjs.This) {
qjsVal, err := callFunc(this, fn)
if err != nil {
_ = this.Promise().Reject(this.Context().NewError(err))
return
}
_ = this.Promise().Resolve(qjsVal)
})
} else {
r.ctx.SetFunc(name, func(this *qjs.This) (*qjs.Value, error) {
return callFunc(this, fn)
})
}
}
return nil
}
// addFunctionTypes adds types from a function to the runtime's type declarations.
// Returns an error if there's a type conflict (same name, different definition).
func (r *Runtime) addFunctionTypes(fn functions.Function) error {
for name, def := range fn.Types() {
if existing, ok := r.typeDecls[name]; ok && existing != def {
return fmt.Errorf("type conflict: %s has conflicting definitions (existing: %s, new: %s)",
name, existing, def)
}
r.typeDecls[name] = def
}
return nil
}
// GetTypeDeclarations returns all TypeScript type declarations for this runtime.
// Includes both type definitions and function declarations.
func (r *Runtime) GetTypeDeclarations() string {
var decls []string
// Add Type Definitions
for _, def := range r.typeDecls {
decls = append(decls, def)
}
// Add Function Declarations
for _, fn := range r.funcs {
decls = append(decls, fn.Definition())
}
return strings.Join(decls, "\n\n")
}
func (r *Runtime) RunFile(filePath string) error {
tsFileContent, err := os.ReadFile(filePath)
if err != nil {
return fmt.Errorf("error reading file: %w", err)
}
return r.RunCode(string(tsFileContent))
}
func (r *Runtime) RunCode(tsCode string) error {
transformedCode, err := r.transformCode(tsCode)
if err != nil {
return err
}
result, err := r.ctx.Eval("code.ts", qjs.Code(string(transformedCode)))
if result != nil {
result.Free()
}
return err
}
func (r *Runtime) transformCode(tsCode string) ([]byte, error) {
result := api.Transform(tsCode, api.TransformOptions{
Loader: api.LoaderTS,
Target: api.ES2022,
Format: api.FormatIIFE,
Sourcemap: api.SourceMapNone,
TreeShaking: api.TreeShakingFalse,
})
if len(result.Errors) > 0 {
var b strings.Builder
for i, e := range result.Errors {
if i > 0 {
b.WriteString(", ")
}
b.WriteString(e.Text)
}
return nil, fmt.Errorf("transpilation failed: %s", b.String())
}
return result.Code, nil
}
func callFunc(this *qjs.This, fn functions.Function) (*qjs.Value, error) {
qjsArgs := this.Args()
fnArgs := fn.Arguments()
var allArgs []any
for i := range min(len(fnArgs), len(qjsArgs)) {
rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i])
if err != nil {
return nil, fmt.Errorf("argument conversion failed: %w", err)
}
allArgs = append(allArgs, rVal.Interface())
}
result, err := fn.Call(this.Context(), allArgs)
if err != nil {
return nil, err
}
val, err := qjs.ToJsValue(this.Context(), result)
if err != nil {
return nil, err
}
return val, nil
}