Files
3cc625ee44 feat(goja): self-contained cap-table bundle + Go embed module (epic #96 pilot) (#2)
Add a self-contained, ESM-free goja bundle of the FULL captable business logic so
the unified hanzoai/cloud binary can host it in-process via dop251/goja
(HIP-0106), backed by per-tenant Base/SQLite — dropping Next.js/Prisma/tRPC from
this path. This is the PILOT of the read-write-Base-via-goja pattern (epic #96)
that esign (#100) and dataroom (#101) reuse via cloud's clients/gojabase binding.

Full fold, ported from the tRPC routers with Prisma/next-auth/zod removed and
persistence delegated to an injected globalThis.__db bridge:

- company (root, seeded per tenant by the host), stakeholders (list/add/update/
  delete), share classes (list/create/update), equity plans (list/create).
- securities issuance: shares (add/list/delete) + options (add/list/delete);
  share transfers (full reassign + partial split, atomic).
- convertibles: SAFEs + convertible notes (list/create/delete).
- rounds + investments: a PRICED round issues shares to each investor and
  dilutes the cap table; SAFE/CONVERTIBLE rounds record capital.
- captable: computed ownership on a fully-diluted basis (shares + options),
  per-class authorized-vs-issued, convertibles + rounds summary.

Structure:
- goja/src/*.ts — domain logic split by concern (host bridge, validators, and a
  routes/ module per domain), exposing globalThis.handle({route,params,orgId,
  body}) -> {status,body}. The bundle carries logic, never a DB engine.
- goja/build.mjs — esbuild → goja/bundle.js (IIFE, ES2015, platform=neutral, no
  node: builtins). bundle.js committed so cloud go:embeds it (as @hanzo/plans
  ships goja/bundle.js).
- goja/test/bundle.test.mjs — smoke + wiring test in node:vm vs a mock __db
  (tenant gate, route table, validation, INSERT shape, idx auto-increment, cap
  table shape, full transfer, round-create gating). 9/9 pass.
- embed.go + go.mod — tiny std-only Go embed module (github.com/hanzoai/captable)
  exposing Bundle(); mirrors github.com/hanzoai/plans. The Next app + goja/src
  stay the single source of truth; the Go layer is a read-only embed.
- tsconfig/biome — exclude goja/ from the Next typecheck + lint.

Claude-Session: https://claude.ai/code/session_016yg7GPhYdWCh9vpp4HEwLZ

Co-authored-by: hanzo-dev <dev@hanzo.ai>
2026-07-10 02:24:59 -07:00

42 lines
1.6 KiB
Go

// Package captable embeds the captable goja bundle so the unified hanzoai/cloud
// binary can host the cap-table business logic in-process via the dop251/goja
// engine (HIP-0106), without copying the bundle into the cloud repo.
//
// The Next.js app + the goja/src TypeScript remain the single source of truth.
// This Go file is a read-only embed:
//
// - Bundle() returns goja/bundle.js — the self-contained, ESM-free port of the
// tRPC routers (stakeholders, share classes, securities, cap-table read)
// that the Go host runs in goja, calling globalThis.handle({route,params,
// orgId,body}) per request. Persistence is the host's injected globalThis.__db
// over per-tenant Base/SQLite; there is no Prisma in this path.
//
// Mirrors github.com/hanzoai/plans exactly (the proven precedent).
package captable
import _ "embed"
// Version identifies the embedded bundle for the host's mount log. Bump on any
// change to goja/bundle.js.
const Version = "0.1.0"
//go:embed goja/bundle.js
var bundle []byte
// Bundle returns the goja bundle source (goja/bundle.js). The host compiles it
// once and runs it on each pooled goja runtime.
func Bundle() ([]byte, error) {
if len(bundle) == 0 {
return nil, errEmptyBundle
}
return bundle, nil
}
// errEmptyBundle is returned if the embed produced no bytes (a build that forgot
// to run goja/build.mjs). Fail loud rather than mount an empty bundle.
var errEmptyBundle = &bundleError{"captable: embedded goja/bundle.js is empty (run `node goja/build.mjs`)"}
type bundleError struct{ msg string }
func (e *bundleError) Error() string { return e.msg }