Help us shape the future of Go! Take the 2026 Go Developer Survey

gobridge

package module
v1.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 6, 2026 License: Apache-2.0 Imports: 23 Imported by: 0

README

gobridge

Expose a Go library as a CLI and typed Python and TypeScript packages. Add comments to ordinary Go functions; gobridge generates the adapters and builds packages with the Go executable included. Calls reuse a private subprocess, so Go objects and caches stay alive between calls.

//gobridge:export
func Greet(name string) string { return "Hello, " + name + "!" }
from greeter import greet

message = await greet(name="World")  # "Hello, World!"
  • Authors: Go 1.23+ and Python 3.10+; Node 24+ and npm for TypeScript builds.
  • Consumers: Python 3.10+ or Node 24+. No Go installation or separate server.
  • Scope: local, typed function calls and stateful objects over subprocess pipes. No browser support, streaming, or shared remote daemon.

Quick start · Build packages · State and options · API guide · Contributing

Performance benchmarks and lifecycle stress tests compare native Go, Python, and TypeScript with reproducible workloads.

Quick start

Install the tool and scaffold a runnable project:

go install github.com/sambhav/gobridge/cmd/gobridge@latest
gobridge init --dir greeter --module example.com/greeter --name acme.greeter
cd greeter
gobridge generate --dir bridge
go mod tidy
gobridge dev -- python app.py
# TypeScript: gobridge dev --typescript -- node app.mts

Use --npm-package @acme/greeter with init for an npm scope. Dotted Python names such as acme.tools.greeter create native namespace packages. Packaging and development guide covers scaffolding, wrappers, assets, dependencies, and live reload.

Add gobridge manually

For an existing library, the equivalent setup is below. Start a new Go module and install the tool:

mkdir greeter
cd greeter
go mod init example.com/greeter
go get github.com/sambhav/gobridge@latest
go install github.com/sambhav/gobridge/cmd/gobridge@latest

Put Go's binary directory on PATH. You need Go 1.23+ and Python 3.10+ for this walkthrough. Generated packages include the runtime and Go executable; consumers do not need Go or a separate gobridge runtime package.

Create greeter.go:

package greeter

//gobridge:export
func Greet(name string) string { return "Hello, " + name + "!" }

Create cmd/greeter/main.go:

package main

import greeter "example.com/greeter"

func main() {
    registry, err := greeter.NewGobridge()
    if err != nil { panic(err) }
    registry.Main()
}

Create gobridge.json:

{
  "name": "greeter",
  "source": ".",
  "command": "./cmd/greeter",
  "version": "0.1.0"
}

Create app.py:

import asyncio
from greeter import greet

async def main():
    print(await greet(name="World"))

asyncio.run(main())

Run it with live rebuilds:

gobridge dev -- python app.py

This generates the Go adapter, builds the binary and writes the complete Python package into build/greeter. --once builds without watching or running an app. In an async application or notebook, use its existing event loop. Imports start no process; calls reuse one private Go daemon, cleaned up at process exit.

The same function is also a CLI:

go run ./cmd/greeter greet --name World

CLI calls print JSON to stdout and diagnostics to stderr. Try greet --help, greet --json '{"name":"World"}', or --json - to read stdin.

Python functions are async and keyword-only. Synchronous scripts can import greet_sync instead:

from greeter import greet_sync

print(greet_sync(name="World"))

Build and install packages

From your Go project, inspect the plan with gobridge build --check, then build the formats you need:

gobridge build --python
gobridge build --typescript
# Or build both:
gobridge build --python --typescript

The command reads gobridge.json, generates bindings, cross-compiles Go, and bundles the executable and private runtime. Artifacts are staged and accompanied by a checksum manifest; use --replace to overwrite different same-version artifacts. Python wheels need only Python's standard library to build. TypeScript packaging also needs Node 24+ and npm.

# Install the packages built above:
pip install --no-index --find-links dist greeter
npm install ./dist/npm/greeter-0.1.0.tgz
import { greet } from "greeter";

console.log(await greet({ name: "World" }));

All six Linux/macOS/Windows × amd64/arm64 targets build by default with CGO_ENABLED=0. Use --targets linux-amd64,darwin-arm64 for fewer targets, --output for another directory, and --version 0.2.0 to override the manifest. Prereleases work too: --version 0.2.0-rc.1 produces Python 0.2.0rc1 and npm 0.2.0-rc.1. See package versioning. Libraries requiring cgo need their own target build recipe.

