@openlivesync/server 1.0.2

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 (82) hide show
  1. package/README.md +366 -0
  2. package/dist/auth/decode-token.d.ts +40 -0
  3. package/dist/auth/decode-token.d.ts.map +1 -0
  4. package/dist/auth/decode-token.js +93 -0
  5. package/dist/auth/decode-token.js.map +1 -0
  6. package/dist/auth/index.d.ts +6 -0
  7. package/dist/auth/index.d.ts.map +1 -0
  8. package/dist/auth/index.js +6 -0
  9. package/dist/auth/index.js.map +1 -0
  10. package/dist/auth/token-auth.d.ts +18 -0
  11. package/dist/auth/token-auth.d.ts.map +1 -0
  12. package/dist/auth/token-auth.js +45 -0
  13. package/dist/auth/token-auth.js.map +1 -0
  14. package/dist/connection.d.ts +36 -0
  15. package/dist/connection.d.ts.map +1 -0
  16. package/dist/connection.js +170 -0
  17. package/dist/connection.js.map +1 -0
  18. package/dist/index.d.ts +19 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +15 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/protocol.d.ts +135 -0
  23. package/dist/protocol.d.ts.map +1 -0
  24. package/dist/protocol.js +17 -0
  25. package/dist/protocol.js.map +1 -0
  26. package/dist/room-manager.d.ts +19 -0
  27. package/dist/room-manager.d.ts.map +1 -0
  28. package/dist/room-manager.js +34 -0
  29. package/dist/room-manager.js.map +1 -0
  30. package/dist/room.d.ts +41 -0
  31. package/dist/room.d.ts.map +1 -0
  32. package/dist/room.js +150 -0
  33. package/dist/room.js.map +1 -0
  34. package/dist/server.d.ts +46 -0
  35. package/dist/server.d.ts.map +1 -0
  36. package/dist/server.js +105 -0
  37. package/dist/server.js.map +1 -0
  38. package/dist/storage/chat-storage.d.ts +19 -0
  39. package/dist/storage/chat-storage.d.ts.map +1 -0
  40. package/dist/storage/chat-storage.js +6 -0
  41. package/dist/storage/chat-storage.js.map +1 -0
  42. package/dist/storage/in-memory.d.ts +11 -0
  43. package/dist/storage/in-memory.d.ts.map +1 -0
  44. package/dist/storage/in-memory.js +35 -0
  45. package/dist/storage/in-memory.js.map +1 -0
  46. package/dist/storage/mysql.d.ts +19 -0
  47. package/dist/storage/mysql.d.ts.map +1 -0
  48. package/dist/storage/mysql.js +70 -0
  49. package/dist/storage/mysql.js.map +1 -0
  50. package/dist/storage/postgres.d.ts +21 -0
  51. package/dist/storage/postgres.d.ts.map +1 -0
  52. package/dist/storage/postgres.js +70 -0
  53. package/dist/storage/postgres.js.map +1 -0
  54. package/dist/storage/sqlite.d.ts +15 -0
  55. package/dist/storage/sqlite.d.ts.map +1 -0
  56. package/dist/storage/sqlite.js +71 -0
  57. package/dist/storage/sqlite.js.map +1 -0
  58. package/package.json +51 -0
  59. package/src/auth/decode-token.test.ts +119 -0
  60. package/src/auth/decode-token.ts +138 -0
  61. package/src/auth/index.ts +16 -0
  62. package/src/auth/token-auth.test.ts +95 -0
  63. package/src/auth/token-auth.ts +55 -0
  64. package/src/connection.test.ts +339 -0
  65. package/src/connection.ts +204 -0
  66. package/src/index.ts +80 -0
  67. package/src/protocol.test.ts +29 -0
  68. package/src/protocol.ts +137 -0
  69. package/src/room-manager.ts +45 -0
  70. package/src/room.test.ts +175 -0
  71. package/src/room.ts +207 -0
  72. package/src/server.test.ts +223 -0
  73. package/src/server.ts +153 -0
  74. package/src/storage/chat-storage.ts +23 -0
  75. package/src/storage/db-types.d.ts +43 -0
  76. package/src/storage/in-memory.test.ts +96 -0
  77. package/src/storage/in-memory.ts +52 -0
  78. package/src/storage/mysql.ts +117 -0
  79. package/src/storage/postgres.ts +117 -0
  80. package/src/storage/sqlite.ts +120 -0
  81. package/tsconfig.json +11 -0
  82. package/vitest.config.ts +32 -0
