Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

@replit/river

Package Overview
Dependencies
Maintainers
28
Versions
162
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@replit/river - npm Package Compare versions

Comparing version 0.1.10 to 0.2.0

dist/__tests__/bandwidth.bench.d.ts

9

dist/__tests__/integration.test.d.ts

@@ -0,1 +1,2 @@

import { TransportMessage } from '../transport/message';
export declare const EchoRequest: import("@sinclair/typebox").TObject<{

@@ -23,5 +24,5 @@ msg: import("@sinclair/typebox").TString;

count: number;
}>, input: import("../transport/message").TransportMessage<{
}>, input: TransportMessage<{
n: number;
}>) => Promise<import("../transport/message").TransportMessage<{
}>) => Promise<TransportMessage<{
result: number;

@@ -42,6 +43,6 @@ }>>;

count: number;
}>, input: AsyncIterable<import("../transport/message").TransportMessage<{
}>, input: AsyncIterable<TransportMessage<{
msg: string;
ignore: boolean;
}>>, output: import("it-pushable").Pushable<import("../transport/message").TransportMessage<{
}>>, output: import("it-pushable").Pushable<TransportMessage<{
response: string;

@@ -48,0 +49,0 @@ }>, void, unknown>) => Promise<void>;

@@ -5,3 +5,3 @@ import http from 'http';

import { reply } from '../transport/message';
import { afterAll, beforeAll, describe, expect, test } from 'vitest';
import { afterAll, describe, expect, test } from 'vitest';
import { createWebSocketServer, createWsTransports, onServerReady, } from '../transport/util';

@@ -44,2 +44,25 @@ import { createServer } from '../router/server';

