// Package jsapi registers the sandbox namespace into the Yao V8 runtime. // // All methods are static on the sandbox object — no constructor. // // # JavaScript API // // const box = sandbox.Create({ image: "node:20", owner: "user1" }) // const result = box.Exec(["node", "-e", "console.log('hi')"]) // console.log(result.stdout) // // const box = sandbox.Get(id) // → Box // const list = sandbox.List({ owner: "u1" }) // → BoxInfo[] // sandbox.Delete(id) // → void // // # Go mapping // // sandbox.Create(opts) → Manager.Create(ctx, CreateOptions) → Box // sandbox.Create(opts) → Manager.GetOrCreate(ctx, opts) → Box (when opts.id is set) // sandbox.Get(id) → Manager.Get(ctx, id) → Box // sandbox.List(filter?) → Manager.List(ctx, ListOptions) → []*Box → BoxInfo[] // sandbox.Delete(id) → Manager.Remove(ctx, id) → void // // Registration happens via init() — import with: // // _ "github.com/yaoapp/yao/sandbox/v2/jsapi" package jsapi import ( v8 "github.com/yaoapp/gou/runtime/v8" "rogchap.com/v8go" ) func init() { v8.RegisterObject("sandbox", ExportObject) } // ExportObject exports the sandbox namespace object to V8. func ExportObject(iso *v8go.Isolate) *v8go.ObjectTemplate { obj := v8go.NewObjectTemplate(iso) obj.Set("Create", v8go.NewFunctionTemplate(iso, sbCreate)) obj.Set("Get", v8go.NewFunctionTemplate(iso, sbGet)) obj.Set("List", v8go.NewFunctionTemplate(iso, sbList)) obj.Set("Delete", v8go.NewFunctionTemplate(iso, sbDelete)) return obj } // sbCreate: `sandbox.Create(options)` → Box // // Go: Manager.Create(ctx, CreateOptions) (*Box, error) // // Manager.GetOrCreate(ctx, CreateOptions) (*Box, error) — when opts.id is set // // JS options → Go CreateOptions mapping: // // { // id: string → CreateOptions.ID // optional; triggers GetOrCreate // owner: string → CreateOptions.Owner // required // pool: string → CreateOptions.Pool // default: first pool // image: string → CreateOptions.Image // required // workdir: string → CreateOptions.WorkDir // user: string → CreateOptions.User // e.g. "1000:1000" // env: object → CreateOptions.Env // map[string]string // memory: number → CreateOptions.Memory // bytes (int64) // cpus: number → CreateOptions.CPUs // float64 e.g. 1.5 // vnc: boolean → CreateOptions.VNC // ports: array → CreateOptions.Ports // [{container, host, host_ip, protocol}] → []PortMapping // policy: string → CreateOptions.Policy // "oneshot"|"session"|"longrunning"|"persistent" // idle_timeout: number → CreateOptions.IdleTimeout // ms → time.Duration // stop_timeout: number → CreateOptions.StopTimeout // ms → time.Duration // workspace_id: string → CreateOptions.WorkspaceID // mount_mode: string → CreateOptions.MountMode // "rw"|"ro" // mount_path: string → CreateOptions.MountPath // labels: object → CreateOptions.Labels // map[string]string // } // // Returns: Box object (see box.go) func sbCreate(info *v8go.FunctionCallbackInfo) *v8go.Value { // TODO: Phase 2 // 1. Parse options from info.Args()[0] // 2. Validate required fields (image, owner) // 3. If opts.id != "" → sandbox.M().GetOrCreate(ctx, opts) // else → sandbox.M().Create(ctx, opts) // 4. Return NewBoxObject(v8ctx, box.ID()) return v8go.Undefined(info.Context().Isolate()) } // sbGet: `sandbox.Get(id)` → Box | null // // Go: Manager.Get(ctx, id) (*Box, error) // // Args: // // id: string — sandbox ID // // Returns: Box object if found, null if not found func sbGet(info *v8go.FunctionCallbackInfo) *v8go.Value { // TODO: Phase 2 // 1. id = info.Args()[0].String() // 2. box, err := sandbox.M().Get(ctx, id) // 3. Return NewBoxObject(v8ctx, id) or null return v8go.Undefined(info.Context().Isolate()) } // sbList: `sandbox.List(filter?)` → BoxInfo[] // // Go: Manager.List(ctx, ListOptions) ([]*Box, error) // // then Box.Info(ctx) for each → BoxInfo // // JS filter → Go ListOptions mapping: // // { // owner: string → ListOptions.Owner // filter by owner; empty = all // pool: string → ListOptions.Pool // filter by pool; empty = all // labels: object → ListOptions.Labels // filter by labels // } // // Returns: BoxInfo[] — each element: // // { // id: string ← BoxInfo.ID // container_id: string ← BoxInfo.ContainerID // pool: string ← BoxInfo.Pool // owner: string ← BoxInfo.Owner // status: string ← BoxInfo.Status // image: string ← BoxInfo.Image // vnc: boolean ← BoxInfo.VNC // policy: string ← BoxInfo.Policy // labels: object ← BoxInfo.Labels // created_at: string ← BoxInfo.CreatedAt (ISO 8601) // last_active: string ← BoxInfo.LastActive (ISO 8601) // process_count: number ← BoxInfo.ProcessCount // } func sbList(info *v8go.FunctionCallbackInfo) *v8go.Value { // TODO: Phase 2 // 1. Parse optional filter from info.Args()[0] // 2. boxes := sandbox.M().List(ctx, opts) // 3. For each box: box.Info(ctx) → BoxInfo → JS object // 4. Return JS array of BoxInfo objects return v8go.Undefined(info.Context().Isolate()) } // sbDelete: `sandbox.Delete(id)` → void // // Go: Manager.Remove(ctx, id) error // // Args: // // id: string — sandbox ID to remove func sbDelete(info *v8go.FunctionCallbackInfo) *v8go.Value { // TODO: Phase 2 // 1. id = info.Args()[0].String() // 2. sandbox.M().Remove(ctx, id) return v8go.Undefined(info.Context().Isolate()) }