yapi-server

Yote API Server -- a JSON RPC server that lets JavaScript clients call methods on persistent Perl objects. Built on top of Yote::SQLObjectStore for automatic object persistence and SQLite storage.

Key Features

Architecture

Browser                              Server
───────                              ──────
AppProvider                          yapi.pl
  ├─ connect(appName) ────POST───►     └─ Yote::YapiServer
  ├─ login/logout                         └─ Handler.pm
  └─ callMethod(target, method)              ├─ validate token → Session
     │                                       ├─ authorize method
     │                                       ├─ call target.$method()
     ▼                                       └─ serialize result
  App proxy object
    ├─ publicVars                     Site.pm (root DB object)
    └─ method stubs                     ├─ versioned app registry
                                        ├─ user management
                                        └─ session/token validation

The server uses a fork-per-connection model. Each incoming HTTP connection is handled by a forked child process with its own SQLite connection.

Directory Structure

yapi.pl                     Entry point (API + page server, compile, pages)
lib/
  Yote/YapiServer.pm        HTTP server (socket, fork, HTTP parsing)
  Yote/YapiServer/
    Site.pm                  Root DB object, versioned app registry, user management
    Handler.pm               JSON request dispatch, auth, serialization
    Session.pm               Token management, object capability tracking
    User.pm                  User model with field visibility
    Compiler.pm              Compiles .yaml and .ydef files to Perl modules
    YapiDef.pm               Parser for .ydef definition format
    App/
      Base.pm                Base class for all apps
      Example.pm             Example message board app
bin/                         (empty — compile via yapi.pl compile)
yaml/                        App definitions (.yaml or .ydef format)
www/webroot/js/
  yapi-provider.js           Client-side AppProvider class
t/server/                    Tests

Dependencies

Request/Response Protocol

All communication is via POST to the server endpoint (default: /yapi).

Request

{
  "action": "connect | call | login | createUser | logout",
  "token": "session_token",
  "app": "appName",
  "target": "appName | _obj_123",
  "method": "methodName",
  "args": { ... }
}

Response (success)

{
  "ok": 1,
  "token": "session_token",
  "resp": "r_obj_42",
  "classes": { "ClassName": ["method1", "method2"] },
  "objects": {
    "_obj_42": { "_class": "ClassName", "data": { "name": "vAlice", "score": "v98" } }
  },
  "apps": { "_app_example": ["hello", "getStats"] }
}

Value encoding (v/r prefixes)

All scalar values in resp and within objects.data use a single-character prefix to distinguish literal strings from object references:

| Prefix | Meaning | Example | |--------|---------|---------| | v | Literal value (string, number) | "vHello""Hello" | | r | Reference to an object or app | "r_obj_42" → look up _obj_42 in objects |

Arrays and plain hashes are returned as-is, with v/r encoding applied to their leaf values. The client-side AppProvider strips prefixes automatically.

Response (error)

{
  "ok": 0,
  "error": "message"
}

Authorization Model

Method-Level Access (%METHODS)

Each app declares which methods are callable and who can call them:

| Level | Meaning | |-------|---------| | public | No authentication required | | auth | Requires a valid session | | owner_only | Caller must own the target object | | admin_only | Caller must be an admin |

Field-Level Access (%FIELD_ACCESS)

Controls what fields are included when serializing objects to the client:

| Level | Meaning | |-------|---------| | public | Visible to everyone | | owner_only | Visible only to the object's owner (and admins) | | admin_only | Visible only to admins | | never | Never sent to the client (e.g., passwords) |

Object Capability Model

The session tracks which objects have been exposed to the client. When Handler's serialize_value() encounters an object, it calls $session->expose_object($obj). Subsequent client requests referencing that object's _obj_ID are validated against the session's exposed set. This prevents clients from accessing arbitrary objects by guessing IDs.

Rate Limiting

Configured in Yote::YapiServer::Site:

our %RATE_LIMITS = (
    createUser => { per_ip => 5,  window => 3600 },   # 5 per hour
    login      => { per_ip => 10, window => 300 },     # 10 per 5 min
    default    => { per_session => 100, window => 60 }, # 100 per min
);

Rate limits are enforced in-memory by Handler.pm and reset on server restart.

Quick Start

# Scaffold a new project
perl yapi.pl init myproject

# Start both API and page servers (default ports 5001 and 5000)
perl yapi.pl

# Or with custom settings
perl yapi.pl --port 3000 --data-dir /tmp/mydata --www-port 4000

yapi.pl init [directory]

Creates a project skeleton with all the standard directories and starter files:

myproject/
├── config/
│   └── yapi.yaml           # Database and server configuration
├── data/                   # SQLite database files (created on first run)
├── webroot/                # Web server root (compiled output + static files)
│   └── js/
│       ├── spiderpup.js    # Client-side runtime (copied)
│       └── yapi-provider.js # API client (copied)
├── lib/                    # Compiled Perl modules
├── spiderpup/
│   ├── pages/              # Page definitions (.yaml or .pup)
│   │   └── index.pup       # Starter page
│   └── recipes/            # Reusable components (.yaml or .pup)
└── yapi/
    ├── site.ydef           # Server definition (.yaml or .ydef)
    ├── apps/               # App definitions (.yaml or .ydef)
    └── modules/            # Object definitions (.yaml or .ydef)

Safe to re-run — skips existing directories and definition files, always refreshes JS files.

See HOWTO.md for a step-by-step guide to building apps.