| name | rpcable-expert |
| description | Expert workflow for implementing, debugging, and refactoring projects that use rpcable. Use this skill whenever the user mentions RpcAble, RpcAbleReceiver, socket.io RPC, native WebSocket RPC, HTTP RPC, Bun.serve, Express adapters, UserSession architecture, collector/pending push queues, role permissions, contract/inputSchema validation, PHP adapter parity, or transport parity. Trigger even when the user asks generic things like "sistema la sessione", "fai parity http/ws", or "migliora DX" in a codebase that contains rpcable patterns. |
RpcAble Expert
Use this skill to keep rpcable implementations coherent, elegant, and transport-agnostic.
Core principles
-
Keep business logic transport-transparent.
- Put domain logic in one
UserSession class.
- Avoid splitting business logic into
UserSessionWs and UserSessionHttp.
- Wire transport in adapters, not in domain methods.
-
Prefer one public outbound API.
- Use
RpcAble for socketio, websocket, http, and collector.
- Use
RpcAbleReceiver for inbound dispatch.
-
Preserve parity between transports.
- WS: push with
this.client.someEvent(...).
- HTTP: return
{ results, push } and auto-apply push client-side.
- For offline/pending HTTP push, keep a server-side session store and flush on next request.
-
Keep DX high and predictable.
- No string dispatch or switch routers.
- No placeholder stubs that hide missing handlers.
- Lean on batching and namespaces already provided by rpcable.
- Register client push handlers with
extend(userSession, ...).
- Keep receiver-side validation in
contract so transport behavior stays aligned.
Execution workflow
When asked to implement or fix rpcable code, follow this sequence.
-
Inspect current architecture.
- Locate
RpcAble/RpcAbleReceiver usage.
- Locate transport-specific wiring points.
- Locate
UserSession and permission model.
-
Normalize architecture if needed.
- Extract shared business logic into one
UserSession.
- Keep adapters thin (
ws-socketio, http-bun, http-express).
-
Apply transport-correct semantics.
socketio / websocket: default fire-and-forget, use .request() when a return value is required.
websocket (native): RpcAble buffers calls made while the socket is still CONNECTING and flushes on open; it also calls destroy() automatically on close. No manual lifecycle wiring needed.
http: same fire-and-forget default as WS; use .request() / .expects() for returned values while push still auto-applies from the HTTP response.
collector: server-side push queue + flush().
-
Keep auth and role in adapter layer.
- Compute
userData and role from request/socket context.
- Call
receiver.dispatch(batch, { role }).
- If
permissions exists and a role is provided, treat it as a whitelist:
- method not listed => denied (
permission)
- method listed but role missing => denied (
forbidden)
- method listed with falsy/non-array value (
false, undefined, []) => denied for everyone
-
Keep validation parity where contracts exist.
- Prefer
new RpcAbleReceiver({ target, contract, validationFailed }).
contract keys are dot-joined method paths like profile.save.
- Validate only
args[0]; leave later args untouched.
validationFailed: 'throw' is the right default for .request() / .expects() flows when the caller needs an explicit error.
- If you update validation in JS, check PHP adapter parity too.
-
Verify end-to-end behavior.
- Batching in same tick.
- Namespace paths and
.set behavior.
- Permissions enforced.
- Contract validation enforced.
- HTTP push delivered and pending queue flushed correctly.
-
Update docs/templates with code changes.
- Keep examples in sync with API and architecture.
- Keep
README.md, examples/, and PHP adapter examples aligned with runtime behavior.
Canonical patterns
Client (socket.io)
import { RpcAble, RpcAbleReceiver } from 'rpcable';
const session = {};
const userSession = new RpcAble({
transport: 'socketio',
socket,
channel: '-userSession',
target: session,
});
const receiver = new RpcAbleReceiver({ target: session });
socket.on('-userSession', (batch) => receiver.dispatch(batch));
Client (HTTP)
import { RpcAble, extend } from 'rpcable';
const session = {};
const userSession = new RpcAble({
transport: 'http',
endpoint: '/rpc/user-session',
target: session,
});
extend(userSession, {
gamesReceived(games) {
},
});
Server adapter (Bun or Express)
const session = getOrCreateSession(sessionKey);
const results = await session.receiver.dispatch(batch, { role });
return { results, push: session.client.flush() };
Business session (shared)
import { RpcAble, RpcAbleReceiver } from 'rpcable';
class UserSession {
client = new RpcAble({ transport: 'collector' });
receiver = new RpcAbleReceiver({ target: this });
async getGames() {
const games = await db.find('games', {});
this.client.gamesReceived(games);
return games.length;
}
}
Receiver log settings
const receiver = new RpcAbleReceiver({
target,
notFound: 'console.error',
permission: false,
forbidden: 'console.log',
validationFailed: 'console.warn',
});
receiver.setSettings({
notFound: 'error',
permission: false,
forbidden: 'console.error',
validationFailed: 'throw',
});
Accepted values: false, undefined, 'console.log', 'console.warn', 'console.error', 'error', 'throw'.
Contract validation
const contract = {
'saveProfile': {
inputSchema: {
type: 'object',
required: ['displayName', 'favoriteNumber'],
additionalProperties: false,
properties: {
displayName: { type: 'string', minLength: 3, maxLength: 20 },
favoriteNumber: { type: 'integer', minimum: 1, maximum: 99 },
},
},
},
'profile.save': {
inputSchema: {
type: 'object',
required: ['name'],
properties: {
name: { type: 'string', minLength: 1 },
},
additionalProperties: false,
},
},
};
const receiver = new RpcAbleReceiver({
target,
contract,
validationFailed: 'throw',
});
Supported schema features currently include:
- boolean schemas
true / false
enum
type as a string or array
minLength, maxLength
minimum, maximum
required, properties, additionalProperties: false
items
Behavior rules:
- validation applies only to the first argument (
args[0])
- methods not listed in
contract are not validated
'throw' makes request/response transports reject with a normal RPC error
- non-throw modes log and skip the method call
PHP adapter parity
When working on templates/adapters/RpcAble.php or synced PHP examples, keep parity with JS receiver behavior:
contract and validationFailed must be supported in PHP too
- PHP receiver log modes should match JS (
console.warn, throw included)
- PHP validation errors should follow the same message shape:
[RpcAble] validation failed for "path": ...
- if template PHP changes, sync any copied example adapter files
Design rules for edits
- Prefer composition and small adapters over inheritance-heavy trees.
- Keep naming explicit:
transport, channel, endpoint, flush, dispatch, contract.
- Keep error messages actionable and specific.
- Make TypeScript definitions match runtime behavior exactly.
- Keep tests behavior-oriented, not implementation-coupled.
Anti-patterns to block
Do not introduce these patterns:
- String-based rpc routers.
switch(method) dispatch blocks.
- Raw app payloads through manual
socket.emit('foo', ...) bypassing rpcable path/args.
- Transport-specific business classes duplicating logic.
- HTTP handlers that ignore queued push data.
- Role checks scattered in controllers instead of
permissions + receiver flow.
- Validation rules hidden inside transport adapters when they belong in receiver
contract.
- Using removed APIs like
receive(), receiveAsync(), or nested { log: { ... } } receiver settings.
Debug playbook
When behavior is wrong:
- Confirm input is a batch array with
{ path, args } entries.
- Confirm adapter is using correct
transport and required fields (socket or endpoint).
- Confirm receiver is called with
{ role } when permissions are expected.
- Confirm
permissions whitelist includes intended methods for that role.
- Confirm target actually contains the method path.
- If validation is involved, confirm the
contract key matches the dot-joined method path and that only args[0] is being checked.
- If
.request() is failing silently, check whether validationFailed should be 'throw'.
- For HTTP parity issues, inspect response shape and whether
push is processed.
- For pending issues, inspect session key strategy (
sessionId/token preferred over plain userId).
- Tune receiver logs (
notFound / permission / forbidden / validationFailed) when debugging noisy adapters.
Output style when using this skill
When you answer the user after code changes:
- Start with what changed and why in architecture terms.
- List touched files.
- Report verification steps run (tests/build).
- Mention natural next steps only if useful.
Keep tone practical and avoid unnecessary theory unless requested.