feat(sdk): P2.5 @insignia/iios-kernel-client — REST + socket facade

MessageSocket (open/send/read/typing via emitWithAck, reconnect re-opens thread,
no socket.io types leak) + RestClient (ingest/getThreadMessages/sendMessage).
tsup ESM+dts. Facade unit test with injectable fake socket (3 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-01 01:21:59 +05:30
parent 3165a5bb59
commit 5a590c7fda
9 changed files with 815 additions and 0 deletions
@@ -0,0 +1,69 @@
import { describe, it, expect } from 'vitest';
import { MessageSocket } from './message-socket';
import type { SocketLike } from './types';
class FakeSocket implements SocketLike {
handlers = new Map<string, Array<(...a: unknown[]) => void>>();
acks: Array<{ event: string; payload: unknown }> = [];
ackReturn: unknown = { threadId: 't1', status: 'OPEN', history: [] };
on(event: string, handler: (...a: unknown[]) => void): unknown {
const list = this.handlers.get(event) ?? [];
list.push(handler);
this.handlers.set(event, list);
return this;
}
off(event: string, handler: (...a: unknown[]) => void): unknown {
this.handlers.set(event, (this.handlers.get(event) ?? []).filter((h) => h !== handler));
return this;
}
emit(_event: string, ..._args: unknown[]): unknown {
return this;
}
async emitWithAck(event: string, ...args: unknown[]): Promise<unknown> {
this.acks.push({ event, payload: args[0] });
return this.ackReturn;
}
connect(): unknown {
return this;
}
disconnect(): unknown {
return this;
}
trigger(event: string, ...args: unknown[]): void {
(this.handlers.get(event) ?? []).forEach((h) => h(...args));
}
listenerCount(event: string): number {
return (this.handlers.get(event) ?? []).length;
}
}
const cfg = { serviceUrl: 'http://localhost:3200', token: 'tok' };
describe('MessageSocket facade', () => {
it('on() registers a handler and the returned fn unsubscribes', () => {
const fake = new FakeSocket();
const ms = new MessageSocket(cfg, fake);
const off = ms.on('message', () => {});
expect(fake.listenerCount('message')).toBe(1);
off();
expect(fake.listenerCount('message')).toBe(0);
});
it('sendMessage invokes emitWithAck("send_message", payload)', async () => {
const fake = new FakeSocket();
fake.ackReturn = { id: 'm1', threadId: 't1', content: 'hello' };
const ms = new MessageSocket(cfg, fake);
await ms.sendMessage('t1', 'hello', { contentRef: 'sas://x' });
const call = fake.acks.find((a) => a.event === 'send_message');
expect(call?.payload).toMatchObject({ threadId: 't1', content: 'hello', contentRef: 'sas://x' });
});
it('openThread returns the ack and tracks the thread', async () => {
const fake = new FakeSocket();
fake.ackReturn = { threadId: 't9', status: 'OPEN', history: [] };
const ms = new MessageSocket(cfg, fake);
const r = await ms.openThread();
expect(r.threadId).toBe('t9');
});
});