serverless-ircd 0.5.0 → 0.6.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/.github/workflows/ci.yml +34 -2
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/CHANGELOG.md +260 -0
- package/README.md +132 -22
- package/apps/aws-stack/README.md +33 -0
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +74 -9
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +2 -2
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.toml +5 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +9 -7
- package/apps/local-cli/tests/e2e.test.ts +112 -28
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +12 -7
- package/biome.json +1 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +15 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/handlers/default.ts +3 -0
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +9 -0
- package/packages/aws-adapter/src/serialize.ts +29 -1
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +79 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +9 -9
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +90 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +60 -8
- package/packages/cf-adapter/package.json +2 -1
- package/packages/cf-adapter/src/connection-do.ts +3 -0
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/tests/cf-runtime.test.ts +269 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +117 -0
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +104 -0
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/caps/capabilities.ts +37 -3
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +34 -0
- package/packages/irc-core/src/commands/isupport.ts +53 -0
- package/packages/irc-core/src/commands/mode.ts +8 -1
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +65 -10
- package/packages/irc-core/src/effects.ts +14 -0
- package/packages/irc-core/src/ports.ts +389 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +15 -0
- package/packages/irc-core/src/protocol/outbound.ts +16 -1
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +112 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/mode.test.ts +72 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +450 -3
- package/packages/irc-server/src/dispatch.ts +108 -16
- package/packages/irc-server/tests/actor.test.ts +1432 -39
- package/packages/irc-server/tests/dispatch.test.ts +263 -2
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/tcp-ws-forwarder/package.json +1 -1
|
@@ -17,26 +17,72 @@
|
|
|
17
17
|
* pipeline can be reconstructed end-to-end.
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import type {
|
|
21
|
-
|
|
20
|
+
import type {
|
|
21
|
+
BroadcastEffect,
|
|
22
|
+
Effect,
|
|
23
|
+
Logger,
|
|
24
|
+
MonitorStore,
|
|
25
|
+
RawLine,
|
|
26
|
+
} from '@serverless-ircd/irc-core';
|
|
27
|
+
import { LogLevel, filterClientTags } from '@serverless-ircd/irc-core';
|
|
22
28
|
import type { IrcRuntime } from './runtime.js';
|
|
23
29
|
|
|
24
|
-
/**
|
|
25
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Discriminated-union handler: each Effect tag maps to one runtime call.
|
|
32
|
+
* The handler also receives the bound {@link MonitorStore} (or
|
|
33
|
+
* `undefined`) so ReserveNick / ChangeNick / ReleaseNick can react to the
|
|
34
|
+
* runtime's success/failure and enqueue `730`/`731` pushes for subscribed
|
|
35
|
+
* watchers.
|
|
36
|
+
*/
|
|
37
|
+
type Handler<E extends Effect> = (
|
|
38
|
+
effect: E,
|
|
39
|
+
runtime: IrcRuntime,
|
|
40
|
+
monitor: MonitorStore | undefined,
|
|
41
|
+
) => Promise<void>;
|
|
26
42
|
|
|
27
43
|
const handlers: { [K in Effect['tag']]: Handler<Extract<Effect, { tag: K }>> } = {
|
|
28
44
|
Send: (e, r) => r.send(e.to, e.lines),
|
|
29
45
|
Broadcast: (e, r) => broadcast(e, r),
|
|
30
46
|
Disconnect: (e, r) => r.disconnect(e.conn, e.reason),
|
|
31
47
|
SendToNick: (e, r) => r.sendToNick(e.nick, e.sender, e.lines, e.notFoundLines),
|
|
32
|
-
ReserveNick: (e, r) =>
|
|
33
|
-
ChangeNick: (e, r) =>
|
|
34
|
-
ReleaseNick: (e, r) =>
|
|
48
|
+
ReserveNick: (e, r, m) => reserveNickEtc(e, r, m),
|
|
49
|
+
ChangeNick: (e, r, m) => changeNickEtc(e, r, m),
|
|
50
|
+
ReleaseNick: (e, r, m) => releaseNickEtc(e, r, m),
|
|
35
51
|
ApplyChannelDelta: (e, r) => r.applyChannelDelta(e.chan, e.delta),
|
|
36
52
|
BroadcastWallops: (e, r) => r.broadcastWallops(e.lines, e.except),
|
|
37
53
|
};
|
|
38
54
|
|
|
39
|
-
function
|
|
55
|
+
async function reserveNickEtc(
|
|
56
|
+
e: Extract<Effect, { tag: 'ReserveNick' }>,
|
|
57
|
+
r: IrcRuntime,
|
|
58
|
+
m: MonitorStore | undefined,
|
|
59
|
+
): Promise<void> {
|
|
60
|
+
const result = await r.reserveNick(e.nick, e.conn);
|
|
61
|
+
if (result.ok) {
|
|
62
|
+
m?.onNickOnline(e.nick);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function changeNickEtc(
|
|
67
|
+
e: Extract<Effect, { tag: 'ChangeNick' }>,
|
|
68
|
+
r: IrcRuntime,
|
|
69
|
+
m: MonitorStore | undefined,
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
const ok = await r.changeNick(e.conn, e.oldNick, e.newNick);
|
|
72
|
+
if (ok) {
|
|
73
|
+
m?.onNickOffline(e.oldNick);
|
|
74
|
+
m?.onNickOnline(e.newNick);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function releaseNickEtc(
|
|
79
|
+
e: Extract<Effect, { tag: 'ReleaseNick' }>,
|
|
80
|
+
r: IrcRuntime,
|
|
81
|
+
m: MonitorStore | undefined,
|
|
82
|
+
): Promise<void> {
|
|
83
|
+
await r.releaseNick(e.nick);
|
|
84
|
+
m?.onNickOffline(e.nick);
|
|
85
|
+
}
|
|
40
86
|
|
|
41
87
|
/**
|
|
42
88
|
* Optional knobs for {@link dispatch}. All observability-related; the
|
|
@@ -59,6 +105,16 @@ export interface DispatchOptions {
|
|
|
59
105
|
* Stamped on every record alongside `traceId`.
|
|
60
106
|
*/
|
|
61
107
|
readonly connectionId?: string;
|
|
108
|
+
/**
|
|
109
|
+
* Optional {@link MonitorStore} consulted by the ReserveNick /
|
|
110
|
+
* ChangeNick / ReleaseNick handlers. When supplied, successful nick
|
|
111
|
+
* registry transitions fan out an asynchronous `730`/`731` push to
|
|
112
|
+
* every subscribed watcher of the affected nick. The actor drains
|
|
113
|
+
* those pushes after `dispatch` returns and emits one `Send` effect
|
|
114
|
+
* per push. Omitted (→ no monitor fanout) preserves the existing
|
|
115
|
+
* dispatch behaviour for callers that have not opted into MONITOR.
|
|
116
|
+
*/
|
|
117
|
+
readonly monitor?: MonitorStore;
|
|
62
118
|
}
|
|
63
119
|
|
|
64
120
|
/**
|
|
@@ -107,17 +163,22 @@ export async function dispatch(
|
|
|
107
163
|
previous = effect;
|
|
108
164
|
continue;
|
|
109
165
|
}
|
|
110
|
-
await handle(effect, runtime, opts);
|
|
166
|
+
await handle(effect, runtime, opts, opts?.monitor);
|
|
111
167
|
previous = effect;
|
|
112
168
|
}
|
|
113
169
|
}
|
|
114
170
|
|
|
115
|
-
async function handle(
|
|
171
|
+
async function handle(
|
|
172
|
+
effect: Effect,
|
|
173
|
+
runtime: IrcRuntime,
|
|
174
|
+
opts: DispatchOptions | undefined,
|
|
175
|
+
monitor: MonitorStore | undefined,
|
|
176
|
+
): Promise<void> {
|
|
116
177
|
// The dispatch table is keyed by tag; the cast is the standard TS pattern
|
|
117
178
|
// for exhaustive-union dispatch.
|
|
118
179
|
const handler = handlers[effect.tag] as Handler<Effect>;
|
|
119
180
|
try {
|
|
120
|
-
await handler(effect, runtime);
|
|
181
|
+
await handler(effect, runtime, monitor);
|
|
121
182
|
} catch (err) {
|
|
122
183
|
if (opts?.logger !== undefined) {
|
|
123
184
|
opts.logger.on(logLevelFor(err), 'dispatch.error', {
|
|
@@ -175,11 +236,18 @@ function logLevelFor(_err: unknown): LogLevel {
|
|
|
175
236
|
* - `cap` without `capLines` (cap-only mode): `lines` go ONLY to
|
|
176
237
|
* cap-enabled members; the rest receive nothing.
|
|
177
238
|
*
|
|
239
|
+
* `caps` (plural) is the OR-semantics variant: `lines` go to members that
|
|
240
|
+
* announced ANY of the listed caps. Used by capabilities like
|
|
241
|
+
* `draft/typing` whose delivery rule unions `message-tags` (the baseline
|
|
242
|
+
* for every TAGMSG) with `draft/typing` (the client-tag exception for
|
|
243
|
+
* peers that did not negotiate `message-tags`). `caps` does not support
|
|
244
|
+
* a `capLines` split — both audiences receive the same `lines`.
|
|
245
|
+
*
|
|
178
246
|
* `except` is always honored. A member whose connection info is unavailable
|
|
179
247
|
* (the connection has gone) is treated as lacking the cap.
|
|
180
248
|
*/
|
|
181
249
|
async function broadcast(e: BroadcastEffect, r: IrcRuntime): Promise<void> {
|
|
182
|
-
if (e.cap === undefined) {
|
|
250
|
+
if (e.cap === undefined && e.caps === undefined) {
|
|
183
251
|
await r.broadcast(e.chan, e.lines, e.except);
|
|
184
252
|
return;
|
|
185
253
|
}
|
|
@@ -187,16 +255,40 @@ async function broadcast(e: BroadcastEffect, r: IrcRuntime): Promise<void> {
|
|
|
187
255
|
const snapshot = await r.getChannelSnapshot(e.chan);
|
|
188
256
|
if (snapshot === null) return;
|
|
189
257
|
|
|
258
|
+
// `caps` (OR semantics) takes precedence; otherwise the single `cap` is
|
|
259
|
+
// the only entry. The early-return above guarantees one of them is set.
|
|
260
|
+
const capsList: readonly string[] = e.caps ?? [e.cap as string];
|
|
190
261
|
for (const connId of snapshot.members.keys()) {
|
|
191
262
|
if (connId === e.except) continue;
|
|
192
263
|
|
|
193
264
|
const info = await r.getConnectionInfo(connId);
|
|
194
|
-
const
|
|
265
|
+
const recipientCaps = info?.caps ?? EMPTY_CAPS;
|
|
266
|
+
const hasAnyCap = capsList.some((c) => recipientCaps.has(c));
|
|
195
267
|
|
|
268
|
+
let toSend: RawLine[] | undefined;
|
|
196
269
|
if (e.capLines !== undefined) {
|
|
197
|
-
|
|
198
|
-
} else if (
|
|
199
|
-
|
|
270
|
+
toSend = hasAnyCap ? e.capLines : e.lines;
|
|
271
|
+
} else if (hasAnyCap) {
|
|
272
|
+
toSend = e.lines;
|
|
273
|
+
}
|
|
274
|
+
if (toSend !== undefined) {
|
|
275
|
+
await r.send(connId, filterLinesForRecipient(toSend, recipientCaps));
|
|
200
276
|
}
|
|
201
277
|
}
|
|
202
278
|
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Applies per-recipient IRCv3 message-tags gating to each outbound line.
|
|
282
|
+
* Client-only tags (`+`-prefixed keys) are stripped for recipients that did
|
|
283
|
+
* NOT negotiate `message-tags` (with the documented `draft/typing` /
|
|
284
|
+
* `draft/read-marker` whitelist exceptions). Recipients that DID negotiate
|
|
285
|
+
* `message-tags` see the line verbatim. Lines with no `@` tag section pass
|
|
286
|
+
* through unchanged.
|
|
287
|
+
*/
|
|
288
|
+
function filterLinesForRecipient(lines: readonly RawLine[], caps: ReadonlySet<string>): RawLine[] {
|
|
289
|
+
if (caps.has('message-tags')) return [...lines];
|
|
290
|
+
return lines.map((l) => filterClientTags(l, caps));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/** Reusable empty cap set for recipients whose connection info is unavailable. */
|
|
294
|
+
const EMPTY_CAPS: ReadonlySet<string> = new Set<string>();
|