@minhworker/agent-room 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.
@@ -0,0 +1,328 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { dirname } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import Database from 'better-sqlite3';
5
+ import { extractMentions, isValidParticipantName, isValidRoomName, } from './domain.js';
6
+ import { AppError } from './errors.js';
7
+ function now() {
8
+ return new Date().toISOString();
9
+ }
10
+ function roomFromRow(row) {
11
+ return { id: row.id, name: row.name, createdAt: row.created_at };
12
+ }
13
+ function participantFromRow(row) {
14
+ return {
15
+ id: row.id,
16
+ roomId: row.room_id,
17
+ name: row.name,
18
+ active: row.active === 1,
19
+ joinedAt: row.joined_at,
20
+ leftAt: row.left_at,
21
+ };
22
+ }
23
+ function messageFromRow(row) {
24
+ return {
25
+ id: row.id,
26
+ roomId: row.room_id,
27
+ sender: row.sender,
28
+ body: row.body,
29
+ replyTo: row.reply_to,
30
+ mentions: JSON.parse(row.mentions_json),
31
+ cursor: row.event_id,
32
+ createdAt: row.created_at,
33
+ };
34
+ }
35
+ export class AgentRoomDatabase {
36
+ #db;
37
+ constructor(path) {
38
+ if (path !== ':memory:') {
39
+ mkdirSync(dirname(path), { recursive: true });
40
+ }
41
+ this.#db = new Database(path);
42
+ this.#db.pragma('journal_mode = WAL');
43
+ this.#db.pragma('foreign_keys = ON');
44
+ this.#migrate();
45
+ }
46
+ close() {
47
+ this.#db.close();
48
+ }
49
+ createRoom(name) {
50
+ if (!isValidRoomName(name)) {
51
+ throw new AppError('Room name must be 1-64 characters using letters, numbers, dot, underscore, or hyphen.', 400, 'invalid_room_name');
52
+ }
53
+ if (this.#findRoom(name) !== undefined) {
54
+ throw new AppError(`Room "${name}" already exists.`, 409, 'room_exists');
55
+ }
56
+ const result = this.#db.transaction(() => {
57
+ const room = { id: randomUUID(), name, createdAt: now() };
58
+ this.#db
59
+ .prepare('INSERT INTO rooms (id, name, created_at) VALUES (?, ?, ?)')
60
+ .run(room.id, room.name, room.createdAt);
61
+ const cursor = this.#insertEvent(room.id, 'room.created', { room });
62
+ return { room, cursor };
63
+ })();
64
+ return { value: result.room, event: this.getEvent(result.cursor) };
65
+ }
66
+ listRooms() {
67
+ const rows = this.#db
68
+ .prepare(`SELECT r.id, r.name, r.created_at,
69
+ (SELECT COUNT(*) FROM participants p WHERE p.room_id = r.id AND p.active = 1) AS participant_count,
70
+ (SELECT COUNT(*) FROM messages m WHERE m.room_id = r.id) AS message_count
71
+ FROM rooms r
72
+ ORDER BY r.name COLLATE NOCASE`)
73
+ .all();
74
+ return rows.map((row) => ({
75
+ ...roomFromRow(row),
76
+ participantCount: row.participant_count,
77
+ messageCount: row.message_count,
78
+ }));
79
+ }
80
+ join(roomName, name) {
81
+ this.#assertParticipantName(name);
82
+ const room = this.#requireRoom(roomName);
83
+ const existing = this.#findParticipant(room.id, name);
84
+ if (existing?.active === true) {
85
+ return { value: { participant: existing, changed: false }, event: null };
86
+ }
87
+ const result = this.#db.transaction(() => {
88
+ const timestamp = now();
89
+ let participant;
90
+ if (existing === undefined) {
91
+ participant = {
92
+ id: randomUUID(),
93
+ roomId: room.id,
94
+ name,
95
+ active: true,
96
+ joinedAt: timestamp,
97
+ leftAt: null,
98
+ };
99
+ this.#db
100
+ .prepare(`INSERT INTO participants (id, room_id, name, active, joined_at, left_at)
101
+ VALUES (?, ?, ?, 1, ?, NULL)`)
102
+ .run(participant.id, room.id, name, timestamp);
103
+ }
104
+ else {
105
+ this.#db
106
+ .prepare('UPDATE participants SET active = 1, joined_at = ?, left_at = NULL WHERE id = ?')
107
+ .run(timestamp, existing.id);
108
+ participant = { ...existing, active: true, joinedAt: timestamp, leftAt: null };
109
+ }
110
+ const cursor = this.#insertEvent(room.id, 'participant.joined', { participant });
111
+ return { participant, cursor };
112
+ })();
113
+ return {
114
+ value: { participant: result.participant, changed: true },
115
+ event: this.getEvent(result.cursor),
116
+ };
117
+ }
118
+ leave(roomName, name) {
119
+ const room = this.#requireRoom(roomName);
120
+ const participant = this.#requireActiveParticipant(room.id, name);
121
+ const result = this.#db.transaction(() => {
122
+ const leftAt = now();
123
+ this.#db
124
+ .prepare('UPDATE participants SET active = 0, left_at = ? WHERE id = ?')
125
+ .run(leftAt, participant.id);
126
+ const updated = { ...participant, active: false, leftAt };
127
+ const cursor = this.#insertEvent(room.id, 'participant.left', { participant: updated });
128
+ return { participant: updated, cursor };
129
+ })();
130
+ return { value: result.participant, event: this.getEvent(result.cursor) };
131
+ }
132
+ send(roomName, senderName, body, replyTo = null) {
133
+ const room = this.#requireRoom(roomName);
134
+ const sender = this.#requireActiveParticipant(room.id, senderName);
135
+ const text = body.trim();
136
+ if (text.length === 0 || text.length > 20_000) {
137
+ throw new AppError('Message must contain 1-20,000 characters.', 400, 'invalid_message');
138
+ }
139
+ if (replyTo !== null) {
140
+ const parent = this.#findMessage(replyTo);
141
+ if (parent === undefined || parent.roomId !== room.id) {
142
+ throw new AppError(`Reply target "${replyTo}" was not found in this room.`, 404, 'reply_not_found');
143
+ }
144
+ }
145
+ const result = this.#db.transaction(() => {
146
+ const id = randomUUID();
147
+ const createdAt = now();
148
+ const mentions = extractMentions(text);
149
+ this.#db
150
+ .prepare(`INSERT INTO messages
151
+ (id, room_id, participant_id, sender, body, reply_to, mentions_json, event_id, created_at)
152
+ VALUES (?, ?, ?, ?, ?, ?, ?, NULL, ?)`)
153
+ .run(id, room.id, sender.id, sender.name, text, replyTo, JSON.stringify(mentions), createdAt);
154
+ const pendingMessage = {
155
+ id,
156
+ roomId: room.id,
157
+ sender: sender.name,
158
+ body: text,
159
+ replyTo,
160
+ mentions,
161
+ cursor: 0,
162
+ createdAt,
163
+ };
164
+ const cursor = this.#insertEvent(room.id, 'message.created', {
165
+ message: pendingMessage,
166
+ });
167
+ this.#db.prepare('UPDATE messages SET event_id = ? WHERE id = ?').run(cursor, id);
168
+ const message = { ...pendingMessage, cursor };
169
+ this.#db
170
+ .prepare('UPDATE events SET payload_json = ? WHERE id = ?')
171
+ .run(JSON.stringify({ message }), cursor);
172
+ return { message, cursor };
173
+ })();
174
+ return { value: result.message, event: this.getEvent(result.cursor) };
175
+ }
176
+ history(roomName, limit, after) {
177
+ const room = this.#requireRoom(roomName);
178
+ const safeLimit = Math.max(1, Math.min(limit, 500));
179
+ let rows;
180
+ if (after === undefined) {
181
+ rows = this.#db
182
+ .prepare(`SELECT * FROM (
183
+ SELECT id, room_id, sender, body, reply_to, mentions_json, event_id, created_at
184
+ FROM messages WHERE room_id = ? ORDER BY event_id DESC LIMIT ?
185
+ ) ORDER BY event_id ASC`)
186
+ .all(room.id, safeLimit);
187
+ }
188
+ else {
189
+ rows = this.#db
190
+ .prepare(`SELECT id, room_id, sender, body, reply_to, mentions_json, event_id, created_at
191
+ FROM messages WHERE room_id = ? AND event_id > ? ORDER BY event_id ASC LIMIT ?`)
192
+ .all(room.id, after, safeLimit);
193
+ }
194
+ return rows.map(messageFromRow);
195
+ }
196
+ latestCursor(roomName) {
197
+ const room = this.#requireRoom(roomName);
198
+ const row = this.#db
199
+ .prepare('SELECT COALESCE(MAX(id), 0) AS cursor FROM events WHERE room_id = ?')
200
+ .get(room.id);
201
+ return row.cursor;
202
+ }
203
+ assertCanWatch(roomName, participantName) {
204
+ const room = this.#requireRoom(roomName);
205
+ this.#requireActiveParticipant(room.id, participantName);
206
+ return room;
207
+ }
208
+ listEvents(roomId, after) {
209
+ const rows = this.#db
210
+ .prepare(`SELECT id, room_id, type, payload_json, created_at
211
+ FROM events WHERE room_id = ? AND id > ? ORDER BY id ASC`)
212
+ .all(roomId, after);
213
+ return rows.map((row) => this.#eventFromRow(row));
214
+ }
215
+ getEvent(cursor) {
216
+ const row = this.#db
217
+ .prepare('SELECT id, room_id, type, payload_json, created_at FROM events WHERE id = ?')
218
+ .get(cursor);
219
+ if (row === undefined) {
220
+ throw new AppError(`Event cursor ${cursor} was not found.`, 500, 'event_not_found');
221
+ }
222
+ return this.#eventFromRow(row);
223
+ }
224
+ #migrate() {
225
+ this.#db.exec(`
226
+ CREATE TABLE IF NOT EXISTS rooms (
227
+ id TEXT PRIMARY KEY,
228
+ name TEXT NOT NULL COLLATE NOCASE UNIQUE,
229
+ created_at TEXT NOT NULL
230
+ );
231
+
232
+ CREATE TABLE IF NOT EXISTS participants (
233
+ id TEXT PRIMARY KEY,
234
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
235
+ name TEXT NOT NULL COLLATE NOCASE,
236
+ active INTEGER NOT NULL CHECK (active IN (0, 1)),
237
+ joined_at TEXT NOT NULL,
238
+ left_at TEXT,
239
+ UNIQUE (room_id, name)
240
+ );
241
+
242
+ CREATE TABLE IF NOT EXISTS events (
243
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
244
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
245
+ type TEXT NOT NULL,
246
+ payload_json TEXT NOT NULL,
247
+ created_at TEXT NOT NULL
248
+ );
249
+
250
+ CREATE TABLE IF NOT EXISTS messages (
251
+ id TEXT PRIMARY KEY,
252
+ room_id TEXT NOT NULL REFERENCES rooms(id) ON DELETE CASCADE,
253
+ participant_id TEXT NOT NULL REFERENCES participants(id),
254
+ sender TEXT NOT NULL,
255
+ body TEXT NOT NULL,
256
+ reply_to TEXT REFERENCES messages(id),
257
+ mentions_json TEXT NOT NULL,
258
+ event_id INTEGER UNIQUE REFERENCES events(id),
259
+ created_at TEXT NOT NULL
260
+ );
261
+
262
+ CREATE INDEX IF NOT EXISTS events_room_cursor_idx ON events(room_id, id);
263
+ CREATE INDEX IF NOT EXISTS messages_room_event_idx ON messages(room_id, event_id);
264
+ `);
265
+ }
266
+ #insertEvent(roomId, type, payload) {
267
+ const result = this.#db
268
+ .prepare('INSERT INTO events (room_id, type, payload_json, created_at) VALUES (?, ?, ?, ?)')
269
+ .run(roomId, type, JSON.stringify(payload), now());
270
+ return Number(result.lastInsertRowid);
271
+ }
272
+ #eventFromRow(row) {
273
+ const base = { cursor: row.id, roomId: row.room_id, createdAt: row.created_at };
274
+ switch (row.type) {
275
+ case 'room.created': {
276
+ const payload = JSON.parse(row.payload_json);
277
+ return { ...base, type: row.type, payload };
278
+ }
279
+ case 'participant.joined':
280
+ case 'participant.left': {
281
+ const payload = JSON.parse(row.payload_json);
282
+ return { ...base, type: row.type, payload };
283
+ }
284
+ case 'message.created': {
285
+ const payload = JSON.parse(row.payload_json);
286
+ return { ...base, type: row.type, payload };
287
+ }
288
+ }
289
+ }
290
+ #findRoom(name) {
291
+ const row = this.#db.prepare('SELECT * FROM rooms WHERE name = ?').get(name);
292
+ return row === undefined ? undefined : roomFromRow(row);
293
+ }
294
+ #requireRoom(name) {
295
+ const room = this.#findRoom(name);
296
+ if (room === undefined) {
297
+ throw new AppError(`Room "${name}" was not found.`, 404, 'room_not_found');
298
+ }
299
+ return room;
300
+ }
301
+ #findParticipant(roomId, name) {
302
+ const row = this.#db
303
+ .prepare('SELECT * FROM participants WHERE room_id = ? AND name = ?')
304
+ .get(roomId, name);
305
+ return row === undefined ? undefined : participantFromRow(row);
306
+ }
307
+ #requireActiveParticipant(roomId, name) {
308
+ this.#assertParticipantName(name);
309
+ const participant = this.#findParticipant(roomId, name);
310
+ if (participant?.active !== true) {
311
+ throw new AppError(`Participant "${name}" has not joined this room or has left it.`, 409, 'participant_not_active');
312
+ }
313
+ return participant;
314
+ }
315
+ #assertParticipantName(name) {
316
+ if (!isValidParticipantName(name)) {
317
+ throw new AppError('Participant name must be 1-32 characters using letters, numbers, dot, underscore, or hyphen.', 400, 'invalid_participant_name');
318
+ }
319
+ }
320
+ #findMessage(id) {
321
+ const row = this.#db
322
+ .prepare(`SELECT id, room_id, sender, body, reply_to, mentions_json, event_id, created_at
323
+ FROM messages WHERE id = ?`)
324
+ .get(id);
325
+ return row === undefined ? undefined : messageFromRow(row);
326
+ }
327
+ }
328
+ //# sourceMappingURL=database.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"database.js","sourceRoot":"","sources":["../src/database.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ export type EventType = 'room.created' | 'participant.joined' | 'participant.left' | 'message.created';
2
+ export interface Room {
3
+ id: string;
4
+ name: string;
5
+ createdAt: string;
6
+ }
7
+ export interface RoomSummary extends Room {
8
+ participantCount: number;
9
+ messageCount: number;
10
+ }
11
+ export interface Participant {
12
+ id: string;
13
+ roomId: string;
14
+ name: string;
15
+ active: boolean;
16
+ joinedAt: string;
17
+ leftAt: string | null;
18
+ }
19
+ export interface Message {
20
+ id: string;
21
+ roomId: string;
22
+ sender: string;
23
+ body: string;
24
+ replyTo: string | null;
25
+ mentions: string[];
26
+ cursor: number;
27
+ createdAt: string;
28
+ }
29
+ interface BaseRoomEvent {
30
+ cursor: number;
31
+ roomId: string;
32
+ createdAt: string;
33
+ }
34
+ export type RoomEvent = (BaseRoomEvent & {
35
+ type: 'room.created';
36
+ payload: {
37
+ room: Room;
38
+ };
39
+ }) | (BaseRoomEvent & {
40
+ type: 'participant.joined';
41
+ payload: {
42
+ participant: Participant;
43
+ };
44
+ }) | (BaseRoomEvent & {
45
+ type: 'participant.left';
46
+ payload: {
47
+ participant: Participant;
48
+ };
49
+ }) | (BaseRoomEvent & {
50
+ type: 'message.created';
51
+ payload: {
52
+ message: Message;
53
+ };
54
+ });
55
+ export interface JoinResult {
56
+ participant: Participant;
57
+ changed: boolean;
58
+ }
59
+ export interface CursorResult {
60
+ cursor: number;
61
+ }
62
+ export declare function isValidRoomName(value: string): boolean;
63
+ export declare function isValidParticipantName(value: string): boolean;
64
+ export declare function extractMentions(body: string): string[];
65
+ export {};
66
+ //# sourceMappingURL=domain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.d.ts","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,GACjB,cAAc,GACd,oBAAoB,GACpB,kBAAkB,GAClB,iBAAiB,CAAC;AAEtB,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAY,SAAQ,IAAI;IACvC,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,OAAO,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,aAAa;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,MAAM,SAAS,GACjB,CAAC,aAAa,GAAG;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE;QAAE,IAAI,EAAE,IAAI,CAAA;KAAE,CAAA;CAAE,CAAC,GACnE,CAAC,aAAa,GAAG;IACf,IAAI,EAAE,oBAAoB,CAAC;IAC3B,OAAO,EAAE;QAAE,WAAW,EAAE,WAAW,CAAA;KAAE,CAAC;CACvC,CAAC,GACF,CAAC,aAAa,GAAG;IACf,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE;QAAE,WAAW,EAAE,WAAW,CAAA;KAAE,CAAC;CACvC,CAAC,GACF,CAAC,aAAa,GAAG;IAAE,IAAI,EAAE,iBAAiB,CAAC;IAAC,OAAO,EAAE;QAAE,OAAO,EAAE,OAAO,CAAA;KAAE,CAAA;CAAE,CAAC,CAAC;AAEjF,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED,wBAAgB,sBAAsB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAE7D;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAkBtD"}
package/dist/domain.js ADDED
@@ -0,0 +1,26 @@
1
+ const ROOM_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
2
+ const PARTICIPANT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,31}$/;
3
+ const MENTION = /@([A-Za-z0-9][A-Za-z0-9._-]{0,31})/g;
4
+ export function isValidRoomName(value) {
5
+ return ROOM_NAME.test(value);
6
+ }
7
+ export function isValidParticipantName(value) {
8
+ return PARTICIPANT_NAME.test(value);
9
+ }
10
+ export function extractMentions(body) {
11
+ const seen = new Set();
12
+ const mentions = [];
13
+ for (const match of body.matchAll(MENTION)) {
14
+ const name = match[1];
15
+ if (name === undefined) {
16
+ continue;
17
+ }
18
+ const key = name.toLowerCase();
19
+ if (!seen.has(key)) {
20
+ seen.add(key);
21
+ mentions.push(name);
22
+ }
23
+ }
24
+ return mentions;
25
+ }
26
+ //# sourceMappingURL=domain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"domain.js","sourceRoot":"","sources":["../src/domain.ts"],"names":[],"mappings":"AAgEA,MAAM,SAAS,GAAG,mCAAmC,CAAC;AACtD,MAAM,gBAAgB,GAAG,mCAAmC,CAAC;AAC7D,MAAM,OAAO,GAAG,qCAAqC,CAAC;AAEtD,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,SAAS,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,KAAa;IAClD,OAAO,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QACtB,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,SAAS;QACX,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACd,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,7 @@
1
+ export declare class AppError extends Error {
2
+ readonly status: number;
3
+ readonly code: string;
4
+ constructor(message: string, status: number, code: string);
5
+ }
6
+ export declare function errorMessage(error: unknown): string;
7
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,qBAAa,QAAS,SAAQ,KAAK;aAGf,MAAM,EAAE,MAAM;aACd,IAAI,EAAE,MAAM;IAH9B,YACE,OAAO,EAAE,MAAM,EACC,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EAI7B;CACF;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEnD"}
package/dist/errors.js ADDED
@@ -0,0 +1,14 @@
1
+ export class AppError extends Error {
2
+ status;
3
+ code;
4
+ constructor(message, status, code) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.name = 'AppError';
9
+ }
10
+ }
11
+ export function errorMessage(error) {
12
+ return error instanceof Error ? error.message : String(error);
13
+ }
14
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,QAAS,SAAQ,KAAK;IAGf,MAAM;IACN,IAAI;IAHtB,YACE,OAAe,EACC,MAAc,EACd,IAAY;QAE5B,KAAK,CAAC,OAAO,CAAC,CAAC;sBAHC,MAAM;oBACN,IAAI;QAGpB,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;IACzB,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,OAAO,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAChE,CAAC"}
@@ -0,0 +1,8 @@
1
+ import type { RoomEvent } from './domain.js';
2
+ export declare class EventHub {
3
+ #private;
4
+ constructor();
5
+ publish(event: RoomEvent): void;
6
+ subscribe(roomId: string, listener: (event: RoomEvent) => void): () => void;
7
+ }
8
+ //# sourceMappingURL=event-hub.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-hub.d.ts","sourceRoot":"","sources":["../src/event-hub.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAE7C,qBAAa,QAAQ;;IAGnB,cAEC;IAEM,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAErC;IAEM,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,MAAM,IAAI,CAGjF;CACF"}
@@ -0,0 +1,15 @@
1
+ import { EventEmitter } from 'node:events';
2
+ export class EventHub {
3
+ #emitter = new EventEmitter();
4
+ constructor() {
5
+ this.#emitter.setMaxListeners(0);
6
+ }
7
+ publish(event) {
8
+ this.#emitter.emit(event.roomId, event);
9
+ }
10
+ subscribe(roomId, listener) {
11
+ this.#emitter.on(roomId, listener);
12
+ return () => this.#emitter.off(roomId, listener);
13
+ }
14
+ }
15
+ //# sourceMappingURL=event-hub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-hub.js","sourceRoot":"","sources":["../src/event-hub.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3C,MAAM,OAAO,QAAQ;IACV,QAAQ,GAAG,IAAI,YAAY,EAAE,CAAC;IAEvC;QACE,IAAI,CAAC,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;IACnC,CAAC;IAEM,OAAO,CAAC,KAAgB;QAC7B,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC1C,CAAC;IAEM,SAAS,CAAC,MAAc,EAAE,QAAoC;QACnE,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACnC,OAAO,GAAG,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnD,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import type { Message, RoomEvent } from './domain.js';
2
+ export declare function formatMessage(message: Message): string;
3
+ export declare function formatEvent(event: RoomEvent): string;
4
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAEtD,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAGtD;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,MAAM,CAWpD"}
package/dist/format.js ADDED
@@ -0,0 +1,17 @@
1
+ export function formatMessage(message) {
2
+ const reply = message.replyTo === null ? '' : ` reply-to=${message.replyTo}`;
3
+ return `[${message.cursor}] ${message.createdAt} ${message.sender}: ${message.body}${reply} id=${message.id}`;
4
+ }
5
+ export function formatEvent(event) {
6
+ switch (event.type) {
7
+ case 'room.created':
8
+ return `[${event.cursor}] room created: ${event.payload.room.name}`;
9
+ case 'participant.joined':
10
+ return `[${event.cursor}] ${event.payload.participant.name} joined`;
11
+ case 'participant.left':
12
+ return `[${event.cursor}] ${event.payload.participant.name} left`;
13
+ case 'message.created':
14
+ return formatMessage(event.payload.message);
15
+ }
16
+ }
17
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,aAAa,CAAC,OAAgB;IAC5C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,aAAa,OAAO,CAAC,OAAO,EAAE,CAAC;IAC7E,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,SAAS,IAAI,OAAO,CAAC,MAAM,KAAK,OAAO,CAAC,IAAI,GAAG,KAAK,OAAO,OAAO,CAAC,EAAE,EAAE,CAAC;AAChH,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,KAAgB;IAC1C,QAAQ,KAAK,CAAC,IAAI,EAAE,CAAC;QACnB,KAAK,cAAc;YACjB,OAAO,IAAI,KAAK,CAAC,MAAM,mBAAmB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;QACtE,KAAK,oBAAoB;YACvB,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,SAAS,CAAC;QACtE,KAAK,kBAAkB;YACrB,OAAO,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,IAAI,OAAO,CAAC;QACpE,KAAK,iBAAiB;YACpB,OAAO,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,CAAC;AACH,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ServerOptions {
2
+ dbPath: string;
3
+ host?: string;
4
+ port?: number;
5
+ }
6
+ export interface ServerAddress {
7
+ host: string;
8
+ port: number;
9
+ url: string;
10
+ }
11
+ export interface AgentRoomServer {
12
+ start(): Promise<ServerAddress>;
13
+ stop(): Promise<void>;
14
+ }
15
+ export declare function createAgentRoomServer(options: ServerOptions): AgentRoomServer;
16
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,IAAI,OAAO,CAAC,aAAa,CAAC,CAAC;IAChC,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AA0ED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,aAAa,GAAG,eAAe,CAyM7E"}