.finalize();
const OrderingServiceConstructor = () => ServiceBuilder.create('test')
.initialState({
msgs: [],
})
.defineProcedure('add', {
type: 'rpc',
input: Type.Object({ n: Type.Number() }),
output: Type.Object({ ok: Type.Boolean() }),
async handler(ctx, msg) {
const { n } = msg.payload;
ctx.state.msgs.push(n);
return reply(msg, { ok: true });
},
})
.defineProcedure('getAll', {
type: 'rpc',
input: Type.Object({}),
output: Type.Object({ msgs: Type.Array(Type.Number()) }),
async handler(ctx, msg) {
return reply(msg, { msgs: ctx.state.msgs });
},
})
.finalize();
test('serialize service to jsonschema', () => {

@@ -101,26 +124,22 @@ const service = TestServiceConstructor();

test('stream basic', async () => {
const [i, o] = asClientStream(initialState, service.procedures.echo);
i.push({ msg: 'abc', ignore: false });
i.push({ msg: 'def', ignore: true });
i.push({ msg: 'ghi', ignore: false });
i.end();
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
const [input, output] = asClientStream(initialState, service.procedures.echo);
input.push({ msg: 'abc', ignore: false });
input.push({ msg: 'def', ignore: true });
input.push({ msg: 'ghi', ignore: false });
input.end();
await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
response: 'abc',
});
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
response: 'ghi',
});
expect(o.readableLength).toBe(0);
expect(output.readableLength).toBe(0);
});
});
const port = 4445;
describe('client <-> server integration test', () => {
describe('client <-> server integration test', async () => {
const server = http.createServer();
let wss;
beforeAll(async () => {
await onServerReady(server, port);
wss = await createWebSocketServer(server);
});
const port = await onServerReady(server);
const webSocketServer = await createWebSocketServer(server);
afterAll(() => {
wss.clients.forEach((socket) => {
webSocketServer.clients.forEach((socket) => {
socket.close();

@@ -131,6 +150,6 @@ });

test('rpc', async () => {
const [ct, st] = await createWsTransports(port, wss);
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
const serviceDefs = { test: TestServiceConstructor() };
const server = await createServer(st, serviceDefs);
const client = createClient(ct);
const server = await createServer(serverTransport, serviceDefs);
const client = createClient(clientTransport);
await expect(client.test.add({ n: 3 })).resolves.toStrictEqual({

@@ -141,15 +160,15 @@ result: 3,

test('stream', async () => {
const [ct, st] = await createWsTransports(port, wss);
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
const serviceDefs = { test: TestServiceConstructor() };
const server = await createServer(st, serviceDefs);
const client = createClient(ct);
const [i, o, close] = await client.test.echo();
i.push({ msg: 'abc', ignore: false });
i.push({ msg: 'def', ignore: true });
i.push({ msg: 'ghi', ignore: false });
i.end();
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
const server = await createServer(serverTransport, serviceDefs);
const client = createClient(clientTransport);
const [input, output, close] = await client.test.echo();
input.push({ msg: 'abc', ignore: false });
input.push({ msg: 'def', ignore: true });
input.push({ msg: 'ghi', ignore: false });
input.end();
await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
response: 'abc',
});
await expect(o.next().then((res) => res.value)).resolves.toStrictEqual({
await expect(output.next().then((res) => res.value)).resolves.toStrictEqual({
response: 'ghi',

@@ -159,2 +178,23 @@ });

});
test('message order is preserved in the face of disconnects', async () => {
const [clientTransport, serverTransport] = createWsTransports(port, webSocketServer);
const serviceDefs = { test: OrderingServiceConstructor() };
const server = await createServer(serverTransport, serviceDefs);
const client = createClient(clientTransport);
const expected = [];
for (let i = 0; i < 50; i++) {
expected.push(i);
if (i == 10) {
clientTransport.ws?.close();
}
if (i == 42) {
clientTransport.ws?.terminate();
}
await client.test.add({
n: i,
});
}
const res = await client.test.getAll({});
return expect(res.msgs).toStrictEqual(expected);
});
});
import { MessageId, OpaqueTransportMessage } from '../transport/message';
import { Transport } from '../transport/types';
export declare const StupidlyLargeService: () => {
name: "test";
state: {};
procedures: {
f1: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f2: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f3: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f4: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f5: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f6: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f7: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f8: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f9: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f10: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f11: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f12: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f13: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f14: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f15: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f16: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f17: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f18: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f19: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f20: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f21: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f22: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f23: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f24: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f25: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f26: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f27: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f28: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f29: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f30: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f31: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f32: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f33: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f34: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f35: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f36: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f37: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f38: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f39: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f40: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f41: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f42: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f43: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f44: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f45: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f46: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f47: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f48: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
} & {
f49: {
input: import("@sinclair/typebox").TObject<{
a: import("@sinclair/typebox").TNumber;
}>;
output: import("@sinclair/typebox").TObject<{
b: import("@sinclair/typebox").TNumber;
}>;
handler: (context: import("..").ServiceContextWithState<{}>, input: import("../transport/message").TransportMessage<{
a: number;
}>) => Promise<import("../transport/message").TransportMessage<{
b: number;
}>>;
type: "rpc";
};
};
};
export declare class MockTransport extends Transport {

@@ -4,0 +744,0 @@ constructor(clientId: string);

@@ -9,6 +9,8 @@ import { describe, expect, test } from 'vitest';

import { createClient } from '../router/client';
const input = Type.Object({ a: Type.Number() });
const output = Type.Object({ b: Type.Number() });
const fnBody = {
type: 'rpc',
input: Type.Object({ a: Type.Number() }),
output: Type.Object({ b: Type.Number() }),
input,
output,
async handler(_state, msg) {

@@ -20,3 +22,3 @@ return reply(msg, { b: msg.payload.a });

// see: https://github.com/microsoft/TypeScript/issues/33541
const svc = () => ServiceBuilder.create('test')
export const StupidlyLargeService = () => ServiceBuilder.create('test')
.defineProcedure('f1', fnBody)

@@ -85,10 +87,10 @@ .defineProcedure('f2', fnBody)

test('service with many procedures hits typescript limit', () => {
expect(serializeService(svc())).toBeTruthy();
expect(serializeService(StupidlyLargeService())).toBeTruthy();
});
test('serverclient should support many services with many procedures', async () => {
const listing = {
a: svc(),
b: svc(),
c: svc(),
d: svc(),
a: StupidlyLargeService(),
b: StupidlyLargeService(),
c: StupidlyLargeService(),
d: StupidlyLargeService(),
};

@@ -95,0 +97,0 @@ const server = await createServer(new MockTransport('SERVER'), listing);

@@ -14,2 +14,2 @@ export { serializeService, ServiceBuilder } from './router/builder';

export { WebSocketTransport } from './transport/ws';
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/util';
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, createLocalWebSocketClient, } from './transport/util';

@@ -9,2 +9,2 @@ export { serializeService, ServiceBuilder } from './router/builder';

export { WebSocketTransport } from './transport/ws';
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, waitForSocketReady, createWebSocketClient, } from './transport/util';
export { createWebSocketServer, onServerReady, createWsTransports, waitForMessage, createLocalWebSocketClient, } from './transport/util';

@@ -26,15 +26,15 @@ import { pushable } from 'it-pushable';

// stream case
const i = pushable({ objectMode: true });
const o = pushable({ objectMode: true });
// i -> transport
const inputStream = pushable({ objectMode: true });
const outputStream = pushable({ objectMode: true });
// input -> transport
// this gets cleaned up on i.end() which is called by closeHandler
(async () => {
for await (const rawIn of i) {
for await (const rawIn of inputStream) {
transport.send(msg(transport.clientId, 'SERVER', serviceName, procName, rawIn));
}
})();
// transport -> o
// transport -> output
const listener = (msg) => {
if (msg.serviceName === serviceName && msg.procedureName === procName) {
o.push(msg.payload);
outputStream.push(msg.payload);
}

@@ -44,7 +44,7 @@ };

const closeHandler = () => {
i.end();
o.end();
inputStream.end();
outputStream.end();
transport.removeMessageListener(listener);
};
return [i, o, closeHandler];
return [inputStream, outputStream, closeHandler];
}

@@ -51,0 +51,0 @@ else {

@@ -33,2 +33,3 @@ import { type Static, TSchema } from '@sinclair/typebox';

export declare const TransportAckSchema: import("@sinclair/typebox").TObject<{
id: import("@sinclair/typebox").TString;
from: import("@sinclair/typebox").TString;

@@ -35,0 +36,0 @@ ack: import("@sinclair/typebox").TString;

@@ -15,2 +15,3 @@ import { Type } from '@sinclair/typebox';

export const TransportAckSchema = Type.Object({
id: Type.String(),
from: Type.String(),

@@ -34,2 +35,3 @@ ack: Type.String(),

return {
id: nanoid(),
from: msg.to,

@@ -36,0 +38,0 @@ ack: msg.id,

@@ -15,2 +15,3 @@ import { Value } from '@sinclair/typebox/value';

onMessage(msg) {
// TODO: try catch from string buf
const parsedMsg = this.codec.fromStringBuf(msg.toString());

@@ -34,2 +35,5 @@ if (Value.Check(TransportAckSchema, parsedMsg)) {

}
else {
// TODO: warn on malformed
}
}

@@ -36,0 +40,0 @@ addMessageListener(handler) {

@@ -6,8 +6,8 @@ /// <reference types="node" />

import { Transport } from './types';
import { WebSocketTransport } from './ws';
import { OpaqueTransportMessage } from './message';
export declare function createWebSocketServer(server: http.Server): Promise<WebSocket.Server<typeof WebSocket, typeof http.IncomingMessage>>;
export declare function onServerReady(server: http.Server, port: number): Promise<void>;
export declare function createWsTransports(port: number, wss: WebSocketServer): Promise<[Transport, Transport]>;
export declare function waitForSocketReady(socket: WebSocket): Promise<void>;
export declare function createWebSocketClient(port: number): Promise<WebSocket>;
export declare function onServerReady(server: http.Server): Promise<number>;
export declare function createLocalWebSocketClient(port: number): Promise<WebSocket>;
export declare function createWsTransports(port: number, wss: WebSocketServer): [WebSocketTransport, WebSocketTransport];
export declare function waitForMessage(t: Transport, filter?: (msg: OpaqueTransportMessage) => boolean): Promise<unknown>;

@@ -7,27 +7,32 @@ import WebSocket from 'isomorphic-ws';

}
export async function onServerReady(server, port) {
return new Promise((resolve) => {
server.listen(port, resolve);
});
}
export async function createWsTransports(port, wss) {
return new Promise((resolve) => {
const clientSockPromise = createWebSocketClient(port);
wss.on('connection', async (serverSock) => {
resolve([
new WebSocketTransport(await clientSockPromise, 'client'),
new WebSocketTransport(serverSock, 'SERVER'),
]);
export async function onServerReady(server) {
return new Promise((resolve, reject) => {
server.listen(() => {
const addr = server.address();
if (typeof addr === 'object' && addr) {
resolve(addr.port);
}
else {
reject(new Error("couldn't find a port to allocate"));
}
});
});
}
export async function waitForSocketReady(socket) {
return new Promise((resolve) => {
socket.addEventListener('open', () => resolve());
});
export async function createLocalWebSocketClient(port) {
return new WebSocket(`ws://localhost:${port}`);
}
export async function createWebSocketClient(port) {
const client = new WebSocket(`ws://localhost:${port}`);
await waitForSocketReady(client);
return client;
export function createWsTransports(port, wss) {
return [
new WebSocketTransport(async () => {
return createLocalWebSocketClient(port);
}, 'client'),
new WebSocketTransport(async () => {
return new Promise((resolve) => {
wss.on('connection', async function onConnect(serverSock) {
wss.removeListener('connection', onConnect);
resolve(serverSock);
});
});
}, 'SERVER'),
];
}

@@ -34,0 +39,0 @@ export async function waitForMessage(t, filter) {

/// <reference types="ws" />
import type WebSocket from 'isomorphic-ws';
import WebSocket from 'isomorphic-ws';
import { Transport } from './types';
import { MessageId, OpaqueTransportMessage, TransportClientId } from './message';
interface Options {
retryIntervalMs: number;
}
type WebSocketResult = {
ws: WebSocket;
} | {
err: string;
};
export declare class WebSocketTransport extends Transport {
ws: WebSocket;
constructor(ws: WebSocket, clientId: TransportClientId);
wsGetter: () => Promise<WebSocket>;
ws?: WebSocket;
destroyed: boolean;
reconnectPromise?: Promise<WebSocketResult>;
options: Options;
sendQueue: Array<MessageId>;
constructor(wsGetter: () => Promise<WebSocket>, clientId: TransportClientId, options?: Partial<Options>);
private tryConnect;
send(msg: OpaqueTransportMessage): MessageId;
close(): Promise<void>;
close(): Promise<void | undefined>;
}
export {};
import { Transport } from './types';
import { NaiveJsonCodec } from '../codec/json';
// TODO should answer:
// - how do we handle graceful client disconnects? (i.e. close tab)
// - how do we handle graceful service disconnects (i.e. a fuck off message)?
// - how do we handle forceful client disconnects? (i.e. broken connection, offline)
// - how do we handle forceful service disconnects (i.e. a crash)?
const defaultOptions = {
retryIntervalMs: 250,
};
export class WebSocketTransport extends Transport {
wsGetter;
ws;
constructor(ws, clientId) {
destroyed;
reconnectPromise;
options;
sendQueue;
constructor(wsGetter, clientId, options) {
super(NaiveJsonCodec, clientId);
this.ws = ws;
ws.onmessage = (msg) => this.onMessage(msg.data.toString());
this.destroyed = false;
this.wsGetter = wsGetter;
this.options = { ...defaultOptions, ...options };
this.sendQueue = [];
this.tryConnect();
}
// postcondition: ws is concretely a WebSocket
async tryConnect() {
// wait until it's ready or we get an error
this.reconnectPromise ??= new Promise(async (resolve) => {
const ws = await this.wsGetter();
if (ws.readyState === ws.OPEN) {
return resolve({ ws });
}
if (ws.readyState === ws.CLOSING || ws.readyState === ws.CLOSED) {
return resolve({ err: 'ws is closing or closed' });
}
ws.addEventListener('open', function onOpen() {
ws.removeEventListener('open', onOpen);
resolve({ ws });
});
ws.addEventListener('error', function onError(err) {
ws.removeEventListener('error', onError);
resolve({ err: err.message });
});
ws.addEventListener('close', function onClose(evt) {
ws.removeEventListener('close', onClose);
resolve({ err: evt.reason });
});
});
const res = await this.reconnectPromise;
// only send if we resolved a valid websocket
if ('ws' in res && res.ws.readyState === res.ws.OPEN) {
this.ws = res.ws;
this.ws.onmessage = (msg) => this.onMessage(msg.data.toString());
this.ws.onclose = () => {
this.reconnectPromise = undefined;
this.tryConnect().catch();
};
// send outstanding
for (const id of this.sendQueue) {
const msg = this.sendBuffer.get(id);
if (!msg) {
throw new Error('tried to resend a message we received an ack for');
}
this.ws.send(this.codec.toStringBuf(msg));
}
this.sendQueue = [];
return;
}
// otherwise try and reconnect again
this.reconnectPromise = undefined;
setTimeout(() => this.tryConnect(), this.options.retryIntervalMs);
}
send(msg) {
const id = msg.id;
this.ws.send(this.codec.toStringBuf(msg));
if (this.destroyed) {
throw new Error('ws is destroyed, cant send');
}
this.sendBuffer.set(id, msg);
if (this.ws && this.ws.readyState === this.ws.OPEN) {
this.ws.send(this.codec.toStringBuf(msg));
}
else {
this.sendQueue.push(id);
this.tryConnect().catch();
}
return id;
}
async close() {
return this.ws.close();
this.destroyed = true;
return this.ws?.close();
}
}
import http from 'http';
import { WebSocketTransport } from './ws';
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
import { createWebSocketClient, createWebSocketServer, onServerReady, waitForMessage, } from './util';
const port = 4444;
describe('sending and receiving across websockets works', () => {
import { describe, test, expect, afterAll } from 'vitest';
import { createWebSocketServer, createWsTransports, onServerReady, waitForMessage, } from './util';
import { nanoid } from 'nanoid';
const getMsg = () => ({
id: nanoid(),
from: 'client',
to: 'SERVER',
serviceName: 'test',
procedureName: 'test',
payload: {
msg: 'cool',
test: Math.random(),
},
});
describe('sending and receiving across websockets works', async () => {
const server = http.createServer();
let wss;
beforeAll(async () => {
await onServerReady(server, port);
wss = await createWebSocketServer(server);
});
const port = await onServerReady(server);
const wss = await createWebSocketServer(server);
afterAll(() => {

@@ -20,23 +27,50 @@ wss.clients.forEach((socket) => {

test('basic send/receive', async () => {
let serverTransport;
wss.on('connection', (conn) => {
serverTransport = new WebSocketTransport(conn, 'SERVER');
const [clientTransport, serverTransport] = createWsTransports(port, wss);
const msg = getMsg();
clientTransport.send(msg);
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg.id)).resolves.toStrictEqual(msg.payload);
});
});
describe('retry logic', async () => {
const server = http.createServer();
const port = await onServerReady(server);
const wss = await createWebSocketServer(server);
afterAll(() => {
wss.clients.forEach((socket) => {
socket.close();
});
const clientSoc = await createWebSocketClient(port);
const clientTransport = new WebSocketTransport(clientSoc, 'client');
const msg = {
msg: 'cool',
test: 123,
};
clientTransport.send({
id: '1',
from: 'client',
to: 'SERVER',
serviceName: 'test',
procedureName: 'test',
payload: msg,
});
expect(serverTransport).toBeTruthy();
return expect(waitForMessage(serverTransport)).resolves.toStrictEqual(msg);
server.close();
});
// TODO: right now, we only test client-side disconnects, we probably
// need to also write tests for server-side crashes (but this involves clearing/restoring state)
// not going to worry about this rn but for future
test('ws transport is recreated after clean disconnect', async () => {
const [clientTransport, serverTransport] = createWsTransports(port, wss);
const msg1 = getMsg();
const msg2 = getMsg();
clientTransport.send(msg1);
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
clientTransport.ws?.close();
clientTransport.send(msg2);
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
});
test('ws transport is recreated after unclean disconnect', async () => {
const [clientTransport, serverTransport] = createWsTransports(port, wss);
const msg1 = getMsg();
const msg2 = getMsg();
clientTransport.send(msg1);
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
clientTransport.ws?.terminate();
clientTransport.send(msg2);
return expect(waitForMessage(serverTransport, (recv) => recv.id === msg2.id)).resolves.toStrictEqual(msg2.payload);
});
test('ws transport is not recreated after manually closing', async () => {
const [clientTransport, serverTransport] = createWsTransports(port, wss);
const msg1 = getMsg();
const msg2 = getMsg();
clientTransport.send(msg1);
await expect(waitForMessage(serverTransport, (recv) => recv.id === msg1.id)).resolves.toStrictEqual(msg1.payload);
clientTransport.close();
return expect(() => clientTransport.send(msg2)).toThrow(new Error('ws is destroyed, cant send'));
});
});

@@ -5,3 +5,3 @@ {

"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
"version": "0.1.10",
"version": "0.2.0",
"type": "module",

@@ -33,3 +33,4 @@ "main": "index.js",

"release": "npm publish --access public",
"test": "vitest"
"test": "vitest",
"bench": "vitest bench"
},

@@ -36,0 +37,0 @@ "engines": {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc