Bootstrap the plugin-facing SDK module so the fleet migration (P2) becomes a mechanical block/core/X -> block/pluginsdk/X import rewrite. - abi/proto/v1: the single source-of-truth ABI proto tree, copied from the cms authoring source (cms/backend/abi/proto/v1) with go_package retargeted to git.dev.alexdunmow.com/block/pluginsdk/abi/v1;abiv1. Kills the hand-kept cms/core proto duplication (audit gap 4). - abi/v1: generated Go bindings (buf generate); byte-identical to core's abi/v1 save the embedded go_package path. - plugin/ (registration + DI surface), plugin/wasmguest/** (transport shim, caps stubs, bnwasm db driver, testdata fixtures + golden .pb), and the guest-facing type packages: blocks (+builtin/shared/tags), templates (+pongo/bn), auth, settings, content, gating, crypto, rbac, video, ai, subscriptions, menus, datasources. Internal imports rewritten core -> pluginsdk; zero block/core references remain. - README/AGENTS(+CLAUDE symlink)/Makefile: proto is the contract, Go is one binding; no replace directives; templates/bn is a synced copy authored in cms. Spec: cms docs/superpowers/specs/2026-07-07-proto-first-plugin-sdk-design.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
121 lines
2.9 KiB
Go
121 lines
2.9 KiB
Go
package bnwasm
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
|
|
abiv1 "git.dev.alexdunmow.com/block/pluginsdk/abi/v1"
|
|
"github.com/jackc/pgx/v5"
|
|
"github.com/jackc/pgx/v5/pgconn"
|
|
)
|
|
|
|
// pgxRows adapts a buffered DbRowsResponse to the pgx.Rows interface. The full
|
|
// result set is already materialized (the host returns it in one message), so
|
|
// iteration is a slice cursor. Only the methods sqlc-generated code exercises
|
|
// carry real behavior; the rest satisfy the interface.
|
|
type pgxRows struct {
|
|
columns []string
|
|
rows []*abiv1.DbRow
|
|
idx int // index of the row Next() last advanced to; -1 before first Next
|
|
err error
|
|
closed bool
|
|
}
|
|
|
|
func newPgxRows(resp *abiv1.DbRowsResponse) *pgxRows {
|
|
return &pgxRows{columns: resp.GetColumns(), rows: resp.GetRows(), idx: -1}
|
|
}
|
|
|
|
func (r *pgxRows) Close() { r.closed = true }
|
|
|
|
func (r *pgxRows) Err() error { return r.err }
|
|
|
|
func (r *pgxRows) CommandTag() pgconn.CommandTag {
|
|
return pgconn.NewCommandTag(fmt.Sprintf("SELECT %d", len(r.rows)))
|
|
}
|
|
|
|
func (r *pgxRows) FieldDescriptions() []pgconn.FieldDescription {
|
|
fds := make([]pgconn.FieldDescription, len(r.columns))
|
|
for i, c := range r.columns {
|
|
fds[i] = pgconn.FieldDescription{Name: c}
|
|
}
|
|
return fds
|
|
}
|
|
|
|
func (r *pgxRows) Next() bool {
|
|
if r.err != nil || r.closed {
|
|
return false
|
|
}
|
|
if r.idx+1 >= len(r.rows) {
|
|
r.Close() // pgx auto-closes when iteration is exhausted
|
|
return false
|
|
}
|
|
r.idx++
|
|
return true
|
|
}
|
|
|
|
func (r *pgxRows) Scan(dest ...any) error {
|
|
if r.err != nil {
|
|
return r.err
|
|
}
|
|
if r.idx < 0 || r.idx >= len(r.rows) {
|
|
return errors.New("bnwasm: Scan called without a current row (call Next first)")
|
|
}
|
|
if err := scanRow(r.rows[r.idx], dest); err != nil {
|
|
r.err = err
|
|
r.Close()
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *pgxRows) Values() ([]any, error) {
|
|
if r.idx < 0 || r.idx >= len(r.rows) {
|
|
return nil, errors.New("bnwasm: Values called without a current row")
|
|
}
|
|
cells := r.rows[r.idx].GetValues()
|
|
out := make([]any, len(cells))
|
|
for i, c := range cells {
|
|
v, _ := naturalValue(c)
|
|
out[i] = v
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func (r *pgxRows) RawValues() [][]byte { return nil }
|
|
|
|
func (r *pgxRows) Conn() *pgx.Conn { return nil }
|
|
|
|
// pgxRow is the single-row QueryRow result. Like pgx, it defers all errors
|
|
// (including the query error) to Scan, and reports pgx.ErrNoRows when empty.
|
|
type pgxRow struct {
|
|
rows *pgxRows
|
|
err error
|
|
}
|
|
|
|
func (r *pgxRow) Scan(dest ...any) error {
|
|
if r.err != nil {
|
|
return r.err
|
|
}
|
|
defer r.rows.Close()
|
|
if !r.rows.Next() {
|
|
if r.rows.Err() != nil {
|
|
return r.rows.Err()
|
|
}
|
|
return pgx.ErrNoRows
|
|
}
|
|
return r.rows.Scan(dest...)
|
|
}
|
|
|
|
func scanRow(row *abiv1.DbRow, dest []any) error {
|
|
cells := row.GetValues()
|
|
if len(dest) != len(cells) {
|
|
return fmt.Errorf("bnwasm: scan expected %d destinations, got %d columns", len(dest), len(cells))
|
|
}
|
|
for i, d := range dest {
|
|
if err := scanValue(cells[i], d); err != nil {
|
|
return fmt.Errorf("bnwasm: scan column %d: %w", i, err)
|
|
}
|
|
}
|
|
return nil
|
|
}
|