shardwire 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,20 +1,87 @@
1
1
  # shardwire
2
2
 
3
- Lightweight TypeScript library that turns a Discord bot host into a WebSocket command/event bridge.
3
+ > Lightweight TypeScript library for building a Discord-hosted WebSocket command and event bridge.
4
4
 
5
- ## Install
5
+ [![npm version](https://img.shields.io/npm/v/shardwire?style=flat-square)](https://www.npmjs.com/package/shardwire)
6
+ [![npm downloads](https://img.shields.io/npm/dm/shardwire?style=flat-square)](https://www.npmjs.com/package/shardwire)
7
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18.18-3c873a?style=flat-square)](https://nodejs.org/)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Strict-3178c6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org/)
9
+ [![License](https://img.shields.io/badge/license-MIT-blue?style=flat-square)](./LICENSE)
10
+
11
+ `shardwire` helps you expose strongly-typed bot capabilities over WebSocket so dashboards, admin tools, and backend services can send commands to your Discord bot host and subscribe to events in real time.
12
+
13
+ [Quick Start](#quick-start) • [Why shardwire](#why-shardwire) • [Host Setup](#host-setup) • [Consumer Setup](#consumer-setup) • [API Overview](#api-overview) • [Configuration](#configuration) • [Security Notes](#security-notes)
14
+
15
+ ## Table of Contents
16
+
17
+ - [Why shardwire](#why-shardwire)
18
+ - [Features](#features)
19
+ - [Quick Start](#quick-start)
20
+ - [Host Setup](#host-setup)
21
+ - [Consumer Setup](#consumer-setup)
22
+ - [Token-Only Host (No Existing discord.js Client)](#token-only-host-no-existing-discordjs-client)
23
+ - [Reconnect and Timeout Hardening](#reconnect-and-timeout-hardening)
24
+ - [API Overview](#api-overview)
25
+ - [Configuration](#configuration)
26
+ - [Error Model](#error-model)
27
+ - [Recipes and Troubleshooting](#recipes-and-troubleshooting)
28
+ - [Compatibility](#compatibility)
29
+ - [Security Notes](#security-notes)
30
+ - [Roadmap Constraints (v1)](#roadmap-constraints-v1)
31
+
32
+ ## Why shardwire
33
+
34
+ Running bot logic inside your Discord host process while orchestrating it from external services is a common pattern, but wiring this safely and ergonomically takes time. `shardwire` gives you a focused transport layer with a typed contract so you can:
35
+
36
+ - call bot-hosted commands from apps and services,
37
+ - stream real-time events back to consumers,
38
+ - keep payloads typed end-to-end with TypeScript,
39
+ - start quickly without deploying extra infrastructure.
40
+
41
+ ## Features
42
+
43
+ - **Typed command RPC** from consumers to host (`send` -> `CommandResult`).
44
+ - **Real-time pub/sub events** from host to all authenticated consumers.
45
+ - **Single factory API** (`createShardwire`) with host and consumer overloads.
46
+ - **Built-in reliability controls** with reconnect backoff, jitter, and timeouts.
47
+ - **Runtime input validation** for config, names, and JSON-serializable payloads.
48
+ - **Optional schema validation** for command and event payloads.
49
+ - **Optional token-only Discord mode** where shardwire can own client lifecycle.
50
+ - **Dual package output** for ESM and CJS consumers.
51
+
52
+ ## Quick Start
53
+
54
+ Install:
6
55
 
7
56
  ```bash
8
57
  pnpm add shardwire
9
58
  ```
10
59
 
11
- ## Host mode
60
+ Define shared message contracts:
61
+
62
+ ```ts
63
+ type Commands = {
64
+ "ban-user": {
65
+ request: { userId: string };
66
+ response: { banned: true; userId: string };
67
+ };
68
+ };
69
+
70
+ type Events = {
71
+ "member-joined": { userId: string; guildId: string };
72
+ };
73
+ ```
74
+
75
+ ## Host Setup
12
76
 
13
77
  ```ts
14
78
  import { createShardwire } from "shardwire";
15
79
 
16
80
  type Commands = {
17
- "ban-user": { userId: string };
81
+ "ban-user": {
82
+ request: { userId: string };
83
+ response: { banned: true; userId: string };
84
+ };
18
85
  };
19
86
 
20
87
  type Events = {
@@ -25,25 +92,30 @@ const wire = createShardwire<Commands, Events>({
25
92
  client: discordClient,
26
93
  server: {
27
94
  port: 3001,
28
- secret: process.env.SHARDWIRE_SECRET!,
95
+ secrets: [process.env.SHARDWIRE_SECRET!],
96
+ primarySecretId: "s0",
29
97
  },
98
+ name: "bot-host",
30
99
  });
31
100
 
32
101
  wire.onCommand("ban-user", async ({ userId }) => {
33
102
  await guild.members.ban(userId);
34
- return { banned: true };
103
+ return { banned: true, userId };
35
104
  });
36
105
 
37
106
  wire.emitEvent("member-joined", { userId: "123", guildId: "456" });
38
107
  ```
39
108
 
40
- ## Consumer mode
109
+ ## Consumer Setup
41
110
 
42
111
  ```ts
43
112
  import { createShardwire } from "shardwire";
44
113
 
45
114
  type Commands = {
46
- "ban-user": { userId: string };
115
+ "ban-user": {
116
+ request: { userId: string };
117
+ response: { banned: true; userId: string };
118
+ };
47
119
  };
48
120
 
49
121
  type Events = {
@@ -53,89 +125,213 @@ type Events = {
53
125
  const wire = createShardwire<Commands, Events>({
54
126
  url: "ws://localhost:3001/shardwire",
55
127
  secret: process.env.SHARDWIRE_SECRET!,
128
+ secretId: "s0",
129
+ clientName: "dashboard-api",
56
130
  });
57
131
 
58
132
  const result = await wire.send("ban-user", { userId: "123" });
59
133
 
60
- wire.on("member-joined", (payload) => {
61
- console.log(payload.guildId);
134
+ if (result.ok) {
135
+ console.log("Command succeeded:", result.data);
136
+ } else {
137
+ console.error("Command failed:", result.error.code, result.error.message);
138
+ }
139
+
140
+ wire.on("member-joined", (payload, meta) => {
141
+ console.log("event", payload, meta.ts);
142
+ });
143
+
144
+ wire.onReconnecting(({ attempt, delayMs }) => {
145
+ console.warn(`reconnecting attempt ${attempt} in ${delayMs}ms`);
146
+ });
147
+
148
+ await wire.ready();
149
+ ```
150
+
151
+ ## Token-Only Host (No Existing discord.js Client)
152
+
153
+ If you do not already manage a `discord.js` client, provide a bot token and shardwire can initialize and own the client lifecycle.
154
+
155
+ ```ts
156
+ const wire = createShardwire<Commands, Events>({
157
+ token: process.env.DISCORD_BOT_TOKEN!,
158
+ server: {
159
+ port: 3001,
160
+ secrets: [process.env.SHARDWIRE_SECRET!],
161
+ primarySecretId: "s0",
162
+ },
163
+ });
164
+ ```
165
+
166
+ > [!IMPORTANT]
167
+ > Keep `DISCORD_BOT_TOKEN` and `SHARDWIRE_SECRET` in environment variables. Never commit them.
168
+
169
+ ## Reconnect and Timeout Hardening
170
+
171
+ For unstable networks, tune reconnect behavior and request timeout explicitly:
172
+
173
+ ```ts
174
+ const wire = createShardwire({
175
+ url: "ws://bot-host:3001/shardwire",
176
+ secret: process.env.SHARDWIRE_SECRET!,
177
+ secretId: "s0",
178
+ requestTimeoutMs: 10_000,
179
+ reconnect: {
180
+ enabled: true,
181
+ initialDelayMs: 500,
182
+ maxDelayMs: 10_000,
183
+ jitter: true,
184
+ },
62
185
  });
63
186
  ```
64
187
 
65
- ## Local examples
188
+ ## Schema Validation (Zod)
66
189
 
67
- Run a local host and consumer in two terminals:
190
+ Use runtime schemas to validate command request/response payloads and emitted event payloads.
68
191
 
69
- 1. Start host:
70
- - `pnpm example:host`
71
- 2. Start consumer:
72
- - `pnpm example:consumer`
192
+ ```ts
193
+ import { z } from "zod";
194
+ import { createShardwire, fromZodSchema } from "shardwire";
73
195
 
74
- Environment overrides:
196
+ type Commands = {
197
+ "ban-user": {
198
+ request: { userId: string };
199
+ response: { banned: true; userId: string };
200
+ };
201
+ };
75
202
 
76
- - `SHARDWIRE_SECRET` (default: `local-dev-secret`)
77
- - `SHARDWIRE_PORT` for host (default: `3001`)
78
- - `SHARDWIRE_URL` for consumer (default: `ws://localhost:3001/shardwire`)
203
+ const wire = createShardwire<Commands, {}>({
204
+ server: {
205
+ port: 3001,
206
+ secrets: [process.env.SHARDWIRE_SECRET!],
207
+ primarySecretId: "s0",
208
+ },
209
+ validation: {
210
+ commands: {
211
+ "ban-user": {
212
+ request: fromZodSchema(z.object({ userId: z.string().min(3) })),
213
+ response: fromZodSchema(z.object({ banned: z.literal(true), userId: z.string() })),
214
+ },
215
+ },
216
+ },
217
+ });
218
+ ```
79
219
 
80
- ## API quick reference
220
+ ## API Overview
81
221
 
82
- ### Host
222
+ ### Host API
83
223
 
84
224
  - `wire.onCommand(name, handler)` register a command handler.
85
225
  - `wire.emitEvent(name, payload)` emit an event to all connected consumers.
86
226
  - `wire.broadcast(name, payload)` alias of `emitEvent`.
87
- - `wire.close()` stop websocket server and close connections.
227
+ - `wire.close()` close the server and active sockets.
88
228
 
89
- ### Consumer
229
+ ### Consumer API
90
230
 
91
- - `wire.send(name, payload, options?)` send command request and await typed result.
231
+ - `wire.send(name, payload, options?)` send command and await typed result.
92
232
  - `wire.on(name, handler)` subscribe to events.
93
- - `wire.off(name, handler)` remove specific event handler.
233
+ - `wire.off(name, handler)` unsubscribe a specific handler.
234
+ - `wire.ready()` wait for authenticated connection.
235
+ - `wire.onConnected(handler)` subscribe to authenticated connection events.
236
+ - `wire.onDisconnected(handler)` subscribe to disconnect events.
237
+ - `wire.onReconnecting(handler)` subscribe to reconnect scheduling events.
94
238
  - `wire.connected()` check authenticated connection state.
239
+ - `wire.connectionId()` get current authenticated connection id (or `null`).
95
240
  - `wire.close()` close socket and stop reconnect attempts.
96
241
 
97
- ### Command result shape
242
+ ## Configuration
243
+
244
+ ### Host options
245
+
246
+ - `server.port` required port.
247
+ - `server.secrets` required shared secret list for authentication.
248
+ - `server.primarySecretId` optional preferred secret id (for example `"s0"`).
249
+ - `server.path` optional WebSocket path (default `/shardwire`).
250
+ - `server.host` optional bind host.
251
+ - `server.heartbeatMs` heartbeat interval.
252
+ - `server.commandTimeoutMs` command execution timeout.
253
+ - `server.maxPayloadBytes` WebSocket payload size limit.
254
+ - `server.corsOrigins` CORS allowlist for browser clients.
255
+ - `client` existing `discord.js` client (optional).
256
+ - `token` Discord bot token (optional, enables token-only mode).
257
+
258
+ ### Consumer options
259
+
260
+ - `url` host endpoint (for example `ws://localhost:3001/shardwire`).
261
+ - `secret` shared secret matching host.
262
+ - `secretId` optional secret id (for example `"s0"`) used during handshake.
263
+ - `clientName` optional identity sent during auth handshake for host-side telemetry.
264
+ - `requestTimeoutMs` default timeout for `send`.
265
+ - `reconnect` reconnect policy (`enabled`, delays, jitter).
266
+ - `webSocketFactory` optional custom client implementation.
267
+
268
+ > [!NOTE]
269
+ > Invalid configuration, empty command/event names, or non-serializable payloads throw synchronously with clear errors.
270
+
271
+ ## Error Model
98
272
 
99
273
  `send()` resolves to:
100
274
 
101
275
  - success: `{ ok: true, requestId, ts, data }`
102
- - failure: `{ ok: false, requestId, ts, error: { code, message, details? } }`
276
+ - failure: `{ ok: false, requestId, ts, error }`
277
+
278
+ When `error.code === "VALIDATION_ERROR"`, `error.details` includes:
279
+
280
+ - `name`: command/event name
281
+ - `stage`: `"command.request" | "command.response" | "event.emit"`
282
+ - `issues`: optional normalized issue list (`path`, `message`)
283
+
284
+ Failure codes:
103
285
 
104
- Error codes: `AUTH_ERROR`, `TIMEOUT`, `COMMAND_NOT_FOUND`, `VALIDATION_ERROR`, `INTERNAL_ERROR`.
286
+ - `UNAUTHORIZED`
287
+ - `TIMEOUT`
288
+ - `DISCONNECTED`
289
+ - `COMMAND_NOT_FOUND`
290
+ - `VALIDATION_ERROR`
291
+ - `INTERNAL_ERROR`
105
292
 
106
- ## Runtime validation behavior
293
+ ## Recipes and Troubleshooting
294
+
295
+ Run local examples:
296
+
297
+ ```bash
298
+ pnpm run example:host
299
+ pnpm run example:consumer
300
+ pnpm run example:host:schema
301
+ pnpm run example:consumer:schema
302
+ ```
107
303
 
108
- - Host config requires a valid `server` block (`port`, `secret`, and positive numeric limits).
109
- - Consumer config requires non-empty `url` and `secret`.
110
- - Command/event names must be non-empty strings.
111
- - Command/event payloads must be JSON-serializable.
304
+ Practical guides:
112
305
 
113
- Invalid input throws synchronously with a descriptive `Error`.
306
+ - [Integration reference](./.agents/skills/shardwire/references.md)
307
+ - [Add command + event flow](./.agents/skills/shardwire/examples/command-event-change.md)
308
+ - [Reconnect hardening](./.agents/skills/shardwire/examples/reconnect-hardening.md)
309
+ - [Token-only host setup](./.agents/skills/shardwire/examples/token-only-host.md)
310
+ - [Troubleshooting flow](./.agents/skills/shardwire/examples/troubleshooting-flow.md)
114
311
 
115
- ## Compatibility matrix
312
+ Common symptoms:
116
313
 
117
- - Node.js: `>=18.18`
118
- - TypeScript: `>=5.x` (consumer project)
119
- - discord.js: `^14` (optional peer dependency)
120
- - Module support: ESM + CJS exports
314
+ - `UNAUTHORIZED`: verify `secret`, `secretId`, and host `server.secrets` ordering.
315
+ - `DISCONNECTED`: host unavailable or connection dropped before response completed.
316
+ - Frequent `TIMEOUT`: increase `requestTimeoutMs` and inspect host command handler duration.
121
317
 
122
- ## Security and operations notes
318
+ ## Compatibility
123
319
 
124
- - Keep `secret` in environment variables, never commit it.
125
- - v1 uses static shared secret (restart host to rotate).
126
- - Use `server.corsOrigins` when browser clients connect.
127
- - Set `server.maxPayloadBytes` and `requestTimeoutMs` for your workload profile.
320
+ - Node.js `>=18.18`
321
+ - TypeScript-first API
322
+ - `discord.js` `^14` as optional peer dependency
323
+ - ESM + CJS package exports
128
324
 
129
- ## CI and release workflow
325
+ ## Security Notes
130
326
 
131
- - CI runs `pnpm verify` (`test`, `typecheck`, `build`) on pushes and pull requests.
132
- - Local verification: `pnpm verify`
133
- - Release guide: see `RELEASING.md`
134
- - Change history: see `CHANGELOG.md`
327
+ - Use strong, rotated secrets via environment variables.
328
+ - Rotate with overlapping `server.secrets` entries and explicit `secretId` cutovers.
329
+ - Set payload and timeout limits appropriate for your workload.
330
+ - Configure `server.corsOrigins` when exposing browser consumers.
135
331
 
136
- ## v1 constraints
332
+ ## Roadmap Constraints (v1)
137
333
 
138
- - Single package, no external services required.
139
- - Single host process only (no cross-host sharding in v1).
140
- - Shared-secret auth for host/consumer handshake.
141
- - In-memory command dedupe and pending request tracking.
334
+ - Single npm package, no external infrastructure requirement.
335
+ - Host process embeds WebSocket server.
336
+ - Single-host process model (no cross-host sharding in v1).
337
+ - Shared-secret handshake with in-memory dedupe and pending-request tracking.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,20 @@
1
- type CommandMap = Record<string, unknown>;
1
+ import { ZodType } from 'zod';
2
+
3
+ interface CommandSchema<Request = unknown, Response = unknown> {
4
+ request: Request;
5
+ response: Response;
6
+ }
7
+ type CommandMap = Record<string, unknown | CommandSchema>;
2
8
  type EventMap = Record<string, unknown>;
9
+ interface SchemaValidationIssue {
10
+ path: string;
11
+ message: string;
12
+ }
13
+ interface RuntimeSchema<T = unknown> {
14
+ parse: (value: unknown) => T;
15
+ }
16
+ type CommandRequestOf<T> = T extends CommandSchema<infer Request, any> ? Request : T;
17
+ type CommandResponseOf<T> = T extends CommandSchema<any, infer Response> ? Response : unknown;
3
18
  type Unsubscribe = () => void;
4
19
  interface ShardwireLogger {
5
20
  debug?: (message: string, meta?: Record<string, unknown>) => void;
@@ -35,7 +50,7 @@ interface CommandFailure {
35
50
  requestId: string;
36
51
  ts: number;
37
52
  error: {
38
- code: "AUTH_ERROR" | "TIMEOUT" | "COMMAND_NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
53
+ code: "UNAUTHORIZED" | "TIMEOUT" | "DISCONNECTED" | "COMMAND_NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
39
54
  message: string;
40
55
  details?: unknown;
41
56
  };
@@ -47,19 +62,33 @@ interface HostOptions<C extends CommandMap, E extends EventMap> {
47
62
  server: {
48
63
  port: number;
49
64
  host?: string;
50
- secret: string;
65
+ secrets: string[];
66
+ primarySecretId?: string;
51
67
  path?: string;
52
68
  heartbeatMs?: number;
53
69
  commandTimeoutMs?: number;
54
70
  maxPayloadBytes?: number;
55
71
  corsOrigins?: string[];
56
72
  };
73
+ validation?: {
74
+ commands?: Partial<{
75
+ [K in keyof C & string]: {
76
+ request?: RuntimeSchema<CommandRequestOf<C[K]>>;
77
+ response?: RuntimeSchema<CommandResponseOf<C[K]>>;
78
+ };
79
+ }>;
80
+ events?: Partial<{
81
+ [K in keyof E & string]: RuntimeSchema<E[K]>;
82
+ }>;
83
+ };
57
84
  name?: string;
58
85
  logger?: ShardwireLogger;
59
86
  }
60
87
  interface ConsumerOptions<C extends CommandMap, E extends EventMap> {
61
88
  url: string;
62
89
  secret: string;
90
+ secretId?: string;
91
+ clientName?: string;
63
92
  webSocketFactory?: (url: string) => {
64
93
  readyState: number;
65
94
  send(data: string): void;
@@ -78,24 +107,63 @@ interface ConsumerOptions<C extends CommandMap, E extends EventMap> {
78
107
  }
79
108
  interface HostShardwire<C extends CommandMap, E extends EventMap> {
80
109
  mode: "host";
81
- onCommand<K extends keyof C & string>(name: K, handler: (payload: C[K], ctx: CommandContext) => Promise<unknown> | unknown): Unsubscribe;
110
+ onCommand<K extends keyof C & string>(name: K, handler: (payload: CommandRequestOf<C[K]>, ctx: CommandContext) => Promise<CommandResponseOf<C[K]>> | CommandResponseOf<C[K]>): Unsubscribe;
82
111
  emitEvent<K extends keyof E & string>(name: K, payload: E[K]): void;
83
112
  broadcast<K extends keyof E & string>(name: K, payload: E[K]): void;
84
113
  close(): Promise<void>;
85
114
  }
86
115
  interface ConsumerShardwire<C extends CommandMap, E extends EventMap> {
87
116
  mode: "consumer";
88
- send<K extends keyof C & string>(name: K, payload: C[K], options?: {
117
+ send<K extends keyof C & string>(name: K, payload: CommandRequestOf<C[K]>, options?: {
89
118
  timeoutMs?: number;
90
119
  requestId?: string;
91
- }): Promise<CommandResult>;
120
+ }): Promise<CommandResult<CommandResponseOf<C[K]>>>;
92
121
  on<K extends keyof E & string>(name: K, handler: (payload: E[K], meta: EventMeta) => void): Unsubscribe;
93
122
  off<K extends keyof E & string>(name: K, handler: (payload: E[K], meta: EventMeta) => void): void;
123
+ onConnected(handler: (info: {
124
+ connectionId: string;
125
+ connectedAt: number;
126
+ }) => void): Unsubscribe;
127
+ onDisconnected(handler: (info: {
128
+ reason: string;
129
+ at: number;
130
+ willReconnect: boolean;
131
+ }) => void): Unsubscribe;
132
+ onReconnecting(handler: (info: {
133
+ attempt: number;
134
+ delayMs: number;
135
+ at: number;
136
+ }) => void): Unsubscribe;
137
+ ready(): Promise<void>;
94
138
  connected(): boolean;
139
+ connectionId(): string | null;
95
140
  close(): Promise<void>;
96
141
  }
142
+ interface CreateShardwire {
143
+ <C extends CommandMap = {}, E extends EventMap = {}>(options: HostOptions<C, E>): HostShardwire<C, E>;
144
+ <C extends CommandMap = {}, E extends EventMap = {}>(options: ConsumerOptions<C, E>): ConsumerShardwire<C, E>;
145
+ }
146
+
147
+ interface SafeParseResultSuccess<T> {
148
+ success: true;
149
+ data: T;
150
+ }
151
+ interface SafeParseResultFailure {
152
+ success: false;
153
+ error: {
154
+ message: string;
155
+ issues?: SchemaValidationIssue[];
156
+ };
157
+ }
158
+ type SafeParseResult<T> = SafeParseResultSuccess<T> | SafeParseResultFailure;
159
+ interface SafeParseSchema<T> {
160
+ safeParse: (value: unknown) => SafeParseResult<T>;
161
+ }
162
+ declare function fromSafeParseSchema<T>(schema: SafeParseSchema<T>): RuntimeSchema<T>;
163
+
164
+ declare function fromZodSchema<T>(schema: ZodType<T>): RuntimeSchema<T>;
97
165
 
98
166
  declare function createShardwire<C extends CommandMap = {}, E extends EventMap = {}>(options: HostOptions<C, E>): HostShardwire<C, E>;
99
167
  declare function createShardwire<C extends CommandMap = {}, E extends EventMap = {}>(options: ConsumerOptions<C, E>): ConsumerShardwire<C, E>;
100
168
 
101
- export { type CommandContext, type CommandFailure, type CommandMap, type CommandResult, type CommandSuccess, type ConsumerOptions, type ConsumerShardwire, type DiscordClientLike, type EventMap, type EventMeta, type HostOptions, type HostShardwire, type ShardwireLogger, type Unsubscribe, createShardwire };
169
+ export { type CommandContext, type CommandFailure, type CommandMap, type CommandRequestOf, type CommandResponseOf, type CommandResult, type CommandSchema, type CommandSuccess, type ConsumerOptions, type ConsumerShardwire, type CreateShardwire, type DiscordClientLike, type EventMap, type EventMeta, type HostOptions, type HostShardwire, type RuntimeSchema, type SchemaValidationIssue, type ShardwireLogger, type Unsubscribe, createShardwire, fromSafeParseSchema, fromZodSchema };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,20 @@
1
- type CommandMap = Record<string, unknown>;
1
+ import { ZodType } from 'zod';
2
+
3
+ interface CommandSchema<Request = unknown, Response = unknown> {
4
+ request: Request;
5
+ response: Response;
6
+ }
7
+ type CommandMap = Record<string, unknown | CommandSchema>;
2
8
  type EventMap = Record<string, unknown>;
9
+ interface SchemaValidationIssue {
10
+ path: string;
11
+ message: string;
12
+ }
13
+ interface RuntimeSchema<T = unknown> {
14
+ parse: (value: unknown) => T;
15
+ }
16
+ type CommandRequestOf<T> = T extends CommandSchema<infer Request, any> ? Request : T;
17
+ type CommandResponseOf<T> = T extends CommandSchema<any, infer Response> ? Response : unknown;
3
18
  type Unsubscribe = () => void;
4
19
  interface ShardwireLogger {
5
20
  debug?: (message: string, meta?: Record<string, unknown>) => void;
@@ -35,7 +50,7 @@ interface CommandFailure {
35
50
  requestId: string;
36
51
  ts: number;
37
52
  error: {
38
- code: "AUTH_ERROR" | "TIMEOUT" | "COMMAND_NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
53
+ code: "UNAUTHORIZED" | "TIMEOUT" | "DISCONNECTED" | "COMMAND_NOT_FOUND" | "VALIDATION_ERROR" | "INTERNAL_ERROR";
39
54
  message: string;
40
55
  details?: unknown;
41
56
  };
@@ -47,19 +62,33 @@ interface HostOptions<C extends CommandMap, E extends EventMap> {
47
62
  server: {
48
63
  port: number;
49
64
  host?: string;
50
- secret: string;
65
+ secrets: string[];
66
+ primarySecretId?: string;
51
67
  path?: string;
52
68
  heartbeatMs?: number;
53
69
  commandTimeoutMs?: number;
54
70
  maxPayloadBytes?: number;
55
71
  corsOrigins?: string[];
56
72
  };
73
+ validation?: {
74
+ commands?: Partial<{
75
+ [K in keyof C & string]: {
76
+ request?: RuntimeSchema<CommandRequestOf<C[K]>>;
77
+ response?: RuntimeSchema<CommandResponseOf<C[K]>>;
78
+ };
79
+ }>;
80
+ events?: Partial<{
81
+ [K in keyof E & string]: RuntimeSchema<E[K]>;
82
+ }>;
83
+ };
57
84
  name?: string;
58
85
  logger?: ShardwireLogger;
59
86
  }
60
87
  interface ConsumerOptions<C extends CommandMap, E extends EventMap> {
61
88
  url: string;
62
89
  secret: string;
90
+ secretId?: string;
91
+ clientName?: string;
63
92
  webSocketFactory?: (url: string) => {
64
93
  readyState: number;
65
94
  send(data: string): void;
@@ -78,24 +107,63 @@ interface ConsumerOptions<C extends CommandMap, E extends EventMap> {
78
107
  }
79
108
  interface HostShardwire<C extends CommandMap, E extends EventMap> {
80
109
  mode: "host";
81
- onCommand<K extends keyof C & string>(name: K, handler: (payload: C[K], ctx: CommandContext) => Promise<unknown> | unknown): Unsubscribe;
110
+ onCommand<K extends keyof C & string>(name: K, handler: (payload: CommandRequestOf<C[K]>, ctx: CommandContext) => Promise<CommandResponseOf<C[K]>> | CommandResponseOf<C[K]>): Unsubscribe;
82
111
  emitEvent<K extends keyof E & string>(name: K, payload: E[K]): void;
83
112
  broadcast<K extends keyof E & string>(name: K, payload: E[K]): void;
84
113
  close(): Promise<void>;
85
114
  }
86
115
  interface ConsumerShardwire<C extends CommandMap, E extends EventMap> {
87
116
  mode: "consumer";
88
- send<K extends keyof C & string>(name: K, payload: C[K], options?: {
117
+ send<K extends keyof C & string>(name: K, payload: CommandRequestOf<C[K]>, options?: {
89
118
  timeoutMs?: number;
90
119
  requestId?: string;
91
- }): Promise<CommandResult>;
120
+ }): Promise<CommandResult<CommandResponseOf<C[K]>>>;
92
121
  on<K extends keyof E & string>(name: K, handler: (payload: E[K], meta: EventMeta) => void): Unsubscribe;
93
122
  off<K extends keyof E & string>(name: K, handler: (payload: E[K], meta: EventMeta) => void): void;
123
+ onConnected(handler: (info: {
124
+ connectionId: string;
125
+ connectedAt: number;
126
+ }) => void): Unsubscribe;
127
+ onDisconnected(handler: (info: {
128
+ reason: string;
129
+ at: number;
130
+ willReconnect: boolean;
131
+ }) => void): Unsubscribe;
132
+ onReconnecting(handler: (info: {
133
+ attempt: number;
134
+ delayMs: number;
135
+ at: number;
136
+ }) => void): Unsubscribe;
137
+ ready(): Promise<void>;
94
138
  connected(): boolean;
139
+ connectionId(): string | null;
95
140
  close(): Promise<void>;
96
141
  }
142
+ interface CreateShardwire {
143
+ <C extends CommandMap = {}, E extends EventMap = {}>(options: HostOptions<C, E>): HostShardwire<C, E>;
144
+ <C extends CommandMap = {}, E extends EventMap = {}>(options: ConsumerOptions<C, E>): ConsumerShardwire<C, E>;
145
+ }
146
+
147
+ interface SafeParseResultSuccess<T> {
148
+ success: true;
149
+ data: T;
150
+ }
151
+ interface SafeParseResultFailure {
152
+ success: false;
153
+ error: {
154
+ message: string;
155
+ issues?: SchemaValidationIssue[];
156
+ };
157
+ }
158
+ type SafeParseResult<T> = SafeParseResultSuccess<T> | SafeParseResultFailure;
159
+ interface SafeParseSchema<T> {
160
+ safeParse: (value: unknown) => SafeParseResult<T>;
161
+ }
162
+ declare function fromSafeParseSchema<T>(schema: SafeParseSchema<T>): RuntimeSchema<T>;
163
+
164
+ declare function fromZodSchema<T>(schema: ZodType<T>): RuntimeSchema<T>;
97
165
 
98
166
  declare function createShardwire<C extends CommandMap = {}, E extends EventMap = {}>(options: HostOptions<C, E>): HostShardwire<C, E>;
99
167
  declare function createShardwire<C extends CommandMap = {}, E extends EventMap = {}>(options: ConsumerOptions<C, E>): ConsumerShardwire<C, E>;
100
168
 
101
- export { type CommandContext, type CommandFailure, type CommandMap, type CommandResult, type CommandSuccess, type ConsumerOptions, type ConsumerShardwire, type DiscordClientLike, type EventMap, type EventMeta, type HostOptions, type HostShardwire, type ShardwireLogger, type Unsubscribe, createShardwire };
169
+ export { type CommandContext, type CommandFailure, type CommandMap, type CommandRequestOf, type CommandResponseOf, type CommandResult, type CommandSchema, type CommandSuccess, type ConsumerOptions, type ConsumerShardwire, type CreateShardwire, type DiscordClientLike, type EventMap, type EventMeta, type HostOptions, type HostShardwire, type RuntimeSchema, type SchemaValidationIssue, type ShardwireLogger, type Unsubscribe, createShardwire, fromSafeParseSchema, fromZodSchema };