@signe/room 2.10.0 → 3.0.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 (82) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/chunk-EUXUH3YW.js +15 -0
  3. package/dist/chunk-EUXUH3YW.js.map +1 -0
  4. package/dist/cloudflare/index.d.ts +71 -0
  5. package/dist/cloudflare/index.js +320 -0
  6. package/dist/cloudflare/index.js.map +1 -0
  7. package/dist/index.d.ts +66 -187
  8. package/dist/index.js +727 -106
  9. package/dist/index.js.map +1 -1
  10. package/dist/node/index.d.ts +164 -0
  11. package/dist/node/index.js +786 -0
  12. package/dist/node/index.js.map +1 -0
  13. package/dist/party-dNs-hqkq.d.ts +175 -0
  14. package/examples/cloudflare/README.md +62 -0
  15. package/examples/cloudflare/node_modules/.bin/tsc +17 -0
  16. package/examples/cloudflare/node_modules/.bin/tsserver +17 -0
  17. package/examples/cloudflare/node_modules/.bin/wrangler +17 -0
  18. package/examples/cloudflare/node_modules/.bin/wrangler2 +17 -0
  19. package/examples/cloudflare/package.json +24 -0
  20. package/examples/cloudflare/public/index.html +443 -0
  21. package/examples/cloudflare/src/index.ts +28 -0
  22. package/examples/cloudflare/src/room.ts +44 -0
  23. package/examples/cloudflare/tsconfig.json +10 -0
  24. package/examples/cloudflare/wrangler.jsonc +25 -0
  25. package/examples/node/README.md +57 -0
  26. package/examples/node/node_modules/.bin/tsc +17 -0
  27. package/examples/node/node_modules/.bin/tsserver +17 -0
  28. package/examples/node/node_modules/.bin/tsx +17 -0
  29. package/examples/node/package.json +23 -0
  30. package/examples/node/public/index.html +443 -0
  31. package/examples/node/room.ts +44 -0
  32. package/examples/node/server.sqlite.ts +52 -0
  33. package/examples/node/server.ts +51 -0
  34. package/examples/node/tsconfig.json +10 -0
  35. package/examples/node-game/README.md +66 -0
  36. package/examples/node-game/package.json +23 -0
  37. package/examples/node-game/public/index.html +705 -0
  38. package/examples/node-game/room.ts +145 -0
  39. package/examples/node-game/server.sqlite.ts +54 -0
  40. package/examples/node-game/server.ts +53 -0
  41. package/examples/node-game/tsconfig.json +10 -0
  42. package/examples/node-shard/README.md +32 -0
  43. package/examples/node-shard/dev.ts +39 -0
  44. package/examples/node-shard/package.json +24 -0
  45. package/examples/node-shard/public/index.html +777 -0
  46. package/examples/node-shard/room-server.ts +68 -0
  47. package/examples/node-shard/room.ts +105 -0
  48. package/examples/node-shard/shared.ts +6 -0
  49. package/examples/node-shard/tsconfig.json +14 -0
  50. package/examples/node-shard/world-server.ts +169 -0
  51. package/package.json +14 -5
  52. package/readme.md +371 -4
  53. package/src/cloudflare/index.ts +474 -0
  54. package/src/jwt.ts +1 -5
  55. package/src/mock.ts +29 -7
  56. package/src/node/index.ts +1112 -0
  57. package/src/server.ts +600 -51
  58. package/src/session.guard.ts +6 -2
  59. package/src/shard.ts +91 -23
  60. package/src/storage.ts +29 -5
  61. package/src/testing.ts +4 -3
  62. package/src/types/party.ts +4 -1
  63. package/src/world.guard.ts +23 -4
  64. package/src/world.ts +121 -21
  65. package/examples/game/.vscode/launch.json +0 -11
  66. package/examples/game/.vscode/settings.json +0 -11
  67. package/examples/game/README.md +0 -40
  68. package/examples/game/app/client.tsx +0 -15
  69. package/examples/game/app/components/Admin.tsx +0 -1089
  70. package/examples/game/app/components/Room.tsx +0 -162
  71. package/examples/game/app/styles.css +0 -31
  72. package/examples/game/package-lock.json +0 -225
  73. package/examples/game/package.json +0 -20
  74. package/examples/game/party/game.room.ts +0 -32
  75. package/examples/game/party/server.ts +0 -10
  76. package/examples/game/party/shard.ts +0 -5
  77. package/examples/game/partykit.json +0 -14
  78. package/examples/game/public/favicon.ico +0 -0
  79. package/examples/game/public/index.html +0 -27
  80. package/examples/game/public/normalize.css +0 -351
  81. package/examples/game/shared/room.schema.ts +0 -14
  82. package/examples/game/tsconfig.json +0 -109
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/node/index.ts"],"sourcesContent":["import type { IncomingMessage, ServerResponse as NodeServerResponse } from \"node:http\";\nimport { EventEmitter } from \"node:events\";\nimport { createRequire } from \"node:module\";\nimport type { Duplex } from \"node:stream\";\nimport type * as Party from \"../types/party\";\n\nexport type NodeRoomStorage = {\n get<T = unknown>(key: string): Promise<T | undefined>;\n put<T = unknown>(key: string, value: T): Promise<void>;\n put<T = unknown>(entries: Record<string, T>): Promise<void>;\n delete(key: string | string[]): Promise<void | boolean | number>;\n list<T = unknown>(options?: NodeRoomStorageListOptions): Promise<Map<string, T>>;\n};\n\nexport type NodeRoomStorageListOptions = {\n prefix?: string;\n start?: string;\n startAfter?: string;\n end?: string;\n reverse?: boolean;\n limit?: number;\n};\n\nexport type NodeRoomStorageFactory = (\n namespace: string,\n roomId: string\n) => NodeRoomStorage | Promise<NodeRoomStorage>;\n\nexport type NodeRoomStorageProvider = {\n getStorage(namespace: string, roomId: string): NodeRoomStorage | Promise<NodeRoomStorage>;\n};\n\nexport type NodeMemoryStorageSnapshot = Record<string, [string, unknown][]>;\n\nexport type NodeSqliteDatabase = {\n exec(sql: string): unknown;\n prepare(sql: string): {\n get(...params: unknown[]): unknown;\n run(...params: unknown[]): { changes: number | bigint };\n all(...params: unknown[]): unknown[];\n };\n};\n\nexport type NodeSqliteStorageOptions = {\n database?: NodeSqliteDatabase;\n databasePath?: string;\n tableName?: string;\n busyTimeoutMs?: number;\n journalMode?: NodeSqliteJournalMode;\n busyRetries?: number;\n};\n\nexport type NodeSqliteJournalMode =\n | \"DELETE\"\n | \"TRUNCATE\"\n | \"PERSIST\"\n | \"MEMORY\"\n | \"WAL\"\n | \"OFF\"\n | \"delete\"\n | \"truncate\"\n | \"persist\"\n | \"memory\"\n | \"wal\"\n | \"off\";\n\nexport type NodeServerConstructor<TServer extends Party.Server = Party.Server> = {\n new (room: Party.Room): TServer;\n};\n\nexport type NodeRoomTransportOptions = {\n partiesPath?: string;\n env?: Record<string, unknown>;\n storage?: NodeRoomStorageFactory | NodeRoomStorageProvider;\n rooms?: Record<string, NodeServerConstructor>;\n externalParties?: Record<string, {\n get(id: string): Partial<Party.Stub>;\n }>;\n};\n\nexport type NodeRequestNext = (error?: unknown) => void;\n\nexport type NodeWebSocketLike = {\n readyState?: number;\n send(data: string | ArrayBuffer | ArrayBufferView, cb?: (error?: Error) => void): void;\n close(code?: number, reason?: string | Buffer): void;\n on(event: string, listener: (...args: any[]) => void): unknown;\n off?(event: string, listener: (...args: any[]) => void): unknown;\n removeListener?(event: string, listener: (...args: any[]) => void): unknown;\n};\n\nexport type NodeWebSocketServerLike = {\n handleUpgrade(\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer,\n cb: (webSocket: NodeWebSocketLike) => void\n ): void;\n emit?(event: \"connection\", webSocket: NodeWebSocketLike, request: IncomingMessage): boolean;\n};\n\ntype RoomRecord = {\n room: NodeRoom;\n server: Party.Server;\n started: Promise<void>;\n};\n\ntype ParsedPartyPath = {\n namespace: string;\n roomId: string;\n restPath: string;\n};\n\nconst DEFAULT_PARTIES_PATH = \"/parties/main\";\nconst WEBSOCKET_OPEN = 1;\nconst DEFAULT_SQLITE_BUSY_TIMEOUT_MS = 5000;\nconst DEFAULT_SQLITE_BUSY_RETRIES = 3;\nconst SQLITE_RETRY_BASE_DELAY_MS = 25;\n\nexport function createMemoryNodeRoomStorage(options: {\n snapshot?: NodeMemoryStorageSnapshot;\n} = {}) {\n return new MemoryNodeRoomStorage(options);\n}\n\nexport function createSqliteNodeRoomStorage(options: NodeSqliteStorageOptions) {\n return new SqliteNodeRoomStorage(options);\n}\n\nexport function createNodeRoomTransport<TServer extends Party.Server>(\n ServerClass: NodeServerConstructor<TServer>,\n options: NodeRoomTransportOptions = {}\n) {\n return new NodeRoomTransport(ServerClass, options);\n}\n\nexport class MemoryNodeRoomStorage implements NodeRoomStorageProvider {\n private readonly rooms = new Map<string, Map<string, unknown>>();\n\n constructor(options: { snapshot?: NodeMemoryStorageSnapshot } = {}) {\n if (options.snapshot) {\n this.restore(options.snapshot);\n }\n }\n\n getStorage(namespace: string, roomId: string): NodeRoomStorage {\n const key = getStorageKey(namespace, roomId);\n let memory = this.rooms.get(key);\n\n if (!memory) {\n memory = new Map();\n this.rooms.set(key, memory);\n }\n\n return new MemoryNodeRoomStorageInstance(memory);\n }\n\n snapshot(): NodeMemoryStorageSnapshot {\n const snapshot: NodeMemoryStorageSnapshot = {};\n\n for (const [roomKey, memory] of this.rooms) {\n if (memory.size === 0) {\n continue;\n }\n\n snapshot[roomKey] = Array.from(memory.entries()).map(([key, value]) => [\n key,\n cloneStorageValue(value),\n ]);\n }\n\n return snapshot;\n }\n\n restore(snapshot: NodeMemoryStorageSnapshot) {\n this.clear();\n\n for (const [roomKey, entries] of Object.entries(snapshot)) {\n this.rooms.set(\n roomKey,\n new Map(entries.map(([key, value]) => [key, cloneStorageValue(value)]))\n );\n }\n }\n\n clear() {\n this.rooms.clear();\n }\n}\n\nclass MemoryNodeRoomStorageInstance implements NodeRoomStorage {\n constructor(private readonly memory: Map<string, unknown>) {}\n\n async put<T = unknown>(keyOrEntries: string | Record<string, T>, value?: T) {\n if (typeof keyOrEntries === \"string\") {\n this.memory.set(keyOrEntries, value);\n return;\n }\n\n for (const [key, entryValue] of Object.entries(keyOrEntries)) {\n this.memory.set(key, entryValue);\n }\n }\n\n async get<T = unknown>(key: string) {\n return this.memory.get(key) as T | undefined;\n }\n\n async delete(keyOrKeys: string | string[]) {\n if (Array.isArray(keyOrKeys)) {\n let deleted = 0;\n for (const key of keyOrKeys) {\n if (this.memory.delete(key)) {\n deleted += 1;\n }\n }\n return deleted;\n }\n\n return this.memory.delete(keyOrKeys);\n }\n\n async list<T = unknown>(options: NodeRoomStorageListOptions = {}) {\n let entries = Array.from(this.memory.entries());\n\n if (options.prefix !== undefined) {\n entries = entries.filter(([key]) => key.startsWith(options.prefix!));\n }\n if (options.start !== undefined) {\n entries = entries.filter(([key]) => key >= options.start!);\n }\n if (options.startAfter !== undefined) {\n entries = entries.filter(([key]) => key > options.startAfter!);\n }\n if (options.end !== undefined) {\n entries = entries.filter(([key]) => key < options.end!);\n }\n\n entries.sort(([a], [b]) => a.localeCompare(b));\n if (options.reverse) {\n entries.reverse();\n }\n if (options.limit !== undefined) {\n entries = entries.slice(0, options.limit);\n }\n\n return new Map(entries) as Map<string, T>;\n }\n}\n\nexport class SqliteNodeRoomStorage implements NodeRoomStorageProvider {\n private readonly tableName: string;\n private databasePromise?: Promise<NodeSqliteDatabase>;\n private configured = false;\n\n constructor(private readonly options: NodeSqliteStorageOptions) {\n if (!options.database && !options.databasePath) {\n throw new Error(\"createSqliteNodeRoomStorage requires `database` or `databasePath`.\");\n }\n\n this.tableName = options.tableName ?? \"signe_room_storage\";\n assertSafeSqlIdentifier(this.tableName);\n }\n\n async getStorage(namespace: string, roomId: string): Promise<NodeRoomStorage> {\n const database = await this.getDatabase();\n await this.ensureConfigured(database);\n return new SqliteNodeRoomStorageInstance(\n database,\n this.tableName,\n namespace,\n roomId,\n this.options.busyRetries ?? DEFAULT_SQLITE_BUSY_RETRIES\n );\n }\n\n private async getDatabase() {\n if (!this.databasePromise) {\n this.databasePromise = this.createDatabase();\n }\n\n return this.databasePromise;\n }\n\n private async createDatabase(): Promise<NodeSqliteDatabase> {\n if (this.options.database) {\n return this.options.database;\n }\n\n const sqliteModule = loadNodeSqliteModule();\n const { DatabaseSync } = sqliteModule;\n return new DatabaseSync(this.options.databasePath!) as NodeSqliteDatabase;\n }\n\n private async ensureConfigured(database: NodeSqliteDatabase) {\n if (this.configured) {\n return;\n }\n\n const busyTimeoutMs = normalizeSqliteTimeout(\n this.options.busyTimeoutMs ?? DEFAULT_SQLITE_BUSY_TIMEOUT_MS\n );\n const journalMode = normalizeSqliteJournalMode(this.options.journalMode ?? \"WAL\");\n\n runSqliteOperation(\n () => {\n database.exec(`PRAGMA busy_timeout = ${busyTimeoutMs}`);\n database.exec(`PRAGMA journal_mode = ${journalMode}`);\n database.exec(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n namespace TEXT NOT NULL,\n room_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value TEXT NOT NULL,\n PRIMARY KEY (namespace, room_id, key)\n )\n `);\n },\n this.options.busyRetries ?? DEFAULT_SQLITE_BUSY_RETRIES\n );\n this.configured = true;\n }\n}\n\nclass SqliteNodeRoomStorageInstance implements NodeRoomStorage {\n constructor(\n private readonly database: NodeSqliteDatabase,\n private readonly tableName: string,\n private readonly namespace: string,\n private readonly roomId: string,\n private readonly busyRetries: number\n ) {}\n\n async get<T = unknown>(key: string): Promise<T | undefined> {\n const row = runSqliteOperation(\n () => this.database\n .prepare(`\n SELECT value\n FROM ${this.tableName}\n WHERE namespace = ? AND room_id = ? AND key = ?\n `)\n .get(this.namespace, this.roomId, key) as { value: string } | undefined,\n this.busyRetries\n );\n\n return row ? JSON.parse(row.value) as T : undefined;\n }\n\n async put<T = unknown>(keyOrEntries: string | Record<string, T>, value?: T): Promise<void> {\n const entries = typeof keyOrEntries === \"string\"\n ? [[keyOrEntries, value] as const]\n : Object.entries(keyOrEntries);\n\n runSqliteOperation(\n () => {\n const statement = this.database.prepare(`\n INSERT INTO ${this.tableName} (namespace, room_id, key, value)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(namespace, room_id, key) DO UPDATE SET value = excluded.value\n `);\n for (const [key, entryValue] of entries) {\n statement.run(this.namespace, this.roomId, key, JSON.stringify(entryValue));\n }\n },\n this.busyRetries\n );\n }\n\n async delete(keyOrKeys: string | string[]): Promise<boolean | number> {\n if (Array.isArray(keyOrKeys)) {\n if (keyOrKeys.length === 0) {\n return 0;\n }\n\n const result = runSqliteOperation(\n () => this.database\n .prepare(`\n DELETE FROM ${this.tableName}\n WHERE namespace = ? AND room_id = ? AND key IN (${keyOrKeys.map(() => \"?\").join(\", \")})\n `)\n .run(this.namespace, this.roomId, ...keyOrKeys),\n this.busyRetries\n );\n\n return Number(result.changes);\n }\n\n const result = runSqliteOperation(\n () => this.database\n .prepare(`\n DELETE FROM ${this.tableName}\n WHERE namespace = ? AND room_id = ? AND key = ?\n `)\n .run(this.namespace, this.roomId, keyOrKeys),\n this.busyRetries\n );\n\n return Number(result.changes) > 0;\n }\n\n async list<T = unknown>(options: NodeRoomStorageListOptions = {}): Promise<Map<string, T>> {\n const conditions = [\"namespace = ?\", \"room_id = ?\"];\n const params: unknown[] = [this.namespace, this.roomId];\n\n if (options.prefix !== undefined) {\n conditions.push(\"key >= ?\", \"key < ?\");\n params.push(options.prefix, getPrefixEnd(options.prefix));\n }\n if (options.start !== undefined) {\n conditions.push(\"key >= ?\");\n params.push(options.start);\n }\n if (options.startAfter !== undefined) {\n conditions.push(\"key > ?\");\n params.push(options.startAfter);\n }\n if (options.end !== undefined) {\n conditions.push(\"key < ?\");\n params.push(options.end);\n }\n\n const limit = options.limit !== undefined ? normalizeSqliteLimit(options.limit) : undefined;\n const rows = runSqliteOperation(\n () => this.database\n .prepare(`\n SELECT key, value\n FROM ${this.tableName}\n WHERE ${conditions.join(\" AND \")}\n ORDER BY key ${options.reverse ? \"DESC\" : \"ASC\"}\n ${limit !== undefined ? `LIMIT ${limit}` : \"\"}\n `)\n .all(...params) as { key: string; value: string }[],\n this.busyRetries\n );\n\n return new Map(rows.map((row) => [row.key, JSON.parse(row.value) as T]));\n }\n}\n\nexport class NodeRoomTransport<TServer extends Party.Server = Party.Server> {\n readonly partiesPath: string;\n readonly env: Record<string, unknown>;\n readonly externalParties: Record<string, {\n get(id: string): Partial<Party.Stub>;\n }>;\n private readonly rooms: Record<string, NodeServerConstructor>;\n private readonly storage: NodeRoomStorageFactory | NodeRoomStorageProvider;\n private readonly records = new Map<string, Promise<RoomRecord>>();\n\n constructor(\n private readonly ServerClass: NodeServerConstructor<TServer>,\n options: NodeRoomTransportOptions = {}\n ) {\n this.partiesPath = normalizePath(options.partiesPath ?? DEFAULT_PARTIES_PATH);\n this.env = options.env ?? {};\n this.rooms = {\n main: ServerClass,\n ...(options.rooms ?? {}),\n };\n this.storage = options.storage ?? createMemoryNodeRoomStorage();\n this.externalParties = options.externalParties ?? {};\n }\n\n async fetch(pathOrRequest: string | Request, init?: RequestInit): Promise<Response> {\n const request = typeof pathOrRequest === \"string\"\n ? new Request(toLocalUrl(pathOrRequest), init)\n : pathOrRequest;\n const parsed = this.parsePartyRequest(request.url);\n\n if (!parsed) {\n return new Response(\"Not Found\", { status: 404 });\n }\n\n const record = await this.getRecord(parsed.namespace, parsed.roomId);\n return record.server.onRequest?.(request as unknown as Party.Request) ?? new Response(\"Not Found\", { status: 404 });\n }\n\n async handleNodeRequest(\n req: IncomingMessage,\n res: NodeServerResponse,\n next?: NodeRequestNext\n ): Promise<void> {\n const url = getRequestUrl(req);\n\n if (!this.parsePartyRequest(url)) {\n if (next) {\n next();\n return;\n }\n await writeNodeResponse(res, new Response(\"Not Found\", { status: 404 }));\n return;\n }\n\n try {\n const request = await createWebRequest(req, url);\n const response = await this.fetch(request);\n await writeNodeResponse(res, response);\n } catch (error) {\n if (next) {\n next(error);\n return;\n }\n await writeNodeResponse(res, new Response(\"Internal Server Error\", { status: 500 }));\n }\n }\n\n handleUpgrade(\n wsServer: NodeWebSocketServerLike,\n request: IncomingMessage,\n socket: Duplex,\n head: Buffer\n ): void {\n const parsed = this.parsePartyRequest(getRequestUrl(request));\n\n if (!parsed) {\n socket.destroy();\n return;\n }\n\n wsServer.handleUpgrade(request, socket, head, (webSocket) => {\n void this.acceptWebSocket(webSocket, request, parsed).catch(() => {\n webSocket.close(1011, \"Unable to start room connection\");\n });\n wsServer.emit?.(\"connection\", webSocket, request);\n });\n }\n\n async acceptWebSocket(\n webSocket: NodeWebSocketLike,\n request: IncomingMessage | Request,\n parsedPath?: ParsedPartyPath\n ): Promise<NodeConnection> {\n const url = request instanceof Request ? request.url : getRequestUrl(request);\n const parsed = parsedPath ?? this.parsePartyRequest(url);\n\n if (!parsed) {\n webSocket.close(1008, \"Invalid room path\");\n throw new Error(`Unable to route WebSocket URL: ${url}`);\n }\n\n const record = await this.getRecord(parsed.namespace, parsed.roomId);\n const connection = new NodeConnection(webSocket, url, getConnectionIdFromUrl(url));\n const connectRequest = request instanceof Request\n ? request\n : await createWebRequest(request, url, false);\n\n await record.server.onConnect?.(connection as unknown as Party.Connection, {\n request: connectRequest as unknown as Party.Request,\n });\n\n const onMessage = (data: unknown) => {\n void record.server.onMessage?.(normalizeWebSocketMessage(data), connection as unknown as Party.Connection);\n };\n const onClose = () => {\n record.room.deleteConnection(connection.id, connection);\n void record.server.onClose?.(connection as unknown as Party.Connection);\n };\n const onError = (error: Error) => {\n void record.server.onError?.(connection as unknown as Party.Connection, error);\n };\n\n webSocket.on(\"message\", onMessage);\n webSocket.on(\"close\", onClose);\n webSocket.on(\"error\", onError);\n record.room.addConnection(connection);\n\n return connection;\n }\n\n getRoom(namespace: string, roomId: string): Promise<NodeRoom> {\n return this.getRecord(namespace, roomId).then((record) => record.room);\n }\n\n getNamespacePath(namespace: string, roomId: string) {\n const baseSegments = trimSlashes(this.getPartiesBase()).split(\"/\").slice(0, -1);\n return `/${[...baseSegments, namespace, encodeURIComponent(roomId)].join(\"/\")}`;\n }\n\n private async getRecord(namespace: string, roomId: string): Promise<RoomRecord> {\n const key = `${namespace}:${roomId}`;\n const existing = this.records.get(key);\n\n if (existing) {\n return existing;\n }\n\n const recordPromise = this.createRecord(namespace, roomId);\n this.records.set(key, recordPromise);\n\n try {\n return await recordPromise;\n } catch (error) {\n this.records.delete(key);\n throw error;\n }\n }\n\n private async createRecord(namespace: string, roomId: string): Promise<RoomRecord> {\n const ServerClass = this.rooms[namespace] ?? this.ServerClass;\n const room = new NodeRoom({\n id: roomId,\n name: namespace,\n env: this.env,\n storage: await this.resolveStorage(namespace, roomId),\n transport: this,\n });\n const server = new ServerClass(room as Party.Room);\n const record: RoomRecord = {\n room,\n server,\n started: Promise.resolve(server.onStart?.()).then(() => undefined),\n };\n\n await record.started;\n return record;\n }\n\n private async resolveStorage(namespace: string, roomId: string): Promise<NodeRoomStorage> {\n if (typeof this.storage === \"function\") {\n return this.storage(namespace, roomId);\n }\n\n return this.storage.getStorage(namespace, roomId);\n }\n\n private parsePartyRequest(url: string): ParsedPartyPath | null {\n const requestUrl = new URL(url, \"http://localhost\");\n const partiesBase = this.getPartiesBase();\n const segments = trimSlashes(requestUrl.pathname).split(\"/\");\n const configuredSegments = trimSlashes(partiesBase).split(\"/\");\n const baseSegments = configuredSegments.slice(0, -1);\n\n if (segments.length < baseSegments.length + 2) {\n return null;\n }\n\n for (let index = 0; index < baseSegments.length; index++) {\n if (segments[index] !== baseSegments[index]) {\n return null;\n }\n }\n\n const namespace = decodeURIComponent(segments[baseSegments.length]);\n const roomId = decodeURIComponent(segments[baseSegments.length + 1]);\n const rest = segments.slice(baseSegments.length + 2).join(\"/\");\n\n return {\n namespace,\n roomId,\n restPath: rest ? `/${rest}` : \"/\",\n };\n }\n\n private getPartiesBase() {\n return this.partiesPath;\n }\n}\n\nexport class NodeRoom implements Party.Room {\n readonly id: string;\n readonly internalID: string;\n readonly name: string;\n readonly env: Record<string, unknown>;\n readonly storage: Party.Storage;\n readonly context: Party.Context;\n readonly connections = new Map<string, Party.Connection>();\n readonly parties: Party.Context[\"parties\"];\n readonly analytics = {} as Party.Room[\"analytics\"];\n\n constructor(options: {\n id: string;\n name: string;\n env: Record<string, unknown>;\n storage: NodeRoomStorage;\n transport: NodeRoomTransport;\n }) {\n this.id = options.id;\n this.internalID = `${options.name}:${options.id}`;\n this.name = options.name;\n this.env = options.env;\n this.storage = options.storage as Party.Storage;\n this.parties = createPartiesContext(options.transport);\n this.context = {\n parties: this.parties,\n ai: {},\n vectorize: {},\n analytics: this.analytics,\n assets: {\n fetch: async () => null,\n },\n bindings: {\n r2: {},\n kv: {},\n },\n } as Party.Context;\n }\n\n blockConcurrencyWhile<T>(callback: () => Promise<T>): Promise<T> {\n return callback();\n }\n\n broadcast(msg: string | ArrayBuffer | ArrayBufferView, without: string[] = []) {\n for (const connection of this.connections.values()) {\n if (!without.includes(connection.id)) {\n connection.send(msg);\n }\n }\n }\n\n getConnection<TState = unknown>(id: string): Party.Connection<TState> | undefined {\n let connection: Party.Connection | undefined;\n for (const current of this.connections.values()) {\n if (current.id === id || current.sessionId === id) {\n connection = current;\n }\n }\n return connection as Party.Connection<TState> | undefined;\n }\n\n getConnections<TState = unknown>(): Iterable<Party.Connection<TState>> {\n return Array.from(this.connections.values()) as Party.Connection<TState>[];\n }\n\n addConnection(connection: NodeConnection) {\n this.connections.set(connection.id, connection as unknown as Party.Connection);\n }\n\n deleteConnection(id: string, connection?: NodeConnection) {\n if (connection) {\n this.connections.delete(connection.id);\n return;\n }\n\n for (const [connectionKey, current] of this.connections) {\n if (current.id === id || current.sessionId === id) {\n this.connections.delete(connectionKey);\n }\n }\n }\n}\n\nexport class NodeConnection<TState = unknown> {\n readonly id = createConnectionId();\n readonly sessionId: string;\n readonly socket: this = this;\n readonly uri: string;\n state: Party.ConnectionState<TState> | TState | null = null;\n private attachment: unknown = null;\n\n constructor(\n private readonly webSocket: NodeWebSocketLike,\n uri: string,\n sessionId?: string\n ) {\n this.sessionId = sessionId || this.id;\n this.uri = uri;\n }\n\n send(data: string | ArrayBuffer | ArrayBufferView) {\n if (this.webSocket.readyState === undefined || this.webSocket.readyState === WEBSOCKET_OPEN) {\n this.webSocket.send(data);\n }\n }\n\n close(code?: number, reason?: string) {\n this.webSocket.close(code, reason);\n }\n\n setState(state: TState | Party.ConnectionSetStateFn<TState> | null) {\n this.state = typeof state === \"function\"\n ? (state as Party.ConnectionSetStateFn<TState>)(this.state as Party.ConnectionState<TState>)\n : state;\n return this.state as Party.ConnectionState<TState>;\n }\n\n serializeAttachment<T = unknown>(attachment: T): void {\n this.attachment = attachment;\n }\n\n deserializeAttachment<T = unknown>(): T | null {\n return this.attachment as T | null;\n }\n}\n\nfunction createPartiesContext(transport: NodeRoomTransport): Party.Context[\"parties\"] {\n return new Proxy({}, {\n get(_target, namespace: string) {\n const externalNamespace = transport.externalParties[namespace];\n if (externalNamespace) {\n return externalNamespace;\n }\n\n return {\n get(roomId: string) {\n return {\n connect: () => {\n throw new Error(\"Party stub connect() is not implemented by @signe/room/node\");\n },\n async socket(pathOrInit?: string | RequestInit, init?: RequestInit) {\n const path = typeof pathOrInit === \"string\" ? pathOrInit : \"/\";\n const requestInit = typeof pathOrInit === \"string\" ? init : pathOrInit;\n const request = new Request(\n toLocalUrl(`${transport.getNamespacePath(namespace, roomId)}${normalizeStubPath(path)}`),\n requestInit\n );\n const pair = createInMemoryWebSocketPair();\n\n void transport.acceptWebSocket(pair.server, request).catch(() => {\n pair.client.close(1011, \"Unable to start room connection\");\n });\n\n return pair.client as unknown as WebSocket;\n },\n fetch(pathOrInit?: string | RequestInit | Request, init?: RequestInit) {\n const path = typeof pathOrInit === \"string\" ? pathOrInit : \"/\";\n const requestInit = typeof pathOrInit === \"string\" ? init : pathOrInit;\n return transport.fetch(`${transport.getNamespacePath(namespace, roomId)}${normalizeStubPath(path)}`, requestInit as RequestInit);\n },\n };\n },\n };\n },\n }) as Party.Context[\"parties\"];\n}\n\nfunction createInMemoryWebSocketPair() {\n const client = new InMemoryWebSocket();\n const server = new InMemoryWebSocket();\n\n client.setPeer(server);\n server.setPeer(client);\n\n return { client, server };\n}\n\nclass InMemoryWebSocket implements NodeWebSocketLike {\n readyState = WEBSOCKET_OPEN;\n private readonly emitter = new EventEmitter();\n private peer?: InMemoryWebSocket;\n\n setPeer(peer: InMemoryWebSocket) {\n this.peer = peer;\n }\n\n send(data: string | ArrayBuffer | ArrayBufferView, cb?: (error?: Error) => void): void {\n if (this.readyState !== WEBSOCKET_OPEN || this.peer?.readyState !== WEBSOCKET_OPEN) {\n cb?.(new Error(\"WebSocket is not open\"));\n return;\n }\n\n queueMicrotask(() => {\n this.peer?.emitMessage(data);\n cb?.();\n });\n }\n\n close(code?: number, reason?: string | Buffer): void {\n if (this.readyState !== WEBSOCKET_OPEN) {\n return;\n }\n\n this.readyState = 3;\n this.emitter.emit(\"close\", code, reason);\n\n if (this.peer?.readyState === WEBSOCKET_OPEN) {\n this.peer.readyState = 3;\n this.peer.emitter.emit(\"close\", code, reason);\n }\n }\n\n on(event: string, listener: (...args: any[]) => void): unknown {\n this.emitter.on(event, listener);\n return this;\n }\n\n off(event: string, listener: (...args: any[]) => void): unknown {\n this.emitter.off(event, listener);\n return this;\n }\n\n removeListener(event: string, listener: (...args: any[]) => void): unknown {\n this.emitter.removeListener(event, listener);\n return this;\n }\n\n addEventListener(type: string, listener: (event: any) => void): void {\n if (type === \"message\") {\n this.on(\"message\", (data) => listener({ data }));\n return;\n }\n\n this.on(type, (event) => listener(event));\n }\n\n private emitMessage(data: string | ArrayBuffer | ArrayBufferView) {\n if (this.readyState === WEBSOCKET_OPEN) {\n this.emitter.emit(\"message\", data);\n }\n }\n}\n\nasync function createWebRequest(req: IncomingMessage, url: string, includeBody = true): Promise<Request> {\n const headers = new Headers();\n\n for (const [key, value] of Object.entries(req.headers)) {\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(key, item);\n }\n } else if (value !== undefined) {\n headers.set(key, String(value));\n }\n }\n\n const method = req.method ?? \"GET\";\n const hasBody = includeBody && ![\"GET\", \"HEAD\"].includes(method);\n const body = hasBody ? await readIncomingBody(req) : undefined;\n\n return new Request(url, {\n method,\n headers,\n body,\n });\n}\n\nasync function readIncomingBody(req: IncomingMessage): Promise<Uint8Array> {\n const chunks: Uint8Array[] = [];\n\n for await (const chunk of req) {\n if (typeof chunk === \"string\") {\n chunks.push(new TextEncoder().encode(chunk));\n } else {\n chunks.push(chunk);\n }\n }\n\n const size = chunks.reduce((total, chunk) => total + chunk.byteLength, 0);\n const body = new Uint8Array(size);\n let offset = 0;\n\n for (const chunk of chunks) {\n body.set(chunk, offset);\n offset += chunk.byteLength;\n }\n\n return body;\n}\n\nasync function writeNodeResponse(res: NodeServerResponse, response: Response) {\n res.statusCode = response.status;\n response.headers.forEach((value, key) => {\n res.setHeader(key, value);\n });\n\n const body = new Uint8Array(await response.arrayBuffer());\n res.end(body);\n}\n\nfunction getRequestUrl(req: IncomingMessage) {\n const protocol = req.headers[\"x-forwarded-proto\"] ?? \"http\";\n const host = req.headers.host ?? \"localhost\";\n return `${protocol}://${host}${req.url ?? \"/\"}`;\n}\n\nfunction normalizeWebSocketMessage(data: unknown): string | ArrayBuffer | ArrayBufferView {\n if (typeof data === \"string\") {\n return data;\n }\n if (data instanceof ArrayBuffer) {\n return new TextDecoder().decode(data);\n }\n if (ArrayBuffer.isView(data)) {\n return new TextDecoder().decode(data);\n }\n\n return String(data);\n}\n\nfunction normalizePath(path: string) {\n return `/${trimSlashes(path)}`;\n}\n\nfunction normalizeStubPath(path: string) {\n if (!path || path === \"/\") {\n return \"\";\n }\n return path.startsWith(\"/\") ? path : `/${path}`;\n}\n\nfunction toLocalUrl(path: string) {\n return path.startsWith(\"http://\") || path.startsWith(\"https://\")\n ? path\n : `http://localhost${path.startsWith(\"/\") ? path : `/${path}`}`;\n}\n\nfunction getConnectionIdFromUrl(url: string) {\n const requestedId = new URL(url).searchParams.get(\"id\")?.trim();\n return requestedId || undefined;\n}\n\nfunction trimSlashes(value: string) {\n return value.replace(/^\\/+|\\/+$/g, \"\");\n}\n\nfunction getStorageKey(namespace: string, roomId: string) {\n return `${encodeURIComponent(namespace)}:${encodeURIComponent(roomId)}`;\n}\n\nfunction assertSafeSqlIdentifier(identifier: string) {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {\n throw new Error(`Invalid SQLite table name: ${identifier}`);\n }\n}\n\nfunction getPrefixEnd(prefix: string) {\n return `${prefix}\\uffff`;\n}\n\nfunction normalizeSqliteLimit(value: number) {\n if (!Number.isFinite(value) || value < 0) {\n return 0;\n }\n\n return Math.floor(value);\n}\n\nfunction normalizeSqliteTimeout(value: number) {\n if (!Number.isFinite(value) || value < 0) {\n return DEFAULT_SQLITE_BUSY_TIMEOUT_MS;\n }\n\n return Math.floor(value);\n}\n\nfunction normalizeSqliteJournalMode(value: NodeSqliteJournalMode) {\n const journalMode = value.toUpperCase() as NodeSqliteJournalMode;\n const allowedModes = new Set<NodeSqliteJournalMode>([\n \"DELETE\",\n \"TRUNCATE\",\n \"PERSIST\",\n \"MEMORY\",\n \"WAL\",\n \"OFF\",\n ]);\n\n if (!allowedModes.has(journalMode)) {\n throw new Error(`Invalid SQLite journal mode: ${value}`);\n }\n\n return journalMode;\n}\n\nfunction runSqliteOperation<T>(operation: () => T, retries: number): T {\n const maxRetries = Math.max(0, Math.floor(retries));\n\n for (let attempt = 0; ; attempt += 1) {\n try {\n return operation();\n } catch (error) {\n if (!isSqliteBusyError(error) || attempt >= maxRetries) {\n throw error;\n }\n\n sleepSync(Math.min(250, SQLITE_RETRY_BASE_DELAY_MS * 2 ** attempt));\n }\n }\n}\n\nfunction isSqliteBusyError(error: unknown) {\n if (!error || typeof error !== \"object\") {\n return false;\n }\n\n const err = error as { errcode?: number; errstr?: string; code?: string; message?: string };\n return (\n err.errcode === 5 ||\n err.errcode === 6 ||\n err.errstr === \"database is locked\" ||\n err.errstr === \"database table is locked\" ||\n err.code === \"SQLITE_BUSY\" ||\n err.code === \"SQLITE_LOCKED\" ||\n err.message?.includes(\"database is locked\") ||\n err.message?.includes(\"database table is locked\")\n );\n}\n\nfunction sleepSync(ms: number) {\n const buffer = new SharedArrayBuffer(4);\n const view = new Int32Array(buffer);\n Atomics.wait(view, 0, 0, ms);\n}\n\nfunction loadNodeSqliteModule(): typeof import(\"node:sqlite\") {\n const require = createRequire(`${process.cwd()}/package.json`);\n return require(\"node:sqlite\");\n}\n\nfunction cloneStorageValue<T>(value: T): T {\n if (typeof structuredClone === \"function\") {\n try {\n return structuredClone(value);\n } catch {\n return value;\n }\n }\n\n return value;\n}\n\nfunction createConnectionId() {\n return Math.random().toString(36).slice(2, 12);\n}\n"],"mappings":"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","names":["result","require"]}
@@ -0,0 +1,175 @@
1
+ import { Request as Request$1, DurableObjectStorage, WebSocket, VectorizeIndex, R2Bucket, KVNamespace, DurableObjectState, AnalyticsEngineDataset } from '@cloudflare/workers-types';
2
+
3
+ type AssetFetcher = {
4
+ fetch(path: string): Promise<Response | null>;
5
+ };
6
+ type StandardRequest = globalThis.Request;
7
+ interface Request extends Request$1 {
8
+ }
9
+ type ReturnRequest = StandardRequest | Request$1;
10
+ /** Per-party key-value storage */
11
+ interface Storage extends DurableObjectStorage {
12
+ }
13
+ /** Connection metadata only available when the connection is made */
14
+ type ConnectionContext = {
15
+ request: Request$1;
16
+ };
17
+ type Stub = {
18
+ /** @deprecated Use `await socket()` instead */
19
+ connect: () => WebSocket;
20
+ socket(pathOrInit?: string | RequestInit): Promise<WebSocket>;
21
+ socket(path: string, init?: RequestInit): Promise<WebSocket>;
22
+ fetch(pathOrInit?: string | RequestInit | ReturnRequest): Promise<Response>;
23
+ fetch(path: string, init?: RequestInit | ReturnRequest): Promise<Response>;
24
+ };
25
+ /** Additional information about other resources in the current project */
26
+ type Context = {
27
+ /** Access other parties in this project */
28
+ parties: Record<string, {
29
+ get(id: string): Stub;
30
+ }>;
31
+ /**
32
+ * A binding to the Cloudflare AI service.
33
+ */
34
+ ai: AI;
35
+ /**
36
+ * A binding to the Cloudflare Vectorize service.
37
+ */
38
+ vectorize: Record<string, VectorizeIndex>;
39
+ /**
40
+ * A binding to fetch static assets
41
+ */
42
+ assets: AssetFetcher;
43
+ /**
44
+ * Custom bindings
45
+ */
46
+ bindings: CustomBindings;
47
+ };
48
+ type AI = Record<string, never>;
49
+ type ImmutablePrimitive = undefined | null | boolean | string | number;
50
+ type Immutable<T> = T extends ImmutablePrimitive ? T : T extends Array<infer U> ? ImmutableArray<U> : T extends Map<infer K, infer V> ? ImmutableMap<K, V> : T extends Set<infer M> ? ImmutableSet<M> : ImmutableObject<T>;
51
+ type ImmutableArray<T> = ReadonlyArray<Immutable<T>>;
52
+ type ImmutableMap<K, V> = ReadonlyMap<Immutable<K>, Immutable<V>>;
53
+ type ImmutableSet<T> = ReadonlySet<Immutable<T>>;
54
+ type ImmutableObject<T> = {
55
+ readonly [K in keyof T]: Immutable<T[K]>;
56
+ };
57
+ type ConnectionState<T> = ImmutableObject<T> | null;
58
+ type ConnectionSetStateFn<T> = (prevState: ConnectionState<T>) => T;
59
+ /** A WebSocket connected to the Room */
60
+ type Connection<TState = unknown> = WebSocket & {
61
+ /** Connection identifier */
62
+ id: string;
63
+ /** Stable private session identifier shared by reconnects or multiple tabs. */
64
+ sessionId?: string;
65
+ /** @deprecated You can access the socket properties directly on the connection*/
66
+ socket: WebSocket;
67
+ uri: string;
68
+ /**
69
+ * Arbitrary state associated with this connection.
70
+ * Read-only, use Connection.setState to update the state.
71
+ */
72
+ state: ConnectionState<TState>;
73
+ setState(state: TState | ConnectionSetStateFn<TState> | null): ConnectionState<TState>;
74
+ /** @deprecated use Connection.setState instead */
75
+ serializeAttachment<T = unknown>(attachment: T): void;
76
+ /** @deprecated use Connection.state instead */
77
+ deserializeAttachment<T = unknown>(): T | null;
78
+ };
79
+ type CustomBindings = {
80
+ r2: Record<string, R2Bucket>;
81
+ kv: Record<string, KVNamespace>;
82
+ };
83
+ /** Room represents a single, self-contained, long-lived session. */
84
+ type Room = {
85
+ /** Room ID defined in the Party URL, e.g. /parties/:name/:id */
86
+ id: string;
87
+ /** Internal ID assigned by the platform. Use Party.id instead. */
88
+ internalID: string;
89
+ /** Party name defined in the Party URL, e.g. /parties/:name/:id */
90
+ name: string;
91
+ /** Environment variables (--var, partykit.json#vars, or .env) */
92
+ env: Record<string, unknown>;
93
+ /** A per-room key-value storage */
94
+ storage: Storage;
95
+ /** `blockConcurrencyWhile()` ensures no requests are delivered until */
96
+ blockConcurrencyWhile: DurableObjectState["blockConcurrencyWhile"];
97
+ /** Additional information about other resources in the current project */
98
+ context: Context;
99
+ /** @deprecated Use `room.getConnections` instead */
100
+ connections: Map<string, Connection>;
101
+ /** @deprecated Use `room.context.parties` instead */
102
+ parties: Context["parties"];
103
+ /** Send a message to all connected clients, except connection ids listed `without` */
104
+ broadcast: (msg: string | ArrayBuffer | ArrayBufferView, without?: string[] | undefined) => void;
105
+ /** Get a connection by connection id */
106
+ getConnection<TState = unknown>(id: string): Connection<TState> | undefined;
107
+ /**
108
+ * Get all connections. Optionally, you can provide a tag to filter returned connections.
109
+ * Use `Party.Server#getConnectionTags` to tag the connection on connect.
110
+ */
111
+ getConnections<TState = unknown>(tag?: string): Iterable<Connection<TState>>;
112
+ /**
113
+ * Cloudflare Analytics Engine dataset. Use this to log custom events and metrics.
114
+ */
115
+ analytics: AnalyticsEngineDataset;
116
+ };
117
+ type Server = {
118
+ /**
119
+ * You can define an `options` field to customise the Party.Server behaviour.
120
+ */
121
+ readonly options?: ServerOptions;
122
+ /**
123
+ * You can tag a connection to filter them in Party#getConnections.
124
+ * Each connection supports up to 9 tags, each tag max length is 256 characters.
125
+ */
126
+ getConnectionTags?(connection: Connection, context: ConnectionContext): string[] | Promise<string[]>;
127
+ /**
128
+ * Called when the server is started, before first `onConnect` or `onRequest`.
129
+ * Useful for loading data from storage.
130
+ *
131
+ * You can use this to load data from storage and perform other asynchronous
132
+ * initialization, such as retrieving data or configuration from other
133
+ * services or databases.
134
+ */
135
+ onStart?(): void | Promise<void>;
136
+ /**
137
+ * Called when a new incoming WebSocket connection is opened.
138
+ */
139
+ onConnect?(connection: Connection, ctx: ConnectionContext): void | Promise<void>;
140
+ /**
141
+ * Called when a WebSocket connection receives a message from a client, or another connected party.
142
+ */
143
+ onMessage?(message: string | ArrayBuffer | ArrayBufferView, sender: Connection): void | Promise<void>;
144
+ /**
145
+ * Called when a WebSocket connection is closed by the client.
146
+ */
147
+ onClose?(connection: Connection): void | Promise<void>;
148
+ /**
149
+ * Called when a WebSocket connection is closed due to a connection error.
150
+ */
151
+ onError?(connection: Connection, error: Error): void | Promise<void>;
152
+ /**
153
+ * Called when a HTTP request is made to the room URL.
154
+ */
155
+ onRequest?(req: Request): Response | Promise<Response>;
156
+ /**
157
+ * Called when an alarm is triggered. Use Party.storage.setAlarm to set an alarm.
158
+ *
159
+ * Alarms have access to most Party resources such as storage, but not Party.id
160
+ * and Party.context.parties properties. Attempting to access them will result in a
161
+ * runtime error.
162
+ */
163
+ onAlarm?(): void | Promise<void>;
164
+ };
165
+ type ServerOptions = {
166
+ /**
167
+ * Whether the PartyKit platform should remove the server from memory
168
+ * between HTTP requests and WebSocket messages.
169
+ *
170
+ * The default value is `false`.
171
+ */
172
+ hibernate?: boolean;
173
+ };
174
+
175
+ export type { Connection as C, Request as R, Server as S, Room as a, ConnectionContext as b, Storage as c, Stub as d, Context as e, ConnectionState as f, ConnectionSetStateFn as g };
@@ -0,0 +1,62 @@
1
+ # `@signe/room` Durable Object Example
2
+
3
+ This example runs a `@signe/room` server on Cloudflare Workers with one Durable
4
+ Object instance per room id.
5
+
6
+ ```bash
7
+ pnpm install
8
+ pnpm --filter @signe/room build
9
+ pnpm --filter @signe/room-cloudflare-example dev
10
+ ```
11
+
12
+ Open http://localhost:8787, choose a room id and a display name, then enter the
13
+ room. The browser URL changes to `/rooms/:roomId`; refreshing that URL serves
14
+ the same app and reconnects to the matching Durable Object room.
15
+
16
+ - App URL: `http://localhost:8787/rooms/demo`
17
+ - HTTP: `GET /parties/main/demo/count`
18
+ - HTTP: `POST /parties/main/demo/reset`
19
+ - WebSocket: `ws://localhost:8787/parties/main/demo?name=Sam&id=browser-session-id`
20
+
21
+ The example pins Wrangler 3.99 because its `workerd` binary still runs on
22
+ systems with GLIBC 2.31. Newer Wrangler 4 releases can be used on newer systems.
23
+
24
+ The Worker entry point exports the generic Durable Object class and installs the
25
+ room server with the `ROOMS` binding:
26
+
27
+ ```ts
28
+ import { createCloudflareRoomWorker, SigneRoomDurableObject } from "@signe/room/cloudflare";
29
+ import { CounterServer } from "./room";
30
+
31
+ export { SigneRoomDurableObject };
32
+
33
+ export default createCloudflareRoomWorker(CounterServer, {
34
+ binding: "ROOMS",
35
+ partiesPath: "/parties/main",
36
+ });
37
+ ```
38
+
39
+ This example wraps that worker so non-room requests are served by the `ASSETS`
40
+ binding from `public/`.
41
+
42
+ The Durable Object binding is configured in `wrangler.jsonc`:
43
+
44
+ ```jsonc
45
+ {
46
+ "durable_objects": {
47
+ "bindings": [
48
+ { "name": "ROOMS", "class_name": "SigneRoomDurableObject" }
49
+ ]
50
+ },
51
+ "migrations": [
52
+ {
53
+ "tag": "v1",
54
+ "new_sqlite_classes": ["SigneRoomDurableObject"]
55
+ }
56
+ ]
57
+ }
58
+ ```
59
+
60
+ The room uses Durable Object storage through the KV-compatible `room.storage`
61
+ API, so synchronized state and sessions are scoped to each Durable Object
62
+ instance.
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsc" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules/typescript/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/typescript@5.4.5/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
15
+ else
16
+ exec node "$basedir/../typescript/bin/tsserver" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../wrangler/bin/wrangler.js" "$@"
15
+ else
16
+ exec node "$basedir/../wrangler/bin/wrangler.js" "$@"
17
+ fi
@@ -0,0 +1,17 @@
1
+ #!/bin/sh
2
+ basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
3
+
4
+ case `uname` in
5
+ *CYGWIN*) basedir=`cygpath -w "$basedir"`;;
6
+ esac
7
+
8
+ if [ -z "$NODE_PATH" ]; then
9
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules"
10
+ else
11
+ export NODE_PATH="/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/bin/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules/wrangler/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/wrangler@3.99.0_@cloudflare+workers-types@4.20260511.1/node_modules:/home/runner/work/signe/signe/node_modules/.pnpm/node_modules:$NODE_PATH"
12
+ fi
13
+ if [ -x "$basedir/node" ]; then
14
+ exec "$basedir/node" "$basedir/../wrangler/bin/wrangler.js" "$@"
15
+ else
16
+ exec node "$basedir/../wrangler/bin/wrangler.js" "$@"
17
+ fi
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@signe/room-cloudflare-example",
3
+ "version": "0.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "wrangler dev",
8
+ "deploy": "wrangler deploy",
9
+ "build": "tsc --noEmit",
10
+ "types": "wrangler types"
11
+ },
12
+ "dependencies": {
13
+ "@signe/reactive": "workspace:*",
14
+ "@signe/room": "workspace:*",
15
+ "@signe/sync": "workspace:*"
16
+ },
17
+ "devDependencies": {
18
+ "@cloudflare/workers-types": "^4.20241218.0",
19
+ "@types/node": "^22.13.9",
20
+ "typescript": "^5.4.5",
21
+ "wrangler": "3.99.0",
22
+ "zod": "^3.23.8"
23
+ }
24
+ }