@pubnub/mcp
Version:
PubNub Model Context Protocol MCP Server for Cursor and Claude
347 lines (256 loc) • 12 kB
Markdown
# PubNub Functions 2.0 Development Guidelines
## Overview
PubNub Functions 2.0 allow you to run custom JavaScript code on PubNub's servers in response to real-time events or HTTP requests, without managing your own infrastructure. Each Function is defined as a JavaScript module with a default exported async function. Functions execute in a **serverless sandbox environment** with a set of built-in modules available via `require()`.
**Key features of PubNub Functions 2.0:**
* **Event-driven execution:** Functions can trigger on PubNub message events (before or after publish, signals, presence events), on a schedule (interval timers), or via HTTP endpoints (on-request Functions).
* **Default export structure:** Each Function module must `export default async function(...) { ... }`. Depending on the event type, the function receives different parameters.
* **Built-in modules:** A variety of modules are available to extend functionality – for example, persistent storage (`kvstore`), making HTTP requests (`xhr`), secret storage (`vault`), PubNub publish/subscribe actions (`pubnub`), utility libraries (UUID generation, cryptography, etc.).
* **Asynchronous code support:** The Functions 2.0 runtime supports modern JavaScript, including `async/await`. Always use `await` and `try/catch` for clarity.
* **Execution limits:** Functions have some limits to prevent infinite loops or excessive work. Notably, a Function that triggers other Functions can only chain up to **3** executions deep. Also, in a single execution, you can perform at most **3** combined operations of certain types (KV store reads/writes, XHR fetches, or PubNub publish/fire calls).
---
## Function Structure and Event Types
Depending on how a Function is triggered, the signature of the exported function and how you handle the event differ:
### Before/After Event (Publish, Signal, etc.)
For non-HTTP event triggers, use a single parameter and return `ok()` or `abort()` on it:
```js
export default async (event) => {
// Your business logic for the event
// Access event data:
const message = event.message; // The published message payload (as an object)
const channel = event.channels[0]; // Channel name that triggered the Function
// ... process the message ...
return event.ok(); // indicate successful completion
// Use event.abort() to stop or block the event (in a Before trigger) if needed
};
```
In a **Before Publish** Function, calling `event.abort()` will prevent the message from being delivered. In an **After** Function, `event.abort()` simply stops further function processing.
### On Request (HTTP Function)
For HTTP-triggered Functions, use two parameters and respond with `response.send()`:
```js
export default async (request, response) => {
try {
// Access request data:
const params = request.params; // URL path parameters, if any
const query = request.query; // Query string parameters
const body = await request.json(); // Parsed JSON body (if JSON content-type)
// ... perform operations based on the request ...
return response.send({ status: 'OK' }, 200); // send JSON response with HTTP 200
} catch (err) {
console.error('Request handling error:', err);
return response.send({ error: 'Internal Server Error' }, 500);
}
};
```
### On Interval (Scheduled Function)
On Interval Functions use the same pattern as other event triggers (single `event` parameter):
```js
export default async (event) => {
// This code executes on a schedule (no incoming message)
// e.g., perform periodic aggregation or cleanup
console.log('On Interval trigger fired at', new Date().toISOString());
// ... your periodic task logic ...
return event.ok(); // complete successfully
};
```
---
## Channel Pattern Wildcards in 2.0
When configuring which channel(s) a Function should trigger on, you can specify a channel name or a wildcard pattern. PubNub Functions 2.0 supports wildcard patterns with specific rules:
* The `*` wildcard matches exactly one channel segment (a segment is a dot-delimited token in a channel name). Wildcards **must be at the end of the channel pattern**.
* You can have up to two literal segments before the wildcard. In other words, patterns of the form `prefix.*` or `prefix1.prefix2.*` are allowed.
**Wildcard pattern examples:**
* ✅ **Valid**: `challenge.*` – matches `challenge.`***anything*** (one segment after "challenge").
* ✅ **Valid**: `challenge.votes.*` – matches `challenge.votes.`***anything*** (one segment after "challenge.votes").
* ❌ **Invalid**: `challenge.*.votes` – wildcard is not in the final position.
* ❌ **Invalid**: `*.global` – cannot start with a wildcard.
---
## Promises and Async/Await in Functions
All PubNub Functions APIs use Promises for asynchronous operations. In Functions 2.0 you can and should use `async/await` for cleaner code. Avoid using `.then()`/`.catch()` chains.
#### **Preferred (async/await with try/catch)**
```js
export default async (request) => {
const db = require('kvstore');
const xhr = require('xhr');
try {
const [fullName, ipResponse] = await Promise.all([
db.get('fullName'),
xhr.fetch("https://httpbin.org/ip")
]);
request.message.fullName = fullName;
request.message.ip = JSON.parse(ipResponse.body).origin;
return request.ok();
} catch (err) {
console.error('Error in function execution:', err);
return request.abort(err);
}
};
```
**Always wrap your function logic in a try/catch** so that errors are caught and handled gracefully.
---
## Available Modules in PubNub Functions 2.0
PubNub provides a collection of built-in modules that you can `require()` in your Function to perform common tasks. **All module methods are asynchronous (return Promises)** unless otherwise noted, so use `await` when calling them.
### Core Modules
* **`xhr`** - HTTP requests to external APIs
* **`kvstore`** - Key-value storage for persisting data
* **`vault`** - Secure storage for sensitive information like API keys
* **`pubnub`** - Core PubNub client API for publishing, files, etc.
### Utility Modules
* **`crypto`** - Cryptographic functions (HMAC, hashing, etc.)
* **`utils`** - General utilities (random numbers, numeric checks)
* **`uuid`** - UUID generation and validation
* **`jwt`** - JSON Web Token creation and verification
* **`advanced_math`** - Geospatial calculations and math functions
### Codec Modules
* **`codec/auth`** - Basic authentication header generation
* **`codec/base64`** - Base64 encoding/decoding
* **`codec/query_string`** - URL query parameter parsing
### Advanced Modules
* **`jsonpath`** - JSONPath queries for complex JSON manipulation
---
## Module Usage Examples
### XHR Module (HTTP Requests)
```js
const xhr = require('xhr');
// GET request
const response = await xhr.fetch('https://api.example.com/data');
const data = JSON.parse(response.body);
// POST request with JSON body
const postOptions = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key: 'value' })
};
const result = await xhr.fetch('https://api.example.com/endpoint', postOptions);
```
### KV Store Module (Persistent Storage)
```js
const db = require('kvstore');
// Store data with TTL
await db.set("user:123", { name: "Alice" }, 1440); // 1 day TTL
// Retrieve data
const userData = await db.get("user:123");
// Atomic counters
const newCount = await db.incrCounter("page_views");
```
### Vault Module (Secrets Storage)
```js
const vault = require('vault');
// Retrieve secret securely
const apiKey = await vault.get("myApiKey");
if (!apiKey) {
throw new Error("API key not found in vault");
}
```
### PubNub Module (Publish/Subscribe)
```js
const pubnub = require('pubnub');
// Publish message
await pubnub.publish({
channel: "alerts",
message: { type: "alert", text: "System notification" }
});
// Send signal (lightweight message)
await pubnub.signal({
channel: "typing",
message: { user: "alice", typing: true }
});
// Fire event (triggers Functions but not subscribers)
await pubnub.fire({
channel: "analytics",
message: { event: "user_action", timestamp: Date.now() }
});
```
### Crypto Module (Cryptographic Operations)
```js
const crypto = require('crypto');
// Generate HMAC signature
const signature = await crypto.hmac('secretKey', 'data', crypto.ALGORITHM.HMAC_SHA256);
// Hash data
const hash = await crypto.sha256('hello world');
```
### UUID Module (Unique Identifiers)
```js
const { v4, validate } = require('uuid');
// Generate random UUID
const id = v4();
// Validate UUID
if (validate(id)) {
console.log('Valid UUID:', id);
}
```
---
## Best Practices for Writing Functions
* **Use `async/await` and `try/catch`**: Structure your code with asynchronous calls in a linear, synchronous-looking style. Always wrap the function body in a `try/catch` to handle errors.
* **Prefer `request.ok()`/`event.ok()` over raw returns**: For event-triggered functions, call the `.ok()` method on the context object to signify successful completion.
* **Import modules correctly**: Use `const module = require('moduleName')` for any module you need.
* **Avoid long blocking operations**: Functions have execution time limits (a few seconds). Heavy computation or waiting loops are discouraged.
* **Limit external calls and recursion**: Remember the limit of 3 external operations (XHR, KV, publish, fire) per function invocation and 3-deep function chaining.
* **Log appropriately**: Use `console.log()` or `console.error()` for debugging. Do not log sensitive information.
### Standard Function Template
```js
export default async (request, response) => {
try {
// 1. Parse inputs (from request.message, request.params, etc.)
// 2. Perform business logic (e.g., read/write KV store, call external APIs)
// 3. Return a result using request.ok()/response.send() as appropriate
return request.ok(); // or response.send() for HTTP
} catch (e) {
console.error('Function error:', e);
return request.abort(); // or response.send("Error", 500) for HTTP
}
};
```
---
## Real-World Implementation Patterns
### Distributed Counter (Atomic Increment)
```js
// Function Type: Before Signal (triggered on channels matching "votes.*")
export default async (request) => {
const db = require('kvstore');
try {
const voteMessage = request.message;
const submissionId = voteMessage.submissionId;
const counterKey = `votes:${submissionId}`;
// Atomically increment the vote count
const newCount = await db.incrCounter(counterKey);
console.log(`Vote count for submission ${submissionId} is now ${newCount}`);
return request.ok();
} catch (error) {
console.error('Error incrementing vote count:', error);
return request.abort();
}
};
```
### Periodic Aggregation and Broadcast
```js
// Function Type: On Interval (runs every 5000 ms)
export default async (event) => {
const db = require('kvstore');
const pubnub = require('pubnub');
try {
const allKeys = await db.getKeys();
const voteData = {};
// Collect all vote counters
for (const key of allKeys) {
if (key.startsWith('votes:')) {
const count = await db.getCounter(key);
voteData[key.replace('votes:', '')] = count;
}
}
// Publish aggregated vote counts
await pubnub.publish({
channel: 'vote_updates',
message: { type: 'vote_counts', data: voteData, timestamp: Date.now() }
});
return event.ok();
} catch (error) {
console.error('Error broadcasting vote totals:', error);
return event.abort();
}
};
```
---
**When generating or transforming PubNub Functions code, always:**
- Use the above patterns for structure.
- Replace `.then()` chains with async/await + try/catch.
- Use the correct module and method signatures as described.
- Ensure proper error handling and completion calls.