@pairlobby/local-server 0.0.0-stage → 0.20.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.
Files changed (47) hide show
  1. package/LICENSE +93 -0
  2. package/README.md +4 -2
  3. package/dist/backup.d.ts +79 -0
  4. package/dist/backup.d.ts.map +1 -0
  5. package/dist/backup.js +184 -0
  6. package/dist/backup.js.map +1 -0
  7. package/dist/check.d.ts +12 -0
  8. package/dist/check.d.ts.map +1 -0
  9. package/dist/check.js +102 -0
  10. package/dist/check.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +10 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/network.d.ts +23 -0
  16. package/dist/network.d.ts.map +1 -0
  17. package/dist/network.js +143 -0
  18. package/dist/network.js.map +1 -0
  19. package/dist/relay-main.d.ts +3 -0
  20. package/dist/relay-main.d.ts.map +1 -0
  21. package/dist/relay-main.js +140 -0
  22. package/dist/relay-main.js.map +1 -0
  23. package/dist/request-log.d.ts +38 -0
  24. package/dist/request-log.d.ts.map +1 -0
  25. package/dist/request-log.js +37 -0
  26. package/dist/request-log.js.map +1 -0
  27. package/dist/s3.d.ts +37 -0
  28. package/dist/s3.d.ts.map +1 -0
  29. package/dist/s3.js +140 -0
  30. package/dist/s3.js.map +1 -0
  31. package/dist/schema.d.ts +3 -0
  32. package/dist/schema.d.ts.map +1 -0
  33. package/dist/schema.js +98 -0
  34. package/dist/schema.js.map +1 -0
  35. package/dist/server.d.ts +57 -0
  36. package/dist/server.d.ts.map +1 -0
  37. package/dist/server.js +268 -0
  38. package/dist/server.js.map +1 -0
  39. package/dist/settings.d.ts +55 -0
  40. package/dist/settings.d.ts.map +1 -0
  41. package/dist/settings.js +152 -0
  42. package/dist/settings.js.map +1 -0
  43. package/dist/sqlite-store.d.ts +57 -0
  44. package/dist/sqlite-store.d.ts.map +1 -0
  45. package/dist/sqlite-store.js +331 -0
  46. package/dist/sqlite-store.js.map +1 -0
  47. package/package.json +43 -4
