starpc 0.49.17 → 0.49.20

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.
Files changed (73) hide show
  1. package/README.md +1 -3
  2. package/cmd/protoc-gen-es-starpc/typescript.ts +11 -6
  3. package/dist/cmd/protoc-gen-es-starpc/typescript.js +6 -5
  4. package/dist/echo/echo.pb.d.ts +1 -1
  5. package/dist/echo/echo.pb.js +3 -3
  6. package/dist/integration/cross-language/ts-client.js +84 -11
  7. package/dist/integration/cross-language/ts-server.js +84 -4
  8. package/dist/mock/mock.pb.d.ts +1 -1
  9. package/dist/mock/mock.pb.js +3 -3
  10. package/dist/rpcstream/rpcstream.pb.d.ts +1 -1
  11. package/dist/rpcstream/rpcstream.pb.js +14 -8
  12. package/dist/srpc/call-receipt.d.ts +17 -0
  13. package/dist/srpc/call-receipt.js +105 -0
  14. package/dist/srpc/call-receipt.test.d.ts +1 -0
  15. package/dist/srpc/call-receipt.test.js +374 -0
  16. package/dist/srpc/client.d.ts +3 -1
  17. package/dist/srpc/client.js +20 -0
  18. package/dist/srpc/common-rpc.d.ts +13 -1
  19. package/dist/srpc/common-rpc.js +86 -6
  20. package/dist/srpc/handler.d.ts +2 -1
  21. package/dist/srpc/index.d.ts +4 -0
  22. package/dist/srpc/index.js +2 -0
  23. package/dist/srpc/invoker.d.ts +2 -1
  24. package/dist/srpc/invoker.js +2 -2
  25. package/dist/srpc/rpcproto.pb.d.ts +1 -1
  26. package/dist/srpc/rpcproto.pb.js +7 -7
  27. package/dist/srpc/server-invocation.d.ts +17 -0
  28. package/dist/srpc/server-invocation.js +37 -0
  29. package/dist/srpc/server-rpc.js +3 -1
  30. package/echo/echo.pb.go +1 -1
  31. package/echo/echo.pb.ts +6 -5
  32. package/echo/echo_srpc.pb.cpp +1 -1
  33. package/echo/echo_srpc.pb.go +1 -1
  34. package/echo/echo_srpc.pb.hpp +1 -1
  35. package/echo/echo_srpc.pb.rs +1 -1
  36. package/integration/cross-language/go-client/main.go +137 -5
  37. package/integration/cross-language/go-server/main.go +146 -3
  38. package/integration/cross-language/run.bash +117 -13
  39. package/integration/cross-language/ts-client.ts +94 -13
  40. package/integration/cross-language/ts-server.ts +94 -6
  41. package/mock/mock.pb.go +1 -1
  42. package/mock/mock.pb.ts +6 -5
  43. package/mock/mock_srpc.pb.cpp +1 -1
  44. package/mock/mock_srpc.pb.go +1 -1
  45. package/mock/mock_srpc.pb.hpp +1 -1
  46. package/mock/mock_srpc.pb.rs +1 -1
  47. package/package.json +1 -1
  48. package/srpc/call-receipt-e2e_test.go +111 -0
  49. package/srpc/call-receipt.go +112 -0
  50. package/srpc/call-receipt.test.ts +438 -0
  51. package/srpc/call-receipt.ts +130 -0
  52. package/srpc/call-receipt_test.go +536 -0
  53. package/srpc/client-rpc.go +1 -8
  54. package/srpc/client.ts +29 -2
  55. package/srpc/common-rpc.go +134 -29
  56. package/srpc/common-rpc.ts +98 -8
  57. package/srpc/common-rpc_test.go +52 -0
  58. package/srpc/handler.ts +2 -0
  59. package/srpc/index.ts +4 -0
  60. package/srpc/invoker.ts +10 -5
  61. package/srpc/msg-stream.go +8 -0
  62. package/srpc/muxed-conn.go +8 -3
  63. package/srpc/muxed-conn_test.go +79 -0
  64. package/srpc/rpcproto.pb.go +1 -1
  65. package/srpc/rpcproto.pb.ts +28 -27
  66. package/srpc/server-invocation.go +58 -0
  67. package/srpc/server-invocation.ts +82 -0
  68. package/srpc/server-rpc-invoke.go +7 -0
  69. package/srpc/server-rpc-invoke_goscript.go +12 -0
  70. package/srpc/server-rpc-invoke_goscript_test.go +27 -0
  71. package/srpc/server-rpc.go +9 -2
  72. package/srpc/server-rpc.ts +6 -2
  73. package/srpc/stream.go +20 -0
