Skip to content

Latest commit

 

History

History
238 lines (171 loc) · 8.78 KB

File metadata and controls

238 lines (171 loc) · 8.78 KB

Plugin Architecture (WASM Extensions)

English | 中文

lowcode-database extends the Postgres-style core with a four-tier plugin model aligned with Supabase primitives:

Tier Supabase / Postgres analogue lowcode-database Manifest key
1 Database Webhooks / change subscription (not Realtime WebSocket) Same event.Emit path as lc_event_sinks; WASM plugin handlers subscriptions
2 Database Trigger before / after row mutation hooks triggers
3 Database Function (RPC) POST/GET /v1/data/rpc/{name} rpc
4 Custom Postgres TYPE Column types plugin.{name}.{type} types

Plugins run in a WASM sandbox (wazero) or as native handlers (builtins / tests). All tiers share one JSON dispatch protocol (internal/plugin/types.go, pkg/pluginsdk).


System diagram

                    ┌─────────────────────────────────────┐
                    │           HTTP API (chi)            │
                    │  /v1/admin/plugins   (CRUD)         │
                    │  /v1/data/rpc/{name} (Tier 3)     │
                    └──────────────┬──────────────────────┘
                                   │
                    ┌──────────────▼──────────────────────┐
                    │      LowcodeService / shared.Base    │
                    │         EmitEvent → event.Bus        │
                    └──────────────┬──────────────────────┘
                                   │ async deliver (same envelope)
           ┌───────────────────────┴───────────────────────┐
           │                                               │
    ┌──────▼──────┐                               ┌───────▼────────┐
    │ lc_event_   │                               │ plugin.Manager │
    │ sinks       │                               │ (Tier 1 subs)  │
    │ HTTP/Kafka… │                               │ WASM/native    │
    └─────────────┘                               └────────────────┘
           │
    ┌──────▼────────┐
    │  data.Data    │  Tier 2 triggers (before/after row SQL)
    └───────────────┘

See also examples/plugins/EXAMPLE.md for copy-paste manifests, curl, and handler source.


Migration: docker/postgres/migrations/meta/000001_init.up.sql (lc_plugins, lc_plugin_kv, lc_plugin_invocations)

Table Purpose
lc_plugins Plugin registry: wasm_module, manifest, config, per tenant
lc_plugin_kv Plugin-private JSON KV (future host import)
lc_plugin_invocations Optional audit log (PLUGIN_LOG_INVOCATIONS=true)

Manifest example

{
  "subscriptions": [
    { "events": ["records.after.insert"], "tableFilter": "orders" }
  ],
  "triggers": [
    { "table": "orders", "timing": "before", "operations": ["insert", "update"] }
  ],
  "rpc": [
    { "name": "hello", "method": "POST" }
  ],
  "types": [
    { "id": "money", "name": "money", "pgType": "numeric", "description": "Validated money column" }
  ]
}

Registered column type id: plugin.{pluginName}.money → e.g. plugin.demo.money.


Tier 1 — Subscriptions (database webhooks)

Concept: Like Postgres change notification or Supabase Database Webhooks — subscribe to row/schema changes and react. This is not Supabase Realtime (browser WebSocket); it is the same class of problem as NOTIFY + HTTP webhook on commit.

Flow:

row/schema change → shared.EmitEvent → event.Bus.Emit (async)
                                          ├─ lc_event_sinks (external URL)
                                          └─ plugin subscriptions (WASM in-process)

Both use the identical JSON envelope from pkg/eventschema (type, tenantId, tableId, occurredAt, data).

Delivery Config Use case
event-sinks POST /v1/admin/event-sinks Push to HTTPS, Kafka, Redis, …
plugin subscriptions manifest.subscriptions Custom logic in WASM (index, transform, fan-out)

Plugin subscriptions run in the same async goroutine as sink delivery (event.Bus.deliver), not on the HTTP request thread.


Tier 2 — Triggers

When: Before/after CreateRow, UpdateRow (bulk/saveGraph: planned extension).

Timing Behavior
before May mutate cells or deny write (deny: true)
after Side effects; errors logged, do not roll back SQL

Demo: native plugin demo uppercases name on insert/update.


Tier 3 — RPC

Supabase-style call:

POST /v1/data/rpc/hello
X-Tenant-Id: default
Content-Type: application/json

{"orderId": "..."}

List functions: GET /v1/data/rpc.

Admin install:

POST /v1/admin/plugins
{
  "name": "demo",
  "nativeBuiltin": true,
  "enabled": true
}

(nativeBuiltin: true + name: demo loads the reference manifest from internal/plugin/builtin.)


Tier 4 — Custom types

  1. Plugin declares types[] in manifest.
  2. On reload, types register in columntype as plugin.{name}.{type}.
  3. GET /v1/admin/types includes plugin types.
  4. Create column with typeId: "plugin.demo.money".
  5. Host calls plugin on validate / toPG / fromPG during row writes.

WASM plugin contract

Guest module exports:

  • memory
  • dispatch(i32 ptr, i32 len) i64 — returns (outLen << 32) | outPtr
  • optional malloc / free

Input/output: JSON DispatchRequest / DispatchResponse.

Author with pkg/pluginsdk; build with TinyGo or GOOS=wasip1 GOARCH=wasm.

Upload via POST /v1/admin/plugins with wasmBase64.


Admin API

Method Path Description
GET /v1/admin/plugins List plugins
POST /v1/admin/plugins Install
GET /v1/admin/plugins/{id} Get
PATCH /v1/admin/plugins/{id} Update manifest/config/wasm
DELETE /v1/admin/plugins/{id} Uninstall
GET /v1/admin/plugin-invocations Audit log

Configuration

Env Default Description
PLUGIN_LOG_INVOCATIONS true Write lc_plugin_invocations

Code layout

internal/plugin/          Runtime: Manager, WASM, native drivers
internal/plugin/builtin/  Reference native plugin "demo"
pkg/pluginsdk/            Author SDK + JSON protocol types
internal/service/platform/plugin.go   CRUD
internal/service/data/row.go          Tier 2 hooks (insert/update)
internal/service/shared/events.go     Tier 1 fan-out
internal/columntype/types.go          Tier 4 registry

Optimization recommendations

Short term

  1. Hook bulk/saveGraph/import — extend Tier 2 to BulkUpsertRows, SaveGraph, ImportRows.
  2. Subscription async pool — run Tier 1 in worker pool with timeout; avoid blocking EmitEvent path.
  3. Trigger timeout — cap WASM execution (e.g. 50ms before-write); configurable per plugin.
  4. Host imports for KV/Log — expose lc_plugin_kv and structured log to WASM (like Paca SDK).
  5. Capability manifest — declare allowed actions; reject undeclared manifest entries at install.

Medium term

  1. Transactional triggers (optional)before commit inside tx for strict invariants (higher risk).
  2. Browser Realtime (optional, separate) — WebSocket layer for clients; not Tier 1 database subscriptions.
  3. Plugin marketplace — signed WASM bundles, version pinning, rollback.
  4. Tier 4 read pathfromPG / compute on ListRows for display formatting.
  5. Row-level auth plugin — authz driver delegating to WASM authorize hook.

Long term

  1. PG extension bridge — optional sidecar for true PL/pgSQL functions where WASM is insufficient.
  2. Multi-tenant plugin isolation — separate wazero runtimes per tenant; memory limits.
  3. Observability — Prometheus metrics: plugin_dispatch_seconds, plugin_denies_total.

Related docs