@@ -0,0 +1,152 @@
1
+ //! Relay settings from flags and the environment, shared by `pairlobby serve` and
2
+ //! `pairlobby-relay`. A container or a service manager configures the relay through
3
+ //! PAIRLOBBY_* variables; a flag wins over its variable. Nothing here opens a database,
4
+ //! so a command can check its settings without loading the relay.
5
+ import { readFileSync, statSync } from 'node:fs';
6
+ /** A setting the relay cannot start with; the message names the flag or variable that holds it. */
7
+ export class RelaySettingsError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = 'RelaySettingsError';
11
+ }
12
+ }
13
+ const LIMIT_VARIABLES = {
14
+ createPerMinutePerAddress: 'PAIRLOBBY_LIMIT_CREATE_PER_MIN_IP',
15
+ redeemPerMinutePerAddress: 'PAIRLOBBY_LIMIT_REDEEM_PER_MIN_IP',
16
+ redeemPerMinuteGlobal: 'PAIRLOBBY_LIMIT_REDEEM_PER_MIN_GLOBAL'
17
+ };
18
+ /** Every variable `serve` reads, for help text and documentation. */
19
+ export const RELAY_VARIABLES = ['PAIRLOBBY_HOST', 'PAIRLOBBY_PORT', 'PAIRLOBBY_DATA_DIR', 'PAIRLOBBY_PUBLIC_URL', 'PAIRLOBBY_TRUSTED_PROXY_HEADER', 'PAIRLOBBY_ALLOWED_HOSTS', 'PAIRLOBBY_CREATE_TOKEN', 'PAIRLOBBY_CREATE_TOKEN_FILE', 'PAIRLOBBY_REQUEST_LOG', 'PAIRLOBBY_BACKUP', 'PAIRLOBBY_BACKUP_INTERVAL', 'PAIRLOBBY_BACKUP_KEEP', 'PAIRLOBBY_BACKUP_S3_ENDPOINT', 'PAIRLOBBY_NO_RESTORE', ...Object.values(LIMIT_VARIABLES)];
20
+ function described({ flag, variable }) {
21
+ return flag ? `--${flag} (${variable})` : variable;
22
+ }
23
+ /** A flag, else its variable; an empty variable counts as unset. */
24
+ function pick(flag, environment, variable) {
25
+ return flag ?? (environment[variable]?.trim() || undefined);
26
+ }
27
+ export function parsePublicUrl(value, source = { flag: 'public-url', variable: 'PAIRLOBBY_PUBLIC_URL' }) {
28
+ let url;
29
+ try {
30
+ url = new URL(value);
31
+ }
32
+ catch {
33
+ throw new RelaySettingsError(`${described(source)} must be a full address such as https://laptop.tailnet.ts.net, not "${value}"`);
34
+ }
35
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash || url.pathname !== '/') {
36
+ throw new RelaySettingsError(`${described(source)} must be an http(s) origin with no path, credentials, query or fragment`);
37
+ }
38
+ return url.origin;
39
+ }
40
+ function wholeNumber(value, source, minimum, maximum) {
41
+ const parsed = Number(value);
42
+ if (!/^\d+$/.test(value) || parsed < minimum || parsed > maximum) {
43
+ throw new RelaySettingsError(`${described(source)} must be a whole number from ${minimum} to ${maximum}, not "${value}"`);
44
+ }
45
+ return parsed;
46
+ }
47
+ /** The token from the variable, or from a file only its owner can read; never both, never from a flag. */
48
+ function createToken(environment) {
49
+ const inline = environment['PAIRLOBBY_CREATE_TOKEN'] || undefined;
50
+ const file = environment['PAIRLOBBY_CREATE_TOKEN_FILE']?.trim() || undefined;
51
+ if (inline && file) {
52
+ throw new RelaySettingsError('set PAIRLOBBY_CREATE_TOKEN or PAIRLOBBY_CREATE_TOKEN_FILE, not both');
53
+ }
54
+ if (!file) {
55
+ return inline;
56
+ }
57
+ let mode;
58
+ try {
59
+ mode = statSync(file).mode;
60
+ }
61
+ catch {
62
+ throw new RelaySettingsError(`PAIRLOBBY_CREATE_TOKEN_FILE names ${file}, which cannot be read`);
63
+ }
64
+ if (process.platform !== 'win32' && (mode & 0o077) !== 0) {
65
+ throw new RelaySettingsError(`${file} must be readable only by its owner: chmod 600 ${file}`);
66
+ }
67
+ const token = readFileSync(file, 'utf8').trim();
68
+ if (!token) {
69
+ throw new RelaySettingsError(`${file} is empty; put the create token in it`);
70
+ }
71
+ return token;
72
+ }
73
+ export function relaySettings(flags, environment) {
74
+ const host = pick(flags.host, environment, 'PAIRLOBBY_HOST');
75
+ const port = pick(flags.port, environment, 'PAIRLOBBY_PORT');
76
+ const publicUrl = pick(flags.publicUrl, environment, 'PAIRLOBBY_PUBLIC_URL');
77
+ const proxyHeader = pick(flags.trustedProxyHeader, environment, 'PAIRLOBBY_TRUSTED_PROXY_HEADER')?.trim().toLowerCase();
78
+ if (proxyHeader !== undefined && !/^[a-z0-9-]+$/.test(proxyHeader)) {
79
+ throw new RelaySettingsError(`${described({ flag: 'trusted-proxy-header', variable: 'PAIRLOBBY_TRUSTED_PROXY_HEADER' })} must be a header name such as x-forwarded-for`);
80
+ }
81
+ const allowedHosts = (environment['PAIRLOBBY_ALLOWED_HOSTS'] ?? '').split(',').map((value) => value.trim().toLowerCase()).filter(Boolean);
82
+ for (const value of allowedHosts) {
83
+ if (!/^[a-z0-9.-]+(:\d{1,5})?$/.test(value)) {
84
+ throw new RelaySettingsError(`PAIRLOBBY_ALLOWED_HOSTS takes Host values such as relay.internal or relay.internal:8790, not "${value}"`);
85
+ }
86
+ }
87
+ const limits = {};
88
+ for (const [key, variable] of Object.entries(LIMIT_VARIABLES)) {
89
+ const value = environment[variable]?.trim();
90
+ if (value) {
91
+ limits[key] = wholeNumber(value, { variable }, 1, 1_000_000);
92
+ }
93
+ }
94
+ const requestLog = pick(flags.requestLog, environment, 'PAIRLOBBY_REQUEST_LOG');
95
+ if (requestLog !== undefined && !['all', 'writes', 'off'].includes(requestLog)) {
96
+ throw new RelaySettingsError(`${described({ flag: 'request-log', variable: 'PAIRLOBBY_REQUEST_LOG' })} must be all, writes or off, not "${requestLog}"`);
97
+ }
98
+ const token = createToken(environment);
99
+ const backup = backupSettings(flags, environment);
100
+ return {
101
+ ...(host ? { host } : {}),
102
+ // 0 lets the system choose a free port.
103
+ ...(port ? { port: wholeNumber(port, { flag: 'port', variable: 'PAIRLOBBY_PORT' }, 0, 65_535) } : {}),
104
+ ...(publicUrl ? { publicUrl: parsePublicUrl(publicUrl) } : {}),
105
+ ...(proxyHeader ? { trustedProxyHeader: proxyHeader } : {}),
106
+ ...(token ? { createToken: token } : {}),
107
+ allowedHosts,
108
+ limits,
109
+ ...(requestLog && requestLog !== 'off' ? { requestLog: requestLog } : {}),
110
+ ...(backup ? { backup } : {})
111
+ };
112
+ }
113
+ function backupSettings(flags, environment) {
114
+ const url = pick(flags.backup, environment, 'PAIRLOBBY_BACKUP');
115
+ const interval = environment['PAIRLOBBY_BACKUP_INTERVAL']?.trim();
116
+ const keep = environment['PAIRLOBBY_BACKUP_KEEP']?.trim();
117
+ const endpoint = environment['PAIRLOBBY_BACKUP_S3_ENDPOINT']?.trim();
118
+ const noRestore = environment['PAIRLOBBY_NO_RESTORE']?.trim().toLowerCase();
119
+ if (noRestore && !['1', 'true', '0', 'false'].includes(noRestore)) {
120
+ throw new RelaySettingsError(`PAIRLOBBY_NO_RESTORE must be 1 or 0, not "${noRestore}"`);
121
+ }
122
+ if (endpoint && !/^https?:\/\/[^/]+\/?$/.test(endpoint)) {
123
+ throw new RelaySettingsError(`PAIRLOBBY_BACKUP_S3_ENDPOINT must be an http(s) origin such as https://<account>.r2.cloudflarestorage.com, not "${endpoint}"`);
124
+ }
125
+ if (!url) {
126
+ if (interval || keep || endpoint || flags.noRestore) {
127
+ throw new RelaySettingsError('backup settings need a place to back up to: set --backup (PAIRLOBBY_BACKUP) to s3://bucket/prefix or a folder');
128
+ }
129
+ return undefined;
130
+ }
131
+ const location = parseBackupLocation(url);
132
+ if (!location) {
133
+ throw new RelaySettingsError(`${described({ flag: 'backup', variable: 'PAIRLOBBY_BACKUP' })} must be s3://bucket/prefix or an absolute folder path, not "${url}"`);
134
+ }
135
+ return {
136
+ location,
137
+ intervalMs: interval ? wholeNumber(interval, { variable: 'PAIRLOBBY_BACKUP_INTERVAL' }, 10, 86_400) * 1000 : 60_000,
138
+ keep: keep ? wholeNumber(keep, { variable: 'PAIRLOBBY_BACKUP_KEEP' }, 1, 10_000) : 48,
139
+ restore: !(flags.noRestore || noRestore === '1' || noRestore === 'true')
140
+ };
141
+ }
142
+ /** `s3://bucket/prefix` or an absolute folder path (`file://` allowed); null for anything else. */
143
+ export function parseBackupLocation(value) {
144
+ if (value.startsWith('s3://')) {
145
+ const [bucket = '', ...rest] = value.slice('s3://'.length).split('/');
146
+ const prefix = rest.filter(Boolean).join('/');
147
+ return /^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$/.test(bucket) ? { kind: 's3', bucket, prefix: prefix ? `${prefix}/` : '' } : null;
148
+ }
149
+ const path = value.startsWith('file://') ? decodeURIComponent(new URL(value).pathname) : value;
150
+ return path.startsWith('/') || /^[A-Za-z]:[\\/]/.test(path) ? { kind: 'folder', path } : null;
151
+ }
152
+ //# sourceMappingURL=settings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"settings.js","sourceRoot":"","sources":["../src/settings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import type { HandoverRecord, InviteRecord, LocalRoom, MessageRequest, ParticipantRecord, RequestPage, RoomEvent, RoomRecord } from '@pairlobby/protocol';
2
+ import type { Mutation, RoomView } from '@pairlobby/room-core';
3
+ import type { EventPage, FileStore, IdempotencyRecord, RoomStore } from '@pairlobby/server-core';
4
+ type IdempotencyKey = {
5
+ key: string;
6
+ requestDigest: string;
7
+ } | null;
8
+ export declare class SqliteRoomStore implements RoomStore {
9
+ private readonly db;
10
+ readonly files: FileStore;
11
+ constructor(location: string);
12
+ close(): void;
13
+ /** Rows this store has changed since it opened, so a backup can tell nothing happened. */
14
+ changeCount(): number;
15
+ /** A consistent copy at `destination`, taken a few pages at a time so requests keep being served. */
16
+ snapshot(destination: string): Promise<void>;
17
+ private transaction;
18
+ createRoom(room: RoomRecord, participant: ParticipantRecord, event: RoomEvent): Promise<void>;
19
+ loadRoom(roomId: string): Promise<RoomView | null>;
20
+ apply(mutation: Mutation, idempotency: IdempotencyKey): Promise<void>;
21
+ readEvents(roomId: string, after: number, limit: number): Promise<EventPage>;
22
+ eventBySeq(roomId: string, seq: number): Promise<RoomEvent | null>;
23
+ eventById(roomId: string, eventId: string): Promise<RoomEvent | null>;
24
+ idempotencyRecord(roomId: string, key: string): Promise<IdempotencyRecord | null>;
25
+ putInvite(invite: InviteRecord): Promise<void>;
26
+ inviteByDigest(digest: string): Promise<InviteRecord | null>;
27
+ /** Open, unexpired rooms that allow local joins: those named `name`, ignoring case, or without a name the newest 50. */
28
+ localJoinRooms(name?: string, now?: number): LocalRoom[];
29
+ /** Whether any invite digest starts with this lowercase hex prefix; a range over the primary key, not a scan. */
30
+ hasInviteDigestPrefix(prefix: string): boolean;
31
+ /**
32
+ * The conditional UPDATE is the whole point: a second attempt matches no row
33
+ * and gets null, rather than racing the first into membership creation.
34
+ */
35
+ reserveInvite(digest: string, attemptId: string, credentialHash: string, expectedOccupantId: string | null): Promise<InviteRecord | null>;
36
+ completeInvite(digest: string, participantId: string): Promise<void>;
37
+ participantByCredential(roomId: string, credentialHash: string): Promise<ParticipantRecord | null>;
38
+ handovers(roomId: string): Promise<HandoverRecord[]>;
39
+ messageRequest(roomId: string, eventId: string): Promise<MessageRequest | null>;
40
+ groupRequests(roomId: string, conversationId: string): Promise<MessageRequest[]>;
41
+ private checkTurnRevision;
42
+ updateTurns(room: RoomRecord, requests: MessageRequest[], expectedRevision: number): Promise<void>;
43
+ messageRequests(roomId: string, after: number, limit: number, recipientId?: string): Promise<RequestPage>;
44
+ private writeRequest;
45
+ setLifecycle(roomId: string, lifecycle: RoomRecord['lifecycle']): Promise<void>;
46
+ /** Physical cleanup. Access was already denied by the lifecycle change. */
47
+ deleteRoom(roomId: string): Promise<void>;
48
+ dueAutoCloses(now: number, limit: number): Promise<string[]>;
49
+ nextAutoCloseAt(): Promise<number | null>;
50
+ /** Simulates retention dropping the front of the log without touching room state. */
51
+ dropHistoryBefore(roomId: string, seq: number): void;
52
+ private writeParticipant;
53
+ private writeEvent;
54
+ private rows;
55
+ }
56
+ export {};
57
+ //# sourceMappingURL=sqlite-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,331 @@
1
+ //! A `RoomStore` on `node:sqlite`. Every mutation lands inside one immediate
2
+ //! transaction: the JavaScript event loop is not a substitute for SQL isolation
3
+ //! once two requests interleave around an await.
4
+ import { ProtocolError, mergeMessageRequest } from '@pairlobby/protocol';
5
+ import * as sqlite from 'node:sqlite';
6
+ import { DatabaseSync } from 'node:sqlite';
7
+ import { REQUEST_BACKFILL_SQL } from '@pairlobby/server-core';
8
+ import { SCHEMA, SCHEMA_VERSION } from './schema.js';
9
+ /** Small enough for any SQLite-backed store, including ones that cap a single value. */
10
+ const FILE_CHUNK_BYTES = 1024 * 1024;
11
+ export class SqliteRoomStore {
12
+ db;
13
+ files = {
14
+ putFile: async (file, bytes, limitBytes) => this.transaction(() => {
15
+ const held = this.db.prepare('SELECT coalesce(sum(size), 0) AS total FROM files WHERE room_id = ?').get(file.roomId);
16
+ if (Number(held.total) + file.size > limitBytes) {
17
+ return false;
18
+ }
19
+ this.db.prepare('INSERT INTO files (file_id, room_id, size, created_at, event_id, body) VALUES (?, ?, ?, ?, ?, ?)').run(file.fileId, file.roomId, file.size, file.createdAt, file.eventId, JSON.stringify(file));
20
+ const insert = this.db.prepare('INSERT INTO file_chunks (file_id, idx, bytes) VALUES (?, ?, ?)');
21
+ for (let offset = 0, index = 0; offset < bytes.byteLength; offset += FILE_CHUNK_BYTES, index++) {
22
+ insert.run(file.fileId, index, bytes.subarray(offset, offset + FILE_CHUNK_BYTES));
23
+ }
24
+ return true;
25
+ }),
26
+ file: async (roomId, fileId) => {
27
+ const row = this.db.prepare('SELECT body FROM files WHERE room_id = ? AND file_id = ?').get(roomId, fileId);
28
+ return row ? JSON.parse(row.body) : null;
29
+ },
30
+ fileBytes: async (roomId, fileId) => {
31
+ const file = this.db.prepare('SELECT size FROM files WHERE room_id = ? AND file_id = ?').get(roomId, fileId);
32
+ if (!file) {
33
+ return null;
34
+ }
35
+ const bytes = new Uint8Array(Number(file.size));
36
+ let offset = 0;
37
+ for (const chunk of this.db.prepare('SELECT bytes FROM file_chunks WHERE file_id = ? ORDER BY idx').all(fileId)) {
38
+ bytes.set(chunk.bytes, offset);
39
+ offset += chunk.bytes.byteLength;
40
+ }
41
+ return offset === bytes.byteLength ? bytes : null;
42
+ },
43
+ attachedFiles: async (roomId) => this.db.prepare('SELECT body FROM files WHERE room_id = ? AND event_id IS NOT NULL ORDER BY created_at, file_id').all(roomId).map((row) => JSON.parse(row.body)),
44
+ usedBytes: async (roomId) => Number(this.db.prepare('SELECT coalesce(sum(size), 0) AS total FROM files WHERE room_id = ?').get(roomId).total),
45
+ deleteUnattachedFiles: async (roomId, before) => this.transaction(() => {
46
+ this.db.prepare('DELETE FROM file_chunks WHERE file_id IN (SELECT file_id FROM files WHERE room_id = ? AND event_id IS NULL AND created_at < ?)').run(roomId, before);
47
+ this.db.prepare('DELETE FROM files WHERE room_id = ? AND event_id IS NULL AND created_at < ?').run(roomId, before);
48
+ }),
49
+ };
50
+ constructor(location) {
51
+ this.db = new DatabaseSync(location);
52
+ this.db.exec('PRAGMA journal_mode = WAL');
53
+ this.db.exec('PRAGMA foreign_keys = ON');
54
+ this.db.exec('PRAGMA busy_timeout = 5000');
55
+ this.db.exec(SCHEMA);
56
+ if (!this.db.prepare("SELECT value FROM meta WHERE key='message_requests_v1'").get()) {
57
+ this.transaction(() => this.db.exec(REQUEST_BACKFILL_SQL));
58
+ }
59
+ this.db.prepare('INSERT INTO meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO NOTHING').run('schema_version', String(SCHEMA_VERSION));
60
+ }
61
+ close() {
62
+ this.db.close();
63
+ }
64
+ /** Rows this store has changed since it opened, so a backup can tell nothing happened. */
65
+ changeCount() {
66
+ return Number(this.db.prepare('SELECT total_changes() AS changes').get().changes);
67
+ }
68
+ /** A consistent copy at `destination`, taken a few pages at a time so requests keep being served. */
69
+ async snapshot(destination) {
70
+ // `backup` arrived in Node 22.16; older 22s block for the whole copy instead.
71
+ if (typeof sqlite.backup === 'function') {
72
+ await sqlite.backup(this.db, destination, { rate: 100 });
73
+ return;
74
+ }
75
+ this.db.prepare('VACUUM INTO ?').run(destination);
76
+ }
77
+ transaction(run) {
78
+ this.db.exec('BEGIN IMMEDIATE');
79
+ try {
80
+ const result = run();
81
+ this.db.exec('COMMIT');
82
+ return result;
83
+ }
84
+ catch (error) {
85
+ this.db.exec('ROLLBACK');
86
+ throw error;
87
+ }
88
+ }
89
+ async createRoom(room, participant, event) {
90
+ this.transaction(() => {
91
+ this.db.prepare('INSERT INTO rooms (room_id, earliest_seq, body) VALUES (?, 1, ?)').run(room.roomId, JSON.stringify(room));
92
+ this.writeParticipant(participant);
93
+ this.writeEvent(event);
94
+ });
95
+ }
96
+ async loadRoom(roomId) {
97
+ const row = this.db.prepare('SELECT earliest_seq, body FROM rooms WHERE room_id = ?').get(roomId);
98
+ if (!row) {
99
+ return null;
100
+ }
101
+ return {
102
+ room: JSON.parse(row.body),
103
+ participants: this.rows('SELECT body FROM participants WHERE room_id = ? ORDER BY participant_id', roomId),
104
+ handovers: this.rows('SELECT body FROM handovers WHERE room_id = ? ORDER BY handover_id', roomId),
105
+ controls: this.rows('SELECT body FROM controls WHERE room_id = ? ORDER BY target_participant_id', roomId),
106
+ earliestSeq: row.earliest_seq
107
+ };
108
+ }
109
+ async apply(mutation, idempotency) {
110
+ this.transaction(() => {
111
+ if (mutation.expectedTurnRevision !== undefined) {
112
+ this.checkTurnRevision(mutation.room, mutation.expectedTurnRevision);
113
+ }
114
+ const current = this.db.prepare('SELECT body FROM rooms WHERE room_id=?').get(mutation.room.roomId);
115
+ if (!current || JSON.parse(current.body).nextSeq !== mutation.appendEvent.seq) {
116
+ throw new ProtocolError(mutation.expectedTurnRevision === undefined ? 'server_unavailable' : 'turn_conflict', 'concurrent room mutation; retry with the same idempotency key');
117
+ }
118
+ this.db.prepare('UPDATE rooms SET body = ? WHERE room_id = ?').run(JSON.stringify(mutation.room), mutation.room.roomId);
119
+ for (const participant of mutation.upsertParticipants)
120
+ this.writeParticipant(participant);
121
+ for (const handover of mutation.upsertHandovers) {
122
+ this.db
123
+ .prepare('INSERT INTO handovers (handover_id, room_id, body) VALUES (?, ?, ?) ON CONFLICT (handover_id) DO UPDATE SET body = excluded.body')
124
+ .run(handover.handoverId, handover.roomId, JSON.stringify(handover));
125
+ }
126
+ for (const control of mutation.upsertControls) {
127
+ this.db
128
+ .prepare('INSERT INTO controls (room_id, target_participant_id, body) VALUES (?, ?, ?) ON CONFLICT (room_id, target_participant_id) DO UPDATE SET body = excluded.body')
129
+ .run(control.roomId, control.targetParticipantId, JSON.stringify(control));
130
+ }
131
+ for (const request of mutation.upsertRequests ?? [])
132
+ this.writeRequest(request);
133
+ for (const fileId of mutation.attachFiles ?? []) {
134
+ const row = this.db.prepare('SELECT body FROM files WHERE room_id = ? AND file_id = ? AND event_id IS NULL').get(mutation.room.roomId, fileId);
135
+ if (!row) {
136
+ throw new ProtocolError('invalid_request', 'an attached file is no longer available; upload it again');
137
+ }
138
+ const file = { ...JSON.parse(row.body), eventId: mutation.appendEvent.eventId };
139
+ this.db.prepare('UPDATE files SET event_id = ?, body = ? WHERE file_id = ?').run(file.eventId, JSON.stringify(file), fileId);
140
+ }
141
+ this.writeEvent(mutation.appendEvent);
142
+ if (idempotency) {
143
+ this.db
144
+ .prepare('INSERT INTO idempotency (room_id, key, request_digest, seq) VALUES (?, ?, ?, ?)')
145
+ .run(mutation.room.roomId, idempotency.key, idempotency.requestDigest, mutation.appendEvent.seq);
146
+ }
147
+ });
148
+ }
149
+ async readEvents(roomId, after, limit) {
150
+ const rows = this.db.prepare('SELECT body FROM events WHERE room_id = ? AND seq > ? ORDER BY seq LIMIT ?').all(roomId, after, limit + 1);
151
+ const events = rows.slice(0, limit).map((row) => JSON.parse(row.body));
152
+ return { events, hasMore: rows.length > limit };
153
+ }
154
+ async eventBySeq(roomId, seq) {
155
+ const row = this.db.prepare('SELECT body FROM events WHERE room_id = ? AND seq = ?').get(roomId, seq);
156
+ return row ? JSON.parse(row.body) : null;
157
+ }
158
+ async eventById(roomId, eventId) {
159
+ const row = this.db.prepare("SELECT body FROM events WHERE room_id = ? AND json_extract(body, '$.eventId') = ?").get(roomId, eventId);
160
+ return row ? JSON.parse(row.body) : null;
161
+ }
162
+ async idempotencyRecord(roomId, key) {
163
+ const row = this.db.prepare('SELECT request_digest, seq FROM idempotency WHERE room_id = ? AND key = ?').get(roomId, key);
164
+ return row ? { requestDigest: row.request_digest, seq: row.seq } : null;
165
+ }
166
+ async putInvite(invite) {
167
+ this.db
168
+ .prepare('INSERT INTO invites (digest, room_id, state, bound_attempt_id, body) VALUES (?, ?, ?, ?, ?)')
169
+ .run(invite.digest, invite.roomId, invite.state, invite.boundAttemptId, JSON.stringify(invite));
170
+ }
171
+ async inviteByDigest(digest) {
172
+ const row = this.db.prepare('SELECT body FROM invites WHERE digest = ?').get(digest);
173
+ return row ? JSON.parse(row.body) : null;
174
+ }
175
+ /** Open, unexpired rooms that allow local joins: those named `name`, ignoring case, or without a name the newest 50. */
176
+ localJoinRooms(name, now = Date.now()) {
177
+ const open = "SELECT room_id, body FROM rooms WHERE json_extract(body, '$.policy.localJoin') = 1 AND json_extract(body, '$.lifecycle') = 'open'";
178
+ const rows = (name === undefined
179
+ ? this.db.prepare(`${open} ORDER BY json_extract(body, '$.createdAt') DESC LIMIT 50`).all()
180
+ : this.db.prepare(`${open} AND lower(json_extract(body, '$.name')) = lower(?)`).all(name));
181
+ const count = this.db.prepare("SELECT count(*) AS active FROM participants WHERE room_id = ? AND json_extract(body, '$.revokedAt') IS NULL AND json_extract(body, '$.leftAt') IS NULL");
182
+ return rows.map((row) => JSON.parse(row.body)).filter((room) => room.expiresAt === null || room.expiresAt > now).map((room) => ({ roomId: room.roomId, name: room.name, createdAt: room.createdAt, participantCount: Number(count.get(room.roomId).active) }));
183
+ }
184
+ /** Whether any invite digest starts with this lowercase hex prefix; a range over the primary key, not a scan. */
185
+ hasInviteDigestPrefix(prefix) {
186
+ return this.db.prepare('SELECT 1 FROM invites WHERE digest >= ? AND digest < ? LIMIT 1').get(prefix, `${prefix}g`) !== undefined;
187
+ }
188
+ /**
189
+ * The conditional UPDATE is the whole point: a second attempt matches no row
190
+ * and gets null, rather than racing the first into membership creation.
191
+ */
192
+ async reserveInvite(digest, attemptId, credentialHash, expectedOccupantId) {
193
+ return this.transaction(() => {
194
+ const row = this.db.prepare('SELECT body FROM invites WHERE digest = ?').get(digest);
195
+ if (!row) {
196
+ return null;
197
+ }
198
+ const invite = JSON.parse(row.body);
199
+ if (invite.boundAttemptId === attemptId) {
200
+ return invite;
201
+ }
202
+ if (invite.state === 'reserved') {
203
+ return null;
204
+ }
205
+ if (invite.state === 'redeemed') {
206
+ if (!invite.reusable) {
207
+ return null;
208
+ }
209
+ if (invite.redeemedParticipantId !== expectedOccupantId) {
210
+ return null;
211
+ }
212
+ }
213
+ else if (expectedOccupantId !== null) {
214
+ return null;
215
+ }
216
+ const reserved = { ...invite, state: 'reserved', boundAttemptId: attemptId, boundCredentialHash: credentialHash };
217
+ // The WHERE clause repeats the precondition so a concurrent writer that
218
+ // slipped in between the read and this update loses rather than overwrites.
219
+ const previous = invite.state === 'redeemed' ? invite.redeemedParticipantId : null;
220
+ const result = previous === null ? this.db.prepare("UPDATE invites SET state = 'reserved', bound_attempt_id = ?, body = ? WHERE digest = ? AND state = 'unused'").run(attemptId, JSON.stringify(reserved), digest) : this.db.prepare("UPDATE invites SET state = 'reserved', bound_attempt_id = ?, body = ? WHERE digest = ? AND state = 'redeemed' AND json_extract(body, '$.redeemedParticipantId') = ?").run(attemptId, JSON.stringify(reserved), digest, previous);
221
+ return result.changes === 1 ? reserved : null;
222
+ });
223
+ }
224
+ async completeInvite(digest, participantId) {
225
+ this.transaction(() => {
226
+ const row = this.db.prepare('SELECT body FROM invites WHERE digest = ?').get(digest);
227
+ if (!row) {
228
+ return;
229
+ }
230
+ const invite = { ...JSON.parse(row.body), state: 'redeemed', redeemedParticipantId: participantId };
231
+ this.db.prepare("UPDATE invites SET state = 'redeemed', body = ? WHERE digest = ?").run(JSON.stringify(invite), digest);
232
+ });
233
+ }
234
+ async participantByCredential(roomId, credentialHash) {
235
+ const row = this.db.prepare('SELECT body FROM participants WHERE room_id = ? AND credential_hash = ?').get(roomId, credentialHash);
236
+ return row ? JSON.parse(row.body) : null;
237
+ }
238
+ async handovers(roomId) {
239
+ return this.rows('SELECT body FROM handovers WHERE room_id = ? ORDER BY handover_id', roomId);
240
+ }
241
+ async messageRequest(roomId, eventId) {
242
+ const row = this.db.prepare('SELECT body FROM message_requests WHERE room_id=? AND event_id=?').get(roomId, eventId);
243
+ return row ? JSON.parse(row.body) : null;
244
+ }
245
+ async groupRequests(roomId, conversationId) {
246
+ const rows = this.db.prepare("SELECT body FROM message_requests WHERE room_id=? AND json_extract(body,'$.conversationId')=? ORDER BY seq").all(roomId, conversationId);
247
+ return rows.map((row) => JSON.parse(row.body));
248
+ }
249
+ checkTurnRevision(room, expected) {
250
+ const row = this.db.prepare('SELECT body FROM rooms WHERE room_id=?').get(room.roomId);
251
+ if (!row || (JSON.parse(row.body).turnRevision ?? 0) !== expected) {
252
+ throw new ProtocolError('turn_conflict', 'the room turn changed; retry');
253
+ }
254
+ }
255
+ async updateTurns(room, requests, expectedRevision) {
256
+ this.transaction(() => {
257
+ this.checkTurnRevision(room, expectedRevision);
258
+ const current = this.db.prepare('SELECT body FROM rooms WHERE room_id=?').get(room.roomId);
259
+ if (!current || JSON.parse(current.body).nextSeq !== room.nextSeq) {
260
+ throw new ProtocolError('turn_conflict', 'the room changed while renewing the turn');
261
+ }
262
+ this.db.prepare('UPDATE rooms SET body=? WHERE room_id=?').run(JSON.stringify(room), room.roomId);
263
+ for (const request of requests) {
264
+ this.writeRequest(request);
265
+ }
266
+ });
267
+ }
268
+ async messageRequests(roomId, after, limit, recipientId) {
269
+ const rows = this.db
270
+ .prepare("SELECT body FROM message_requests WHERE room_id=? AND seq>? AND requires_reply=1 AND response_event_id IS NULL AND (? IS NULL OR json_extract(body,'$.to')=?) ORDER BY seq LIMIT ?")
271
+ .all(roomId, after, recipientId ?? null, recipientId ?? null, limit + 1);
272
+ return { requests: rows.slice(0, limit).map((row) => JSON.parse(row.body)), hasMore: rows.length > limit };
273
+ }
274
+ writeRequest(request) {
275
+ // Keep concurrent receipt/progress writes from erasing an accepted reply.
276
+ const old = this.db.prepare('SELECT body FROM message_requests WHERE event_id=?').get(request.eventId);
277
+ const previous = old ? JSON.parse(old.body) : null;
278
+ const merged = mergeMessageRequest(previous, request);
279
+ this.db
280
+ .prepare('INSERT INTO message_requests(event_id,room_id,seq,received_at,response_event_id,requires_reply,body) VALUES(?,?,?,?,?,?,?) ON CONFLICT(event_id) DO UPDATE SET received_at=excluded.received_at,response_event_id=excluded.response_event_id,requires_reply=excluded.requires_reply,body=excluded.body')
281
+ .run(merged.eventId, merged.roomId, merged.seq, merged.receivedAt, merged.responseEventId, merged.requiresReply ? 1 : 0, JSON.stringify(merged));
282
+ }
283
+ async setLifecycle(roomId, lifecycle) {
284
+ this.transaction(() => {
285
+ const row = this.db.prepare('SELECT body FROM rooms WHERE room_id = ?').get(roomId);
286
+ if (!row) {
287
+ return;
288
+ }
289
+ const room = { ...JSON.parse(row.body), lifecycle };
290
+ this.db.prepare('UPDATE rooms SET body = ? WHERE room_id = ?').run(JSON.stringify(room), roomId);
291
+ });
292
+ }
293
+ /** Physical cleanup. Access was already denied by the lifecycle change. */
294
+ async deleteRoom(roomId) {
295
+ this.transaction(() => {
296
+ this.db.prepare('DELETE FROM file_chunks WHERE file_id IN (SELECT file_id FROM files WHERE room_id = ?)').run(roomId);
297
+ for (const table of ['files', 'message_requests', 'events', 'idempotency', 'handovers', 'controls', 'invites', 'participants']) {
298
+ this.db.prepare(`DELETE FROM ${table} WHERE room_id = ?`).run(roomId);
299
+ }
300
+ });
301
+ }
302
+ async dueAutoCloses(now, limit) {
303
+ const rows = this.db
304
+ .prepare("SELECT room_id FROM rooms WHERE json_extract(body, '$.lifecycle') = 'open' AND json_extract(body, '$.autoCloseAt') IS NOT NULL AND json_extract(body, '$.autoCloseAt') <= ? ORDER BY json_extract(body, '$.autoCloseAt') LIMIT ?")
305
+ .all(now, limit);
306
+ return rows.map((row) => row.room_id);
307
+ }
308
+ async nextAutoCloseAt() {
309
+ const row = this.db.prepare("SELECT min(json_extract(body, '$.autoCloseAt')) AS at FROM rooms WHERE json_extract(body, '$.lifecycle') = 'open' AND json_extract(body, '$.autoCloseAt') IS NOT NULL").get();
310
+ return row.at;
311
+ }
312
+ /** Simulates retention dropping the front of the log without touching room state. */
313
+ dropHistoryBefore(roomId, seq) {
314
+ this.transaction(() => {
315
+ this.db.prepare('DELETE FROM events WHERE room_id = ? AND seq < ?').run(roomId, seq);
316
+ this.db.prepare('UPDATE rooms SET earliest_seq = ? WHERE room_id = ?').run(seq, roomId);
317
+ });
318
+ }
319
+ writeParticipant(participant) {
320
+ this.db
321
+ .prepare('INSERT INTO participants (participant_id, room_id, credential_hash, body) VALUES (?, ?, ?, ?) ON CONFLICT (participant_id) DO UPDATE SET body = excluded.body')
322
+ .run(participant.participantId, participant.roomId, participant.credentialHash, JSON.stringify(participant));
323
+ }
324
+ writeEvent(event) {
325
+ this.db.prepare('INSERT INTO events (room_id, seq, body) VALUES (?, ?, ?)').run(event.roomId, event.seq, JSON.stringify(event));
326
+ }
327
+ rows(sql, roomId) {
328
+ return this.db.prepare(sql).all(roomId).map((row) => JSON.parse(row.body));
329
+ }
330
+ }
331
+ //# sourceMappingURL=sqlite-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite-store.js","sourceRoot":"","sources":["../src/sqlite-store.ts"],"names":[],"mappings":"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"}