@@ -2,8 +2,10 @@
2
2
  // @generated from file github.com/aperturerobotics/starpc/srpc/rpcproto.proto (package srpc, syntax proto3)
3
3
  /* eslint-disable */
4
4
 
5
- import type { MessageType, PartialFieldInfo } from '@aptre/protobuf-es-lite'
6
- import { createMessageType, ScalarType } from '@aptre/protobuf-es-lite'
5
+ import type { MessageType } from '@aptre/protobuf-es-lite/message'
6
+ import { createMessageType } from '@aptre/protobuf-es-lite/message'
7
+ import { ScalarType } from '@aptre/protobuf-es-lite/scalar'
8
+ import type { PartialFieldInfo } from '@aptre/protobuf-es-lite/field'
7
9
 
8
10
  export const protobufPackage = 'srpc'
9
11
 
@@ -42,17 +44,17 @@ export interface CallStart {
42
44
  dataIsZero?: boolean
43
45
  }
44
46
 
45
- // CallStart contains the message type declaration for CallStart.
46
- export const CallStart: MessageType<CallStart> = createMessageType({
47
- typeName: 'srpc.CallStart',
48
- fields: [
49
- { no: 1, name: 'rpc_service', kind: 'scalar', T: ScalarType.STRING },
50
- { no: 2, name: 'rpc_method', kind: 'scalar', T: ScalarType.STRING },
51
- { no: 3, name: 'data', kind: 'scalar', T: ScalarType.BYTES },
52
- { no: 4, name: 'data_is_zero', kind: 'scalar', T: ScalarType.BOOL },
53
- ] as readonly PartialFieldInfo[],
54
- packedByDefault: true,
55
- })
47
+ export const CallStart: MessageType<CallStart> =
48
+ /* @__PURE__ */ createMessageType({
49
+ typeName: 'srpc.CallStart',
50
+ fields: [
51
+ { no: 1, name: 'rpc_service', kind: 'scalar', T: ScalarType.STRING },
52
+ { no: 2, name: 'rpc_method', kind: 'scalar', T: ScalarType.STRING },
53
+ { no: 3, name: 'data', kind: 'scalar', T: ScalarType.BYTES },
54
+ { no: 4, name: 'data_is_zero', kind: 'scalar', T: ScalarType.BOOL },
55
+ ] satisfies readonly PartialFieldInfo[],
56
+ packedByDefault: true,
57
+ })
56
58
 
57
59
  /**
58
60
  * CallData contains a message in a streaming RPC sequence.
@@ -87,17 +89,17 @@ export interface CallData {
87
89
  error?: string
88
90
  }
89
91
 
90
- // CallData contains the message type declaration for CallData.
91
- export const CallData: MessageType<CallData> = createMessageType({
92
- typeName: 'srpc.CallData',
93
- fields: [
94
- { no: 1, name: 'data', kind: 'scalar', T: ScalarType.BYTES },
95
- { no: 2, name: 'data_is_zero', kind: 'scalar', T: ScalarType.BOOL },
96
- { no: 3, name: 'complete', kind: 'scalar', T: ScalarType.BOOL },
97
- { no: 4, name: 'error', kind: 'scalar', T: ScalarType.STRING },
98
- ] as readonly PartialFieldInfo[],
99
- packedByDefault: true,
100
- })
92
+ export const CallData: MessageType<CallData> =
93
+ /* @__PURE__ */ createMessageType({
94
+ typeName: 'srpc.CallData',
95
+ fields: [
96
+ { no: 1, name: 'data', kind: 'scalar', T: ScalarType.BYTES },
97
+ { no: 2, name: 'data_is_zero', kind: 'scalar', T: ScalarType.BOOL },
98
+ { no: 3, name: 'complete', kind: 'scalar', T: ScalarType.BOOL },
99
+ { no: 4, name: 'error', kind: 'scalar', T: ScalarType.STRING },
100
+ ] satisfies readonly PartialFieldInfo[],
101
+ packedByDefault: true,
102
+ })
101
103
 