@@ -0,0 +1,117 @@
1
+ /**
2
+ * MySQL chat storage. Requires optional peer dependency: mysql2
3
+ * Install with: npm install mysql2
4
+ */
5
+
6
+ import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
7
+ import type { ChatStorage } from "./chat-storage.js";
8
+
9
+ export interface MySQLChatStorageOptions {
10
+ tableName?: string;
11
+ historyLimit?: number;
12
+ }
13
+
14
+ const DEFAULT_TABLE = "openlivesync_chat";
15
+
16
+ export interface MySQLConnectionConfig {
17
+ host?: string;
18
+ port?: number;
19
+ database?: string;
20
+ user?: string;
21
+ password?: string;
22
+ [key: string]: unknown;
23
+ }
24
+
25
+ export async function createMySQLChatStorage(
26
+ connectionConfig: MySQLConnectionConfig,
27
+ options: MySQLChatStorageOptions = {}
28
+ ): Promise<ChatStorage> {
29
+ let createPool: typeof import("mysql2/promise").createPool;
30
+ try {
31
+ const mysql = await import("mysql2/promise");
32
+ createPool = mysql.createPool;
33
+ } catch {
34
+ throw new Error(
35
+ 'MySQL storage requires the "mysql2" package. Install it with: npm install mysql2'
36
+ );
37
+ }
38
+
39
+ const pool = createPool(connectionConfig);
40
+ const tableName = options.tableName ?? DEFAULT_TABLE;
41
+
42
+ const init = async (): Promise<void> => {
43
+ await pool.query(`
44
+ CREATE TABLE IF NOT EXISTS \`${tableName}\` (
45
+ id VARCHAR(64) PRIMARY KEY,
46
+ room_id VARCHAR(255) NOT NULL,
47
+ connection_id VARCHAR(255) NOT NULL,
48
+ user_id VARCHAR(255),
49
+ message TEXT NOT NULL,
50
+ metadata JSON,
51
+ created_at BIGINT NOT NULL,
52
+ INDEX idx_room_created (room_id, created_at)
53
+ )
54
+ `);
55
+ };
56
+ await init();
57
+
58
+ return {
59
+ async append(roomId: string, message: ChatMessageInput): Promise<void> {
60
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
61
+ const createdAt = Date.now();
62
+ await pool.query(
63
+ `INSERT INTO \`${tableName}\` (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES (?, ?, ?, ?, ?, ?, ?)`,
64
+ [
65
+ id,
66
+ roomId,
67
+ message.connectionId,
68
+ message.userId ?? null,
69
+ message.message,
70
+ message.metadata ? JSON.stringify(message.metadata) : null,
71
+ createdAt,
72
+ ]
73
+ );
74
+ },
75
+
76
+ async getHistory(
77
+ roomId: string,
78
+ limit: number = (options.historyLimit ?? 100),
79
+ offset: number = 0
80
+ ): Promise<StoredChatMessage[]> {
81
+ const [rows] = await pool.query<
82
+ Array<{
83
+ id: string;
84
+ room_id: string;
85
+ connection_id: string;
86
+ user_id: string | null;
87
+ message: string;
88
+ metadata: string | null;
89
+ created_at: number | string;
90
+ }>
91
+ >(
92
+ `SELECT id, room_id, connection_id, user_id, message, metadata, created_at
93
+ FROM \`${tableName}\`
94
+ WHERE room_id = ?
95
+ ORDER BY created_at DESC
96
+ LIMIT ? OFFSET ?`,
97
+ [roomId, limit, offset]
98
+ );
99
+ const list = Array.isArray(rows) ? rows : [];
100
+ return list
101
+ .map((r) => ({
102
+ id: r.id,
103
+ roomId: r.room_id,
104
+ connectionId: r.connection_id,
105
+ userId: r.user_id ?? undefined,
106
+ message: r.message,
107
+ metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
108
+ createdAt: Number(r.created_at),
109
+ }))
110
+ .reverse();
111
+ },
112
+
113
+ async close(): Promise<void> {
114
+ await pool.end();
115
+ },
116
+ };
117
+ }
@@ -0,0 +1,117 @@
1
+ /**
2
+ * Postgres chat storage. Requires optional peer dependency: pg
3
+ * Install with: npm install pg
4
+ */
5
+
6
+ import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
7
+ import type { ChatStorage } from "./chat-storage.js";
8
+
9
+ export interface PostgresChatStorageOptions {
10
+ tableName?: string;
11
+ historyLimit?: number;
12
+ }
13
+
14
+ const DEFAULT_TABLE = "openlivesync_chat";
15
+
16
+ export type PostgresConnectionConfig =
17
+ | { connectionString: string }
18
+ | {
19
+ host?: string;
20
+ port?: number;
21
+ database?: string;
22
+ user?: string;
23
+ password?: string;
24
+ [key: string]: unknown;
25
+ };
26
+
27
+ export async function createPostgresChatStorage(
28
+ connectionConfig: PostgresConnectionConfig,
29
+ options: PostgresChatStorageOptions = {}
30
+ ): Promise<ChatStorage> {
31
+ let Pool: typeof import("pg").Pool;
32
+ try {
33
+ const pg = await import("pg");
34
+ Pool = pg.Pool;
35
+ } catch {
36
+ throw new Error(
37
+ 'Postgres storage requires the "pg" package. Install it with: npm install pg'
38
+ );
39
+ }
40
+
41
+ const pool = new Pool(connectionConfig as import("pg").PoolConfig);
42
+ const tableName = options.tableName ?? DEFAULT_TABLE;
43
+
44
+ const init = async (): Promise<void> => {
45
+ await pool.query(`
46
+ CREATE TABLE IF NOT EXISTS ${tableName} (
47
+ id TEXT PRIMARY KEY,
48
+ room_id TEXT NOT NULL,
49
+ connection_id TEXT NOT NULL,
50
+ user_id TEXT,
51
+ message TEXT NOT NULL,
52
+ metadata JSONB,
53
+ created_at BIGINT NOT NULL
54
+ );
55
+ CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
56
+ `);
57
+ };
58
+ await init();
59
+
60
+ return {
61
+ async append(roomId: string, message: ChatMessageInput): Promise<void> {
62
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
63
+ const createdAt = Date.now();
64
+ await pool.query(
65
+ `INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7)`,
66
+ [
67
+ id,
68
+ roomId,
69
+ message.connectionId,
70
+ message.userId ?? null,
71
+ message.message,
72
+ message.metadata ? JSON.stringify(message.metadata) : null,
73
+ createdAt,
74
+ ]
75
+ );
76
+ },
77
+
78
+ async getHistory(
79
+ roomId: string,
80
+ limit: number = (options.historyLimit ?? 100),
81
+ offset: number = 0
82
+ ): Promise<StoredChatMessage[]> {
83
+ const result = await pool.query(
84
+ `SELECT id, room_id AS "roomId", connection_id AS "connectionId", user_id AS "userId", message, metadata, created_at AS "createdAt"
85
+ FROM ${tableName}
86
+ WHERE room_id = $1
87
+ ORDER BY created_at DESC
88
+ LIMIT $2 OFFSET $3`,
89
+ [roomId, limit, offset]
90
+ );
91
+ const rows = result.rows as Array<{
92
+ id: string;
93
+ roomId: string;
94
+ connectionId: string;
95
+ userId: string | null;
96
+ message: string;
97
+ metadata: string | null;
98
+ createdAt: string;
99
+ }>;
100
+ return rows
101
+ .map((r) => ({
102
+ id: r.id,
103
+ roomId: r.roomId,
104
+ connectionId: r.connectionId,
105
+ userId: r.userId ?? undefined,
106
+ message: r.message,
107
+ metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
108
+ createdAt: Number(r.createdAt),
109
+ }))
110
+ .reverse();
111
+ },
112
+
113
+ async close(): Promise<void> {
114
+ await pool.end();
115
+ },
116
+ };
117
+ }
@@ -0,0 +1,120 @@
1
+ /**
2
+ * SQLite chat storage. Requires optional peer dependency: better-sqlite3
3
+ * Install with: npm install better-sqlite3
4
+ */
5
+
6
+ import { createRequire } from "node:module";
7
+ import type { ChatMessageInput, StoredChatMessage } from "../protocol.js";
8
+ import type { ChatStorage } from "./chat-storage.js";
9
+
10
+ const require = createRequire(import.meta.url);
11
+
12
+ export interface SQLiteChatStorageOptions {
13
+ tableName?: string;
14
+ historyLimit?: number;
15
+ }
16
+
17
+ const DEFAULT_TABLE = "openlivesync_chat";
18
+
19
+ export type SQLiteConnectionConfig = string | { filename: string; [key: string]: unknown };
20
+
21
+ export function createSQLiteChatStorage(
22
+ connectionConfig: SQLiteConnectionConfig,
23
+ options: SQLiteChatStorageOptions = {}
24
+ ): ChatStorage {
25
+ let Database: new (filename: string, options?: Record<string, unknown>) => {
26
+ exec(sql: string): unknown;
27
+ prepare(sql: string): { run(...args: unknown[]): unknown; all(...args: unknown[]): unknown[] };
28
+ close(): void;
29
+ };
30
+ try {
31
+ Database = require("better-sqlite3") as typeof Database;
32
+ } catch {
33
+ throw new Error(
34
+ 'SQLite storage requires the "better-sqlite3" package. Install it with: npm install better-sqlite3'
35
+ );
36
+ }
37
+
38
+ const config =
39
+ typeof connectionConfig === "string"
40
+ ? { filename: connectionConfig }
41
+ : connectionConfig;
42
+ const db = new Database(config.filename as string);
43
+ const tableName = options.tableName ?? DEFAULT_TABLE;
44
+
45
+ db.exec(`
46
+ CREATE TABLE IF NOT EXISTS ${tableName} (
47
+ id TEXT PRIMARY KEY,
48
+ room_id TEXT NOT NULL,
49
+ connection_id TEXT NOT NULL,
50
+ user_id TEXT,
51
+ message TEXT NOT NULL,
52
+ metadata TEXT,
53
+ created_at INTEGER NOT NULL
54
+ );
55
+ CREATE INDEX IF NOT EXISTS idx_${tableName.replace(/-/g, "_")}_room_created ON ${tableName}(room_id, created_at);
56
+ `);
57
+
58
+ const appendStmt = db.prepare(`
59
+ INSERT INTO ${tableName} (id, room_id, connection_id, user_id, message, metadata, created_at)
60
+ VALUES (?, ?, ?, ?, ?, ?, ?)
61
+ `);
62
+
63
+ return {
64
+ append(roomId: string, message: ChatMessageInput): Promise<void> {
65
+ const id = `msg_${Date.now()}_${Math.random().toString(36).slice(2, 11)}`;
66
+ const createdAt = Date.now();
67
+ appendStmt.run(
68
+ id,
69
+ roomId,
70
+ message.connectionId,
71
+ message.userId ?? null,
72
+ message.message,
73
+ message.metadata ? JSON.stringify(message.metadata) : null,
74
+ createdAt
75
+ );
76
+ return Promise.resolve();
77
+ },
78
+
79
+ getHistory(
80
+ roomId: string,
81
+ limit: number = (options.historyLimit ?? 100),
82
+ offset: number = 0
83
+ ): Promise<StoredChatMessage[]> {
84
+ const rows = db
85
+ .prepare(
86
+ `SELECT id, room_id AS roomId, connection_id AS connectionId, user_id AS userId, message, metadata, created_at AS createdAt
87
+ FROM ${tableName}
88
+ WHERE room_id = ?
89
+ ORDER BY created_at DESC
90
+ LIMIT ? OFFSET ?`
91
+ )
92
+ .all(roomId, limit, offset) as Array<{
93
+ id: string;
94
+ roomId: string;
95
+ connectionId: string;
96
+ userId: string | null;
97
+ message: string;
98
+ metadata: string | null;
99
+ createdAt: number;
100
+ }>;
101
+ const result = rows
102
+ .map((r) => ({
103
+ id: r.id,
104
+ roomId: r.roomId,
105
+ connectionId: r.connectionId,
106
+ userId: r.userId ?? undefined,
107
+ message: r.message,
108
+ metadata: r.metadata ? (JSON.parse(r.metadata) as Record<string, unknown>) : undefined,
109
+ createdAt: r.createdAt,
110
+ }))
111
+ .reverse();
112
+ return Promise.resolve(result);
113
+ },
114
+
115
+ close(): Promise<void> {
116
+ db.close();
117
+ return Promise.resolve();
118
+ },
119
+ };
120
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,11 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "module": "NodeNext",
7
+ "target": "ES2022"
8
+ },
9
+ "include": ["src"],
10
+ "exclude": ["src/**/*.test.ts", "src/**/*.spec.ts"]
11
+ }
@@ -0,0 +1,32 @@
1
+ import { defineConfig } from "vitest/config";
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: false,
6
+ environment: "node",
7
+ include: ["src/**/*.test.ts"],
8
+ hookTimeout: 15000,
9
+ testTimeout: 15000,
10
+ coverage: {
11
+ provider: "v8",
12
+ reporter: ["text", "text-summary", "html", "lcov"],
13
+ reportsDirectory: "./coverage",
14
+ include: ["src/**/*.ts"],
15
+ exclude: [
16
+ "node_modules/",
17
+ "dist/",
18
+ "examples/", // examples are demos, not library code
19
+ "**/*.test.ts",
20
+ "**/*.spec.ts",
21
+ "**/db-types.d.ts",
22
+ "vitest.config.ts",
23
+ "src/storage/postgres.ts",
24
+ "src/storage/mysql.ts",
25
+ "src/storage/sqlite.ts",
26
+ "src/index.ts",
27
+ "src/storage/chat-storage.ts",
28
+ "src/auth/index.ts",
29
+ ],
30
+ },
31
+ },
32
+ });