registerCommand -> exposeCommand

This commit is contained in:
nvms 2025-04-21 08:05:20 -04:00
parent 978fd71d85
commit 6e153b1b44
6 changed files with 32 additions and 32 deletions

View File

@ -58,7 +58,7 @@ const server = new MeshServer({
redisOptions: { host: "localhost", port: 6379 }, redisOptions: { host: "localhost", port: 6379 },
}); });
server.registerCommand("echo", async (ctx) => { server.exposeCommand("echo", async (ctx) => {
return `echo: ${ctx.payload}`; return `echo: ${ctx.payload}`;
}); });
``` ```
@ -296,7 +296,7 @@ These can be used to implement custom commands or manage room state manually if
You can guard room joins using command middleware, just like any other command. The built-in room join command is "mesh/join-room", and the payload contains a `roomName` string: You can guard room joins using command middleware, just like any other command. The built-in room join command is "mesh/join-room", and the payload contains a `roomName` string:
```ts ```ts
server.addMiddleware(async (ctx) => { server.useMiddleware(async (ctx) => {
if (ctx.command === "mesh/join-room") { if (ctx.command === "mesh/join-room") {
const { roomName } = ctx.payload; const { roomName } = ctx.payload;
const meta = await server.connectionManager.getMetadata(ctx.connection); const meta = await server.connectionManager.getMetadata(ctx.connection);
@ -444,7 +444,7 @@ You can associate data like user IDs, tokens, or custom attributes with a connec
Metadata can be any JSON-serializable object, including nested structures. Updates fully replace the previous value—partial updates (patches) are not supported. While there is no hard size limit, large metadata objects may impact Redis performance. Metadata can be any JSON-serializable object, including nested structures. Updates fully replace the previous value—partial updates (patches) are not supported. While there is no hard size limit, large metadata objects may impact Redis performance.
```ts ```ts
server.registerCommand("authenticate", async (ctx) => { server.exposeCommand("authenticate", async (ctx) => {
// maybe do some actual authentication here // maybe do some actual authentication here
const { userId } = ctx.payload; const { userId } = ctx.payload;
const token = encode({ const token = encode({
@ -711,11 +711,11 @@ Middleware can be applied globally to all commands or specifically to individual
Applied to every command received by the server. Applied to every command received by the server.
```ts ```ts
server.addMiddleware(async (ctx) => { server.useMiddleware(async (ctx) => {
console.log(`Received command: ${ctx.command} from ${ctx.connection.id}`); console.log(`Received command: ${ctx.command} from ${ctx.connection.id}`);
}); });
server.addMiddleware(async (ctx) => { server.useMiddleware(async (ctx) => {
const metadata = await server.connectionManager.getMetadata(ctx.connection); const metadata = await server.connectionManager.getMetadata(ctx.connection);
if (!metadata?.userId) { if (!metadata?.userId) {
throw new Error("Unauthorized"); throw new Error("Unauthorized");
@ -741,7 +741,7 @@ const validateProfileUpdate = async (ctx) => {
} }
}; };
server.registerCommand( server.exposeCommand(
"update-profile", "update-profile",
async (ctx) => { async (ctx) => {
// .. // ..

View File

@ -25,7 +25,7 @@ export class CommandManager {
* @param {SocketMiddleware[]} [middlewares=[]] - An optional array of middleware functions to apply to the command. Defaults to an empty array. * @param {SocketMiddleware[]} [middlewares=[]] - An optional array of middleware functions to apply to the command. Defaults to an empty array.
* @throws {Error} May throw an error if the command registration or middleware addition fails. * @throws {Error} May throw an error if the command registration or middleware addition fails.
*/ */
registerCommand<T = any, U = any>( exposeCommand<T = any, U = any>(
command: string, command: string,
callback: (context: MeshContext<T>) => Promise<U> | U, callback: (context: MeshContext<T>) => Promise<U> | U,
middlewares: SocketMiddleware[] = [] middlewares: SocketMiddleware[] = []
@ -33,7 +33,7 @@ export class CommandManager {
this.commands[command] = callback; this.commands[command] = callback;
if (middlewares.length > 0) { if (middlewares.length > 0) {
this.addMiddlewareToCommand(command, middlewares); this.useMiddlewareWithCommand(command, middlewares);
} }
} }
@ -45,7 +45,7 @@ export class CommandManager {
* @returns {void} * @returns {void}
* @throws {Error} If the provided middlewares are not valid or fail validation (if applicable). * @throws {Error} If the provided middlewares are not valid or fail validation (if applicable).
*/ */
addMiddleware(...middlewares: SocketMiddleware[]): void { useMiddleware(...middlewares: SocketMiddleware[]): void {
this.globalMiddlewares.push(...middlewares); this.globalMiddlewares.push(...middlewares);
} }
@ -56,7 +56,7 @@ export class CommandManager {
* @param {SocketMiddleware[]} middlewares - An array of middleware functions to be added to the command. * @param {SocketMiddleware[]} middlewares - An array of middleware functions to be added to the command.
* @returns {void} * @returns {void}
*/ */
addMiddlewareToCommand( useMiddlewareWithCommand(
command: string, command: string,
middlewares: SocketMiddleware[] middlewares: SocketMiddleware[]
): void { ): void {

View File

@ -216,12 +216,12 @@ export class MeshServer extends WebSocketServer {
* @param {SocketMiddleware[]} [middlewares=[]] - An optional array of middleware functions to apply to the command. Defaults to an empty array. * @param {SocketMiddleware[]} [middlewares=[]] - An optional array of middleware functions to apply to the command. Defaults to an empty array.
* @throws {Error} May throw an error if the command registration or middleware addition fails. * @throws {Error} May throw an error if the command registration or middleware addition fails.
*/ */
registerCommand<T = any, U = any>( exposeCommand<T = any, U = any>(
command: string, command: string,
callback: (context: MeshContext<T>) => Promise<U> | U, callback: (context: MeshContext<T>) => Promise<U> | U,
middlewares: SocketMiddleware[] = [] middlewares: SocketMiddleware[] = []
) { ) {
this.commandManager.registerCommand(command, callback, middlewares); this.commandManager.exposeCommand(command, callback, middlewares);
} }
/** /**
@ -232,8 +232,8 @@ export class MeshServer extends WebSocketServer {
* @returns {void} * @returns {void}
* @throws {Error} If the provided middlewares are not valid or fail validation (if applicable). * @throws {Error} If the provided middlewares are not valid or fail validation (if applicable).
*/ */
addMiddleware(...middlewares: SocketMiddleware[]): void { useMiddleware(...middlewares: SocketMiddleware[]): void {
this.commandManager.addMiddleware(...middlewares); this.commandManager.useMiddleware(...middlewares);
} }
/** /**
@ -243,11 +243,11 @@ export class MeshServer extends WebSocketServer {
* @param {SocketMiddleware[]} middlewares - An array of middleware functions to be added to the command. * @param {SocketMiddleware[]} middlewares - An array of middleware functions to be added to the command.
* @returns {void} * @returns {void}
*/ */
addMiddlewareToCommand( useMiddlewareWithCommand(
command: string, command: string,
middlewares: SocketMiddleware[] middlewares: SocketMiddleware[]
): void { ): void {
this.commandManager.addMiddlewareToCommand(command, middlewares); this.commandManager.useMiddlewareWithCommand(command, middlewares);
} }
// #endregion // #endregion
@ -492,7 +492,7 @@ export class MeshServer extends WebSocketServer {
// #region Command Registration // #region Command Registration
private registerBuiltinCommands() { private registerBuiltinCommands() {
this.registerCommand< this.exposeCommand<
{ channel: string; historyLimit?: number }, { channel: string; historyLimit?: number },
{ success: boolean; history?: string[] } { success: boolean; history?: string[] }
>("mesh/subscribe-channel", async (ctx) => { >("mesh/subscribe-channel", async (ctx) => {
@ -527,7 +527,7 @@ export class MeshServer extends WebSocketServer {
} }
}); });
this.registerCommand<{ channel: string }, boolean>( this.exposeCommand<{ channel: string }, boolean>(
"mesh/unsubscribe-channel", "mesh/unsubscribe-channel",
async (ctx) => { async (ctx) => {
const { channel } = ctx.payload; const { channel } = ctx.payload;
@ -544,7 +544,7 @@ export class MeshServer extends WebSocketServer {
} }
); );
this.registerCommand< this.exposeCommand<
{ roomName: string }, { roomName: string },
{ success: boolean; present: string[] } { success: boolean; present: string[] }
>("mesh/join-room", async (ctx) => { >("mesh/join-room", async (ctx) => {
@ -556,7 +556,7 @@ export class MeshServer extends WebSocketServer {
return { success: true, present }; return { success: true, present };
}); });
this.registerCommand<{ roomName: string }, { success: boolean }>( this.exposeCommand<{ roomName: string }, { success: boolean }>(
"mesh/leave-room", "mesh/leave-room",
async (ctx) => { async (ctx) => {
const { roomName } = ctx.payload; const { roomName } = ctx.payload;
@ -567,7 +567,7 @@ export class MeshServer extends WebSocketServer {
} }
private registerRecordCommands() { private registerRecordCommands() {
this.registerCommand< this.exposeCommand<
{ recordId: string; mode?: "patch" | "full" }, { recordId: string; mode?: "patch" | "full" },
{ success: boolean; record?: any; version?: number } { success: boolean; record?: any; version?: number }
>("mesh/subscribe-record", async (ctx) => { >("mesh/subscribe-record", async (ctx) => {
@ -600,7 +600,7 @@ export class MeshServer extends WebSocketServer {
} }
}); });
this.registerCommand<{ recordId: string }, boolean>( this.exposeCommand<{ recordId: string }, boolean>(
"mesh/unsubscribe-record", "mesh/unsubscribe-record",
async (ctx) => { async (ctx) => {
const { recordId } = ctx.payload; const { recordId } = ctx.payload;
@ -612,7 +612,7 @@ export class MeshServer extends WebSocketServer {
} }
); );
this.registerCommand< this.exposeCommand<
{ recordId: string; newValue: any }, { recordId: string; newValue: any },
{ success: boolean } { success: boolean }
>("mesh/publish-record-update", async (ctx) => { >("mesh/publish-record-update", async (ctx) => {
@ -639,7 +639,7 @@ export class MeshServer extends WebSocketServer {
} }
}); });
this.registerCommand< this.exposeCommand<
{ roomName: string }, { roomName: string },
{ success: boolean; present: string[] } { success: boolean; present: string[] }
>("mesh/subscribe-presence", async (ctx) => { >("mesh/subscribe-presence", async (ctx) => {
@ -672,7 +672,7 @@ export class MeshServer extends WebSocketServer {
} }
}); });
this.registerCommand<{ roomName: string }, boolean>( this.exposeCommand<{ roomName: string }, boolean>(
"mesh/unsubscribe-presence", "mesh/unsubscribe-presence",
async (ctx) => { async (ctx) => {
const { roomName } = ctx.payload; const { roomName } = ctx.payload;

View File

@ -65,7 +65,7 @@ describe("MeshServer", () => {
}); });
test("clients can send a command and receive a response", async () => { test("clients can send a command and receive a response", async () => {
server.registerCommand("echo", async (c) => `echo: ${c.payload}`); server.exposeCommand("echo", async (c) => `echo: ${c.payload}`);
await clientA.connect(); await clientA.connect();
const response = await clientA.command("echo", "Hello, World!"); const response = await clientA.command("echo", "Hello, World!");
expect(response).toBe("echo: Hello, World!"); expect(response).toBe("echo: Hello, World!");

View File

@ -43,7 +43,7 @@ describe("MeshClient", () => {
}); });
test("command times out when server doesn't respond", async () => { test("command times out when server doesn't respond", async () => {
server.registerCommand("never-responds", async () => new Promise(() => {})); server.exposeCommand("never-responds", async () => new Promise(() => {}));
await client.connect(); await client.connect();
@ -64,7 +64,7 @@ describe("MeshClient", () => {
}); });
test("thrown servers errors are serialized to the client", async () => { test("thrown servers errors are serialized to the client", async () => {
server.registerCommand("throws-error", async () => { server.exposeCommand("throws-error", async () => {
throw new Error("This is a test error"); throw new Error("This is a test error");
}); });
@ -79,7 +79,7 @@ describe("MeshClient", () => {
}); });
test("handles large payloads without issue", async () => { test("handles large payloads without issue", async () => {
server.registerCommand("echo", async (ctx) => ctx.payload); server.exposeCommand("echo", async (ctx) => ctx.payload);
await client.connect(); await client.connect();
const largeData = { const largeData = {

View File

@ -56,7 +56,7 @@ describe.sequential("Multiple instances", () => {
}); });
test("broadcast should work across instances", async () => { test("broadcast should work across instances", async () => {
serverA.registerCommand("broadcast", async (ctx) => { serverA.exposeCommand("broadcast", async (ctx) => {
await serverA.broadcast("hello", "Hello!"); await serverA.broadcast("hello", "Hello!");
}); });
@ -97,13 +97,13 @@ describe.sequential("Multiple instances", () => {
test("broadcastRoom should work across instances", async () => { test("broadcastRoom should work across instances", async () => {
[serverA, serverB].forEach((server) => [serverA, serverB].forEach((server) =>
server.registerCommand("join-room", async (ctx) => { server.exposeCommand("join-room", async (ctx) => {
await server.addToRoom(ctx.payload.room, ctx.connection); await server.addToRoom(ctx.payload.room, ctx.connection);
return { joined: true }; return { joined: true };
}) })
); );
serverA.registerCommand("broadcast-room", async (ctx) => { serverA.exposeCommand("broadcast-room", async (ctx) => {
await serverA.broadcastRoom( await serverA.broadcastRoom(
ctx.payload.room, ctx.payload.room,
"room-message", "room-message",