102
104
  /**
103
105
  * Packet is a message sent over a srpc packet connection.
@@ -144,8 +146,7 @@ export interface Packet {
144
146
  }
145
147
  }
146
148
 
147
- // Packet contains the message type declaration for Packet.
148
- export const Packet: MessageType<Packet> = createMessageType({
149
+ export const Packet: MessageType<Packet> = /* @__PURE__ */ createMessageType({
149
150
  typeName: 'srpc.Packet',
150
151
  fields: [
151
152
  {
@@ -169,6 +170,6 @@ export const Packet: MessageType<Packet> = createMessageType({
169
170
  T: ScalarType.BOOL,
170
171
  oneof: 'body',
171
172
  },
172
- ] as readonly PartialFieldInfo[],
173
+ ] satisfies readonly PartialFieldInfo[],
173
174
  packedByDefault: true,
174
175
  })
@@ -0,0 +1,58 @@
1
+ package srpc
2
+
3
+ import (
4
+ "context"
5
+ )
6
+
7
+ // TerminalKind is the terminal state of a held unary invocation.
8
+ type TerminalKind int
9
+
10
+ const (
11
+ // TerminalCommitted identifies an explicit remote CallData completion.
12
+ TerminalCommitted TerminalKind = iota
13
+ // TerminalCanceled identifies a remote CallCancel packet.
14
+ TerminalCanceled
15
+ // TerminalLost identifies a remote error or transport failure.
16
+ TerminalLost
17
+ // TerminalClosed identifies a bare remote close without completion.
18
+ TerminalClosed
19
+ // TerminalAbandoned identifies owner-context expiry without a remote terminal.
20
+ TerminalAbandoned
21
+ )
22
+
23
+ // ServerInvocation exposes the terminal of a held unary server invocation.
24
+ type ServerInvocation interface {
25
+ // WaitTerminal waits for a remote terminal or owner-context expiry. The
26
+ // owner context must outlive the invocation context.
27
+ WaitTerminal(ownerCtx context.Context) (TerminalKind, error)
28
+ // Done returns the invocation context cancellation channel for diagnostics.
29
+ Done() <-chan struct{}
30
+ }
31
+
32
+ type serverInvocationKey struct{}
33
+
34
+ func withServerInvocation(ctx context.Context, invocation ServerInvocation) context.Context {
35
+ return context.WithValue(ctx, serverInvocationKey{}, invocation)
36
+ }
37
+
38
+ // GetServerInvocation returns the held unary invocation attached to a context.
39
+ func GetServerInvocation(ctx context.Context) (ServerInvocation, bool) {
40
+ if ctx == nil {
41
+ return nil, false
42
+ }
43
+ invocation, ok := ctx.Value(serverInvocationKey{}).(ServerInvocation)
44
+ return invocation, ok
45
+ }
46
+
47
+ // WaitTerminal waits for and classifies the terminal of a held unary invocation.
48
+ func (r *ServerRPC) WaitTerminal(ownerCtx context.Context) (TerminalKind, error) {
49
+ return r.commonRPC.WaitTerminal(ownerCtx)
50
+ }
51
+
52
+ // Done returns the invocation context cancellation channel for diagnostics.
53
+ func (r *ServerRPC) Done() <-chan struct{} {
54
+ return r.ctx.Done()
55
+ }
56
+
57
+ // _ is a type assertion.
58
+ var _ ServerInvocation = (*ServerRPC)(nil)
@@ -0,0 +1,82 @@
1
+ // TerminalKind is the terminal state of a held unary invocation.
2
+ export type TerminalKind =
3
+ | 'committed'
4
+ | 'canceled'
5
+ | 'transportLost'
6
+ | 'closed'
7
+ | 'abandoned'
8
+
9
+ // ServerInvocation exposes the invocation signal and terminal wait.
10
+ export class ServerInvocation implements AbortSignal {
11
+ public constructor(
12
+ public readonly signal: AbortSignal,
13
+ private readonly waitFn: (
14
+ ownerSignal: AbortSignal,
15
+ ) => Promise<TerminalKind>,
16
+ ) {}
17
+
18
+ public get aborted(): boolean {
19
+ return this.signal.aborted
20
+ }
21
+
22
+ public get onabort() {
23
+ return this.signal.onabort
24
+ }
25
+
26
+ public set onabort(value: typeof this.signal.onabort) {
27
+ this.signal.onabort = value
28
+ }
29
+
30
+ public get reason() {
31
+ return this.signal.reason
32
+ }
33
+
34
+ public throwIfAborted(): void {
35
+ this.signal.throwIfAborted()
36
+ }
37
+
38
+ public addEventListener<K extends keyof AbortSignalEventMap>(
39
+ type: K,
40
+ listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => unknown,
41
+ options?: boolean | AddEventListenerOptions,
42
+ ): void
43
+ public addEventListener(
44
+ type: string,
45
+ listener: EventListenerOrEventListenerObject,
46
+ options?: boolean | AddEventListenerOptions,
47
+ ): void
48
+ public addEventListener(
49
+ type: string,
50
+ listener: EventListenerOrEventListenerObject,
51
+ options?: boolean | AddEventListenerOptions,
52
+ ): void {
53
+ this.signal.addEventListener(type, listener, options)
54
+ }
55
+
56
+ public removeEventListener<K extends keyof AbortSignalEventMap>(
57
+ type: K,
58
+ listener: (this: AbortSignal, ev: AbortSignalEventMap[K]) => unknown,
59
+ options?: boolean | EventListenerOptions,
60
+ ): void
61
+ public removeEventListener(
62
+ type: string,
63
+ listener: EventListenerOrEventListenerObject,
64
+ options?: boolean | EventListenerOptions,
65
+ ): void
66
+ public removeEventListener(
67
+ type: string,
68
+ listener: EventListenerOrEventListenerObject,
69
+ options?: boolean | EventListenerOptions,
70
+ ): void {
71
+ this.signal.removeEventListener(type, listener, options)
72
+ }
73
+
74
+ public dispatchEvent(event: Event): boolean {
75
+ return this.signal.dispatchEvent(event)
76
+ }
77
+
78
+ // waitTerminal waits for a remote terminal or owner-signal cancellation.
79
+ public waitTerminal(ownerSignal: AbortSignal): Promise<TerminalKind> {
80
+ return this.waitFn(ownerSignal)
81
+ }
82
+ }
@@ -0,0 +1,7 @@
1
+ //go:build !goscript
2
+
3
+ package srpc
4
+
5
+ func startServerRPCInvoke(fn func()) {
6
+ go fn()
7
+ }
@@ -0,0 +1,12 @@
1
+ //go:build goscript
2
+
3
+ package srpc
4
+
5
+ import "time"
6
+
7
+ func startServerRPCInvoke(fn func()) {
8
+ if fn == nil {
9
+ return
10
+ }
11
+ time.AfterFunc(0, fn)
12
+ }
@@ -0,0 +1,27 @@
1
+ //go:build goscript
2
+
3
+ package srpc
4
+
5
+ import (
6
+ "testing"
7
+ "time"
8
+ )
9
+
10
+ func TestStartServerRPCInvokeDefersGoscriptWork(t *testing.T) {
11
+ ran := make(chan struct{}, 1)
12
+ startServerRPCInvoke(func() {
13
+ ran <- struct{}{}
14
+ })
15
+
16
+ select {
17
+ case <-ran:
18
+ t.Fatal("server rpc invoke ran inline")
19
+ default:
20
+ }
21
+
22
+ select {
23
+ case <-ran:
24
+ case <-time.After(time.Second):
25
+ t.Fatal("server rpc invoke did not run")
26
+ }
27
+ }
@@ -80,9 +80,16 @@ func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
80
80
  r.dataQueue = append(r.dataQueue, data)
81
81
  }
