@replit/river 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/__tests__/integration.test.d.ts +5 -4
- package/dist/__tests__/integration.test.js +4 -4
- package/dist/__tests__/typescript-stress.test.d.ts +7 -0
- package/dist/__tests__/typescript-stress.test.js +97 -0
- package/dist/index.d.ts +1 -0
- package/dist/router/builder.d.ts +13 -13
- package/dist/router/client.d.ts +4 -4
- package/dist/router/context.d.ts +28 -0
- package/dist/router/context.js +1 -0
- package/dist/router/server.d.ts +3 -2
- package/dist/router/server.js +14 -5
- package/dist/router/server.util.d.ts +3 -2
- package/dist/router/server.util.js +4 -4
- package/package.json +1 -1
|
@@ -18,15 +18,16 @@ export declare const TestServiceConstructor: () => {
|
|
|
18
18
|
output: import("@sinclair/typebox").TObject<{
|
|
19
19
|
result: import("@sinclair/typebox").TNumber;
|
|
20
20
|
}>;
|
|
21
|
-
handler: (
|
|
21
|
+
handler: (context: import("..").ServiceContextWithState<{
|
|
22
22
|
count: number;
|
|
23
|
-
}
|
|
23
|
+
}>, input: import("../transport/message").TransportMessage<{
|
|
24
24
|
n: number;
|
|
25
25
|
}>) => Promise<import("../transport/message").TransportMessage<{
|
|
26
26
|
result: number;
|
|
27
27
|
}>>;
|
|
28
28
|
type: "rpc";
|
|
29
29
|
};
|
|
30
|
+
} & {
|
|
30
31
|
echo: {
|
|
31
32
|
input: import("@sinclair/typebox").TObject<{
|
|
32
33
|
msg: import("@sinclair/typebox").TString;
|
|
@@ -35,9 +36,9 @@ export declare const TestServiceConstructor: () => {
|
|
|
35
36
|
output: import("@sinclair/typebox").TObject<{
|
|
36
37
|
response: import("@sinclair/typebox").TString;
|
|
37
38
|
}>;
|
|
38
|
-
handler: (
|
|
39
|
+
handler: (context: import("..").ServiceContextWithState<{
|
|
39
40
|
count: number;
|
|
40
|
-
}
|
|
41
|
+
}>, input: AsyncIterable<import("../transport/message").TransportMessage<{
|
|
41
42
|
msg: string;
|
|
42
43
|
ignore: boolean;
|
|
43
44
|
}>>, output: import("it-pushable").Pushable<import("../transport/message").TransportMessage<{
|
|
@@ -20,17 +20,17 @@ export const TestServiceConstructor = () => ServiceBuilder.create('test')
|
|
|
20
20
|
type: 'rpc',
|
|
21
21
|
input: Type.Object({ n: Type.Number() }),
|
|
22
22
|
output: Type.Object({ result: Type.Number() }),
|
|
23
|
-
async handler(
|
|
23
|
+
async handler(ctx, msg) {
|
|
24
24
|
const { n } = msg.payload;
|
|
25
|
-
state.count += n;
|
|
26
|
-
return reply(msg, { result: state.count });
|
|
25
|
+
ctx.state.count += n;
|
|
26
|
+
return reply(msg, { result: ctx.state.count });
|
|
27
27
|
},
|
|
28
28
|
})
|
|
29
29
|
.defineProcedure('echo', {
|
|
30
30
|
type: 'stream',
|
|
31
31
|
input: EchoRequest,
|
|
32
32
|
output: EchoResponse,
|
|
33
|
-
async handler(
|
|
33
|
+
async handler(_ctx, msgStream, returnStream) {
|
|
34
34
|
for await (const msg of msgStream) {
|
|
35
35
|
const req = msg.payload;
|
|
36
36
|
if (!req.ignore) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { MessageId, OpaqueTransportMessage } from '../transport/message';
|
|
2
|
+
import { Transport } from '../transport/types';
|
|
3
|
+
export declare class MockTransport extends Transport {
|
|
4
|
+
constructor(clientId: string);
|
|
5
|
+
send(msg: OpaqueTransportMessage): MessageId;
|
|
6
|
+
close(): Promise<void>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, test } from 'vitest';
|
|
2
|
+
import { ServiceBuilder, serializeService } from '../router/builder';
|
|
3
|
+
import { Type } from '@sinclair/typebox';
|
|
4
|
+
import { reply } from '../transport/message';
|
|
5
|
+
import { createServer } from '../router/server';
|
|
6
|
+
import { Transport } from '../transport/types';
|
|
7
|
+
import { NaiveJsonCodec } from '../codec/json';
|
|
8
|
+
import { createClient } from '../router/client';
|
|
9
|
+
const fnBody = {
|
|
10
|
+
type: 'rpc',
|
|
11
|
+
input: Type.Object({ a: Type.Number() }),
|
|
12
|
+
output: Type.Object({ b: Type.Number() }),
|
|
13
|
+
async handler(_state, msg) {
|
|
14
|
+
return reply(msg, { b: msg.payload.a });
|
|
15
|
+
},
|
|
16
|
+
};
|
|
17
|
+
// typescript is limited to max 50 constraints
|
|
18
|
+
// see: https://github.com/microsoft/TypeScript/issues/33541
|
|
19
|
+
const svc = () => ServiceBuilder.create('test')
|
|
20
|
+
.defineProcedure('f1', fnBody)
|
|
21
|
+
.defineProcedure('f2', fnBody)
|
|
22
|
+
.defineProcedure('f3', fnBody)
|
|
23
|
+
.defineProcedure('f4', fnBody)
|
|
24
|
+
.defineProcedure('f5', fnBody)
|
|
25
|
+
.defineProcedure('f6', fnBody)
|
|
26
|
+
.defineProcedure('f7', fnBody)
|
|
27
|
+
.defineProcedure('f8', fnBody)
|
|
28
|
+
.defineProcedure('f9', fnBody)
|
|
29
|
+
.defineProcedure('f10', fnBody)
|
|
30
|
+
.defineProcedure('f11', fnBody)
|
|
31
|
+
.defineProcedure('f12', fnBody)
|
|
32
|
+
.defineProcedure('f13', fnBody)
|
|
33
|
+
.defineProcedure('f14', fnBody)
|
|
34
|
+
.defineProcedure('f15', fnBody)
|
|
35
|
+
.defineProcedure('f16', fnBody)
|
|
36
|
+
.defineProcedure('f17', fnBody)
|
|
37
|
+
.defineProcedure('f18', fnBody)
|
|
38
|
+
.defineProcedure('f19', fnBody)
|
|
39
|
+
.defineProcedure('f20', fnBody)
|
|
40
|
+
.defineProcedure('f21', fnBody)
|
|
41
|
+
.defineProcedure('f22', fnBody)
|
|
42
|
+
.defineProcedure('f23', fnBody)
|
|
43
|
+
.defineProcedure('f24', fnBody)
|
|
44
|
+
.defineProcedure('f25', fnBody)
|
|
45
|
+
.defineProcedure('f26', fnBody)
|
|
46
|
+
.defineProcedure('f27', fnBody)
|
|
47
|
+
.defineProcedure('f28', fnBody)
|
|
48
|
+
.defineProcedure('f29', fnBody)
|
|
49
|
+
.defineProcedure('f30', fnBody)
|
|
50
|
+
.defineProcedure('f31', fnBody)
|
|
51
|
+
.defineProcedure('f32', fnBody)
|
|
52
|
+
.defineProcedure('f33', fnBody)
|
|
53
|
+
.defineProcedure('f34', fnBody)
|
|
54
|
+
.defineProcedure('f35', fnBody)
|
|
55
|
+
.defineProcedure('f36', fnBody)
|
|
56
|
+
.defineProcedure('f37', fnBody)
|
|
57
|
+
.defineProcedure('f38', fnBody)
|
|
58
|
+
.defineProcedure('f39', fnBody)
|
|
59
|
+
.defineProcedure('f40', fnBody)
|
|
60
|
+
.defineProcedure('f41', fnBody)
|
|
61
|
+
.defineProcedure('f42', fnBody)
|
|
62
|
+
.defineProcedure('f43', fnBody)
|
|
63
|
+
.defineProcedure('f44', fnBody)
|
|
64
|
+
.defineProcedure('f45', fnBody)
|
|
65
|
+
.defineProcedure('f46', fnBody)
|
|
66
|
+
.defineProcedure('f47', fnBody)
|
|
67
|
+
.defineProcedure('f48', fnBody)
|
|
68
|
+
.defineProcedure('f49', fnBody)
|
|
69
|
+
.finalize();
|
|
70
|
+
// mock transport
|
|
71
|
+
export class MockTransport extends Transport {
|
|
72
|
+
constructor(clientId) {
|
|
73
|
+
super(NaiveJsonCodec, clientId);
|
|
74
|
+
}
|
|
75
|
+
send(msg) {
|
|
76
|
+
const id = msg.id;
|
|
77
|
+
return id;
|
|
78
|
+
}
|
|
79
|
+
async close() { }
|
|
80
|
+
}
|
|
81
|
+
describe("ensure typescript doesn't give up trying to infer the types for large services", () => {
|
|
82
|
+
test('service with many procedures hits typescript limit', () => {
|
|
83
|
+
expect(serializeService(svc())).toBeTruthy();
|
|
84
|
+
});
|
|
85
|
+
test('serverclient should support many services with many procedures', async () => {
|
|
86
|
+
const listing = {
|
|
87
|
+
a: svc(),
|
|
88
|
+
b: svc(),
|
|
89
|
+
c: svc(),
|
|
90
|
+
d: svc(),
|
|
91
|
+
};
|
|
92
|
+
const server = await createServer(new MockTransport('SERVER'), listing);
|
|
93
|
+
const client = createClient(new MockTransport('client'));
|
|
94
|
+
expect(server).toBeTruthy();
|
|
95
|
+
expect(client).toBeTruthy();
|
|
96
|
+
});
|
|
97
|
+
});
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export type { ServerClient } from './router/client';
|
|
|
5
5
|
export { createServer } from './router/server';
|
|
6
6
|
export { asClientRpc, asClientStream } from './router/server.util';
|
|
7
7
|
export type { Server } from './router/server';
|
|
8
|
+
export type { ServiceContext, ServiceContextWithState } from './router/context';
|
|
8
9
|
export { Transport } from './transport/types';
|
|
9
10
|
export { TransportMessageSchema, OpaqueTransportMessageSchema, TransportAckSchema, msg, payloadToTransportMessage, ack, reply, } from './transport/message';
|
|
10
11
|
export type { TransportMessage, MessageId, OpaqueTransportMessage, TransportClientId, TransportMessageAck, } from './transport/message';
|
package/dist/router/builder.d.ts
CHANGED
|
@@ -1,27 +1,29 @@
|
|
|
1
1
|
import { TObject, Static } from '@sinclair/typebox';
|
|
2
2
|
import type { Pushable } from 'it-pushable';
|
|
3
3
|
import { TransportMessage } from '../transport/message';
|
|
4
|
+
import { ServiceContextWithState } from './context';
|
|
4
5
|
export type ValidProcType = 'stream' | 'rpc';
|
|
5
6
|
export type ProcListing = Record<string, Procedure<object, ValidProcType, TObject, TObject>>;
|
|
6
|
-
export interface Service<Name extends string
|
|
7
|
+
export interface Service<Name extends string, State extends object, Procs extends ProcListing> {
|
|
7
8
|
name: Name;
|
|
8
9
|
state: State;
|
|
9
10
|
procedures: Procs;
|
|
10
11
|
}
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export type
|
|
14
|
-
export type
|
|
15
|
-
export type
|
|
12
|
+
export type AnyService = Service<string, object, any>;
|
|
13
|
+
export declare function serializeService(s: AnyService): object;
|
|
14
|
+
export type ProcHandler<S extends AnyService, ProcName extends keyof S['procedures']> = S['procedures'][ProcName]['handler'];
|
|
15
|
+
export type ProcInput<S extends AnyService, ProcName extends keyof S['procedures']> = S['procedures'][ProcName]['input'];
|
|
16
|
+
export type ProcOutput<S extends AnyService, ProcName extends keyof S['procedures']> = S['procedures'][ProcName]['output'];
|
|
17
|
+
export type ProcType<S extends AnyService, ProcName extends keyof S['procedures']> = S['procedures'][ProcName]['type'];
|
|
16
18
|
export type Procedure<State extends object | unknown, Ty extends ValidProcType, I extends TObject, O extends TObject> = Ty extends 'rpc' ? {
|
|
17
19
|
input: I;
|
|
18
20
|
output: O;
|
|
19
|
-
handler: (
|
|
21
|
+
handler: (context: ServiceContextWithState<State>, input: TransportMessage<Static<I>>) => Promise<TransportMessage<Static<O>>>;
|
|
20
22
|
type: Ty;
|
|
21
23
|
} : {
|
|
22
24
|
input: I;
|
|
23
25
|
output: O;
|
|
24
|
-
handler: (
|
|
26
|
+
handler: (context: ServiceContextWithState<State>, input: AsyncIterable<TransportMessage<Static<I>>>, output: Pushable<TransportMessage<Static<O>>>) => Promise<void>;
|
|
25
27
|
type: Ty;
|
|
26
28
|
};
|
|
27
29
|
export declare class ServiceBuilder<T extends Service<string, object, ProcListing>> {
|
|
@@ -33,13 +35,11 @@ export declare class ServiceBuilder<T extends Service<string, object, ProcListin
|
|
|
33
35
|
state: InitState;
|
|
34
36
|
procedures: T['procedures'];
|
|
35
37
|
}>;
|
|
36
|
-
defineProcedure<ProcName extends string, Ty extends ValidProcType, I extends TObject, O extends TObject,
|
|
37
|
-
[k in ProcName]: Procedure<T['state'], Ty, I, O>;
|
|
38
|
-
}>(procName: ProcName, procDef: Procedure<T['state'], Ty, I, O>): ServiceBuilder<{
|
|
38
|
+
defineProcedure<ProcName extends string, Ty extends ValidProcType, I extends TObject, O extends TObject>(procName: ProcName, procDef: Procedure<T['state'], Ty, I, O>): ServiceBuilder<{
|
|
39
39
|
name: T['name'];
|
|
40
40
|
state: T['state'];
|
|
41
|
-
procedures: {
|
|
42
|
-
[
|
|
41
|
+
procedures: T['procedures'] & {
|
|
42
|
+
[k in ProcName]: Procedure<T['state'], Ty, I, O>;
|
|
43
43
|
};
|
|
44
44
|
}>;
|
|
45
45
|
static create<Name extends string>(name: Name): ServiceBuilder<{
|
package/dist/router/client.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { Transport } from '../transport/types';
|
|
2
|
-
import { ProcInput, ProcOutput, ProcType
|
|
2
|
+
import { AnyService, ProcInput, ProcOutput, ProcType } from './builder';
|
|
3
3
|
import type { Pushable } from 'it-pushable';
|
|
4
4
|
import { Server } from './server';
|
|
5
5
|
import { Static } from '@sinclair/typebox';
|
|
6
|
-
type ServiceClient<Router extends
|
|
6
|
+
type ServiceClient<Router extends AnyService> = {
|
|
7
7
|
[ProcName in keyof Router['procedures']]: ProcType<Router, ProcName> extends 'rpc' ? (input: Static<ProcInput<Router, ProcName>>) => Promise<Static<ProcOutput<Router, ProcName>>> : () => Promise<[
|
|
8
8
|
Pushable<Static<ProcInput<Router, ProcName>>>,
|
|
9
9
|
AsyncIterator<Static<ProcOutput<Router, ProcName>>>,
|
|
10
10
|
() => void
|
|
11
11
|
]>;
|
|
12
12
|
};
|
|
13
|
-
export type ServerClient<Srv extends Server<Record<string,
|
|
13
|
+
export type ServerClient<Srv extends Server<Record<string, AnyService>>> = {
|
|
14
14
|
[SvcName in keyof Srv['services']]: ServiceClient<Srv['services'][SvcName]>;
|
|
15
15
|
};
|
|
16
|
-
export declare const createClient: <Srv extends Server<Record<string,
|
|
16
|
+
export declare const createClient: <Srv extends Server<Record<string, AnyService>>>(transport: Transport) => ServerClient<Srv>;
|
|
17
17
|
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The context for services/procedures. This is used only on
|
|
3
|
+
* the server.
|
|
4
|
+
*
|
|
5
|
+
* An important detail is that the state prop is always on
|
|
6
|
+
* this interface and it shouldn't be changed, removed, or
|
|
7
|
+
* extended. This prop is for the state of a service.
|
|
8
|
+
*
|
|
9
|
+
* You should use declaration merging to extend this interface
|
|
10
|
+
* with whatever you need. For example, if you need to access
|
|
11
|
+
* a database, you could do:
|
|
12
|
+
* ```ts
|
|
13
|
+
* declare module '@replit/river' {
|
|
14
|
+
* interface ServiceContext {
|
|
15
|
+
* db: Database;
|
|
16
|
+
* }
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export interface ServiceContext {
|
|
21
|
+
state: object | unknown;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* The {@link ServiceContext} with state. This is what is passed to procedures.
|
|
25
|
+
*/
|
|
26
|
+
export type ServiceContextWithState<State extends object | unknown> = ServiceContext & {
|
|
27
|
+
state: State;
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/router/server.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Transport } from '../transport/types';
|
|
2
|
-
import {
|
|
2
|
+
import { AnyService } from './builder';
|
|
3
|
+
import { ServiceContext } from './context';
|
|
3
4
|
export interface Server<Services> {
|
|
4
5
|
services: Services;
|
|
5
6
|
close(): Promise<void>;
|
|
6
7
|
}
|
|
7
|
-
export declare function createServer<Services extends Record<string,
|
|
8
|
+
export declare function createServer<Services extends Record<string, AnyService>>(transport: Transport, services: Services, extendedContext?: Omit<ServiceContext, 'state'>): Promise<Server<Services>>;
|
package/dist/router/server.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
import { Value } from '@sinclair/typebox/value';
|
|
2
2
|
import { pushable } from 'it-pushable';
|
|
3
|
-
export async function createServer(transport, services) {
|
|
4
|
-
|
|
3
|
+
export async function createServer(transport, services, extendedContext) {
|
|
4
|
+
const contextMap = new Map();
|
|
5
5
|
const streamMap = new Map();
|
|
6
|
+
function getContext(service) {
|
|
7
|
+
const context = contextMap.get(service);
|
|
8
|
+
if (!context) {
|
|
9
|
+
throw new Error(`No context found for ${service.name}`);
|
|
10
|
+
}
|
|
11
|
+
return context;
|
|
12
|
+
}
|
|
6
13
|
for (const [serviceName, service] of Object.entries(services)) {
|
|
14
|
+
// populate the context map
|
|
15
|
+
contextMap.set(service, { ...extendedContext, state: service.state });
|
|
16
|
+
// create streams for every stream procedure
|
|
7
17
|
for (const [procedureName, proc] of Object.entries(service.procedures)) {
|
|
8
18
|
const procedure = proc;
|
|
9
19
|
if (procedure.type === 'stream') {
|
|
@@ -14,7 +24,7 @@ export async function createServer(transport, services) {
|
|
|
14
24
|
outgoing,
|
|
15
25
|
doneCtx: Promise.all([
|
|
16
26
|
// processing the actual procedure
|
|
17
|
-
procedure.handler(service
|
|
27
|
+
procedure.handler(getContext(service), incoming, outgoing),
|
|
18
28
|
// sending outgoing messages back to client
|
|
19
29
|
(async () => {
|
|
20
30
|
for await (const response of outgoing) {
|
|
@@ -39,8 +49,7 @@ export async function createServer(transport, services) {
|
|
|
39
49
|
const inputMessage = msg;
|
|
40
50
|
if (procedure.type === 'rpc' &&
|
|
41
51
|
Value.Check(procedure.input, inputMessage.payload)) {
|
|
42
|
-
|
|
43
|
-
const response = await procedure.handler(service.state, inputMessage);
|
|
52
|
+
const response = await procedure.handler(getContext(service), inputMessage);
|
|
44
53
|
transport.send(response);
|
|
45
54
|
return;
|
|
46
55
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Static, TObject } from '@sinclair/typebox';
|
|
2
2
|
import { Procedure } from './builder';
|
|
3
3
|
import type { Pushable } from 'it-pushable';
|
|
4
|
-
|
|
5
|
-
export declare function
|
|
4
|
+
import { ServiceContext } from './context';
|
|
5
|
+
export declare function asClientRpc<State extends object | unknown, I extends TObject, O extends TObject>(state: State, proc: Procedure<State, 'rpc', I, O>, extendedContext?: Omit<ServiceContext, 'state'>): (msg: Static<I>) => Promise<Static<O>>;
|
|
6
|
+
export declare function asClientStream<State extends object | unknown, I extends TObject, O extends TObject>(state: State, proc: Procedure<State, 'stream', I, O>, extendedContext?: Omit<ServiceContext, 'state'>): [Pushable<Static<I>>, Pushable<Static<O>>];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { payloadToTransportMessage, } from '../transport/message';
|
|
2
2
|
import { pushable } from 'it-pushable';
|
|
3
|
-
export function asClientRpc(state, proc) {
|
|
3
|
+
export function asClientRpc(state, proc, extendedContext) {
|
|
4
4
|
return (msg) => proc
|
|
5
|
-
.handler(state, payloadToTransportMessage(msg))
|
|
5
|
+
.handler({ ...extendedContext, state }, payloadToTransportMessage(msg))
|
|
6
6
|
.then((res) => res.payload);
|
|
7
7
|
}
|
|
8
|
-
export function asClientStream(state, proc) {
|
|
8
|
+
export function asClientStream(state, proc, extendedContext) {
|
|
9
9
|
const i = pushable({ objectMode: true });
|
|
10
10
|
const o = pushable({ objectMode: true });
|
|
11
11
|
const ri = pushable({ objectMode: true });
|
|
@@ -25,7 +25,7 @@ export function asClientStream(state, proc) {
|
|
|
25
25
|
})();
|
|
26
26
|
// handle
|
|
27
27
|
(async () => {
|
|
28
|
-
await proc.handler(state, ri, ro);
|
|
28
|
+
await proc.handler({ ...extendedContext, state }, ri, ro);
|
|
29
29
|
ro.end();
|
|
30
30
|
})();
|
|
31
31
|
return [i, o];
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@replit/river",
|
|
3
3
|
"description": "It's like tRPC but... with JSON Schema Support, duplex streaming and support for service multiplexing. Transport agnostic!",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.8",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|