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 +251 -55
- package/dist/index.d.mts +75 -7
- package/dist/index.d.ts +75 -7
- package/dist/index.js +267 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +264 -23
- package/dist/index.mjs.map +1 -1
- package/package.json +27 -5
package/README.md
CHANGED
|
@@ -1,20 +1,87 @@
|
|
|
1
1
|
# shardwire
|
|
2
2
|
|
|
3
|
-
Lightweight TypeScript library
|
|
3
|
+
> Lightweight TypeScript library for building a Discord-hosted WebSocket command and event bridge.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/shardwire)
|
|
6
|
+
[](https://www.npmjs.com/package/shardwire)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
[](https://www.typescriptlang.org/)
|
|
9
|
+
[](./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
|
-
|
|
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": {
|
|
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
|
-
|
|
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
|
|
109
|
+
## Consumer Setup
|
|
41
110
|
|
|
42
111
|
```ts
|
|
43
112
|
import { createShardwire } from "shardwire";
|
|
44
113
|
|
|
45
114
|
type Commands = {
|
|
46
|
-
"ban-user": {
|
|
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
|
-
|
|
61
|
-
console.log(
|
|
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
|
-
##
|
|
188
|
+
## Schema Validation (Zod)
|
|
66
189
|
|
|
67
|
-
|
|
190
|
+
Use runtime schemas to validate command request/response payloads and emitted event payloads.
|
|
68
191
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
- `pnpm example:consumer`
|
|
192
|
+
```ts
|
|
193
|
+
import { z } from "zod";
|
|
194
|
+
import { createShardwire, fromZodSchema } from "shardwire";
|
|
73
195
|
|
|
74
|
-
|
|
196
|
+
type Commands = {
|
|
197
|
+
"ban-user": {
|
|
198
|
+
request: { userId: string };
|
|
199
|
+
response: { banned: true; userId: string };
|
|
200
|
+
};
|
|
201
|
+
};
|
|
75
202
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
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()`
|
|
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
|
|
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)`
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
286
|
+
- `UNAUTHORIZED`
|
|
287
|
+
- `TIMEOUT`
|
|
288
|
+
- `DISCONNECTED`
|
|
289
|
+
- `COMMAND_NOT_FOUND`
|
|
290
|
+
- `VALIDATION_ERROR`
|
|
291
|
+
- `INTERNAL_ERROR`
|
|
105
292
|
|
|
106
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
312
|
+
Common symptoms:
|
|
116
313
|
|
|
117
|
-
-
|
|
118
|
-
-
|
|
119
|
-
-
|
|
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
|
-
##
|
|
318
|
+
## Compatibility
|
|
123
319
|
|
|
124
|
-
-
|
|
125
|
-
-
|
|
126
|
-
-
|
|
127
|
-
-
|
|
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
|
-
##
|
|
325
|
+
## Security Notes
|
|
130
326
|
|
|
131
|
-
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
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
|
|
332
|
+
## Roadmap Constraints (v1)
|
|
137
333
|
|
|
138
|
-
- Single package, no external
|
|
139
|
-
-
|
|
140
|
-
-
|
|
141
|
-
-
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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
|
-
|
|
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: "
|
|
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
|
-
|
|
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]
|
|
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]
|
|
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 };
|