82
82
 
83
+ // Wait is used as a resource lifetime barrier by rpcstream components.
84
+ // Mark the method active before scheduling it so cancellation cannot make
85
+ // Wait return and release a mux while invokeRPC is still running user code.
86
+ r.localActive = true
87
+
83
88
  // invoke the rpc
84
89
  locked.Broadcast()
85
- go r.invokeRPC(service, method)
90
+ startServerRPCInvoke(func() {
91
+ r.invokeRPC(service, method)
92
+ })
86
93
  locked.Unlock()
87
94
 
88
95
  return err
@@ -91,7 +98,7 @@ func (r *ServerRPC) HandleCallStart(pkt *CallStart) error {
91
98
  // invokeRPC invokes the RPC after CallStart is received.
92
99
  func (r *ServerRPC) invokeRPC(serviceID, methodID string) {
93
100
  // on the server side, the writer is closed by invokeRPC.
94
- strm := NewMsgStream(r.ctx, r, r.cancelContext)
101
+ strm := NewMsgStream(withServerInvocation(r.ctx, r), r, r.cancelContext)
95
102
  ok, err := r.invoker.InvokeMethod(serviceID, methodID, strm)
96
103
  if err == nil && !ok {
97
104
  err = ErrUnimplemented
@@ -3,6 +3,7 @@ import type { Sink, Source } from 'it-stream-types'
3
3
  import type { CallData, CallStart } from './rpcproto.pb.js'
4
4
  import { CommonRPC } from './common-rpc.js'
5
5
  import { InvokeFn } from './handler.js'
6
+ import { ServerInvocation } from './server-invocation.js'
6
7
  import { LookupMethod } from './mux.js'
7
8
 
8
9
  // ServerRPC is an ongoing RPC from the server side.
@@ -43,12 +44,15 @@ export class ServerRPC extends CommonRPC {
43
44
  }
44
45
  return super.handleCallData(packet)
45
46
  }
46
-
47
47
  // invokeRPC starts invoking the RPC handler.
48
48
  private async invokeRPC(invokeFn: InvokeFn) {
49
49
  const dataSink = this._createDataSink()
50
+ const invocation = new ServerInvocation(
51
+ this.invocationSignal,
52
+ (ownerSignal) => this.waitTerminal(ownerSignal),
53
+ )
50
54
  try {
51
- await invokeFn(this.rpcDataSource, dataSink)
55
+ await invokeFn(this.rpcDataSource, dataSink, invocation)
52
56
  } catch (err) {
53
57
  this.close(err as Error)
54
58
  }
package/srpc/stream.go CHANGED
@@ -23,6 +23,10 @@ type Stream interface {
23
23
  Close() error
24
24
  }
25
25
 
26
+ type receiptTerminalStream interface {
27
+ receiptTerminalKind() (TerminalKind, bool)
28
+ }
29
+
26
30
  // StreamRecv is a stream that can receive typed messages.
27
31
  //
28
32
  // T is the response type.
@@ -69,6 +73,14 @@ func (s *streamWithClose) Close() error {
69
73
  return err2
70
74
  }
71
75
 
76
+ func (s *streamWithClose) receiptTerminalKind() (TerminalKind, bool) {
77
+ receipt, ok := s.Stream.(receiptTerminalStream)
78
+ if !ok {
79
+ return 0, false
80
+ }
81
+ return receipt.receiptTerminalKind()
82
+ }
83
+
72
84
  // streamWithContext is a Stream with a wrapped Context function.
73
85
  type streamWithContext struct {
74
86
  Stream
@@ -85,6 +97,14 @@ func (s *streamWithContext) Context() context.Context {
85
97
  return s.ctx
86
98
  }
87
99
 
100
+ func (s *streamWithContext) receiptTerminalKind() (TerminalKind, bool) {
101
+ receipt, ok := s.Stream.(receiptTerminalStream)
102
+ if !ok {
103
+ return 0, false
104
+ }
105
+ return receipt.receiptTerminalKind()
106
+ }
107
+
88
108
  // _ is a type assertion
89
109
  var (
90
110
  _ Stream = (*streamWithClose)(nil)