Publish dist/*.whl to PyPI with Twine or dist/npm/*.tgz with npm publish. Optional manifest fields python_distribution and npm_package set registry names independently of the Python import name, for example acme-greeter and @acme/greeter. repository and license set package metadata. Consumers install your package; gobridge's internal runtimes and examples are not published separately.

For an organization-owned package, keep the names explicit:

Setting Example Used by
name acme.greeter Python import path; executable is acme_greeter
python_distribution acme-greeter pip install acme-greeter
npm_package @acme/greeter npm install @acme/greeter and TypeScript imports
class Greeter Python Greeter/SyncGreeter and TypeScript Greeter

See the packaging guide for a complete manifest, generated package layouts, local installation, publishing, and TypeScript namespace imports. Dotted names such as acme.greeter generate native Python namespace packages: from acme.greeter import greet. Both wheels and dev mode support them.

Add state and options

Add the following to greeter.go to introduce constructor options:

type Options struct {
    Prefix *string `json:"prefix,omitempty"`
}

type Greeter struct { prefix string }

//gobridge:constructor
func NewGreeter(options Options) *Greeter {
    prefix := "Welcome, "
    if options.Prefix != nil { prefix = *options.Prefix }
    return &Greeter{prefix: prefix}
}

//gobridge:export
func (g *Greeter) Welcome(name string) string { return g.prefix + name }

The dev command regenerates the Python constructor and methods. Each instance owns an independent Go object and daemon:

from greeter import Greeter

async with Greeter(prefix="Hey, ") as greeter:
    print(await greeter.welcome(name="Sam"))  # Hey, Sam

Results are normal scalars or frozen dataclasses. Void Go methods return None. There is no required Pydantic dependency. Keep a client alive for useful work; creating a process per call is expensive.

Synchronous applications use SyncGreeter and ordinary with:

from greeter import SyncGreeter

with SyncGreeter(prefix="Hey, ") as greeter:
    print(greeter.welcome(name="Sam"))

The CLI supplies these options with --config '{"prefix":"Hey, "}' before welcome --name Sam. Defaults stay in your Go constructor.

Supported APIs

Export functions and methods with //gobridge:export; mark an optional constructor with //gobridge:constructor. Names become snake_case in Python and camelCase in TypeScript. Leading context.Context parameters receive cancellation and deadlines. Go errors become exceptions. Only annotated declarations are exposed.

Values can be strings, booleans, signed integers, finite floats, named structs, slices, string-keyed maps, and pointers. Struct fields need explicit json names. Python returns frozen dataclasses; TypeScript returns typed plain objects and uses bigint for Go int64. Recursive types, interfaces, custom marshalers, variadic functions, and multiple non-error results need adapters.

Use the API guide for manual registration, validation, sessions, timeouts, development reloads, and Cobra embedding. The runnable examples are greeter and Cobra. For tests, benchmarks, and releasing gobridge itself, see Contributing.

Documentation

Overview

Package gobridge exposes typed Go operations through a CLI and a local multiplexed stdio protocol. Register operations before serving; handlers run concurrently and must respect context cancellation.

Index

Constants

View Source
const MaxFrame = 1024 * 1024

Variables

This section is empty.

Functions

func Bind

func Bind(r *Registry, name string, fn any, paramNames ...string) error

Bind exposes an ordinary Go function or bound method. Supply every parameter name explicitly, except an optional leading context.Context. Supported results are T, (T, error), error, or no results. Pointer parameters are optional. Reflection adapts calls; Register retains its direct typed invocation path.

func Failure

func Failure(code, message string) error

func Register

func Register[I, O any](r *Registry, name, description string, fn func(context.Context, I) (O, error)) error

Register adapts an ordinary typed Go function. Both wire types must be named structs using the supported JSON subset; schema errors fail at registration.

Types

type Constraints

type Constraints struct {
	Minimum   json.Number `json:"minimum,omitempty"`
	Maximum   json.Number `json:"maximum,omitempty"`
	MinLength *int        `json:"min_length,omitempty"`
	MaxLength *int        `json:"max_length,omitempty"`
}

Constraints describes inclusive limits on a non-null wire field. JSON numbers preserve exact integer bounds; an empty Number means the bound is absent. Length counts Unicode code points for strings and entries for slices/maps.

type Error

type Error struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

Error is a stable error code and a user-facing message, preserved in Python.

func (*Error) Error

func (e *Error) Error() string

type Field

type Field struct {
	Name        string       `json:"name"`
	Type        Type         `json:"type"`
	Description string       `json:"description,omitempty"`
	Constraints *Constraints `json:"constraints,omitempty"`
}

type Memo

type Memo[K comparable, V any] struct {
	// contains filtered or unexported fields
}

Memo is a bounded, concurrency-safe TTL/LRU cache with per-key request coalescing. Values must be immutable or copied by the caller. Only use it for pure operations; keys must include every input and identity boundary.

func NewMemo

func NewMemo[K comparable, V any](capacity int, ttl time.Duration) *Memo[K, V]

func (*Memo[K, V]) Get

func (m *Memo[K, V]) Get(ctx context.Context, key K, load func(context.Context) (V, error)) (V, error)

type Object

type Object struct {
	// contains filtered or unexported fields
}

Object binds method expressions to one constructor-created receiver per daemon. Register all methods before serving; mutable receiver state must be safe for concurrent method calls. Constructors never run during schema generation.

func NewObject

func NewObject(r *Registry, fn any) (*Object, error)

NewObject declares the registry's constructor. It accepts func(Config) *T or func(Config) (*T, error), optionally with a leading context.Context. Config must be a named wire struct and *T a pointer to a named struct. One constructor is supported per registry; Bind can also register ordinary functions alongside it.

func (*Object) Bind

func (o *Object) Bind(name string, method any, paramNames ...string) error

Bind exposes a method expression such as (*Counter).Add. The receiver is supplied by the bridge; paramNames describe only the remaining wire arguments.

type Operation

type Operation struct {
	Name        string `json:"name"`
	Description string `json:"description"`
	Input       Type   `json:"input"`
	Output      Type   `json:"output"`
}

type Registry

type Registry struct {
	// contains filtered or unexported fields
}

Registry is immutable once serving starts. Registration is not concurrent.

func New

func New() *Registry

func (*Registry) Call

func (r *Registry) Call(ctx context.Context, name string, params json.RawMessage) (result any, err error)

func (*Registry) Describe

func (r *Registry) Describe(name, description string) error

Describe supplies help text for an already registered function or method.

func (*Registry) GeneratePython

func (r *Registry) GeneratePython(w io.Writer, class, binary string) error

GeneratePython emits concrete dataclasses, constructors and keyword-only functions/methods, plus lazy module defaults and explicit scope controls.

func (*Registry) GenerateTypeScript

func (r *Registry) GenerateTypeScript(w io.Writer, class, binary string) error

GenerateTypeScript emits ESM bindings with concrete readonly models, exact int64 bigint types, lazy instance/module clients, and separate runtime options. The matching package runtime is gobridge-runtime (Node.js 24 and newer).

func (*Registry) Initialize

func (r *Registry) Initialize(ctx context.Context, config json.RawMessage) (err error)

Initialize creates the process-owned receiver exactly once. Even failed attempts cannot be retried: constructors may have irreversible side effects. A new client process provides a fresh initialization attempt.

func (*Registry) Main

func (r *Registry) Main()

Main runs with the process streams and sets a nonzero exit status on error.

func (*Registry) NeedsInit

func (r *Registry) NeedsInit() bool

NeedsInit reports whether a declared constructor has not successfully run.

func (*Registry) Run

func (r *Registry) Run(ctx context.Context, args []string, in io.Reader, out, stderr io.Writer) error

Run exposes the same registry as a CLI, schema command, generator, or daemon. Operation flags use JSON literals, except strings which are passed as text.

func (*Registry) Schema

func (r *Registry) Schema() Schema

func (*Registry) Serve

func (r *Registry) Serve(parent context.Context, in io.Reader, out io.Writer, maxConcurrent int) error

Serve owns one session. EOF cancels all calls and returns immediately; cooperative handlers stop via their context. The hosting main must exit on return. stdout is exclusively protocol data; send logging to stderr.

type Request

type Request struct {
	ID        string          `json:"id"`
	Method    string          `json:"method"`
	Params    json.RawMessage `json:"params"`
	TimeoutMS int64           `json:"timeout_ms,omitempty"`
}

type Response

type Response struct {
	ID     string `json:"id"`
	Result any    `json:"result,omitempty"`
	Error  *Error `json:"error,omitempty"`
}

func (Response) MarshalJSON

func (r Response) MarshalJSON() ([]byte, error)

A successful void operation must include result:null. Omitting it would make the response indistinguishable from an invalid envelope to strict clients.

type Schema

type Schema struct {
	Protocol    int         `json:"protocol"`
	Hash        string      `json:"schema_hash"`
	Operations  []Operation `json:"operations"`
	Constructor *Type       `json:"constructor,omitempty"`
}

type Type

type Type struct {
	Kind   string  `json:"kind"`
	Name   string  `json:"name,omitempty"`
	Elem   *Type   `json:"elem,omitempty"`
	Fields []Field `json:"fields,omitempty"`
}

Directories

Path Synopsis
cmd
gobridge command
Command gobridge generates bridge adapters from annotated Go source.
Command gobridge generates bridge adapters from annotated Go source.
examples
greeter
Package greeter demonstrates an ordinary Go library with generated CLI and Python adapters.
Package greeter demonstrates an ordinary Go library with generated CLI and Python adapters.
greeter/cmd/greeter command
Command greeter exposes the greeter library as a CLI and stdio daemon.
Command greeter exposes the greeter library as a CLI and stdio daemon.
internal
fixtures/hello command
Hello exercises typed registration and cache ownership in integration tests.
Hello exercises typed registration and cache ownership in integration tests.
fixtures/perf command
perf supplies identical work to native Go and generated bridge clients.
perf supplies identical work to native Go and generated bridge clients.
sourcegen
Package sourcegen generates bridge registration from explicitly annotated Go declarations.
Package sourcegen generates bridge registration from explicitly annotated Go declarations.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL