Goop² Documentation

User guide & reference

Home

JavaScript SDK

The Goop2 SDK is a set of JavaScript modules that templates load to interact with the peer. Each module is a single <script> tag served from /sdk/ and attaches to the global Goop object.

Loading

<script src="/sdk/goop-data.js"></script>
<script src="/sdk/goop-identity.js"></script>
<script src="/sdk/goop-ui.js"></script>
<script src="app.js"></script>

Load only the modules you need. The SDK auto-detects whether the template is running on the local peer (/) or viewing a remote peer (/p/<peerID>/), and routes API calls to the correct peer transparently.

graph LR
    T["Template JS"] --> SDK["Goop SDK"]
    SDK -->|"local context"| LA["Local API"]
    SDK -->|"remote context"| RA["Remote API"]
    LA --> DB[("Local SQLite")]
    RA -->|"P2P stream"| RDB[("Remote SQLite")]

Modules

Module Global Purpose
goop-data.js Goop.data Database CRUD, schema, Lua function calls
goop-identity.js Goop.identity Peer ID, display name, email
goop-peers.js Goop.peers Peer discovery and status polling
goop-group.js Goop.group Group membership, messaging
goop-chat.js Goop.chat Direct and broadcast chat over MQ
goop-realtime.js Goop.realtime Virtual MQ-based channels
goop-call.js Goop.call Audio/video calling
goop-api.js Goop.api Virtual REST API over Lua data functions
goop-site.js Goop.site File storage (read, upload, delete)
goop-ui.js Goop.ui Toast, confirm, prompt dialogs
goop-form.js Goop.form JSON-driven form renderer
goop-forms.js Goop.forms Auto-generated CRUD UI from schema
goop-drag.js Goop.drag Drag-and-drop with sortable lists
goop-engine.js GameLoop, Renderer, ... 2D game engine (Canvas)

Goop.data

Database access. Works in both self and remote peer context.

const db = Goop.data;

// List tables (includes mode: orm/classic)
const tables = await db.tables();

// Describe a table's columns
const info = await db.describe("tasks");

// Create table (ORM format with typed columns)
await db.createTable("tasks", [
  {name: "id",    type: "integer", key: true},
  {name: "title", type: "text",    required: true},
  {name: "score", type: "real"}
]);

// Insert
const {id} = await db.insert("tasks", {title: "Hello", score: 9.5});

// Query with options
const rows = await db.query("tasks", {limit: 10, where: "score > ?", args: [5]});

// Update by _id
await db.update("tasks", id, {score: 10});

// Delete by _id
await db.remove("tasks", id);

// Drop a table
await db.dropTable("tasks");

// Add a column
await db.addColumn("tasks", {name: "priority", type: "INTEGER", not_null: false});

// Call a Lua data function
const result = await db.call("score-quiz", {answers: [1, 2, 3]});

// List available Lua functions
const fns = await db.functions();

Goop.api

Virtual REST-like API backed by a server-side Lua function. Requires goop-data.js. Templates define endpoints in api.json; without it, all tables get default CRUD.

const api = Goop.api;

// Get a single record by slug or id
const post = await api.get("posts", {slug: "hello-world"});
// → {found: true, item: {_id, title, body, ...}}

// List records (paginated)
const result = await api.list("posts", {limit: 10, offset: 0});
// → {items: [...]}

// Insert, update, delete
await api.insert("posts", {title: "New", body: "Content"});
await api.update("posts", 42, {title: "Updated"});
await api.delete("posts", 42);

// Config table as key-value map
const config = await api.map("config");
// → {theme: "dark", accent: "#2d6a9f"}

api.json

Endpoint declarations placed in the site root:

{
  "posts": {
    "table": "posts",
    "slug": "slug",
    "filter": "published = 1",
    "fields": ["title", "body", "author_name"],
    "get": true,
    "list": {"order": "_id DESC", "limit": 50},
    "insert": true, "update": true, "delete": true
  },
  "config": {
    "table": "blog_config",
    "map": {"key": "key", "value": "value"}
  }
}

Without api.json, all tables are exposed with default CRUD (get by _id, list by _id DESC, limit 50). See the Lua scripting page for the server-side architecture.

Goop.identity

const info  = await Goop.identity.get();    // {id, label, email}
const myId  = await Goop.identity.id();     // peer ID string
const name  = await Goop.identity.label();  // display name
const email = await Goop.identity.email();  // email string
Goop.identity.refresh();                    // clear cache, force re-fetch

Goop.peers

// One-time fetch
const peers = await Goop.peers.list();
// Each peer: {ID, Content, Email, AvatarHash, VideoDisabled,
//   ActiveTemplate, Verified, Reachable, Offline, LastSeen}

// Live updates (polls every 5s by default)
Goop.peers.subscribe({
  onSnapshot(peers) { /* full list on first load */ },
  onUpdate(peerId, peer) { /* peer came online or changed */ },
  onRemove(peerId) { /* peer pruned from list */ },
  onError() { /* optional error handler */ }
}, 5000);

// Stop polling
Goop.peers.unsubscribe();

Goop.group

// Create a hosted group
await Goop.group.create("My Room", "realtime", 10);

// List hosted groups
const groups = await Goop.group.list();

// Join a remote group
await Goop.group.join(hostPeerId, groupId);

// Send a message to the group
await Goop.group.send({action: "move", x: 5}, groupId);

// Leave a group
await Goop.group.leave();

// Host joins/leaves own group
await Goop.group.joinOwn(groupId);
await Goop.group.leaveOwn(groupId);

// Close a hosted group
await Goop.group.close(groupId);

// SSE event stream
const es = Goop.group.subscribe(function(evt) {
  // evt.type: "welcome", "members", "msg", "state", "leave", "close", "invite"
});
Goop.group.unsubscribe();

Goop.chat

// Direct message
await Goop.chat.send(peerId, "Hello!");

// Broadcast to all peers
await Goop.chat.broadcast("Server restarting");

// Subscribe to incoming messages via MQ
Goop.chat.subscribe(function(msg) {
  // msg: {from, content, type, timestamp}
  // type: "broadcast" or "direct"
});
Goop.chat.unsubscribe();

Goop.realtime

Virtual MQ-based channels for real-time peer communication.

// Connect to a peer
const ch = await Goop.realtime.connect(peerId);

// Channel methods
ch.send({action: "ping"});
ch.onMessage(function(msg, env) { /* env: {channel, from} */ });
ch.close();

// Accept an incoming channel
const ch = await Goop.realtime.accept(channelId, hostPeerId);

// Listen for incoming channel invitations
Goop.realtime.onIncoming(function(info) {
  // info: {channelId, hostPeerId}
});

Goop.call

Audio/video calling.

// Start a call
const session = await Goop.call.start(peerId, {video: true, audio: true});

session.onRemoteStream(function(stream) { video.srcObject = stream; });
session.onHangup(function() { /* call ended */ });
session.hangup();
session.toggleAudio();
session.toggleVideo();

// Listen for incoming calls
Goop.call.onIncoming(function(info) {
  const session = await info.accept({video: true, audio: true});
  // or: info.reject();
});

Goop.site

File storage for the peer's site content directory.

const files = await Goop.site.files();
const content = await Goop.site.read("data.json");
const response = await Goop.site.fetch("image.png");
await Goop.site.upload("data.json", fileOrBlob);
await Goop.site.remove("data.json");

Goop.ui

Portable UI helpers. Auto-injects minimal CSS.

Goop.ui.toast("Saved!");
Goop.ui.toast({title: "Error", message: "Something failed", duration: 6000});

const ok = await Goop.ui.confirm("Delete this item?", "Confirm");
const name = await Goop.ui.prompt("Enter your name:", "Default", "Title");
const theme = Goop.ui.theme();  // "dark" or "light"

Goop.form

JSON-driven form renderer. Requires goop-data.js and goop-identity.js.

await Goop.form.render(document.getElementById("form"), {
  table: "responses",
  fields: [
    {name: "name",    label: "Your Name", type: "text",     required: true},
    {name: "rating",  label: "Rating",    type: "number"},
    {name: "comment", label: "Comment",   type: "textarea"},
    {name: "color",   label: "Color",     type: "select",   options: ["Red", "Blue", "Green"]},
    {name: "agree",   label: "I agree",   type: "checkbox"}
  ],
  submitLabel: "Send",
  singleResponse: true,
  onDone: function() { /* callback after save */ }
});

Goop.forms

Auto-generated CRUD UI from table schemas. Requires goop-data.js and goop-ui.js.

// Full CRUD interface
await Goop.forms.render(document.getElementById("crud"), "tasks");

// Insert form only
await Goop.forms.insertForm(document.getElementById("form"), "tasks", function() {
  // called after row inserted
});

Goop.drag

Reusable drag-and-drop with sortable lists.

const instance = Goop.drag.sortable(container, {
  items: "> .card",
  handle: ".drag-handle",
  group: "kanban",
  direction: "vertical",
  onEnd(evt) { /* {item, from, to, oldIndex, newIndex} */ }
});
instance.destroy();

Goop Engine

2D game engine for Canvas-based templates. Exposes global classes (not under Goop).

const loop = new GameLoop(60);
loop.start(update, render);

const renderer = new Renderer(canvas);
renderer.clear("#000");
renderer.drawRect(x, y, w, h, color);
renderer.drawSprite(image, sx, sy, sw, sh, dx, dy, dw, dh);
renderer.drawText(text, x, y, font, color, align);

const sheet = new SpriteSheet("sprites.png", 16, 16);
const input = new Input();
input.bind(canvas);

const scenes = new SceneManager();
scenes.add("menu", { enter(){}, update(dt){}, render(r){}, exit(){} });
scenes.switch("menu");