more cleanup
This commit is contained in:
105
README.md
105
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Poiesis
|
# Poiesis
|
||||||
|
|
||||||
A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with goja, with an extensible builtin system.
|
A Go tool that transpiles TypeScript to JavaScript using esbuild and executes it with qjs, with an extensible function system.
|
||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
@@ -10,35 +10,37 @@ reichard.io/poiesis/
|
|||||||
│ └── poiesis/ # CLI application entry point
|
│ └── poiesis/ # CLI application entry point
|
||||||
│ └── main.go
|
│ └── main.go
|
||||||
├── internal/
|
├── internal/
|
||||||
│ └── runtime/
|
│ ├── runtime/ # Runtime management, transpilation, execution
|
||||||
│ ├── pkg/
|
│ │ ├── runtime.go # Core runtime, transpilation, execution
|
||||||
│ │ └── builtin/ # Builtin framework (framework only)
|
│ │ └── runtime_test.go # Runtime tests
|
||||||
│ │ └── builtin.go # Registration system & type conversion
|
│ ├── functions/ # Function registration framework
|
||||||
│ ├── standard/ # Standard builtin implementations
|
│ │ ├── registry.go # Registration system
|
||||||
│ │ ├── fetch.go # HTTP fetch builtin
|
│ │ ├── types.go # Core interfaces and types
|
||||||
│ │ └── fetch_test.go # Tests for fetch
|
│ │ ├── typescript.go # TypeScript definition generation
|
||||||
│ ├── runtime.go # Transpilation & execution
|
│ │ ├── collector.go # Type collection utilities
|
||||||
│ └── runtime_test.go # Runtime tests
|
│ │ └── typescript_test.go # Type system tests
|
||||||
|
│ └── stdlib/ # Standard library implementations
|
||||||
|
│ ├── fetch.go # HTTP fetch implementation
|
||||||
|
│ └── fetch_test.go # Fetch tests
|
||||||
```
|
```
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The project is cleanly separated into three packages:
|
The project is cleanly separated into three packages:
|
||||||
|
|
||||||
1. **`internal/runtime/pkg/builtin`** - The framework for registering builtins and type conversion
|
1. **`internal/runtime`** - Runtime management
|
||||||
- Generic registration with automatic type inference
|
|
||||||
- Bidirectional Go ↔ JavaScript type conversion
|
|
||||||
- No builtin implementations (pure framework)
|
|
||||||
|
|
||||||
2. **`internal/runtime/standard`** - Standard builtin implementations
|
|
||||||
- `fetch`, `add`, `greet`
|
|
||||||
- Custom type converters for complex types
|
|
||||||
- Independent and easily extensible
|
|
||||||
|
|
||||||
3. **`internal/runtime`** - Runtime management
|
|
||||||
- TypeScript transpilation with esbuild
|
- TypeScript transpilation with esbuild
|
||||||
- JavaScript execution with goja
|
- JavaScript execution with qjs
|
||||||
- Automatically imports and registers standard builtins
|
- 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
|
## Installation & Build
|
||||||
|
|
||||||
@@ -57,55 +59,74 @@ golangci-lint run
|
|||||||
|
|
||||||
```bash
|
```bash
|
||||||
poiesis <typescript-file>
|
poiesis <typescript-file>
|
||||||
|
poiesis -print-types
|
||||||
```
|
```
|
||||||
|
|
||||||
## Builtin System
|
## Function System
|
||||||
|
|
||||||
The builtin system allows you to easily expose Go functions to TypeScript/JavaScript.
|
The function system allows you to easily expose Go functions to TypeScript/JavaScript.
|
||||||
|
|
||||||
### Adding a Builtin
|
### Adding a Function
|
||||||
|
|
||||||
Just write a Go function and register it:
|
Just write a Go function and register it:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Your function
|
package mystdlib
|
||||||
func add(a, b int) int {
|
|
||||||
return a + b
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register it
|
|
||||||
func init() {
|
func init() {
|
||||||
builtin.RegisterBuiltin("add", add)
|
functions.RegisterFunction[AddArgs, int]("add", Add)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it! The framework automatically:
|
That's it! The framework automatically:
|
||||||
|
- Converts JavaScript values to Go types
|
||||||
- Converts TypeScript values to Go types
|
|
||||||
- Handles errors (panics as JS errors)
|
- Handles errors (panics as JS errors)
|
||||||
- Generates TypeScript definitions
|
- Generates TypeScript definitions
|
||||||
- Manages the goja integration
|
- Manages the qjs integration
|
||||||
|
|
||||||
### Example
|
### Example
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// TypeScript code
|
// TypeScript code
|
||||||
console.log("5 + 10 =", add(5, 10));
|
const response = fetch({input: "https://httpbin.org/get"});
|
||||||
|
|
||||||
const response = fetch("https://httpbin.org/get");
|
|
||||||
console.log("OK:", response.ok);
|
console.log("OK:", response.ok);
|
||||||
console.log("Status:", response.status);
|
console.log("Status:", response.status);
|
||||||
console.log("Body:", response.text());
|
console.log("Body:", response.body);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Built-in Functions
|
### Built-in Functions
|
||||||
|
|
||||||
- `fetch(url, options?)` - HTTP requests
|
- `fetch(options)` - HTTP requests
|
||||||
- `add(a, b)` - Simple arithmetic example
|
- `options.input` (string) - URL to fetch
|
||||||
- `greet(name)` - String manipulation example
|
- `options.init` (object) - Optional init object with `method`, `headers`, `body`
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation
|
- `github.com/evanw/esbuild/pkg/api` - TypeScript transpilation
|
||||||
- `github.com/dop251/goja` - JavaScript execution
|
- `github.com/fastschema/qjs` - JavaScript execution (QuickJS)
|
||||||
- `github.com/stretchr/testify/assert` - Test assertions
|
- `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
|
||||||
|
|||||||
@@ -67,7 +67,14 @@ func GetFunctionDeclarations() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func GetRegisteredFunctions() map[string]Function {
|
func GetRegisteredFunctions() map[string]Function {
|
||||||
return functionRegistry
|
registryMutex.RLock()
|
||||||
|
defer registryMutex.RUnlock()
|
||||||
|
|
||||||
|
result := make(map[string]Function, len(functionRegistry))
|
||||||
|
for k, v := range functionRegistry {
|
||||||
|
result[k] = v
|
||||||
|
}
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterFunction[T Args, R any](name string, fn GoFunc[T, R]) {
|
func RegisterFunction[T Args, R any](name string, fn GoFunc[T, R]) {
|
||||||
|
|||||||
@@ -94,11 +94,14 @@ func (r *Runtime) transformCode(tsCode string) ([]byte, error) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if len(result.Errors) > 0 {
|
if len(result.Errors) > 0 {
|
||||||
var allErrs []string
|
var b strings.Builder
|
||||||
for _, e := range result.Errors {
|
for i, e := range result.Errors {
|
||||||
allErrs = append(allErrs, e.Text)
|
if i > 0 {
|
||||||
|
b.WriteString(", ")
|
||||||
|
}
|
||||||
|
b.WriteString(e.Text)
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("transpilation failed: %s", strings.Join(allErrs, ", "))
|
return nil, fmt.Errorf("transpilation failed: %s", b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.Code, nil
|
return result.Code, nil
|
||||||
@@ -112,7 +115,7 @@ func callFunc(this *qjs.This, fn functions.Function) (*qjs.Value, error) {
|
|||||||
for i := range min(len(fnArgs), len(qjsArgs)) {
|
for i := range min(len(fnArgs), len(qjsArgs)) {
|
||||||
rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i])
|
rVal, err := qjs.JsArgToGo(qjsArgs[i], fnArgs[i])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
return nil, fmt.Errorf("argument conversion failed: %w", err)
|
||||||
}
|
}
|
||||||
allArgs = append(allArgs, rVal.Interface())
|
allArgs = append(allArgs, rVal.Interface())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,9 +31,6 @@ type RequestInit struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (o *RequestInit) Validate() error {
|
func (o *RequestInit) Validate() error {
|
||||||
if o.Method == "" {
|
|
||||||
o.Method = "GET"
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -44,35 +41,20 @@ type Response struct {
|
|||||||
Headers map[string]string `json:"headers"`
|
Headers map[string]string `json:"headers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddArgs struct {
|
func Fetch(ctx context.Context, args FetchArgs) (Response, error) {
|
||||||
A int `json:"a"`
|
|
||||||
B int `json:"b"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a AddArgs) Validate() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type GreetArgs struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g GreetArgs) Validate() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func Fetch(_ context.Context, args FetchArgs) (Response, error) {
|
|
||||||
method := "GET"
|
method := "GET"
|
||||||
headers := make(map[string]string)
|
headers := make(map[string]string)
|
||||||
|
|
||||||
if args.Init != nil {
|
if args.Init != nil {
|
||||||
method = args.Init.Method
|
if args.Init.Method != "" {
|
||||||
|
method = args.Init.Method
|
||||||
|
}
|
||||||
if args.Init.Headers != nil {
|
if args.Init.Headers != nil {
|
||||||
maps.Copy(headers, args.Init.Headers)
|
maps.Copy(headers, args.Init.Headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequest(method, args.Input, nil)
|
req, err := http.NewRequestWithContext(ctx, method, args.Input, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return Response{}, fmt.Errorf("failed to create request: %w", err)
|
return Response{}, fmt.Errorf("failed to create request: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user