From 221aff0050364297af1200ea782d351f4d68fbbc Mon Sep 17 00:00:00 2001 From: Izan Gil <66965250+SrIzan10@users.noreply.github.com> Date: Fri, 30 Jan 2026 16:42:50 +0100 Subject: [PATCH] feat: preliminary chat api --- packages/sdk/package.json | 1 + packages/sdk/src/chat.ts | 94 ++++ packages/sdk/src/index.ts | 23 +- packages/sdk/src/types.ts | 18 + packages/sdk/tests/chat.test.ts | 793 +++++++++++++++++++++++++++++++ packages/sdk/tests/index.test.ts | 7 - packages/sdk/tsconfig.json | 18 +- pnpm-lock.yaml | 110 +++-- 8 files changed, 988 insertions(+), 76 deletions(-) create mode 100644 packages/sdk/src/chat.ts create mode 100644 packages/sdk/src/types.ts create mode 100644 packages/sdk/tests/chat.test.ts delete mode 100644 packages/sdk/tests/index.test.ts diff --git a/packages/sdk/package.json b/packages/sdk/package.json index 79400e9..63e13a2 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -26,6 +26,7 @@ "author": "Izan Gil ", "license": "MIT", "devDependencies": { + "@types/node": "^25.1.0", "@typescript-eslint/eslint-plugin": "^8.50.1", "@typescript-eslint/parser": "^8.50.1", "eslint": "^9.39.2", diff --git a/packages/sdk/src/chat.ts b/packages/sdk/src/chat.ts new file mode 100644 index 0000000..f8c2eff --- /dev/null +++ b/packages/sdk/src/chat.ts @@ -0,0 +1,94 @@ +// most code here has been written by claude opus 4.5 +import type { ChatMessage, MessageHandler, SystemMessage, SystemMessageHandler } from './types'; + +export class ChatClient { + private botToken: string; + private ws: WebSocket | null = null; + private messageHandlers: Set = new Set(); + private systemMessageHandlers: Set = new Set(); + private channelName: string | null = null; + + constructor(botToken: string) { + this.botToken = botToken; + } + + connect(channelName: string): Promise { + if (this.isConnected) { + return Promise.reject(new Error('Already connected. Disconnect first.')); + } + + this.channelName = channelName; + const wsUrl = `${process.env.CHAT_WS_URL || 'ws://localhost:3001'}/ws`; + this.ws = new WebSocket(wsUrl); + + return new Promise((resolve, reject) => { + this.ws!.onopen = () => { + this.ws!.send(JSON.stringify({ type: 'auth', token: this.botToken, channelName })); + this.emit('system', { type: 'connected', channelName, message: `Connected to ${channelName}`, timestamp: Date.now() }); + resolve(); + }; + + this.ws!.onmessage = (event) => { + const data = JSON.parse(event.data); + if (data.type === 'message' && typeof data.message === 'string') { + this.emit('message', { + id: data.id || `${Date.now()}-${Math.random()}`, + channelName: data.channelName || channelName, + username: data.username || data.user?.username || 'Unknown', + message: data.message, + timestamp: data.timestamp || Date.now(), + type: data.messageType || 'message', + }); + } + }; + + this.ws!.onerror = () => { + this.emit('system', { type: 'error', channelName, message: 'WebSocket error', timestamp: Date.now() }); + reject(new Error('WebSocket error')); + }; + + this.ws!.onclose = () => { + this.emit('system', { type: 'disconnected', channelName, message: 'Disconnected', timestamp: Date.now() }); + this.ws = null; + }; + }); + } + + disconnect(): void { + this.ws?.close(); + this.ws = null; + this.channelName = null; + } + + sendMessage(message: string): void { + if (!this.isConnected) { + throw new Error('Not connected to a channel'); + } + this.ws!.send(JSON.stringify({ type: 'message', message, channelName: this.channelName })); + } + + onMessage(handler: MessageHandler): () => void { + this.messageHandlers.add(handler); + return () => this.messageHandlers.delete(handler); + } + + onSystemMessage(handler: SystemMessageHandler): () => void { + this.systemMessageHandlers.add(handler); + return () => this.systemMessageHandlers.delete(handler); + } + + private emit(type: 'message', data: ChatMessage): void; + private emit(type: 'system', data: SystemMessage): void; + private emit(type: 'message' | 'system', data: ChatMessage | SystemMessage): void { + const handlers = type === 'message' ? this.messageHandlers : this.systemMessageHandlers; + handlers.forEach((handler) => handler(data as any)); + } + + get isConnected(): boolean { + return this.ws?.readyState === WebSocket.OPEN; + } + + get currentChannel(): string | null { + return this.channelName; + } +} diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index d98ee71..f6ee92c 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -1,21 +1,18 @@ +import { ChatClient } from './chat.js'; +import type { ChatMessage, MessageHandler } from './types.js'; + export class HctvSdk { - private botToken: string + private botToken: string; + public chat: ChatClient; + constructor(args: ConstructorArgs) { - this.botToken = args.botToken + this.botToken = args.botToken; + this.chat = new ChatClient(args.botToken); } } - - -/* -const client = new HctvSdk({ botToken: 'hctvb_asddfasdfasdfasdfasdf' }); -await client.chat.connect('channelName'); -client.chat.onMessage((message) => { - // message would include data like the channelname etc - console.log('New message:', message); -}); -*/ - interface ConstructorArgs { botToken: string; } +export { ChatClient } from './chat.js'; +export type { ChatMessage, MessageHandler } from './types.js'; diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts new file mode 100644 index 0000000..f16cadd --- /dev/null +++ b/packages/sdk/src/types.ts @@ -0,0 +1,18 @@ +export interface ChatMessage { + id: string; + channelName: string; + username: string; + message: string; + timestamp: number; + type: 'message' | 'systemMsg'; +} + +export interface SystemMessage { + type: 'connected' | 'disconnected' | 'error'; + channelName: string; + message: string; + timestamp: number; +} + +export type MessageHandler = (message: ChatMessage) => void; +export type SystemMessageHandler = (message: SystemMessage) => void; diff --git a/packages/sdk/tests/chat.test.ts b/packages/sdk/tests/chat.test.ts new file mode 100644 index 0000000..7b8df9e --- /dev/null +++ b/packages/sdk/tests/chat.test.ts @@ -0,0 +1,793 @@ +// testing completely controlled by claude opus 4.5 because i'm lazy as heck +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import { HctvSdk, ChatClient } from '../src/index.js'; +import type { ChatMessage, SystemMessage } from '../src/types.js'; + +class MockWebSocket { + static CONNECTING = 0; + static OPEN = 1; + static CLOSING = 2; + static CLOSED = 3; + + readyState = MockWebSocket.CONNECTING; + onopen: (() => void) | null = null; + onmessage: ((event: { data: string }) => void) | null = null; + onerror: ((error: any) => void) | null = null; + onclose: (() => void) | null = null; + + sentMessages: string[] = []; + url: string; + + constructor(url: string) { + this.url = url; + setTimeout(() => { + this.readyState = MockWebSocket.OPEN; + this.onopen?.(); + }, 10); + } + + send(data: string) { + this.sentMessages.push(data); + } + + close() { + this.readyState = MockWebSocket.CLOSED; + this.onclose?.(); + } + + simulateMessage(data: any) { + this.onmessage?.({ data: JSON.stringify(data) }); + } + + simulateError(error: any) { + this.onerror?.(error); + } +} + +let mockWebSocketInstance: MockWebSocket | null = null; + +vi.stubGlobal('WebSocket', class extends MockWebSocket { + constructor(url: string) { + super(url); + mockWebSocketInstance = this; + } +}); + +describe('HctvSdk', () => { + beforeEach(() => { + mockWebSocketInstance = null; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('constructor', () => { + it('should initialize with bot token', () => { + const sdk = new HctvSdk({ botToken: 'test-token' }); + expect(sdk).toBeDefined(); + expect(sdk.chat).toBeInstanceOf(ChatClient); + }); + }); +}); + +describe('ChatClient', () => { + let client: ChatClient; + + beforeEach(() => { + mockWebSocketInstance = null; + client = new ChatClient('test-bot-token'); + }); + + afterEach(() => { + client.disconnect(); + vi.clearAllMocks(); + }); + + describe('constructor', () => { + it('should initialize with bot token', () => { + expect(client).toBeDefined(); + expect(client.isConnected).toBe(false); + expect(client.currentChannel).toBeNull(); + }); + }); + + describe('connect', () => { + it('should connect to a channel', async () => { + const connectPromise = client.connect('testchannel'); + + await connectPromise; + + expect(client.isConnected).toBe(true); + expect(mockWebSocketInstance).not.toBeNull(); + expect(mockWebSocketInstance?.url).toContain('/ws'); + }); + + it('should send auth message on connect', async () => { + await client.connect('testchannel'); + + expect(mockWebSocketInstance?.sentMessages.length).toBeGreaterThan(0); + const authMsg = JSON.parse(mockWebSocketInstance!.sentMessages[0]); + expect(authMsg.type).toBe('auth'); + expect(authMsg.token).toBe('test-bot-token'); + expect(authMsg.channelName).toBe('testchannel'); + }); + + it('should throw error when already connected', async () => { + await client.connect('testchannel'); + + await expect(client.connect('anotherchannel')).rejects.toThrow( + 'Already connected to a channel' + ); + }); + + it('should emit connected system message', async () => { + const systemHandler = vi.fn(); + client.onSystemMessage(systemHandler); + + await client.connect('testchannel'); + + expect(systemHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'connected', + channelName: 'testchannel', + }) + ); + }); + }); + + describe('disconnect', () => { + it('should disconnect from channel', async () => { + await client.connect('testchannel'); + expect(client.isConnected).toBe(true); + + client.disconnect(); + + expect(client.isConnected).toBe(false); + expect(client.currentChannel).toBeNull(); + }); + + it('should emit disconnected system message', async () => { + const systemHandler = vi.fn(); + client.onSystemMessage(systemHandler); + + await client.connect('testchannel'); + client.disconnect(); + + expect(systemHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'disconnected', + }) + ); + }); + }); + + describe('sendMessage', () => { + it('should send a message when connected', async () => { + await client.connect('testchannel'); + + client.sendMessage('Hello, world!'); + + const messages = mockWebSocketInstance!.sentMessages; + const lastMsg = JSON.parse(messages[messages.length - 1]); + expect(lastMsg.type).toBe('message'); + expect(lastMsg.message).toBe('Hello, world!'); + }); + + it('should throw error when not connected', () => { + expect(() => client.sendMessage('test')).toThrow('Not connected to a channel'); + }); + }); + + describe('onMessage', () => { + it('should call handler when message received', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Hello from server', + user: { + id: 'user-123', + username: 'testuser', + pfpUrl: 'https://example.com/pfp.jpg', + }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Hello from server', + username: 'testuser', + }) + ); + }); + + it('should return unsubscribe function', async () => { + const messageHandler = vi.fn(); + const unsubscribe = client.onMessage(messageHandler); + + await client.connect('testchannel'); + + unsubscribe(); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Should not receive', + user: { username: 'testuser' }, + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + + it('should handle multiple message handlers', async () => { + const handler1 = vi.fn(); + const handler2 = vi.fn(); + + client.onMessage(handler1); + client.onMessage(handler2); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'test message', + user: { username: 'testuser' }, + }); + + expect(handler1).toHaveBeenCalled(); + expect(handler2).toHaveBeenCalled(); + }); + }); + + describe('onSystemMessage', () => { + it('should call handler for system events', async () => { + const systemHandler = vi.fn(); + client.onSystemMessage(systemHandler); + + await client.connect('testchannel'); + + expect(systemHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'connected', + }) + ); + }); + + it('should return unsubscribe function', async () => { + const systemHandler = vi.fn(); + const unsubscribe = client.onSystemMessage(systemHandler); + + unsubscribe(); + + await client.connect('testchannel'); + + expect(systemHandler).not.toHaveBeenCalled(); + }); + }); + + describe('message parsing', () => { + it('should handle message with full user object', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Hello', + user: { + id: 'user-123', + username: 'johndoe', + pfpUrl: 'https://example.com/pfp.jpg', + displayName: 'John Doe', + isBot: false, + }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + username: 'johndoe', + message: 'Hello', + }) + ); + }); + + it('should handle message with username directly', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Direct username', + username: 'directuser', + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + username: 'directuser', + message: 'Direct username', + }) + ); + }); + + it('should ignore invalid messages', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + + it('should ignore non-message types', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'pong', + }); + + mockWebSocketInstance?.simulateMessage({ + type: 'history', + messages: [], + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + }); + + describe('currentChannel', () => { + it('should return null when not connected', () => { + expect(client.currentChannel).toBeNull(); + }); + + it('should return channel name when connected', async () => { + await client.connect('mychannel'); + expect(client.currentChannel).toBe('mychannel'); + }); + + it('should return null after disconnect', async () => { + await client.connect('mychannel'); + client.disconnect(); + expect(client.currentChannel).toBeNull(); + }); + }); + + describe('isConnected', () => { + it('should return false initially', () => { + expect(client.isConnected).toBe(false); + }); + + it('should return true when connected', async () => { + await client.connect('testchannel'); + expect(client.isConnected).toBe(true); + }); + + it('should return false after disconnect', async () => { + await client.connect('testchannel'); + client.disconnect(); + expect(client.isConnected).toBe(false); + }); + }); + + describe('error handling', () => { + it('should emit error system message on WebSocket error', async () => { + const systemHandler = vi.fn(); + client.onSystemMessage(systemHandler); + + const connectPromise = client.connect('testchannel'); + + await new Promise(resolve => setTimeout(resolve, 5)); + + mockWebSocketInstance?.simulateError(new Error('Connection failed')); + + await expect(connectPromise).rejects.toBeDefined(); + + expect(systemHandler).toHaveBeenCalledWith( + expect.objectContaining({ + type: 'error', + }) + ); + }); + + it('should handle malformed message data gracefully', async () => { + const messageHandler = vi.fn(); + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + client.onMessage(messageHandler); + await client.connect('testchannel'); + + mockWebSocketInstance?.onmessage?.({ data: 'not valid json{' }); + + expect(messageHandler).not.toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); + }); +}); + +describe('ChatMessage type', () => { + it('should have correct structure', () => { + const message: ChatMessage = { + id: 'msg-123', + channelName: 'testchannel', + username: 'testuser', + message: 'Hello, world!', + timestamp: Date.now(), + type: 'message', + }; + + expect(message.id).toBe('msg-123'); + expect(message.channelName).toBe('testchannel'); + expect(message.username).toBe('testuser'); + expect(message.message).toBe('Hello, world!'); + expect(typeof message.timestamp).toBe('number'); + expect(message.type).toBe('message'); + }); + + it('should support systemMsg type', () => { + const message: ChatMessage = { + id: 'sys-123', + channelName: 'testchannel', + username: 'system', + message: 'User joined', + timestamp: Date.now(), + type: 'systemMsg', + }; + + expect(message.type).toBe('systemMsg'); + }); +}); + +describe('SystemMessage type', () => { + it('should support connected type', () => { + const message: SystemMessage = { + type: 'connected', + channelName: 'testchannel', + message: 'Connected to testchannel', + timestamp: Date.now(), + }; + + expect(message.type).toBe('connected'); + }); + + it('should support disconnected type', () => { + const message: SystemMessage = { + type: 'disconnected', + channelName: 'testchannel', + message: 'Disconnected from testchannel', + timestamp: Date.now(), + }; + + expect(message.type).toBe('disconnected'); + }); + + it('should support error type', () => { + const message: SystemMessage = { + type: 'error', + channelName: 'testchannel', + message: 'An error occurred', + timestamp: Date.now(), + }; + + expect(message.type).toBe('error'); + }); +}); + +describe('Integration with Chat Server Protocol', () => { + let client: ChatClient; + + beforeEach(() => { + mockWebSocketInstance = null; + client = new ChatClient('test-bot-token'); + }); + + afterEach(() => { + client.disconnect(); + vi.clearAllMocks(); + }); + + describe('history messages', () => { + it('should handle history message from server', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'history', + messages: [ + { + user: { id: 'u1', username: 'user1', pfpUrl: '' }, + message: 'First message', + type: 'message', + }, + { + user: { id: 'u2', username: 'user2', pfpUrl: '' }, + message: 'Second message', + type: 'message', + }, + ], + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + }); + + describe('ping/pong', () => { + it('should handle pong response from server', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'pong', + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + }); + + describe('emoji responses', () => { + it('should handle emojiMsgResponse from server', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'emojiMsgResponse', + emojis: { + smile: 'https://example.com/emoji/smile.png', + laugh: 'https://example.com/emoji/laugh.png', + }, + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + + it('should handle emojiSearchResponse from server', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'emojiSearchResponse', + results: ['smile', 'smirk', 'smiley'], + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + }); + + describe('bot user messages', () => { + it('should handle messages from bot users', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Hello from bot!', + user: { + id: 'bot-123', + username: 'mybot', + pfpUrl: 'https://example.com/bot-avatar.png', + displayName: 'My Bot', + isBot: true, + }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + username: 'mybot', + message: 'Hello from bot!', + }) + ); + }); + }); + + describe('message structure from server', () => { + it('should handle message structure matching chat server format', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + user: { + id: 'user-abc', + username: 'streamuser', + pfpUrl: 'https://example.com/pfp.jpg', + displayName: 'Stream User', + isBot: false, + }, + message: 'Great stream!', + }); + + expect(messageHandler).not.toHaveBeenCalled(); + }); + + it('should handle message with explicit type', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + user: { + id: 'user-abc', + username: 'streamuser', + pfpUrl: 'https://example.com/pfp.jpg', + }, + message: 'Great stream!', + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + username: 'streamuser', + message: 'Great stream!', + }) + ); + }); + }); +}); + +describe('Edge cases', () => { + let client: ChatClient; + + beforeEach(() => { + mockWebSocketInstance = null; + client = new ChatClient('test-bot-token'); + }); + + afterEach(() => { + client.disconnect(); + vi.clearAllMocks(); + }); + + it('should handle empty message', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: '', + user: { username: 'testuser' }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + message: '', + }) + ); + }); + + it('should handle message with special characters', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + const specialMessage = '🎉 Hello & "quotes" \'apostrophe\''; + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: specialMessage, + user: { username: 'testuser' }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + message: specialMessage, + }) + ); + }); + + it('should handle very long messages', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + const longMessage = 'a'.repeat(10000); + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: longMessage, + user: { username: 'testuser' }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + message: longMessage, + }) + ); + }); + + it('should handle unicode usernames', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Hello', + user: { username: '日本語ユーザー' }, + }); + + expect(messageHandler).toHaveBeenCalledWith( + expect.objectContaining({ + username: '日本語ユーザー', + }) + ); + }); + + it('should handle rapid successive messages', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + for (let i = 0; i < 100; i++) { + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: `Message ${i}`, + user: { username: 'testuser' }, + }); + } + + expect(messageHandler).toHaveBeenCalledTimes(100); + }); + + it('should handle errors in message handlers gracefully', async () => { + const consoleSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); + + const errorHandler = vi.fn(() => { + throw new Error('Handler error'); + }); + const goodHandler = vi.fn(); + + client.onMessage(errorHandler); + client.onMessage(goodHandler); + + await client.connect('testchannel'); + + mockWebSocketInstance?.simulateMessage({ + type: 'message', + message: 'Test', + user: { username: 'testuser' }, + }); + + expect(goodHandler).toHaveBeenCalled(); + expect(consoleSpy).toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); + + it('should handle disconnect while message is being processed', async () => { + const messageHandler = vi.fn(); + client.onMessage(messageHandler); + + await client.connect('testchannel'); + + client.disconnect(); + + expect(() => client.sendMessage('test')).toThrow('Not connected'); + }); +}); \ No newline at end of file diff --git a/packages/sdk/tests/index.test.ts b/packages/sdk/tests/index.test.ts deleted file mode 100644 index 1e46a5a..0000000 --- a/packages/sdk/tests/index.test.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { describe, it, expect } from 'vitest' - -describe('index', () => { - it('should pass', () => { - expect(1 + 1).toBe(2) - }) -}) \ No newline at end of file diff --git a/packages/sdk/tsconfig.json b/packages/sdk/tsconfig.json index c64929a..976a772 100644 --- a/packages/sdk/tsconfig.json +++ b/packages/sdk/tsconfig.json @@ -49,13 +49,12 @@ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + "declarationMap": true, /* Create sourcemaps for d.ts files. */ + "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ + "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ @@ -105,5 +104,12 @@ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true /* Skip type checking all .d.ts files. */ - } + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b54e4e4..a3209a0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,16 +56,16 @@ importers: dependencies: '@astrojs/starlight': specifier: ^0.35.2 - version: 0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) + version: 0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) '@catppuccin/starlight': specifier: ^1.0.2 - version: 1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) + version: 1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) astro: specifier: ^5.6.1 - version: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + version: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) astro-mermaid: specifier: ^1.0.4 - version: 1.2.0(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2) + version: 1.2.0(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2) mermaid: specifier: ^11.10.1 version: 11.12.2 @@ -364,6 +364,9 @@ importers: packages/sdk: devDependencies: + '@types/node': + specifier: ^25.1.0 + version: 25.1.0 '@typescript-eslint/eslint-plugin': specifier: ^8.50.1 version: 8.51.0(@typescript-eslint/parser@8.51.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) @@ -381,7 +384,7 @@ importers: version: 5.9.3 vitest: specifier: ^4.0.16 - version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(jiti@2.6.1)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) + version: 4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.1.0)(jiti@2.6.1)(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) packages: @@ -3174,6 +3177,9 @@ packages: '@types/node@24.10.4': resolution: {integrity: sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg==} + '@types/node@25.1.0': + resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==} + '@types/pg-pool@2.0.6': resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} @@ -8125,12 +8131,12 @@ snapshots: transitivePeerDependencies: - supports-color - '@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': + '@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.10 '@mdx-js/mdx': 3.1.1 acorn: 8.15.0 - astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) es-module-lexer: 1.7.0 estree-util-visit: 2.0.0 hast-util-to-html: 9.0.5 @@ -8154,17 +8160,17 @@ snapshots: stream-replace-string: 2.0.0 zod: 3.25.76 - '@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': + '@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': dependencies: '@astrojs/markdown-remark': 6.3.10 - '@astrojs/mdx': 4.3.13(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) + '@astrojs/mdx': 4.3.13(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) '@astrojs/sitemap': 3.6.0 '@pagefind/default-ui': 1.4.0 '@types/hast': 3.0.4 '@types/js-yaml': 4.0.9 '@types/mdast': 4.0.4 - astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) - astro-expressive-code: 0.41.5(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) + astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + astro-expressive-code: 0.41.5(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) bcp-47: 2.1.0 hast-util-from-html: 2.0.3 hast-util-select: 6.0.4 @@ -8369,10 +8375,10 @@ snapshots: dependencies: fontkit: 2.0.4 - '@catppuccin/starlight@1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': + '@catppuccin/starlight@1.0.2(@astrojs/starlight@0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)))(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))': dependencies: - '@astrojs/starlight': 0.35.3(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) - astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + '@astrojs/starlight': 0.35.3(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)) + astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) '@chevrotain/cst-dts-gen@11.0.3': dependencies: @@ -9026,12 +9032,12 @@ snapshots: optionalDependencies: '@types/node': 20.19.27 - '@inquirer/confirm@5.1.21(@types/node@24.10.4)': + '@inquirer/confirm@5.1.21(@types/node@25.1.0)': dependencies: - '@inquirer/core': 10.3.2(@types/node@24.10.4) - '@inquirer/type': 3.0.10(@types/node@24.10.4) + '@inquirer/core': 10.3.2(@types/node@25.1.0) + '@inquirer/type': 3.0.10(@types/node@25.1.0) optionalDependencies: - '@types/node': 24.10.4 + '@types/node': 25.1.0 optional: true '@inquirer/core@10.3.2(@types/node@20.19.27)': @@ -9047,18 +9053,18 @@ snapshots: optionalDependencies: '@types/node': 20.19.27 - '@inquirer/core@10.3.2(@types/node@24.10.4)': + '@inquirer/core@10.3.2(@types/node@25.1.0)': dependencies: '@inquirer/ansi': 1.0.2 '@inquirer/figures': 1.0.15 - '@inquirer/type': 3.0.10(@types/node@24.10.4) + '@inquirer/type': 3.0.10(@types/node@25.1.0) cli-width: 4.1.0 mute-stream: 2.0.0 signal-exit: 4.1.0 wrap-ansi: 6.2.0 yoctocolors-cjs: 2.1.3 optionalDependencies: - '@types/node': 24.10.4 + '@types/node': 25.1.0 optional: true '@inquirer/figures@1.0.15': {} @@ -9067,9 +9073,9 @@ snapshots: optionalDependencies: '@types/node': 20.19.27 - '@inquirer/type@3.0.10(@types/node@24.10.4)': + '@inquirer/type@3.0.10(@types/node@25.1.0)': optionalDependencies: - '@types/node': 24.10.4 + '@types/node': 25.1.0 optional: true '@internationalized/date@3.10.1': @@ -11273,6 +11279,10 @@ snapshots: dependencies: undici-types: 7.16.0 + '@types/node@25.1.0': + dependencies: + undici-types: 7.16.0 + '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.15.6 @@ -11578,14 +11588,14 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.0.3 - '@vitest/mocker@4.0.16(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))': + '@vitest/mocker@4.0.16(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0))': dependencies: '@vitest/spy': 4.0.16 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - msw: 2.12.7(@types/node@24.10.4)(typescript@5.9.3) - vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) + msw: 2.12.7(@types/node@25.1.0)(typescript@5.9.3) + vite: 7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) '@vitest/pretty-format@4.0.16': dependencies: @@ -11961,21 +11971,21 @@ snapshots: astring@1.9.0: {} - astro-expressive-code@0.41.5(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)): + astro-expressive-code@0.41.5(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0)): dependencies: - astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) rehype-expressive-code: 0.41.5 - astro-mermaid@1.2.0(astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2): + astro-mermaid@1.2.0(astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0))(mermaid@11.12.2): dependencies: '@anthropic-ai/claude-code': 1.0.128 - astro: 5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) + astro: 5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0) import-meta-resolve: 4.2.0 mdast-util-to-string: 4.0.0 mermaid: 11.12.2 unist-util-visit: 5.0.0 - astro@5.16.6(@types/node@24.10.4)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0): + astro@5.16.6(@types/node@25.1.0)(ioredis@5.8.2)(jiti@2.6.1)(rollup@4.54.0)(terser@5.44.1)(tsx@4.21.0)(typescript@5.9.3)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0)))(yaml@2.8.0): dependencies: '@astrojs/compiler': 2.13.0 '@astrojs/internal-helpers': 0.7.5 @@ -12032,8 +12042,8 @@ snapshots: unist-util-visit: 5.0.0 unstorage: 1.17.3(ioredis@5.8.2)(uploadthing@7.7.4(express@5.2.1)(h3@1.15.4)(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.0))) vfile: 6.0.3 - vite: 6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) - vitefu: 1.1.1(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)) + vite: 6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) + vitefu: 1.1.1(vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)) xxhash-wasm: 1.1.0 yargs-parser: 21.1.1 yocto-spinner: 0.2.3 @@ -13074,7 +13084,7 @@ snapshots: '@typescript-eslint/parser': 8.51.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.1) eslint-plugin-react: 7.37.5(eslint@8.57.1) @@ -13094,7 +13104,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1): + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1): dependencies: '@nolyfill/is-core-module': 1.0.39 debug: 4.4.3 @@ -13109,14 +13119,14 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.51.0(eslint@8.57.1)(typescript@5.9.3) eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1) + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@8.57.1) transitivePeerDependencies: - supports-color @@ -13131,7 +13141,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1))(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.51.0(eslint@8.57.1)(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -15060,9 +15070,9 @@ snapshots: transitivePeerDependencies: - '@types/node' - msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3): + msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3): dependencies: - '@inquirer/confirm': 5.1.21(@types/node@24.10.4) + '@inquirer/confirm': 5.1.21(@types/node@25.1.0) '@mswjs/interceptors': 0.40.0 '@open-draft/deferred-promise': 2.2.0 '@types/statuses': 2.0.6 @@ -16973,7 +16983,7 @@ snapshots: '@types/unist': 3.0.3 vfile-message: 4.0.3 - vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): + vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): dependencies: esbuild: 0.25.12 fdir: 6.5.0(picomatch@4.0.3) @@ -16982,14 +16992,14 @@ snapshots: rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.4 + '@types/node': 25.1.0 fsevents: 2.3.3 jiti: 2.6.1 terser: 5.44.1 tsx: 4.21.0 yaml: 2.8.0 - vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): + vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): dependencies: esbuild: 0.27.2 fdir: 6.5.0(picomatch@4.0.3) @@ -16998,21 +17008,21 @@ snapshots: rollup: 4.54.0 tinyglobby: 0.2.15 optionalDependencies: - '@types/node': 24.10.4 + '@types/node': 25.1.0 fsevents: 2.3.3 jiti: 2.6.1 terser: 5.44.1 tsx: 4.21.0 yaml: 2.8.0 - vitefu@1.1.1(vite@6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)): + vitefu@1.1.1(vite@6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)): optionalDependencies: - vite: 6.4.1(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) + vite: 6.4.1(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) - vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@24.10.4)(jiti@2.6.1)(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): + vitest@4.0.16(@opentelemetry/api@1.9.0)(@types/node@25.1.0)(jiti@2.6.1)(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.16 - '@vitest/mocker': 4.0.16(msw@2.12.7(@types/node@24.10.4)(typescript@5.9.3))(vite@7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)) + '@vitest/mocker': 4.0.16(msw@2.12.7(@types/node@25.1.0)(typescript@5.9.3))(vite@7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0)) '@vitest/pretty-format': 4.0.16 '@vitest/runner': 4.0.16 '@vitest/snapshot': 4.0.16 @@ -17029,11 +17039,11 @@ snapshots: tinyexec: 1.0.2 tinyglobby: 0.2.15 tinyrainbow: 3.0.3 - vite: 7.3.0(@types/node@24.10.4)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) + vite: 7.3.0(@types/node@25.1.0)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.0 - '@types/node': 24.10.4 + '@types/node': 25.1.0 transitivePeerDependencies: - jiti - less