npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@objectql/protocol-json-rpc

v4.2.2

Published

JSON-RPC 2.0 Protocol Plugin for ObjectStack

Downloads

52

Readme

@objectql/protocol-json-rpc

JSON-RPC 2.0 Protocol Plugin for ObjectStack

Overview

This plugin provides full JSON-RPC 2.0 specification compliance for ObjectStack applications. It exposes ObjectStack data and metadata through a remote procedure call interface with built-in introspection support.

Features

  • JSON-RPC 2.0 Compliant - Full specification support
  • Batch Requests - Execute multiple RPC calls in a single request
  • Notifications - Support for requests without response
  • Introspection - Discover available methods and signatures
  • CRUD Operations - Full data manipulation support
  • Metadata Access - Query object schemas and configurations
  • Action Execution - Execute custom actions
  • CORS Support - Configurable Cross-Origin Resource Sharing
  • No Direct DB Access - All operations through ObjectStackRuntimeProtocol

Installation

pnpm add @objectql/protocol-json-rpc

Usage

Basic Setup

import { ObjectKernel } from '@objectstack/runtime';
import { JSONRPCPlugin } from '@objectql/protocol-json-rpc';

const kernel = new ObjectKernel([
  new JSONRPCPlugin({
    port: 9000,
    basePath: '/rpc',
    enableCORS: true,
    enableIntrospection: true
  })
]);

await kernel.start();

Configuration Options

interface JSONRPCPluginConfig {
  /** Port to listen on (default: 9000) */
  port?: number;
  
  /** Base path for JSON-RPC endpoint (default: '/rpc') */
  basePath?: string;
  
  /** Enable CORS (default: true) */
  enableCORS?: boolean;
  
  /** Enable introspection methods (default: true) */
  enableIntrospection?: boolean;
  
  /** Enable session management (default: true) */
  enableSessions?: boolean;
  
  /** Session timeout in milliseconds (default: 30 minutes) */
  sessionTimeout?: number;
  
  /** Enable progress notifications via SSE (default: true) */
  enableProgress?: boolean;
  
  /** Enable method call chaining in batch requests (default: true) */
  enableChaining?: boolean;
}

Available Methods

Object Methods

object.find

Find multiple records.

Request:

{
  "jsonrpc": "2.0",
  "method": "object.find",
  "params": ["users", {"where": {"active": true}}],
  "id": 1
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "value": [
      {"id": "1", "name": "Alice", "email": "[email protected]"},
      {"id": "2", "name": "Bob", "email": "[email protected]"}
    ],
    "count": 2
  },
  "id": 1
}

object.get

Get a single record by ID.

Request:

{
  "jsonrpc": "2.0",
  "method": "object.get",
  "params": ["users", "123"],
  "id": 2
}

object.create

Create a new record.

Request:

{
  "jsonrpc": "2.0",
  "method": "object.create",
  "params": ["users", {"name": "Charlie", "email": "[email protected]"}],
  "id": 3
}

object.update

Update an existing record.

Request:

{
  "jsonrpc": "2.0",
  "method": "object.update",
  "params": ["users", "123", {"name": "Charlie Updated"}],
  "id": 4
}

object.delete

Delete a record.

Request:

{
  "jsonrpc": "2.0",
  "method": "object.delete",
  "params": ["users", "123"],
  "id": 5
}

object.count

Count records matching filters.

Request (no filter - count all):

{
  "jsonrpc": "2.0",
  "method": "object.count",
  "params": ["users"],
  "id": 6
}

Response:

{
  "jsonrpc": "2.0",
  "result": 42,
  "id": 6
}

Request (with filter):

{
  "jsonrpc": "2.0",
  "method": "object.count",
  "params": ["users", {
    "type": "comparison",
    "field": "active",
    "operator": "=",
    "value": true
  }],
  "id": 7
}

Response:

{
  "jsonrpc": "2.0",
  "result": 28,
  "id": 7
}

Request (with complex filter):

{
  "jsonrpc": "2.0",
  "method": "object.count",
  "params": ["users", {
    "type": "logical",
    "operator": "and",
    "conditions": [
      {
        "type": "comparison",
        "field": "active",
        "operator": "=",
        "value": true
      },
      {
        "type": "comparison",
        "field": "role",
        "operator": "=",
        "value": "admin"
      }
    ]
  }],
  "id": 8
}

Response:

{
  "jsonrpc": "2.0",
  "result": 5,
  "id": 8
}

Metadata Methods

metadata.list

List all registered object types.

Request:

{
  "jsonrpc": "2.0",
  "method": "metadata.list",
  "id": 7
}

Response:

{
  "jsonrpc": "2.0",
  "result": ["users", "projects", "tasks"],
  "id": 7
}

metadata.get

Get metadata for a specific object.

Request:

