starpc 0.50.0 → 0.52.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cmd/protoc-gen-es-starpc/typescript.ts +37 -0
- package/dist/cmd/protoc-gen-es-starpc/typescript.js +24 -0
- package/dist/echo/echo_srpc.pb.d.ts +44 -1
- package/dist/echo/server.d.ts +9 -8
- package/dist/echo/server.js +6 -6
- package/dist/integration/cross-language/ts-client.js +5 -79
- package/dist/integration/cross-language/ts-server.js +4 -100
- package/dist/mock/mock_srpc.pb.d.ts +14 -1
- package/dist/srpc/channel.js +6 -3
- package/dist/srpc/channel.test.js +20 -1
- package/dist/srpc/client.d.ts +1 -3
- package/dist/srpc/client.js +2 -22
- package/dist/srpc/common-rpc.d.ts +1 -9
- package/dist/srpc/common-rpc.js +4 -62
- package/dist/srpc/handler.d.ts +12 -4
- package/dist/srpc/index.d.ts +2 -4
- package/dist/srpc/index.js +1 -3
- package/dist/srpc/invoker.d.ts +2 -2
- package/dist/srpc/invoker.js +2 -2
- package/dist/srpc/rpcproto.pb.d.ts +0 -44
- package/dist/srpc/rpcproto.pb.js +0 -53
- package/dist/srpc/server-context.d.ts +11 -0
- package/dist/srpc/server-context.js +28 -0
- package/dist/srpc/server-rpc.js +3 -3
- package/dist/srpc/server.test.js +65 -2
- package/dist/srpc/watchdog.test.js +1 -0
- package/echo/echo.go +8 -8
- package/echo/echo.pb.go +12 -36
- package/echo/echo_srpc.pb.cpp +1 -1
- package/echo/echo_srpc.pb.go +1 -1
- package/echo/echo_srpc.pb.hpp +1 -1
- package/echo/echo_srpc.pb.rs +1 -1
- package/echo/echo_srpc.pb.ts +74 -0
- package/echo/server.go +1 -1
- package/echo/server.ts +24 -5
- package/go.mod +1 -1
- package/go.sum +2 -0
- package/integration/cross-language/go-client/main.go +4 -142
- package/integration/cross-language/go-server/main.go +2 -170
- package/integration/cross-language/run.bash +5 -105
- package/integration/cross-language/ts-client.ts +5 -90
- package/integration/cross-language/ts-server.ts +4 -112
- package/mock/mock.go +1 -1
- package/mock/mock.pb.go +12 -36
- package/mock/mock_srpc.pb.cpp +1 -1
- package/mock/mock_srpc.pb.go +1 -1
- package/mock/mock_srpc.pb.hpp +1 -1
- package/mock/mock_srpc.pb.rs +1 -1
- package/mock/mock_srpc.pb.ts +19 -1
- package/package.json +2 -2
- package/srpc/channel.test.ts +21 -1
- package/srpc/channel.ts +7 -3
- package/srpc/client-prefix.go +1 -1
- package/srpc/client-set.go +1 -1
- package/srpc/client.go +1 -1
- package/srpc/client.ts +4 -31
- package/srpc/common-rpc.go +15 -69
- package/srpc/common-rpc.ts +4 -77
- package/srpc/common-rpc_test.go +81 -0
- package/srpc/errors.go +26 -1
- package/srpc/handler.ts +54 -5
- package/srpc/index.ts +7 -4
- package/srpc/invoker-prefix.go +1 -1
- package/srpc/invoker.go +2 -2
- package/srpc/invoker.ts +20 -7
- package/srpc/message.go +1 -1
- package/srpc/msg-stream.go +1 -9
- package/srpc/mux-verbose.go +1 -1
- package/srpc/mux.go +1 -1
- package/srpc/rpcproto.pb.cc +4 -15
- package/srpc/rpcproto.pb.go +102 -419
- package/srpc/rpcproto.pb.h +0 -59
- package/srpc/rpcproto.pb.rs +0 -45
- package/srpc/rpcproto.pb.ts +0 -62
- package/srpc/rpcproto.proto +0 -16
- package/srpc/server-context.ts +55 -0
- package/srpc/server-rpc.go +1 -1
- package/srpc/server-rpc.ts +4 -6
- package/srpc/server.test.ts +94 -1
- package/srpc/stream-pipe.go +1 -1
- package/srpc/stream-rwc.go +1 -1
- package/srpc/stream.go +0 -20
- package/srpc/watchdog.test.ts +1 -0
- package/dist/rpcstream/receipt.test.d.ts +0 -1
- package/dist/rpcstream/receipt.test.js +0 -41
- package/dist/srpc/call-receipt.d.ts +0 -17
- package/dist/srpc/call-receipt.js +0 -106
- package/dist/srpc/call-receipt.test.d.ts +0 -1
- package/dist/srpc/call-receipt.test.js +0 -375
- package/dist/srpc/server-invocation.d.ts +0 -17
- package/dist/srpc/server-invocation.js +0 -37
- package/integration/cross-language/go-server/fixture-owner_test.go +0 -127
- package/srpc/call-receipt-e2e_test.go +0 -111
- package/srpc/call-receipt.go +0 -112
- package/srpc/call-receipt.test.ts +0 -441
- package/srpc/call-receipt.ts +0 -131
- package/srpc/call-receipt_test.go +0 -536
- package/srpc/schema-ownership_test.go +0 -115
- package/srpc/server-invocation.go +0 -40
- package/srpc/server-invocation.ts +0 -76
package/dist/srpc/common-rpc.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { pushable } from 'it-pushable';
|
|
2
|
-
import { Packet
|
|
2
|
+
import { Packet } from './rpcproto.pb.js';
|
|
3
3
|
import { ERR_RPC_ABORT, RemoteRPCError } from './errors.js';
|
|
4
4
|
const maxBufferedOutgoingPackets = 1;
|
|
5
5
|
// CommonRPC is common logic between server and client RPCs.
|
|
@@ -24,25 +24,13 @@ export class CommonRPC {
|
|
|
24
24
|
method;
|
|
25
25
|
// closed indicates this rpc has been closed already.
|
|
26
26
|
closed;
|
|
27
|
-
// remoteCompleted is set only by an explicit remote CallData completion.
|
|
28
|
-
remoteCompleted = false;
|
|
29
27
|
// remoteError records a remote error or transport failure.
|
|
30
28
|
remoteError;
|
|
31
|
-
//
|
|
32
|
-
remoteSourceClosed = false;
|
|
33
|
-
// remoteTerminal is the first valid remote terminal.
|
|
34
|
-
remoteTerminal;
|
|
35
|
-
// invocationController cancels the server invocation on a remote terminal.
|
|
29
|
+
// invocationController cancels the server invocation when the RPC closes.
|
|
36
30
|
invocationController = new AbortController();
|
|
37
|
-
// terminalPromise resolves when a remote terminal is recorded.
|
|
38
|
-
terminalPromise;
|
|
39
|
-
resolveTerminal;
|
|
40
31
|
// writeDrainAbort wakes writers waiting for outbound stream drain on close.
|
|
41
32
|
writeDrainAbort = new AbortController();
|
|
42
33
|
constructor() {
|
|
43
|
-
const { promise, resolve } = Promise.withResolvers();
|
|
44
|
-
this.terminalPromise = promise;
|
|
45
|
-
this.resolveTerminal = resolve;
|
|
46
34
|
this.sink = this._createSink();
|
|
47
35
|
this.source = this._source;
|
|
48
36
|
this.rpcDataSource = this._rpcDataSource;
|
|
@@ -51,49 +39,10 @@ export class CommonRPC {
|
|
|
51
39
|
get isClosed() {
|
|
52
40
|
return this.closed ?? false;
|
|
53
41
|
}
|
|
54
|
-
// invocationSignal is canceled when the RPC
|
|
42
|
+
// invocationSignal is canceled when the RPC closes.
|
|
55
43
|
get invocationSignal() {
|
|
56
44
|
return this.invocationController.signal;
|
|
57
45
|
}
|
|
58
|
-
// waitTerminal waits for the remote terminal or external owner cancellation.
|
|
59
|
-
async waitTerminal(ownerSignal) {
|
|
60
|
-
const { promise: ownerDone, resolve: resolveOwnerDone } = Promise.withResolvers();
|
|
61
|
-
const onAbort = () => resolveOwnerDone();
|
|
62
|
-
ownerSignal.addEventListener('abort', onAbort, { once: true });
|
|
63
|
-
let ownerAborted = ownerSignal.aborted;
|
|
64
|
-
try {
|
|
65
|
-
for (;;) {
|
|
66
|
-
const terminal = this.getTerminalKind();
|
|
67
|
-
if (terminal !== undefined) {
|
|
68
|
-
if (terminal === TerminalKind.CLOSED &&
|
|
69
|
-
this.remoteSourceClosed &&
|
|
70
|
-
!this.closed) {
|
|
71
|
-
await this.close();
|
|
72
|
-
}
|
|
73
|
-
return terminal;
|
|
74
|
-
}
|
|
75
|
-
if (ownerAborted) {
|
|
76
|
-
return TerminalKind.ABANDONED;
|
|
77
|
-
}
|
|
78
|
-
await Promise.race([this.terminalPromise, ownerDone]);
|
|
79
|
-
ownerAborted = ownerSignal.aborted;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
finally {
|
|
83
|
-
ownerSignal.removeEventListener('abort', onAbort);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
// getTerminalKind returns the observed remote terminal, if any.
|
|
87
|
-
getTerminalKind() {
|
|
88
|
-
return this.remoteTerminal;
|
|
89
|
-
}
|
|
90
|
-
recordRemoteTerminal(kind) {
|
|
91
|
-
if (this.remoteTerminal !== undefined) {
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
this.remoteTerminal = kind;
|
|
95
|
-
this.resolveTerminal();
|
|
96
|
-
}
|
|
97
46
|
// writeCallData writes the call data packet.
|
|
98
47
|
async writeCallData(data, complete, error) {
|
|
99
48
|
await this.writeCallDataPacket(data, complete, error);
|
|
@@ -221,20 +170,16 @@ export class CommonRPC {
|
|
|
221
170
|
if (remoteError) {
|
|
222
171
|
this.remoteError ??= remoteError;
|
|
223
172
|
this.invocationController.abort();
|
|
224
|
-
this.recordRemoteTerminal(TerminalKind.TRANSPORT_LOST);
|
|
225
173
|
}
|
|
226
174
|
if (packet.complete && !remoteError) {
|
|
227
|
-
this.remoteCompleted = true;
|
|
228
|
-
this.recordRemoteTerminal(TerminalKind.COMMITTED);
|
|
229
175
|
this._rpcDataSource.end(remoteError);
|
|
230
176
|
}
|
|
231
177
|
else if (remoteError) {
|
|
232
178
|
this._rpcDataSource.end(remoteError);
|
|
233
179
|
}
|
|
234
180
|
}
|
|
235
|
-
// handleCallCancel
|
|
181
|
+
// handleCallCancel aborts the invocation and closes the call.
|
|
236
182
|
async handleCallCancel() {
|
|
237
|
-
this.recordRemoteTerminal(TerminalKind.CANCELED);
|
|
238
183
|
await this.close(new Error(ERR_RPC_ABORT));
|
|
239
184
|
}
|
|
240
185
|
// close closes the call, optionally with an error.
|
|
@@ -246,7 +191,6 @@ export class CommonRPC {
|
|
|
246
191
|
if (!this.remoteError && err) {
|
|
247
192
|
this.remoteError = err;
|
|
248
193
|
}
|
|
249
|
-
this.recordRemoteTerminal(err ? TerminalKind.TRANSPORT_LOST : TerminalKind.CLOSED);
|
|
250
194
|
this.invocationController.abort();
|
|
251
195
|
// note: this does nothing if _source is already ended.
|
|
252
196
|
if (err && err.message) {
|
|
@@ -272,8 +216,6 @@ export class CommonRPC {
|
|
|
272
216
|
await this.handlePacket(msg);
|
|
273
217
|
}
|
|
274
218
|
}
|
|
275
|
-
this.remoteSourceClosed = true;
|
|
276
|
-
this.recordRemoteTerminal(TerminalKind.CLOSED);
|
|
277
219
|
}
|
|
278
220
|
catch (err) {
|
|
279
221
|
this.close(err);
|
package/dist/srpc/handler.d.ts
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import type { Sink, Source } from 'it-stream-types';
|
|
2
|
-
import {
|
|
3
|
-
import type {
|
|
4
|
-
|
|
2
|
+
import { MethodKind, type MessageType } from '@aptre/protobuf-es-lite';
|
|
3
|
+
import type { MessageStream } from './message.js';
|
|
4
|
+
import { type MethodDefinition, ServiceDefinition, ServiceMethodDefinitions } from './definition.js';
|
|
5
|
+
import type { ServerContext } from './server-context.js';
|
|
6
|
+
type MessageOf<T> = T extends MessageType<infer M> ? M : never;
|
|
7
|
+
type ServerMethod<T> = T extends MethodDefinition<infer Request, infer Response, infer Kind, infer _Idempotency> ? Kind extends MethodKind.Unary ? (request: MessageOf<Request>, abortSignal: AbortSignal, context: ServerContext) => Promise<MessageOf<Response>> : Kind extends MethodKind.ServerStreaming ? (request: MessageOf<Request>, abortSignal: AbortSignal, context: ServerContext) => MessageStream<MessageOf<Response>> : Kind extends MethodKind.ClientStreaming ? (request: MessageStream<MessageOf<Request>>, abortSignal: AbortSignal, context: ServerContext) => Promise<MessageOf<Response>> : (request: MessageStream<MessageOf<Request>>, abortSignal: AbortSignal, context: ServerContext) => MessageStream<MessageOf<Response>> : never;
|
|
8
|
+
export type HandlerImplementation<T extends ServiceMethodDefinitions> = Partial<{
|
|
9
|
+
[Method in keyof T]: ServerMethod<T[Method]>;
|
|
10
|
+
}>;
|
|
11
|
+
export type InvokeFn = (dataSource: Source<Uint8Array>, dataSink: Sink<Source<Uint8Array>>, context: ServerContext) => Promise<void>;
|
|
5
12
|
export interface Handler {
|
|
6
13
|
getServiceID(): string;
|
|
7
14
|
getMethodIDs(): string[];
|
|
@@ -18,4 +25,5 @@ export declare class StaticHandler implements Handler {
|
|
|
18
25
|
getMethodIDs(): string[];
|
|
19
26
|
lookupMethod(serviceID: string, methodID: string): Promise<InvokeFn | null>;
|
|
20
27
|
}
|
|
21
|
-
export declare function createHandler<T extends ServiceMethodDefinitions = ServiceMethodDefinitions>(definition: ServiceDefinition<T>, impl:
|
|
28
|
+
export declare function createHandler<T extends ServiceMethodDefinitions = ServiceMethodDefinitions>(definition: ServiceDefinition<T>, impl: HandlerImplementation<T>, serviceID?: string): Handler;
|
|
29
|
+
export {};
|
package/dist/srpc/index.d.ts
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
export { ERR_RPC_ABORT, isAbortError, ERR_STREAM_IDLE, isStreamIdleError, castToError, } from './errors.js';
|
|
2
2
|
export { Client } from './client.js';
|
|
3
|
-
export { CallReceipt } from './call-receipt.js';
|
|
4
|
-
export type { HeldCall, ReceiptRpc } from './call-receipt.js';
|
|
5
|
-
export { ServerInvocation } from './server-invocation.js';
|
|
6
|
-
export { TerminalKind } from './rpcproto.pb.js';
|
|
7
3
|
export { Server } from './server.js';
|
|
8
4
|
export { StreamConn } from './conn.js';
|
|
9
5
|
export type { StreamConnParams, StreamHandler } from './conn.js';
|
|
@@ -31,3 +27,5 @@ export { HandleStreamCtr } from './handle-stream-ctr.js';
|
|
|
31
27
|
export { writeToPushable, buildPushableSink, messagePushable, } from './pushable.js';
|
|
32
28
|
export { Watchdog } from './watchdog.js';
|
|
33
29
|
export type { ProtoRpc } from './proto-rpc.js';
|
|
30
|
+
export { createContextKey, serverContextValue, withServerContextValue, } from './server-context.js';
|
|
31
|
+
export type { ContextKey, ServerContext } from './server-context.js';
|
package/dist/srpc/index.js
CHANGED
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
export { ERR_RPC_ABORT, isAbortError, ERR_STREAM_IDLE, isStreamIdleError, castToError, } from './errors.js';
|
|
2
2
|
export { Client } from './client.js';
|
|
3
|
-
export { CallReceipt } from './call-receipt.js';
|
|
4
|
-
export { ServerInvocation } from './server-invocation.js';
|
|
5
|
-
export { TerminalKind } from './rpcproto.pb.js';
|
|
6
3
|
export { Server } from './server.js';
|
|
7
4
|
export { StreamConn } from './conn.js';
|
|
8
5
|
export { WebSocketConn } from './websocket.js';
|
|
@@ -21,3 +18,4 @@ export { OpenStreamCtr } from './open-stream-ctr.js';
|
|
|
21
18
|
export { HandleStreamCtr } from './handle-stream-ctr.js';
|
|
22
19
|
export { writeToPushable, buildPushableSink, messagePushable, } from './pushable.js';
|
|
23
20
|
export { Watchdog } from './watchdog.js';
|
|
21
|
+
export { createContextKey, serverContextValue, withServerContextValue, } from './server-context.js';
|
package/dist/srpc/invoker.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { MethodDefinition } from './definition.js';
|
|
2
|
+
import type { ServerContext } from './server-context.js';
|
|
2
3
|
import { InvokeFn } from './handler.js';
|
|
3
4
|
import type { MessageType, Message } from '@aptre/protobuf-es-lite';
|
|
4
5
|
import { MethodIdempotency, MethodKind } from '@aptre/protobuf-es-lite';
|
|
5
|
-
|
|
6
|
-
export type MethodProto<R extends Message<R>, O extends Message<O>> = ((request: R, invocation?: ServerInvocation) => Promise<O>) | ((request: R, invocation?: ServerInvocation) => AsyncIterable<O>) | ((request: AsyncIterable<R>, invocation?: ServerInvocation) => Promise<O>) | ((request: AsyncIterable<R>, invocation?: ServerInvocation) => AsyncIterable<O>);
|
|
6
|
+
export type MethodProto<R extends Message<R>, O extends Message<O>> = ((request: R, abortSignal: AbortSignal, context: ServerContext) => Promise<O>) | ((request: R, abortSignal: AbortSignal, context: ServerContext) => AsyncIterable<O>) | ((request: AsyncIterable<R>, abortSignal: AbortSignal, context: ServerContext) => Promise<O>) | ((request: AsyncIterable<R>, abortSignal: AbortSignal, context: ServerContext) => AsyncIterable<O>);
|
|
7
7
|
export declare function createInvokeFn<R extends Message<R>, O extends Message<O>>(methodInfo: MethodDefinition<MessageType<R>, MessageType<O>, MethodKind, MethodIdempotency | undefined>, methodProto: MethodProto<R, O>): InvokeFn;
|
package/dist/srpc/invoker.js
CHANGED
|
@@ -6,7 +6,7 @@ import { MethodKind } from '@aptre/protobuf-es-lite';
|
|
|
6
6
|
// createInvokeFn builds an InvokeFn from a method definition and a function prototype.
|
|
7
7
|
export function createInvokeFn(methodInfo, methodProto) {
|
|
8
8
|
const requestDecode = buildDecodeMessageTransform(methodInfo.I);
|
|
9
|
-
return async (dataSource, dataSink,
|
|
9
|
+
return async (dataSource, dataSink, context) => {
|
|
10
10
|
// responseSink is a Sink for response messages.
|
|
11
11
|
const responseSink = pushable({
|
|
12
12
|
objectMode: true,
|
|
@@ -34,7 +34,7 @@ export function createInvokeFn(methodInfo, methodProto) {
|
|
|
34
34
|
}
|
|
35
35
|
// Call the implementation.
|
|
36
36
|
try {
|
|
37
|
-
const responseObj = methodProto(requestArg,
|
|
37
|
+
const responseObj = methodProto(requestArg, context.signal, context);
|
|
38
38
|
if (!responseObj) {
|
|
39
39
|
throw new Error('return value was undefined');
|
|
40
40
|
}
|
|
@@ -1,49 +1,5 @@
|
|
|
1
1
|
import type { MessageType } from '@aptre/protobuf-es-lite/message';
|
|
2
2
|
export declare const protobufPackage = "srpc";
|
|
3
|
-
/**
|
|
4
|
-
* TerminalKind is the terminal state of a held unary invocation.
|
|
5
|
-
*
|
|
6
|
-
* @generated from enum srpc.TerminalKind
|
|
7
|
-
*/
|
|
8
|
-
export declare enum TerminalKind {
|
|
9
|
-
/**
|
|
10
|
-
* TERMINAL_KIND_UNSPECIFIED is not a valid terminal state.
|
|
11
|
-
*
|
|
12
|
-
* @generated from enum value: TERMINAL_KIND_UNSPECIFIED = 0;
|
|
13
|
-
*/
|
|
14
|
-
UNSPECIFIED = 0,
|
|
15
|
-
/**
|
|
16
|
-
* TERMINAL_KIND_COMMITTED identifies an explicit remote CallData completion.
|
|
17
|
-
*
|
|
18
|
-
* @generated from enum value: TERMINAL_KIND_COMMITTED = 1;
|
|
19
|
-
*/
|
|
20
|
-
COMMITTED = 1,
|
|
21
|
-
/**
|
|
22
|
-
* TERMINAL_KIND_CANCELED identifies a remote CallCancel packet.
|
|
23
|
-
*
|
|
24
|
-
* @generated from enum value: TERMINAL_KIND_CANCELED = 2;
|
|
25
|
-
*/
|
|
26
|
-
CANCELED = 2,
|
|
27
|
-
/**
|
|
28
|
-
* TERMINAL_KIND_TRANSPORT_LOST identifies a remote error or transport failure.
|
|
29
|
-
*
|
|
30
|
-
* @generated from enum value: TERMINAL_KIND_TRANSPORT_LOST = 3;
|
|
31
|
-
*/
|
|
32
|
-
TRANSPORT_LOST = 3,
|
|
33
|
-
/**
|
|
34
|
-
* TERMINAL_KIND_CLOSED identifies a bare remote close without completion.
|
|
35
|
-
*
|
|
36
|
-
* @generated from enum value: TERMINAL_KIND_CLOSED = 4;
|
|
37
|
-
*/
|
|
38
|
-
CLOSED = 4,
|
|
39
|
-
/**
|
|
40
|
-
* TERMINAL_KIND_ABANDONED identifies owner-context expiry without a remote terminal.
|
|
41
|
-
*
|
|
42
|
-
* @generated from enum value: TERMINAL_KIND_ABANDONED = 5;
|
|
43
|
-
*/
|
|
44
|
-
ABANDONED = 5
|
|
45
|
-
}
|
|
46
|
-
export declare const TerminalKind_Enum: import("@aptre/protobuf-es-lite/enum").EnumType;
|
|
47
3
|
/**
|
|
48
4
|
* CallStart requests starting a new RPC call.
|
|
49
5
|
*
|
package/dist/srpc/rpcproto.pb.js
CHANGED
|
@@ -1,62 +1,9 @@
|
|
|
1
1
|
// @generated by protoc-gen-es-lite unknown with parameter "target=ts,ts_nocheck=false"
|
|
2
2
|
// @generated from file github.com/aperturerobotics/starpc/srpc/rpcproto.proto (package srpc, syntax proto3)
|
|
3
3
|
/* eslint-disable */
|
|
4
|
-
import { createEnumType } from '@aptre/protobuf-es-lite/enum';
|
|
5
4
|
import { createMessageType } from '@aptre/protobuf-es-lite/message';
|
|
6
5
|
import { ScalarType } from '@aptre/protobuf-es-lite/scalar';
|
|
7
6
|
export const protobufPackage = 'srpc';
|
|
8
|
-
/**
|
|
9
|
-
* TerminalKind is the terminal state of a held unary invocation.
|
|
10
|
-
*
|
|
11
|
-
* @generated from enum srpc.TerminalKind
|
|
12
|
-
*/
|
|
13
|
-
export var TerminalKind;
|
|
14
|
-
(function (TerminalKind) {
|
|
15
|
-
/**
|
|
16
|
-
* TERMINAL_KIND_UNSPECIFIED is not a valid terminal state.
|
|
17
|
-
*
|
|
18
|
-
* @generated from enum value: TERMINAL_KIND_UNSPECIFIED = 0;
|
|
19
|
-
*/
|
|
20
|
-
TerminalKind[TerminalKind["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
21
|
-
/**
|
|
22
|
-
* TERMINAL_KIND_COMMITTED identifies an explicit remote CallData completion.
|
|
23
|
-
*
|
|
24
|
-
* @generated from enum value: TERMINAL_KIND_COMMITTED = 1;
|
|
25
|
-
*/
|
|
26
|
-
TerminalKind[TerminalKind["COMMITTED"] = 1] = "COMMITTED";
|
|
27
|
-
/**
|
|
28
|
-
* TERMINAL_KIND_CANCELED identifies a remote CallCancel packet.
|
|
29
|
-
*
|
|
30
|
-
* @generated from enum value: TERMINAL_KIND_CANCELED = 2;
|
|
31
|
-
*/
|
|
32
|
-
TerminalKind[TerminalKind["CANCELED"] = 2] = "CANCELED";
|
|
33
|
-
/**
|
|
34
|
-
* TERMINAL_KIND_TRANSPORT_LOST identifies a remote error or transport failure.
|
|
35
|
-
*
|
|
36
|
-
* @generated from enum value: TERMINAL_KIND_TRANSPORT_LOST = 3;
|
|
37
|
-
*/
|
|
38
|
-
TerminalKind[TerminalKind["TRANSPORT_LOST"] = 3] = "TRANSPORT_LOST";
|
|
39
|
-
/**
|
|
40
|
-
* TERMINAL_KIND_CLOSED identifies a bare remote close without completion.
|
|
41
|
-
*
|
|
42
|
-
* @generated from enum value: TERMINAL_KIND_CLOSED = 4;
|
|
43
|
-
*/
|
|
44
|
-
TerminalKind[TerminalKind["CLOSED"] = 4] = "CLOSED";
|
|
45
|
-
/**
|
|
46
|
-
* TERMINAL_KIND_ABANDONED identifies owner-context expiry without a remote terminal.
|
|
47
|
-
*
|
|
48
|
-
* @generated from enum value: TERMINAL_KIND_ABANDONED = 5;
|
|
49
|
-
*/
|
|
50
|
-
TerminalKind[TerminalKind["ABANDONED"] = 5] = "ABANDONED";
|
|
51
|
-
})(TerminalKind || (TerminalKind = {}));
|
|
52
|
-
export const TerminalKind_Enum = /* @__PURE__ */ createEnumType('srpc.TerminalKind', [
|
|
53
|
-
[0, 'TERMINAL_KIND_UNSPECIFIED'],
|
|
54
|
-
[1, 'TERMINAL_KIND_COMMITTED'],
|
|
55
|
-
[2, 'TERMINAL_KIND_CANCELED'],
|
|
56
|
-
[3, 'TERMINAL_KIND_TRANSPORT_LOST'],
|
|
57
|
-
[4, 'TERMINAL_KIND_CLOSED'],
|
|
58
|
-
[5, 'TERMINAL_KIND_ABANDONED'],
|
|
59
|
-
]);
|
|
60
7
|
export const CallStart =
|
|
61
8
|
/* @__PURE__ */ createMessageType({
|
|
62
9
|
typeName: 'srpc.CallStart',
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
declare const contextKeyValue: unique symbol;
|
|
2
|
+
export interface ContextKey<T> {
|
|
3
|
+
readonly [contextKeyValue]?: T;
|
|
4
|
+
}
|
|
5
|
+
export interface ServerContext {
|
|
6
|
+
readonly signal: AbortSignal;
|
|
7
|
+
}
|
|
8
|
+
export declare function createContextKey<T>(): ContextKey<T>;
|
|
9
|
+
export declare function withServerContextValue<T>(context: ServerContext, key: ContextKey<T>, value: T): ServerContext;
|
|
10
|
+
export declare function serverContextValue<T>(context: ServerContext, key: ContextKey<T>): T | undefined;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const contextParent = Symbol('server context parent');
|
|
2
|
+
const contextKey = Symbol('server context key');
|
|
3
|
+
const contextStoredValue = Symbol('server context value');
|
|
4
|
+
// createContextKey constructs an identity key for one server-context value.
|
|
5
|
+
export function createContextKey() {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
// withServerContextValue derives a context with one immutable typed value.
|
|
9
|
+
export function withServerContextValue(context, key, value) {
|
|
10
|
+
const derived = {
|
|
11
|
+
signal: context.signal,
|
|
12
|
+
[contextParent]: context,
|
|
13
|
+
[contextKey]: key,
|
|
14
|
+
[contextStoredValue]: value,
|
|
15
|
+
};
|
|
16
|
+
return derived;
|
|
17
|
+
}
|
|
18
|
+
// serverContextValue retrieves the nearest value for a typed identity key.
|
|
19
|
+
export function serverContextValue(context, key) {
|
|
20
|
+
let current = context;
|
|
21
|
+
while (current) {
|
|
22
|
+
if (current[contextKey] === key) {
|
|
23
|
+
return current[contextStoredValue];
|
|
24
|
+
}
|
|
25
|
+
current = current[contextParent];
|
|
26
|
+
}
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
package/dist/srpc/server-rpc.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { CommonRPC } from './common-rpc.js';
|
|
2
|
-
import { ServerInvocation } from './server-invocation.js';
|
|
3
2
|
// ServerRPC is an ongoing RPC from the server side.
|
|
4
3
|
export class ServerRPC extends CommonRPC {
|
|
5
4
|
// lookupMethod looks up the incoming RPC methods.
|
|
@@ -38,9 +37,10 @@ export class ServerRPC extends CommonRPC {
|
|
|
38
37
|
// invokeRPC starts invoking the RPC handler.
|
|
39
38
|
async invokeRPC(invokeFn) {
|
|
40
39
|
const dataSink = this._createDataSink();
|
|
41
|
-
const invocation = new ServerInvocation(this.invocationSignal, (ownerSignal) => this.waitTerminal(ownerSignal));
|
|
42
40
|
try {
|
|
43
|
-
await invokeFn(this.rpcDataSource, dataSink,
|
|
41
|
+
await invokeFn(this.rpcDataSource, dataSink, {
|
|
42
|
+
signal: this.invocationSignal,
|
|
43
|
+
});
|
|
44
44
|
}
|
|
45
45
|
catch (err) {
|
|
46
46
|
this.close(err);
|
package/dist/srpc/server.test.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { describe, it, beforeEach, expect, vi } from 'vitest';
|
|
2
2
|
import { pipe } from 'it-pipe';
|
|
3
|
-
import { createHandler, createMux, Server, Client, StreamConn, ChannelStream, combineUint8ArrayListTransform, Packet, } from '../srpc/index.js';
|
|
4
|
-
import { EchoerDefinition, EchoerServer, EchoerServiceName, runClientTest, } from '../echo/index.js';
|
|
3
|
+
import { createHandler, createMux, Server, Client, StreamConn, ChannelStream, combineUint8ArrayListTransform, Packet, createContextKey, serverContextValue, withServerContextValue, } from '../srpc/index.js';
|
|
4
|
+
import { EchoerDefinition, EchoerServer, EchoerServiceName, EchoMsg, runClientTest, } from '../echo/index.js';
|
|
5
5
|
import { runAbortControllerTest, runRpcStreamTest, } from '../echo/client-test.js';
|
|
6
6
|
describe('srpc server', () => {
|
|
7
7
|
let client;
|
|
@@ -39,6 +39,62 @@ describe('srpc server', () => {
|
|
|
39
39
|
it('should pass rpc stream tests', async () => {
|
|
40
40
|
await runRpcStreamTest(client);
|
|
41
41
|
});
|
|
42
|
+
it('passes the exact invocation context after async request decode', async () => {
|
|
43
|
+
const controller = new AbortController();
|
|
44
|
+
const callerKey = createContextKey();
|
|
45
|
+
let observedAbortSignal;
|
|
46
|
+
let observedContextSignal;
|
|
47
|
+
let observedCaller;
|
|
48
|
+
const handler = createHandler(EchoerDefinition, {
|
|
49
|
+
Echo: async (request, abortSignal, context) => {
|
|
50
|
+
observedAbortSignal = abortSignal;
|
|
51
|
+
observedContextSignal = context.signal;
|
|
52
|
+
observedCaller = serverContextValue(context, callerKey);
|
|
53
|
+
return request;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
const invokeFn = await handler.lookupMethod(EchoerServiceName, 'Echo');
|
|
57
|
+
if (!invokeFn) {
|
|
58
|
+
throw new Error('Echo method was not found');
|
|
59
|
+
}
|
|
60
|
+
const request = EchoMsg.create({ body: 'signal identity' });
|
|
61
|
+
const drained = Promise.withResolvers();
|
|
62
|
+
await invokeFn((async function* () {
|
|
63
|
+
await Promise.resolve();
|
|
64
|
+
yield EchoMsg.toBinary(request);
|
|
65
|
+
})(), async (source) => {
|
|
66
|
+
for await (const _data of source) {
|
|
67
|
+
// Drain the encoded response so the invocation pipeline completes.
|
|
68
|
+
}
|
|
69
|
+
drained.resolve();
|
|
70
|
+
}, withServerContextValue({ signal: controller.signal }, callerKey, 'caller-1'));
|
|
71
|
+
await drained.promise;
|
|
72
|
+
expect(observedAbortSignal).toBe(controller.signal);
|
|
73
|
+
expect(observedContextSignal).toBe(controller.signal);
|
|
74
|
+
expect(observedCaller).toBe('caller-1');
|
|
75
|
+
});
|
|
76
|
+
it('keeps two-argument server handlers compatible', async () => {
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
let observedSignal;
|
|
79
|
+
const handler = createHandler(EchoerDefinition, {
|
|
80
|
+
Echo: async (request, abortSignal) => {
|
|
81
|
+
observedSignal = abortSignal;
|
|
82
|
+
return request;
|
|
83
|
+
},
|
|
84
|
+
});
|
|
85
|
+
const invokeFn = await handler.lookupMethod(EchoerServiceName, 'Echo');
|
|
86
|
+
if (!invokeFn)
|
|
87
|
+
throw new Error('Echo method was not found');
|
|
88
|
+
const request = EchoMsg.create({ body: 'legacy handler' });
|
|
89
|
+
await invokeFn((async function* () {
|
|
90
|
+
yield EchoMsg.toBinary(request);
|
|
91
|
+
})(), async (source) => {
|
|
92
|
+
for await (const _data of source) {
|
|
93
|
+
// Drain the response.
|
|
94
|
+
}
|
|
95
|
+
}, { signal: controller.signal });
|
|
96
|
+
expect(observedSignal).toBe(controller.signal);
|
|
97
|
+
});
|
|
42
98
|
it('keeps detached server-streaming responses open after request source completes', async () => {
|
|
43
99
|
const mux = createMux();
|
|
44
100
|
const response = new TextEncoder().encode('delayed init');
|
|
@@ -166,6 +222,13 @@ describe('srpc server', () => {
|
|
|
166
222
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
167
223
|
expect(removeEventListener).toHaveBeenCalledWith('abort', expect.any(Function));
|
|
168
224
|
});
|
|
225
|
+
it('settles a late abort after the call has already closed', async () => {
|
|
226
|
+
const controller = new AbortController();
|
|
227
|
+
vi.spyOn(controller.signal, 'removeEventListener').mockImplementation(() => undefined);
|
|
228
|
+
await client.request(EchoerServiceName, 'Echo', new TextEncoder().encode('late abort'), controller.signal);
|
|
229
|
+
controller.abort();
|
|
230
|
+
await Promise.resolve();
|
|
231
|
+
});
|
|
169
232
|
it('tears down passive channel close state', async () => {
|
|
170
233
|
const { port1, port2 } = new MessageChannel();
|
|
171
234
|
const opts = { idleTimeoutMs: 1000, keepAliveMs: 1000 };
|
package/echo/echo.go
CHANGED
|
@@ -7,15 +7,15 @@ import (
|
|
|
7
7
|
|
|
8
8
|
// _ is a type assertion
|
|
9
9
|
var (
|
|
10
|
-
_ srpc.StreamRecv[*EchoMsg] =
|
|
11
|
-
_ srpc.StreamRecv[*EchoMsg] =
|
|
10
|
+
_ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
|
|
11
|
+
_ srpc.StreamRecv[*EchoMsg] = SRPCEchoer_EchoServerStreamClient(nil)
|
|
12
12
|
|
|
13
|
-
_ srpc.StreamSend[*EchoMsg] =
|
|
14
|
-
_ srpc.StreamSend[*EchoMsg] =
|
|
13
|
+
_ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoBidiStreamClient(nil)
|
|
14
|
+
_ srpc.StreamSend[*EchoMsg] = SRPCEchoer_EchoClientStreamClient(nil)
|
|
15
15
|
|
|
16
|
-
_ srpc.StreamSendAndClose[*EchoMsg] =
|
|
17
|
-
_ srpc.StreamSendAndClose[*EchoMsg] =
|
|
16
|
+
_ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoBidiStreamStream(nil)
|
|
17
|
+
_ srpc.StreamSendAndClose[*EchoMsg] = SRPCEchoer_EchoServerStreamStream(nil)
|
|
18
18
|
|
|
19
|
-
_ srpc.StreamRecv[*rpcstream.RpcStreamPacket] =
|
|
20
|
-
_ srpc.StreamSendAndClose[*rpcstream.RpcStreamPacket] =
|
|
19
|
+
_ srpc.StreamRecv[*rpcstream.RpcStreamPacket] = SRPCEchoer_RpcStreamStream(nil)
|
|
20
|
+
_ srpc.StreamSendAndClose[*rpcstream.RpcStreamPacket] = SRPCEchoer_RpcStreamStream(nil)
|
|
21
21
|
)
|
package/echo/echo.pb.go
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Code generated by protoc-gen-go-lite. DO NOT EDIT.
|
|
2
|
-
// protoc-gen-go-lite version: v0.
|
|
2
|
+
// protoc-gen-go-lite version: v0.16.0
|
|
3
3
|
// source: github.com/aperturerobotics/starpc/echo/echo.proto
|
|
4
4
|
|
|
5
5
|
package echo
|
|
@@ -8,8 +8,6 @@ import (
|
|
|
8
8
|
fmt "fmt"
|
|
9
9
|
io "io"
|
|
10
10
|
slices "slices"
|
|
11
|
-
strconv "strconv"
|
|
12
|
-
strings "strings"
|
|
13
11
|
|
|
14
12
|
protobuf_go_lite "github.com/aperturerobotics/protobuf-go-lite"
|
|
15
13
|
json "github.com/aperturerobotics/protobuf-go-lite/json"
|
|
@@ -141,13 +139,10 @@ func (m *EchoMsg) MarshalToSizedBufferVT(dAtA []byte) (int, error) {
|
|
|
141
139
|
var l int
|
|
142
140
|
_ = l
|
|
143
141
|
if m.unknownFields != nil {
|
|
144
|
-
i
|
|
145
|
-
copy(dAtA[i:], m.unknownFields)
|
|
142
|
+
i = protobuf_go_lite.EncodeRawBytes(dAtA, i, m.unknownFields)
|
|
146
143
|
}
|
|
147
144
|
if len(m.Body) > 0 {
|
|
148
|
-
i
|
|
149
|
-
copy(dAtA[i:], m.Body)
|
|
150
|
-
i = protobuf_go_lite.EncodeVarint(dAtA, i, uint64(len(m.Body)))
|
|
145
|
+
i = protobuf_go_lite.EncodeString(dAtA, i, m.Body)
|
|
151
146
|
i--
|
|
152
147
|
dAtA[i] = 0xa
|
|
153
148
|
}
|
|
@@ -160,26 +155,19 @@ func (m *EchoMsg) SizeVT() (n int) {
|
|
|
160
155
|
}
|
|
161
156
|
var l int
|
|
162
157
|
_ = l
|
|
163
|
-
|
|
164
|
-
if l > 0 {
|
|
165
|
-
n += 1 + l + protobuf_go_lite.SizeOfVarint(uint64(l))
|
|
166
|
-
}
|
|
158
|
+
n += protobuf_go_lite.SizeStringNonEmpty(1, m.Body)
|
|
167
159
|
n += len(m.unknownFields)
|
|
168
160
|
return n
|
|
169
161
|
}
|
|
170
162
|
|
|
171
163
|
func (x *EchoMsg) MarshalProtoText() string {
|
|
172
|
-
var sb
|
|
173
|
-
|
|
164
|
+
var sb protobuf_go_lite.TextBuilder
|
|
165
|
+
initialLen := protobuf_go_lite.TextStartMessage(&sb, "EchoMsg")
|
|
174
166
|
if x.Body != "" {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
sb.WriteString("body: ")
|
|
179
|
-
sb.WriteString(strconv.Quote(x.Body))
|
|
167
|
+
protobuf_go_lite.TextWriteFieldPrefix(&sb, initialLen, "body")
|
|
168
|
+
protobuf_go_lite.TextWriteString(&sb, x.Body)
|
|
180
169
|
}
|
|
181
|
-
|
|
182
|
-
return sb.String()
|
|
170
|
+
return protobuf_go_lite.TextFinishMessage(&sb)
|
|
183
171
|
}
|
|
184
172
|
|
|
185
173
|
func (x *EchoMsg) String() string {
|
|
@@ -210,24 +198,12 @@ func (m *EchoMsg) UnmarshalVT(dAtA []byte) error {
|
|
|
210
198
|
if wireType != 2 {
|
|
211
199
|
return fmt.Errorf("proto: wrong wireType = %d for field Body", wireType)
|
|
212
200
|
}
|
|
213
|
-
var
|
|
214
|
-
|
|
201
|
+
var v string
|
|
202
|
+
v, iNdEx, err = protobuf_go_lite.DecodeString(dAtA, iNdEx)
|
|
215
203
|
if err != nil {
|
|
216
204
|
return err
|
|
217
205
|
}
|
|
218
|
-
|
|
219
|
-
if intStringLen < 0 {
|
|
220
|
-
return protobuf_go_lite.ErrInvalidLength
|
|
221
|
-
}
|
|
222
|
-
postIndex := iNdEx + intStringLen
|
|
223
|
-
if postIndex < 0 {
|
|
224
|
-
return protobuf_go_lite.ErrInvalidLength
|
|
225
|
-
}
|
|
226
|
-
if postIndex > l {
|
|
227
|
-
return io.ErrUnexpectedEOF
|
|
228
|
-
}
|
|
229
|
-
m.Body = string(dAtA[iNdEx:postIndex])
|
|
230
|
-
iNdEx = postIndex
|
|
206
|
+
m.Body = v
|
|
231
207
|
default:
|
|
232
208
|
iNdEx = preIndex
|
|
233
209
|
skippy, err := protobuf_go_lite.Skip(dAtA[iNdEx:])
|
package/echo/echo_srpc.pb.cpp
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//go:build deps_only
|
|
2
2
|
|
|
3
3
|
// Code generated by protoc-gen-starpc-cpp. DO NOT EDIT.
|
|
4
|
-
// protoc-gen-starpc-cpp version: v0.49.
|
|
4
|
+
// protoc-gen-starpc-cpp version: v0.49.21
|
|
5
5
|
// source: github.com/aperturerobotics/starpc/echo/echo.proto
|
|
6
6
|
|
|
7
7
|
#include "echo_srpc.pb.hpp"
|
package/echo/echo_srpc.pb.go
CHANGED
package/echo/echo_srpc.pb.hpp
CHANGED
package/echo/echo_srpc.pb.rs
CHANGED