serverless-ircd 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/.github/workflows/ci.yml +47 -0
- package/.github/workflows/deploy-cf.yml +97 -0
- package/LICENSE +29 -0
- package/README.md +323 -0
- package/apps/cf-worker/package.json +37 -0
- package/apps/cf-worker/scripts/smoke.mjs +175 -0
- package/apps/cf-worker/src/worker.ts +59 -0
- package/apps/cf-worker/tests/smoke.test.ts +150 -0
- package/apps/cf-worker/tsconfig.build.json +17 -0
- package/apps/cf-worker/tsconfig.test.json +18 -0
- package/apps/cf-worker/vitest.config.ts +31 -0
- package/apps/cf-worker/wrangler.test.toml +33 -0
- package/apps/cf-worker/wrangler.toml +98 -0
- package/apps/local-cli/package.json +35 -0
- package/apps/local-cli/src/line-scanner.ts +60 -0
- package/apps/local-cli/src/main.ts +145 -0
- package/apps/local-cli/src/motd-file.ts +45 -0
- package/apps/local-cli/src/server.ts +409 -0
- package/apps/local-cli/tests/e2e.test.ts +346 -0
- package/apps/local-cli/tests/id-factory.test.ts +25 -0
- package/apps/local-cli/tests/line-scanner.test.ts +81 -0
- package/apps/local-cli/tests/motd-file.test.ts +71 -0
- package/apps/local-cli/tests/tcp.test.ts +358 -0
- package/apps/local-cli/tsconfig.build.json +17 -0
- package/apps/local-cli/tsconfig.test.json +15 -0
- package/apps/local-cli/vitest.config.ts +24 -0
- package/biome.json +52 -0
- package/package.json +35 -0
- package/packages/cf-adapter/package.json +38 -0
- package/packages/cf-adapter/src/cf-runtime.ts +308 -0
- package/packages/cf-adapter/src/channel-do.ts +374 -0
- package/packages/cf-adapter/src/connection-do.ts +542 -0
- package/packages/cf-adapter/src/env.ts +67 -0
- package/packages/cf-adapter/src/index.ts +38 -0
- package/packages/cf-adapter/src/registry-do.ts +130 -0
- package/packages/cf-adapter/src/serialize.ts +142 -0
- package/packages/cf-adapter/src/sharding.ts +101 -0
- package/packages/cf-adapter/tests/cf-harness.ts +264 -0
- package/packages/cf-adapter/tests/cf-integration.test.ts +73 -0
- package/packages/cf-adapter/tests/cf-runtime.test.ts +527 -0
- package/packages/cf-adapter/tests/channel-do.test.ts +480 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +329 -0
- package/packages/cf-adapter/tests/integration/harness.test.ts +229 -0
- package/packages/cf-adapter/tests/integration/harness.ts +102 -0
- package/packages/cf-adapter/tests/integration/in-memory-harness.ts +242 -0
- package/packages/cf-adapter/tests/integration/index.ts +17 -0
- package/packages/cf-adapter/tests/integration/scenarios.test.ts +20 -0
- package/packages/cf-adapter/tests/integration/scenarios.ts +495 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +335 -0
- package/packages/cf-adapter/tests/sharding.test.ts +100 -0
- package/packages/cf-adapter/tests/worker/main.ts +33 -0
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +50 -0
- package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +57 -0
- package/packages/cf-adapter/tsconfig.build.json +16 -0
- package/packages/cf-adapter/tsconfig.test.json +18 -0
- package/packages/cf-adapter/vitest.config.ts +32 -0
- package/packages/cf-adapter/wrangler.test.toml +50 -0
- package/packages/in-memory-runtime/package.json +29 -0
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +245 -0
- package/packages/in-memory-runtime/src/index.ts +9 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +502 -0
- package/packages/in-memory-runtime/tsconfig.build.json +16 -0
- package/packages/in-memory-runtime/tsconfig.test.json +14 -0
- package/packages/in-memory-runtime/vitest.config.ts +21 -0
- package/packages/irc-core/package.json +29 -0
- package/packages/irc-core/src/caps/capabilities.ts +96 -0
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/commands/away.ts +82 -0
- package/packages/irc-core/src/commands/cap.ts +257 -0
- package/packages/irc-core/src/commands/chghost.ts +30 -0
- package/packages/irc-core/src/commands/index.ts +40 -0
- package/packages/irc-core/src/commands/invite.ts +156 -0
- package/packages/irc-core/src/commands/isupport.ts +133 -0
- package/packages/irc-core/src/commands/join.ts +309 -0
- package/packages/irc-core/src/commands/kick.ts +162 -0
- package/packages/irc-core/src/commands/list.ts +126 -0
- package/packages/irc-core/src/commands/mode.ts +655 -0
- package/packages/irc-core/src/commands/motd.ts +146 -0
- package/packages/irc-core/src/commands/names.ts +164 -0
- package/packages/irc-core/src/commands/part.ts +118 -0
- package/packages/irc-core/src/commands/ping.ts +47 -0
- package/packages/irc-core/src/commands/privmsg.ts +256 -0
- package/packages/irc-core/src/commands/quit.ts +70 -0
- package/packages/irc-core/src/commands/registration.ts +251 -0
- package/packages/irc-core/src/commands/topic.ts +171 -0
- package/packages/irc-core/src/commands/who.ts +169 -0
- package/packages/irc-core/src/commands/whois.ts +165 -0
- package/packages/irc-core/src/effects.ts +184 -0
- package/packages/irc-core/src/index.ts +20 -0
- package/packages/irc-core/src/ports.ts +153 -0
- package/packages/irc-core/src/protocol/batch.ts +85 -0
- package/packages/irc-core/src/protocol/index.ts +18 -0
- package/packages/irc-core/src/protocol/messages.ts +36 -0
- package/packages/irc-core/src/protocol/numerics.ts +155 -0
- package/packages/irc-core/src/protocol/outbound.ts +145 -0
- package/packages/irc-core/src/protocol/parser.ts +175 -0
- package/packages/irc-core/src/protocol/serializer.ts +71 -0
- package/packages/irc-core/src/state/channel.ts +293 -0
- package/packages/irc-core/src/state/connection.ts +153 -0
- package/packages/irc-core/src/state/index.ts +25 -0
- package/packages/irc-core/src/state/registry.ts +22 -0
- package/packages/irc-core/src/types.ts +83 -0
- package/packages/irc-core/tests/batch.test.ts +79 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +148 -0
- package/packages/irc-core/tests/commands/away.test.ts +233 -0
- package/packages/irc-core/tests/commands/batch.test.ts +178 -0
- package/packages/irc-core/tests/commands/cap.test.ts +499 -0
- package/packages/irc-core/tests/commands/chghost.test.ts +186 -0
- package/packages/irc-core/tests/commands/echo-message.test.ts +212 -0
- package/packages/irc-core/tests/commands/extended-join.test.ts +147 -0
- package/packages/irc-core/tests/commands/invite-notify.test.ts +160 -0
- package/packages/irc-core/tests/commands/invite.test.ts +321 -0
- package/packages/irc-core/tests/commands/isupport.test.ts +209 -0
- package/packages/irc-core/tests/commands/join.test.ts +687 -0
- package/packages/irc-core/tests/commands/kick.test.ts +316 -0
- package/packages/irc-core/tests/commands/list.test.ts +452 -0
- package/packages/irc-core/tests/commands/mode.test.ts +1048 -0
- package/packages/irc-core/tests/commands/motd.test.ts +322 -0
- package/packages/irc-core/tests/commands/names.test.ts +342 -0
- package/packages/irc-core/tests/commands/part.test.ts +265 -0
- package/packages/irc-core/tests/commands/ping.test.ts +144 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +665 -0
- package/packages/irc-core/tests/commands/quit.test.ts +220 -0
- package/packages/irc-core/tests/commands/registration.test.ts +599 -0
- package/packages/irc-core/tests/commands/topic.test.ts +337 -0
- package/packages/irc-core/tests/commands/who.test.ts +441 -0
- package/packages/irc-core/tests/commands/whois.test.ts +422 -0
- package/packages/irc-core/tests/effects.test.ts +147 -0
- package/packages/irc-core/tests/message-tags.test.ts +182 -0
- package/packages/irc-core/tests/numerics.test.ts +98 -0
- package/packages/irc-core/tests/outbound.test.ts +232 -0
- package/packages/irc-core/tests/parser.test.ts +162 -0
- package/packages/irc-core/tests/ports.test.ts +101 -0
- package/packages/irc-core/tests/serializer.test.ts +164 -0
- package/packages/irc-core/tests/state/channel.test.ts +351 -0
- package/packages/irc-core/tests/state/connection.test.ts +122 -0
- package/packages/irc-core/tests/state/registry.test.ts +20 -0
- package/packages/irc-core/tests/types.test.ts +127 -0
- package/packages/irc-core/tsconfig.build.json +12 -0
- package/packages/irc-core/tsconfig.test.json +10 -0
- package/packages/irc-core/vitest.config.ts +21 -0
- package/packages/irc-server/package.json +28 -0
- package/packages/irc-server/src/actor.ts +449 -0
- package/packages/irc-server/src/dispatch.ts +120 -0
- package/packages/irc-server/src/index.ts +11 -0
- package/packages/irc-server/src/runtime.ts +61 -0
- package/packages/irc-server/tests/actor.test.ts +816 -0
- package/packages/irc-server/tests/dispatch.test.ts +512 -0
- package/packages/irc-server/tests/runtime.test.ts +52 -0
- package/packages/irc-server/tsconfig.build.json +13 -0
- package/packages/irc-server/tsconfig.test.json +11 -0
- package/packages/irc-server/vitest.config.ts +21 -0
- package/pnpm-workspace.yaml +4 -0
- package/tools/tcp-ws-forwarder/package.json +32 -0
- package/tools/tcp-ws-forwarder/src/forwarder.ts +244 -0
- package/tools/tcp-ws-forwarder/src/line-scanner.ts +55 -0
- package/tools/tcp-ws-forwarder/src/main.ts +114 -0
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +618 -0
- package/tools/tcp-ws-forwarder/tests/framing.test.ts +51 -0
- package/tools/tcp-ws-forwarder/tests/line-scanner.test.ts +75 -0
- package/tools/tcp-ws-forwarder/tsconfig.build.json +12 -0
- package/tools/tcp-ws-forwarder/tsconfig.test.json +10 -0
- package/tools/tcp-ws-forwarder/vitest.config.ts +27 -0
- package/tsconfig.base.json +25 -0
- package/turbo.json +29 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
#:schema node_modules/wrangler/config-schema.json
|
|
2
|
+
name = "cf-adapter-test"
|
|
3
|
+
main = "tests/worker/main.ts"
|
|
4
|
+
compatibility_date = "2024-11-01"
|
|
5
|
+
compatibility_flags = ["nodejs_compat"]
|
|
6
|
+
|
|
7
|
+
# Server-level vars. MOTD_LINES is newline-delimited; content mirrors the
|
|
8
|
+
# integration-test harness so scenario 18 (MOTD) asserts against the same
|
|
9
|
+
# body text in every runtime factory.
|
|
10
|
+
[vars]
|
|
11
|
+
MOTD_LINES = "Welcome to the ServerlessIRCd integration-test harness.\nAll scenarios run parametrized over the IrcRuntime factory matrix."
|
|
12
|
+
|
|
13
|
+
# Production bindings: REGISTRY_DO and CHANNEL_DO point at the real DO
|
|
14
|
+
# classes so CfRuntime, ConnectionDO, and the integration suite all
|
|
15
|
+
# exercise the actual sharded-registry + per-channel fanout path.
|
|
16
|
+
[[durable_objects.bindings]]
|
|
17
|
+
name = "CONNECTION_DO"
|
|
18
|
+
class_name = "ConnectionDO"
|
|
19
|
+
|
|
20
|
+
[[durable_objects.bindings]]
|
|
21
|
+
name = "REGISTRY_DO"
|
|
22
|
+
class_name = "RegistryDO"
|
|
23
|
+
|
|
24
|
+
[[durable_objects.bindings]]
|
|
25
|
+
name = "CHANNEL_DO"
|
|
26
|
+
class_name = "ChannelDO"
|
|
27
|
+
|
|
28
|
+
# Recording stubs (kept under separate names for any test that wants to
|
|
29
|
+
# assert "this exact RPC was emitted" rather than the end-to-end effect).
|
|
30
|
+
[[durable_objects.bindings]]
|
|
31
|
+
name = "REGISTRY_DO_STUB"
|
|
32
|
+
class_name = "RecordingRegistryDO"
|
|
33
|
+
|
|
34
|
+
[[durable_objects.bindings]]
|
|
35
|
+
name = "CHANNEL_DO_STUB"
|
|
36
|
+
class_name = "RecordingChannelDO"
|
|
37
|
+
|
|
38
|
+
# Real DO aliases — exercised by the per-DO unit suites (registry-do,
|
|
39
|
+
# channel-do) which pre-date the binding switch.
|
|
40
|
+
[[durable_objects.bindings]]
|
|
41
|
+
name = "REGISTRY_DO_REAL"
|
|
42
|
+
class_name = "RegistryDO"
|
|
43
|
+
|
|
44
|
+
[[durable_objects.bindings]]
|
|
45
|
+
name = "CHANNEL_DO_REAL"
|
|
46
|
+
class_name = "ChannelDO"
|
|
47
|
+
|
|
48
|
+
[[migrations]]
|
|
49
|
+
tag = "v1"
|
|
50
|
+
new_classes = ["ConnectionDO", "RegistryDO", "ChannelDO", "RecordingRegistryDO", "RecordingChannelDO"]
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@serverless-ircd/in-memory-runtime",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Single-process reference implementation of the IrcRuntime port, backed by Maps",
|
|
6
|
+
"license": "BSD-3-Clause",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc -p tsconfig.build.json",
|
|
18
|
+
"typecheck": "tsc -p tsconfig.test.json --noEmit",
|
|
19
|
+
"test": "vitest run",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"coverage": "vitest run --coverage",
|
|
22
|
+
"clean": "rimraf dist coverage .tsbuildinfo .turbo"
|
|
23
|
+
},
|
|
24
|
+
"files": ["dist", "src", "README.md"],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@serverless-ircd/irc-core": "workspace:*",
|
|
27
|
+
"@serverless-ircd/irc-server": "workspace:*"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-process reference {@link IrcRuntime}.
|
|
3
|
+
*
|
|
4
|
+
* Backed by plain `Map`s. Owns the authoritative channel and nick-registry
|
|
5
|
+
* state; shares live {@link ConnectionState} references with the actor layer
|
|
6
|
+
* (reducers mutate those in place). Used by integration tests, the local
|
|
7
|
+
* CLI, and as the contract spec for the CF and AWS runtimes.
|
|
8
|
+
*
|
|
9
|
+
* PLAN §2.2 — every method here corresponds one-to-one with an {@link Effect}
|
|
10
|
+
* tag interpreted by `dispatch`. Extra methods on this class
|
|
11
|
+
* ({@link registerConnection}, {@link getOrCreateChannel}, …) are lifecycle
|
|
12
|
+
* helpers for the actor and are NOT part of the port.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
type ChanName,
|
|
17
|
+
type ChanSnapshot,
|
|
18
|
+
type ChannelDelta,
|
|
19
|
+
type ChannelState,
|
|
20
|
+
type Clock,
|
|
21
|
+
type ConnId,
|
|
22
|
+
type ConnSnapshot,
|
|
23
|
+
type ConnectionState,
|
|
24
|
+
type Nick,
|
|
25
|
+
type RawLine,
|
|
26
|
+
applyChannelDelta as applyDelta,
|
|
27
|
+
createChannel,
|
|
28
|
+
toChannelSnapshot,
|
|
29
|
+
toSnapshot as toConnSnapshot,
|
|
30
|
+
} from '@serverless-ircd/irc-core';
|
|
31
|
+
import type { IrcRuntime } from '@serverless-ircd/irc-server';
|
|
32
|
+
|
|
33
|
+
/** Per-connection transport callbacks the actor wires up. */
|
|
34
|
+
export interface ConnectionHandlers {
|
|
35
|
+
/** Called by `send` / `broadcast` / `sendToNick` to deliver outbound lines. */
|
|
36
|
+
send: (lines: RawLine[]) => void;
|
|
37
|
+
/** Called by `disconnect` to close the underlying transport. */
|
|
38
|
+
disconnect: (reason?: string) => void;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface TrackedConnection {
|
|
42
|
+
state: ConnectionState;
|
|
43
|
+
handlers: ConnectionHandlers;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function lower(s: string): string {
|
|
47
|
+
return s.toLowerCase();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export class InMemoryRuntime implements IrcRuntime {
|
|
51
|
+
private readonly clock: Clock;
|
|
52
|
+
private readonly connections = new Map<ConnId, TrackedConnection>();
|
|
53
|
+
private readonly channels = new Map<string, ChannelState>();
|
|
54
|
+
private readonly nicks = new Map<string, ConnId>();
|
|
55
|
+
|
|
56
|
+
constructor(opts: { clock: Clock }) {
|
|
57
|
+
this.clock = opts.clock;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ------------------------------------------------------------------
|
|
61
|
+
// Lifecycle (NOT part of the IrcRuntime port — actor/CLI only)
|
|
62
|
+
// ------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Tracks a connection and its transport handlers. The runtime keeps a
|
|
66
|
+
* reference to `state`; the actor may continue mutating it in place and
|
|
67
|
+
* the runtime will observe the changes via {@link getConnectionInfo}.
|
|
68
|
+
*/
|
|
69
|
+
registerConnection(state: ConnectionState, handlers: ConnectionHandlers): void {
|
|
70
|
+
this.connections.set(state.id, { state, handlers });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Stops tracking a connection. Idempotent. Does NOT release its nick. */
|
|
74
|
+
unregisterConnection(conn: ConnId): void {
|
|
75
|
+
this.connections.delete(conn);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** True iff {@link registerConnection} has been called and not undone. */
|
|
79
|
+
hasConnection(conn: ConnId): boolean {
|
|
80
|
+
return this.connections.has(conn);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ------------------------------------------------------------------
|
|
84
|
+
// IrcRuntime — lookups
|
|
85
|
+
// ------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Returns the authoritative {@link ChannelState} for `name`, creating an
|
|
89
|
+
* empty channel on first access. Subsequent calls (case-insensitively)
|
|
90
|
+
* return the same object.
|
|
91
|
+
*/
|
|
92
|
+
getOrCreateChannel(name: ChanName): ChannelState {
|
|
93
|
+
const key = lower(name);
|
|
94
|
+
const existing = this.channels.get(key);
|
|
95
|
+
if (existing !== undefined) return existing;
|
|
96
|
+
const created = createChannel({
|
|
97
|
+
name,
|
|
98
|
+
nameLower: key,
|
|
99
|
+
createdAt: this.clock.now(),
|
|
100
|
+
});
|
|
101
|
+
this.channels.set(key, created);
|
|
102
|
+
return created;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ------------------------------------------------------------------
|
|
106
|
+
// IrcRuntime — transport
|
|
107
|
+
// ------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
async send(to: ConnId, lines: RawLine[]): Promise<void> {
|
|
110
|
+
const conn = this.connections.get(to);
|
|
111
|
+
if (conn === undefined) return;
|
|
112
|
+
conn.handlers.send(lines);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async broadcast(chan: ChanName, lines: RawLine[], except?: ConnId): Promise<void> {
|
|
116
|
+
const channel = this.channels.get(lower(chan));
|
|
117
|
+
if (channel === undefined) return;
|
|
118
|
+
for (const member of channel.members.keys()) {
|
|
119
|
+
if (member === except) continue;
|
|
120
|
+
const conn = this.connections.get(member);
|
|
121
|
+
if (conn === undefined) continue;
|
|
122
|
+
conn.handlers.send(lines);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async disconnect(conn: ConnId, reason?: string): Promise<void> {
|
|
127
|
+
const c = this.connections.get(conn);
|
|
128
|
+
if (c === undefined) return;
|
|
129
|
+
c.handlers.disconnect(reason);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async sendToNick(
|
|
133
|
+
nick: Nick,
|
|
134
|
+
sender: ConnId,
|
|
135
|
+
lines: RawLine[],
|
|
136
|
+
notFoundLines?: RawLine[],
|
|
137
|
+
): Promise<void> {
|
|
138
|
+
const owner = this.nicks.get(lower(nick));
|
|
139
|
+
if (owner !== undefined) {
|
|
140
|
+
const conn = this.connections.get(owner);
|
|
141
|
+
if (conn !== undefined) {
|
|
142
|
+
conn.handlers.send(lines);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (notFoundLines !== undefined) {
|
|
147
|
+
const senderConn = this.connections.get(sender);
|
|
148
|
+
if (senderConn !== undefined) {
|
|
149
|
+
senderConn.handlers.send(notFoundLines);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ------------------------------------------------------------------
|
|
155
|
+
// IrcRuntime — nick registry
|
|
156
|
+
// ------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
async reserveNick(nick: Nick, conn: ConnId): Promise<{ ok: true } | { ok: false }> {
|
|
159
|
+
const key = lower(nick);
|
|
160
|
+
const existing = this.nicks.get(key);
|
|
161
|
+
if (existing !== undefined && existing !== conn) {
|
|
162
|
+
return { ok: false };
|
|
163
|
+
}
|
|
164
|
+
this.nicks.set(key, conn);
|
|
165
|
+
return { ok: true };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async changeNick(conn: ConnId, oldNick: Nick, newNick: Nick): Promise<boolean> {
|
|
169
|
+
const newKey = lower(newNick);
|
|
170
|
+
const oldKey = lower(oldNick);
|
|
171
|
+
const owner = this.nicks.get(newKey);
|
|
172
|
+
if (owner !== undefined && owner !== conn) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
this.nicks.set(newKey, conn);
|
|
176
|
+
// Only clear the old mapping if it still points at us; a stale release
|
|
177
|
+
// would clobber a third party that grabbed it via re-reserve.
|
|
178
|
+
if (this.nicks.get(oldKey) === conn) {
|
|
179
|
+
this.nicks.delete(oldKey);
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async releaseNick(nick: Nick): Promise<void> {
|
|
185
|
+
this.nicks.delete(lower(nick));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ------------------------------------------------------------------
|
|
189
|
+
// IrcRuntime — channel ownership
|
|
190
|
+
// ------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
async applyChannelDelta(chan: ChanName, delta: ChannelDelta): Promise<void> {
|
|
193
|
+
const channel = this.getOrCreateChannel(chan);
|
|
194
|
+
// The irc-core `applyChannelDelta` returns a *new* ChannelState; we
|
|
195
|
+
// replace the stored object so subsequent snapshots see the new data
|
|
196
|
+
// while any outstanding references to the prior state stay coherent.
|
|
197
|
+
const next = applyDelta(channel, delta);
|
|
198
|
+
this.channels.set(lower(chan), next);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ------------------------------------------------------------------
|
|
202
|
+
// IrcRuntime — lookups
|
|
203
|
+
// ------------------------------------------------------------------
|
|
204
|
+
|
|
205
|
+
async lookupNick(nick: Nick): Promise<ConnId | null> {
|
|
206
|
+
return this.nicks.get(lower(nick)) ?? null;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async getConnectionInfo(conn: ConnId): Promise<ConnSnapshot | null> {
|
|
210
|
+
const tracked = this.connections.get(conn);
|
|
211
|
+
if (tracked === undefined) return null;
|
|
212
|
+
return toConnSnapshot(tracked.state);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Returns the live {@link ConnectionState} for `conn`, or `null` if the
|
|
217
|
+
* connection is unknown. Adapters that materialize state from storage
|
|
218
|
+
* return a fresh object; the in-memory runtime hands out its authority-
|
|
219
|
+
* owned reference (reducers and the actor mutate it in place).
|
|
220
|
+
*/
|
|
221
|
+
async getConnection(conn: ConnId): Promise<ConnectionState | null> {
|
|
222
|
+
return this.connections.get(conn)?.state ?? null;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async getChannelSnapshot(chan: ChanName): Promise<ChanSnapshot | null> {
|
|
226
|
+
const channel = this.channels.get(lower(chan));
|
|
227
|
+
if (channel === undefined) return null;
|
|
228
|
+
return toChannelSnapshot(channel);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getChannelConnections(chan: ChanName): Promise<ReadonlyMap<ConnId, ConnectionState>> {
|
|
232
|
+
const channel = this.channels.get(lower(chan));
|
|
233
|
+
const out = new Map<ConnId, ConnectionState>();
|
|
234
|
+
if (channel === undefined) return out;
|
|
235
|
+
for (const connId of channel.members.keys()) {
|
|
236
|
+
const tracked = this.connections.get(connId);
|
|
237
|
+
if (tracked !== undefined) out.set(connId, tracked.state);
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
async listChannels(): Promise<ChanSnapshot[]> {
|
|
243
|
+
return Array.from(this.channels.values(), (c) => toChannelSnapshot(c));
|
|
244
|
+
}
|
|
245
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @serverless-ircd/in-memory-runtime
|
|
3
|
+
*
|
|
4
|
+
* Single-process reference {@link IrcRuntime} backed by `Map`s. Used by
|
|
5
|
+
* integration tests, the local CLI, and as the contract spec for the CF
|
|
6
|
+
* and AWS runtimes.
|
|
7
|
+
*/
|
|
8
|
+
export { InMemoryRuntime } from './in-memory-runtime.js';
|
|
9
|
+
export type { ConnectionHandlers } from './in-memory-runtime.js';
|