{
  "jsonrpc": "2.0",
  "method": "metadata.get",
  "params": ["users"],
  "id": 8
}

metadata.getAll

Get all metadata items of a specific type.

Request:

{
  "jsonrpc": "2.0",
  "method": "metadata.getAll",
  "params": ["object"],
  "id": 9
}

Action Methods

action.execute

Execute a custom action.

Request:

{
  "jsonrpc": "2.0",
  "method": "action.execute",
  "params": ["sendEmail", {"to": "[email protected]", "subject": "Hello"}],
  "id": 10
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "success": true,
    "messageId": "msg_1234567890",
    "to": "[email protected]",
    "subject": "Hello"
  },
  "id": 10
}

Example: Calculate Discount

{
  "jsonrpc": "2.0",
  "method": "action.execute",
  "params": ["calculateDiscount", {
    "amount": 100,
    "percentage": 20
  }],
  "id": 11
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "originalAmount": 100,
    "discountPercentage": 20,
    "discountAmount": 20,
    "finalAmount": 80
  },
  "id": 11
}

Error Response (action not found):

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32603,
    "message": "Action not found: unknownAction"
  },
  "id": 12
}

action.list

List all available actions.

Request:

{
  "jsonrpc": "2.0",
  "method": "action.list",
  "id": 13
}

Response:

{
  "jsonrpc": "2.0",
  "result": [
    "sendEmail",
    "calculateDiscount",
    "processPayment",
    "generateReport"
  ],
  "id": 13
}

System Methods (Introspection)

system.listMethods

List all available RPC methods.

Request:

{
  "jsonrpc": "2.0",
  "method": "system.listMethods",
  "id": 12
}

Response:

{
  "jsonrpc": "2.0",
  "result": [
    "object.find",
    "object.get",
    "object.create",
    "metadata.list",
    "system.listMethods"
  ],
  "id": 12
}

system.describe

Get method signature and description.

Request:

{
  "jsonrpc": "2.0",
  "method": "system.describe",
  "params": ["object.find"],
  "id": 13
}

Response:

{
  "jsonrpc": "2.0",
  "result": {
    "description": "Find multiple records",
    "params": [
      {"name": "objectName", "type": "string", "required": true},
      {"name": "query", "type": "object", "required": false}
    ],
    "returns": {"type": "object", "properties": ["value", "count"]}
  },
  "id": 13
}

Advanced Features

Batch Requests (JSON-RPC 2.0 §6)

Execute multiple RPC calls in a single HTTP request. Per JSON-RPC 2.0 specification section 6, batch requests allow you to send an array of request objects and receive an array of response objects.

Basic Batch Request

Request:

[
  {
    "jsonrpc": "2.0",
    "method": "metadata.list",
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "object.find",
    "params": ["users", {}],
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "method": "object.count",
    "params": ["users"],
    "id": 3
  }
]

Response:

[
  {
    "jsonrpc": "2.0",
    "result": ["users", "projects"],
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "result": [
      {"id": "1", "name": "Alice"},
      {"id": "2", "name": "Bob"}
    ],
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "result": 2,
    "id": 3
  }
]

Batch with Mixed Operations

Execute CRUD operations, counts, and actions in a single batch:

Request:

[
  {
    "jsonrpc": "2.0",
    "method": "object.create",
    "params": ["products", {"name": "Laptop", "price": 999}],
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "object.count",
    "params": ["products"],
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "method": "action.execute",
    "params": ["sendEmail", {"to": "[email protected]", "subject": "New Product"}],
    "id": 3
  }
]

Response:

[
  {
    "jsonrpc": "2.0",
    "result": {"id": "prod-123", "name": "Laptop", "price": 999},
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "result": 42,
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "result": {"success": true, "messageId": "msg_123"},
    "id": 3
  }
]

Batch with Notifications

Requests without an id are notifications and don't return responses:

Request:

[
  {
    "jsonrpc": "2.0",
    "method": "object.count",
    "params": ["users"],
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "action.execute",
    "params": ["logEvent", {"event": "user_login"}]
    // No id - this is a notification
  },
  {
    "jsonrpc": "2.0",
    "method": "metadata.list",
    "id": 2
  }
]

Response:

[
  {
    "jsonrpc": "2.0",
    "result": 100,
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "result": ["users", "products"],
    "id": 2
  }
]

Note: Only 2 responses because the notification (no id) doesn't return a response.

Batch with Partial Errors

Individual requests can fail without affecting other requests in the batch:

Request:

[
  {
    "jsonrpc": "2.0",
    "method": "object.count",
    "params": ["users"],
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "object.get",
    "params": ["users", "non-existent-id"],
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "method": "metadata.list",
    "id": 3
  }
]

Response:

[
  {
    "jsonrpc": "2.0",
    "result": 100,
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "result": null,
    "id": 2
  },
  {
    "jsonrpc": "2.0",
    "result": ["users", "products"],
    "id": 3
  }
]

Call Chaining in Batch Requests

When enableChaining is enabled, you can reference results from previous requests in the same batch using the $N.result.path syntax, where N is the request ID.

Example: Create a user and then fetch it

[
  {
    "jsonrpc": "2.0",
    "method": "object.create",
    "params": ["users", {"name": "Alice", "email": "[email protected]"}],
    "id": 1
  },
  {
    "jsonrpc": "2.0",
    "method": "object.get",
    "params": ["users", "$1.result._id"],
    "id": 2
  }
]

In this example:

  • Request 1 creates a new user
  • Request 2 references the _id from the result of request 1 using $1.result._id

Complex Reference Example:

[
  {
    "jsonrpc": "2.0",
    "method": "object.create",
    "params": ["projects", {"name": "New Project", "owner": "$1.result._id"}],
    "id": 3
  }
]

Reference Syntax:

  • $N.result - References the entire result of request N
  • $N.result.fieldName - References a specific field
  • $N.result.nested.field - References nested fields
  • Works with arrays and objects in parameters

Server-Sent Events (SSE) Progress Notifications

For long-running operations, you can receive real-time progress updates via Server-Sent Events.

Connecting to Progress Stream:

const sessionId = 'your-session-id';
const eventSource = new EventSource(`http://localhost:9000/rpc/progress/${sessionId}`);

eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  
  if (data.type === 'connected') {
    console.log('Connected to progress stream');
  } else if (data.method === 'progress.update') {
    const { id, progress, total, message } = data.params;
    console.log(`Operation ${id}: ${progress}/${total} - ${message}`);
  }
});

eventSource.addEventListener('error', (error) => {
  console.error('SSE connection error:', error);
});

Progress Notification Format:

The notification follows JSON-RPC 2.0 format but without the id field (as it's a notification):

{
  "jsonrpc": "2.0",
  "method": "progress.update",
  "params": {
    "id": "operation-123",
    "progress": 50,
    "total": 100,
    "message": "Processing item 50 of 100"
  }
}

Note: The complete SSE message includes the data: prefix and double newline:

data: {"jsonrpc":"2.0","method":"progress.update","params":{...}}

Example: Batch Import with Progress

// 1. Start the batch import
const response = await fetch('http://localhost:9000/rpc', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'object.batchImport',
    params: ['users', usersData],
    id: 1
  })
});

// 2. Connect to progress stream (if operation returns a session ID)
const result = await response.json();
const sessionId = result.result.sessionId;

const eventSource = new EventSource(`http://localhost:9000/rpc/progress/${sessionId}`);
eventSource.addEventListener('message', (event) => {
  const data = JSON.parse(event.data);
  if (data.method === 'progress.update') {
    updateProgressBar(data.params.progress, data.params.total);
  }
});

Features:

  • ✅ Real-time progress updates for long-running operations
  • ✅ Multiple clients can subscribe to the same session
  • ✅ Automatic heartbeat to keep connection alive
  • ✅ Graceful handling of client disconnections
  • ✅ Session-based progress tracking

Notifications

Requests without an id field are treated as notifications and don't receive a response.

Request:

{
  "jsonrpc": "2.0",
  "method": "log.info",
  "params": ["User logged in"]
}

No response is sent for notifications.

Error Codes

JSON-RPC 2.0 standard error codes:

| Code | Message | Meaning | |------|---------|---------| | -32700 | Parse error | Invalid JSON | | -32600 | Invalid Request | Not JSON-RPC 2.0 format | | -32601 | Method not found | Method doesn't exist | | -32602 | Invalid params | Invalid method parameters | | -32603 | Internal error | Server error |

Error Response Example:

{
  "jsonrpc": "2.0",
  "error": {
    "code": -32601,
    "message": "Method not found: unknown.method"
  },
  "id": 1
}

Architecture

This plugin follows the ObjectStack protocol plugin pattern:

  1. RuntimePlugin Interface - Implements the standard plugin lifecycle
  2. ObjectStackRuntimeProtocol Bridge - Uses the protocol bridge for all kernel interactions
  3. No Direct DB Access - All data operations through the bridge layer
  4. Lifecycle Management - Proper initialization and cleanup
export class JSONRPCPlugin implements RuntimePlugin {
  async install(ctx: RuntimeContext) {
    // Initialize protocol bridge
    this.protocol = new ObjectStackRuntimeProtocol(ctx.engine);
    // Register RPC methods
    this.registerMethods();
  }

  async onStart(ctx: RuntimeContext) {
    // Start HTTP server
  }

  async onStop(ctx: RuntimeContext) {
    // Stop HTTP server
  }
}

Examples

See the multi-protocol-server example for a complete working example.

License

MIT