@nmtjs/gateway 0.15.0-beta.1 → 0.15.0-beta.3
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/api.d.ts +15 -0
- package/dist/api.js +4 -0
- package/dist/api.js.map +1 -0
- package/dist/connections.d.ts +24 -0
- package/dist/connections.js +30 -0
- package/dist/connections.js.map +1 -0
- package/dist/enums.d.ts +13 -0
- package/dist/enums.js +17 -0
- package/dist/enums.js.map +1 -0
- package/dist/gateway.d.ts +66 -0
- package/dist/gateway.js +457 -0
- package/dist/gateway.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/injectables.d.ts +30 -0
- package/dist/injectables.js +28 -0
- package/dist/injectables.js.map +1 -0
- package/dist/rpcs.d.ts +13 -0
- package/dist/rpcs.js +79 -0
- package/dist/rpcs.js.map +1 -0
- package/dist/streams.d.ts +64 -0
- package/dist/streams.js +278 -0
- package/dist/streams.js.map +1 -0
- package/dist/transport.d.ts +50 -0
- package/dist/transport.js +4 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +17 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +11 -11
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Container, MetadataStore } from '@nmtjs/core';
|
|
2
|
+
import type { GatewayConnection } from './connections.ts';
|
|
3
|
+
export type GatewayApiCallOptions = {
|
|
4
|
+
connection: GatewayConnection;
|
|
5
|
+
procedure: string;
|
|
6
|
+
container: Container;
|
|
7
|
+
payload: any;
|
|
8
|
+
signal: AbortSignal;
|
|
9
|
+
metadata?: (store: MetadataStore) => void;
|
|
10
|
+
};
|
|
11
|
+
export type GatewayApiCallResult = unknown;
|
|
12
|
+
export interface GatewayApi {
|
|
13
|
+
call(options: GatewayApiCallOptions): Promise<GatewayApiCallResult>;
|
|
14
|
+
}
|
|
15
|
+
export declare function isAsyncIterable(value: GatewayApiCallResult): value is AsyncIterable<unknown> | Iterable<unknown>;
|
package/dist/api.js
ADDED
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAmBA,MAAM,UAAU,eAAe,CAC7B,KAA2B;IAE3B,OAAO,OAAO,CACZ,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,IAAI,KAAK,CACpE,CAAA;AACH,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { Container } from '@nmtjs/core';
|
|
2
|
+
import type { ConnectionType } from '@nmtjs/protocol';
|
|
3
|
+
import type { BaseServerDecoder, BaseServerEncoder, ProtocolVersionInterface } from '@nmtjs/protocol/server';
|
|
4
|
+
export interface GatewayConnection {
|
|
5
|
+
readonly id: string;
|
|
6
|
+
readonly type: ConnectionType;
|
|
7
|
+
readonly transport: string;
|
|
8
|
+
readonly protocol: ProtocolVersionInterface;
|
|
9
|
+
readonly identity: string;
|
|
10
|
+
readonly container: Container;
|
|
11
|
+
readonly encoder: BaseServerEncoder;
|
|
12
|
+
readonly decoder: BaseServerDecoder;
|
|
13
|
+
readonly abortController: AbortController;
|
|
14
|
+
}
|
|
15
|
+
export declare class ConnectionManager {
|
|
16
|
+
readonly connections: Map<string, GatewayConnection>;
|
|
17
|
+
readonly streamIds: Map<string, number>;
|
|
18
|
+
add(connection: GatewayConnection): void;
|
|
19
|
+
get(id: string): GatewayConnection;
|
|
20
|
+
has(id: string): boolean;
|
|
21
|
+
remove(id: string): void;
|
|
22
|
+
getAll(): MapIterator<GatewayConnection>;
|
|
23
|
+
getStreamId(connectionId: string): number;
|
|
24
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { MAX_UINT32, throwError } from '@nmtjs/common';
|
|
2
|
+
export class ConnectionManager {
|
|
3
|
+
connections = new Map();
|
|
4
|
+
streamIds = new Map();
|
|
5
|
+
add(connection) {
|
|
6
|
+
this.connections.set(connection.id, connection);
|
|
7
|
+
this.streamIds.set(connection.id, 0);
|
|
8
|
+
}
|
|
9
|
+
get(id) {
|
|
10
|
+
return this.connections.get(id) ?? throwError('Connection not found');
|
|
11
|
+
}
|
|
12
|
+
has(id) {
|
|
13
|
+
return this.connections.has(id);
|
|
14
|
+
}
|
|
15
|
+
remove(id) {
|
|
16
|
+
this.connections.delete(id);
|
|
17
|
+
this.streamIds.delete(id);
|
|
18
|
+
}
|
|
19
|
+
getAll() {
|
|
20
|
+
return this.connections.values();
|
|
21
|
+
}
|
|
22
|
+
getStreamId(connectionId) {
|
|
23
|
+
let streamId = this.streamIds.get(connectionId);
|
|
24
|
+
if (streamId >= MAX_UINT32)
|
|
25
|
+
streamId = 0;
|
|
26
|
+
this.streamIds.set(connectionId, streamId + 1);
|
|
27
|
+
return streamId;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=connections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connections.js","sourceRoot":"","sources":["../src/connections.ts"],"names":[],"mappings":"AAOA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AActD,MAAM,OAAO,iBAAiB;IACnB,WAAW,GAAG,IAAI,GAAG,EAA6B,CAAA;IAClD,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAE9C,GAAG,CAAC,UAA6B;QAC/B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,UAAU,CAAC,CAAA;QAC/C,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC,CAAA;IACtC,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,UAAU,CAAC,sBAAsB,CAAC,CAAA;IACvE,CAAC;IAED,GAAG,CAAC,EAAU;QACZ,OAAO,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;IACjC,CAAC;IAED,MAAM,CAAC,EAAU;QACf,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC3B,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;IAC3B,CAAC;IAED,MAAM;QACJ,OAAO,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,CAAA;IAClC,CAAC;IAED,WAAW,CAAC,YAAoB;QAC9B,IAAI,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,CAAE,CAAA;QAChD,IAAI,QAAQ,IAAI,UAAU;YAAE,QAAQ,GAAG,CAAC,CAAA;QACxC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,YAAY,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAA;QAC9C,OAAO,QAAQ,CAAA;IACjB,CAAC;CACF"}
|
package/dist/enums.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare enum GatewayHook {
|
|
2
|
+
Connect = "Connect",
|
|
3
|
+
Disconnect = "Disconnect"
|
|
4
|
+
}
|
|
5
|
+
export declare enum ProxyableTransportType {
|
|
6
|
+
WebSocket = "WebSocket",
|
|
7
|
+
HTTP = "HTTP"
|
|
8
|
+
}
|
|
9
|
+
export declare enum StreamTimeout {
|
|
10
|
+
Pull = "Pull",
|
|
11
|
+
Consume = "Consume",
|
|
12
|
+
Finish = "Finish"
|
|
13
|
+
}
|
package/dist/enums.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export var GatewayHook;
|
|
2
|
+
(function (GatewayHook) {
|
|
3
|
+
GatewayHook["Connect"] = "Connect";
|
|
4
|
+
GatewayHook["Disconnect"] = "Disconnect";
|
|
5
|
+
})(GatewayHook || (GatewayHook = {}));
|
|
6
|
+
export var ProxyableTransportType;
|
|
7
|
+
(function (ProxyableTransportType) {
|
|
8
|
+
ProxyableTransportType["WebSocket"] = "WebSocket";
|
|
9
|
+
ProxyableTransportType["HTTP"] = "HTTP";
|
|
10
|
+
})(ProxyableTransportType || (ProxyableTransportType = {}));
|
|
11
|
+
export var StreamTimeout;
|
|
12
|
+
(function (StreamTimeout) {
|
|
13
|
+
StreamTimeout["Pull"] = "Pull";
|
|
14
|
+
StreamTimeout["Consume"] = "Consume";
|
|
15
|
+
StreamTimeout["Finish"] = "Finish";
|
|
16
|
+
})(StreamTimeout || (StreamTimeout = {}));
|
|
17
|
+
//# sourceMappingURL=enums.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enums.js","sourceRoot":"","sources":["../src/enums.ts"],"names":[],"mappings":"AAAA,MAAM,CAAN,IAAY,WAGX;AAHD,WAAY,WAAW;IACrB,kCAAmB,CAAA;IACnB,wCAAyB,CAAA;AAC3B,CAAC,EAHW,WAAW,KAAX,WAAW,QAGtB;AAED,MAAM,CAAN,IAAY,sBAGX;AAHD,WAAY,sBAAsB;IAChC,iDAAuB,CAAA;IACvB,uCAAa,CAAA;AACf,CAAC,EAHW,sBAAsB,KAAtB,sBAAsB,QAGjC;AAED,MAAM,CAAN,IAAY,aAIX;AAJD,WAAY,aAAa;IACvB,8BAAa,CAAA;IACb,oCAAmB,CAAA;IACnB,kCAAiB,CAAA;AACnB,CAAC,EAJW,aAAa,KAAb,aAAa,QAIxB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { Container, Hooks, Logger, ResolveInjectableType } from '@nmtjs/core';
|
|
2
|
+
import type { ClientStreamConsumer, ProtocolFormats } from '@nmtjs/protocol/server';
|
|
3
|
+
import type { GatewayApi } from './api.ts';
|
|
4
|
+
import type { GatewayConnection } from './connections.ts';
|
|
5
|
+
import type { ProxyableTransportType } from './enums.ts';
|
|
6
|
+
import type { StreamConfig } from './streams.ts';
|
|
7
|
+
import type { TransportWorker, TransportWorkerParams } from './transport.ts';
|
|
8
|
+
import type { ConnectionIdentity, GatewayRpc, GatewayRpcContext } from './types.ts';
|
|
9
|
+
import { ConnectionManager } from './connections.ts';
|
|
10
|
+
import * as injectables from './injectables.ts';
|
|
11
|
+
import { RpcManager } from './rpcs.ts';
|
|
12
|
+
import { BlobStreamsManager } from './streams.ts';
|
|
13
|
+
export interface GatewayOptions {
|
|
14
|
+
logger: Logger;
|
|
15
|
+
container: Container;
|
|
16
|
+
hooks: Hooks;
|
|
17
|
+
formats: ProtocolFormats;
|
|
18
|
+
api: GatewayApi;
|
|
19
|
+
transports: {
|
|
20
|
+
[key: string]: {
|
|
21
|
+
transport: TransportWorker;
|
|
22
|
+
proxyable?: ProxyableTransportType;
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
identity?: ConnectionIdentity;
|
|
26
|
+
rpcStreamConsumeTimeout?: number;
|
|
27
|
+
streamTimeouts?: Partial<StreamConfig['timeouts']>;
|
|
28
|
+
}
|
|
29
|
+
export declare class Gateway {
|
|
30
|
+
readonly logger: Logger;
|
|
31
|
+
readonly connections: ConnectionManager;
|
|
32
|
+
readonly rpcs: RpcManager;
|
|
33
|
+
readonly blobStreams: BlobStreamsManager;
|
|
34
|
+
options: Required<Omit<GatewayOptions, 'streamTimeouts'> & {
|
|
35
|
+
streamTimeouts: Required<Exclude<GatewayOptions['streamTimeouts'], undefined>>;
|
|
36
|
+
}>;
|
|
37
|
+
constructor(options: GatewayOptions);
|
|
38
|
+
start(): Promise<{
|
|
39
|
+
url: string;
|
|
40
|
+
type: ProxyableTransportType;
|
|
41
|
+
}[]>;
|
|
42
|
+
stop(): Promise<void>;
|
|
43
|
+
send(transport: string, connectionId: string, data: ArrayBufferView): boolean | null | undefined;
|
|
44
|
+
reload(): Promise<void>;
|
|
45
|
+
protected createRpcContext(connection: GatewayConnection, messageContext: ReturnType<typeof this.createMessageContext>, logger: Logger, gatewayRpc: GatewayRpc, signal?: AbortSignal): GatewayRpcContext;
|
|
46
|
+
protected createMessageContext(connection: GatewayConnection, transportKey: string): {
|
|
47
|
+
connectionId: string;
|
|
48
|
+
protocol: import("@nmtjs/protocol/server").ProtocolVersionInterface;
|
|
49
|
+
encoder: import("@nmtjs/protocol/server").BaseServerEncoder;
|
|
50
|
+
decoder: import("@nmtjs/protocol/server").BaseServerDecoder;
|
|
51
|
+
transport: TransportWorker<import("@nmtjs/protocol").ConnectionType>;
|
|
52
|
+
streamId: () => number;
|
|
53
|
+
addClientStream: ({ streamId, callId, metadata }: {
|
|
54
|
+
streamId: number;
|
|
55
|
+
metadata: import("@nmtjs/protocol").ProtocolBlobMetadata;
|
|
56
|
+
callId: number;
|
|
57
|
+
}) => ClientStreamConsumer;
|
|
58
|
+
};
|
|
59
|
+
protected onConnect(transport: string): TransportWorkerParams['onConnect'];
|
|
60
|
+
protected onDisconnect(transport: string): TransportWorkerParams['onDisconnect'];
|
|
61
|
+
protected onMessage(transport: string): TransportWorkerParams['onMessage'];
|
|
62
|
+
protected onRpc(transport: string): TransportWorkerParams['onRpc'];
|
|
63
|
+
protected handleRpcMessage(connection: GatewayConnection, context: GatewayRpcContext): Promise<void>;
|
|
64
|
+
protected closeConnection(connectionId: string): Promise<void>;
|
|
65
|
+
protected createBlobFunction(context: GatewayRpcContext): ResolveInjectableType<typeof injectables.createBlob>;
|
|
66
|
+
}
|
package/dist/gateway.js
ADDED
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { isTypedArray } from 'node:util/types';
|
|
3
|
+
import { anyAbortSignal, isAbortError } from '@nmtjs/common';
|
|
4
|
+
import { createFactoryInjectable, provide, Scope } from '@nmtjs/core';
|
|
5
|
+
import { ClientMessageType, isBlobInterface, kBlobKey, ProtocolBlob, ServerMessageType, } from '@nmtjs/protocol';
|
|
6
|
+
import { getFormat, ProtocolError, versions } from '@nmtjs/protocol/server';
|
|
7
|
+
import { ConnectionManager } from "./connections.js";
|
|
8
|
+
import { StreamTimeout } from "./enums.js";
|
|
9
|
+
import * as injectables from "./injectables.js";
|
|
10
|
+
import { RpcManager } from "./rpcs.js";
|
|
11
|
+
import { BlobStreamsManager } from "./streams.js";
|
|
12
|
+
export class Gateway {
|
|
13
|
+
logger;
|
|
14
|
+
connections;
|
|
15
|
+
rpcs;
|
|
16
|
+
blobStreams;
|
|
17
|
+
options;
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.options = {
|
|
20
|
+
rpcStreamConsumeTimeout: 5000,
|
|
21
|
+
streamTimeouts: {
|
|
22
|
+
//@ts-expect-error
|
|
23
|
+
[StreamTimeout.Pull]: options.streamTimeouts?.[StreamTimeout.Pull] ?? 5000,
|
|
24
|
+
//@ts-expect-error
|
|
25
|
+
[StreamTimeout.Consume]: options.streamTimeouts?.[StreamTimeout.Consume] ?? 5000,
|
|
26
|
+
//@ts-expect-error
|
|
27
|
+
[StreamTimeout.Finish]: options.streamTimeouts?.[StreamTimeout.Finish] ?? 10000,
|
|
28
|
+
},
|
|
29
|
+
...options,
|
|
30
|
+
identity: options.identity ??
|
|
31
|
+
createFactoryInjectable({
|
|
32
|
+
dependencies: { connectionId: injectables.connectionId },
|
|
33
|
+
factory: ({ connectionId }) => connectionId,
|
|
34
|
+
}),
|
|
35
|
+
};
|
|
36
|
+
this.logger = options.logger.child({}, gatewayLoggerOptions);
|
|
37
|
+
this.connections = new ConnectionManager();
|
|
38
|
+
this.rpcs = new RpcManager();
|
|
39
|
+
this.blobStreams = new BlobStreamsManager({
|
|
40
|
+
timeouts: this.options.streamTimeouts,
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async start() {
|
|
44
|
+
const hosts = [];
|
|
45
|
+
for (const key in this.options.transports) {
|
|
46
|
+
const { transport, proxyable } = this.options.transports[key];
|
|
47
|
+
const url = await transport.start({
|
|
48
|
+
formats: this.options.formats,
|
|
49
|
+
onConnect: this.onConnect(key),
|
|
50
|
+
onDisconnect: this.onDisconnect(key),
|
|
51
|
+
onMessage: this.onMessage(key),
|
|
52
|
+
onRpc: this.onRpc(key),
|
|
53
|
+
});
|
|
54
|
+
this.logger.info(`Transport [${key}] started on [${url}]`);
|
|
55
|
+
if (proxyable)
|
|
56
|
+
hosts.push({ url, type: proxyable });
|
|
57
|
+
}
|
|
58
|
+
return hosts;
|
|
59
|
+
}
|
|
60
|
+
async stop() {
|
|
61
|
+
// Close all connections
|
|
62
|
+
for (const connection of this.connections.getAll()) {
|
|
63
|
+
await this.closeConnection(connection.id);
|
|
64
|
+
}
|
|
65
|
+
for (const key in this.options.transports) {
|
|
66
|
+
const { transport } = this.options.transports[key];
|
|
67
|
+
await transport.stop({ formats: this.options.formats });
|
|
68
|
+
this.logger.debug(`Transport [${key}] stopped`);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
send(transport, connectionId, data) {
|
|
72
|
+
if (transport in this.options.transports) {
|
|
73
|
+
const transportInstance = this.options.transports[transport].transport;
|
|
74
|
+
if (transportInstance.send) {
|
|
75
|
+
return transportInstance.send(connectionId, data);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
async reload() {
|
|
80
|
+
for (const connections of this.connections.connections.values()) {
|
|
81
|
+
await connections.container.dispose();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
createRpcContext(connection, messageContext, logger, gatewayRpc, signal) {
|
|
85
|
+
const { callId, payload, procedure, metadata } = gatewayRpc;
|
|
86
|
+
const controller = new AbortController();
|
|
87
|
+
this.rpcs.set(connection.id, callId, controller);
|
|
88
|
+
signal = signal
|
|
89
|
+
? anyAbortSignal(signal, controller.signal)
|
|
90
|
+
: controller.signal;
|
|
91
|
+
const container = connection.container.fork(Scope.Call);
|
|
92
|
+
const dispose = async () => {
|
|
93
|
+
const streamAbortReason = 'Stream is not consumed by a user';
|
|
94
|
+
// Abort streams related to this call
|
|
95
|
+
this.blobStreams.abortClientCallStreams(connection.id, callId, streamAbortReason);
|
|
96
|
+
this.rpcs.delete(connection.id, callId);
|
|
97
|
+
this.rpcs.releasePull(connection.id, callId);
|
|
98
|
+
await container.dispose();
|
|
99
|
+
};
|
|
100
|
+
return {
|
|
101
|
+
...messageContext,
|
|
102
|
+
callId,
|
|
103
|
+
payload,
|
|
104
|
+
procedure,
|
|
105
|
+
metadata,
|
|
106
|
+
container,
|
|
107
|
+
signal,
|
|
108
|
+
logger: logger.child({ callId, procedure }),
|
|
109
|
+
[Symbol.asyncDispose]: dispose,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
createMessageContext(connection, transportKey) {
|
|
113
|
+
const transport = this.options.transports[transportKey].transport;
|
|
114
|
+
const { id: connectionId, protocol, decoder, encoder } = connection;
|
|
115
|
+
const streamId = this.connections.getStreamId.bind(this.connections, connectionId);
|
|
116
|
+
return {
|
|
117
|
+
connectionId,
|
|
118
|
+
protocol,
|
|
119
|
+
encoder,
|
|
120
|
+
decoder,
|
|
121
|
+
transport,
|
|
122
|
+
streamId,
|
|
123
|
+
addClientStream: ({ streamId, callId, metadata }) => {
|
|
124
|
+
const stream = this.blobStreams.createClientStream(connectionId, callId, streamId, metadata, {
|
|
125
|
+
read: (size) => {
|
|
126
|
+
transport.send(connectionId, protocol.encodeMessage(this.createMessageContext(connection, transportKey), ServerMessageType.ClientStreamPull, { streamId, size: size || 65535 }));
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
stream.once('error', () => {
|
|
130
|
+
this.send(transportKey, connectionId, protocol.encodeMessage(this.createMessageContext(connection, transportKey), ServerMessageType.ClientStreamAbort, { streamId }));
|
|
131
|
+
});
|
|
132
|
+
const consume = () => {
|
|
133
|
+
this.blobStreams.consumeClientStream(connectionId, callId, streamId);
|
|
134
|
+
return stream;
|
|
135
|
+
};
|
|
136
|
+
const consumer = Object.defineProperties(consume, {
|
|
137
|
+
[kBlobKey]: {
|
|
138
|
+
enumerable: false,
|
|
139
|
+
configurable: false,
|
|
140
|
+
writable: false,
|
|
141
|
+
value: true,
|
|
142
|
+
},
|
|
143
|
+
metadata: {
|
|
144
|
+
value: metadata,
|
|
145
|
+
enumerable: true,
|
|
146
|
+
configurable: false,
|
|
147
|
+
writable: false,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
return consumer;
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
onConnect(transport) {
|
|
155
|
+
const logger = this.logger.child({ transport });
|
|
156
|
+
return async (options, ...injections) => {
|
|
157
|
+
logger.debug('Initiating new connection');
|
|
158
|
+
const protocol = versions[options.protocolVersion];
|
|
159
|
+
if (!protocol)
|
|
160
|
+
throw new Error('Unsupported protocol version');
|
|
161
|
+
const id = randomUUID();
|
|
162
|
+
const container = this.options.container.fork(Scope.Connection);
|
|
163
|
+
try {
|
|
164
|
+
await container.provide([
|
|
165
|
+
provide(injectables.connectionData, options.data),
|
|
166
|
+
provide(injectables.connectionId, id),
|
|
167
|
+
]);
|
|
168
|
+
await container.provide(injections);
|
|
169
|
+
const identity = await container.resolve(this.options.identity);
|
|
170
|
+
const { accept, contentType, type } = options;
|
|
171
|
+
const { decoder, encoder } = getFormat(this.options.formats, {
|
|
172
|
+
accept,
|
|
173
|
+
contentType,
|
|
174
|
+
});
|
|
175
|
+
const connection = {
|
|
176
|
+
id,
|
|
177
|
+
type,
|
|
178
|
+
identity,
|
|
179
|
+
transport,
|
|
180
|
+
protocol,
|
|
181
|
+
container,
|
|
182
|
+
encoder,
|
|
183
|
+
decoder,
|
|
184
|
+
abortController: new AbortController(),
|
|
185
|
+
};
|
|
186
|
+
this.connections.add(connection);
|
|
187
|
+
await container.provide(injectables.connectionAbortSignal, connection.abortController.signal);
|
|
188
|
+
logger.debug({
|
|
189
|
+
id,
|
|
190
|
+
protocol: options.protocolVersion,
|
|
191
|
+
type,
|
|
192
|
+
accept,
|
|
193
|
+
contentType,
|
|
194
|
+
identity,
|
|
195
|
+
transportData: options.data,
|
|
196
|
+
}, 'Connection established');
|
|
197
|
+
return Object.assign(connection, {
|
|
198
|
+
[Symbol.asyncDispose]: async () => {
|
|
199
|
+
await this.onDisconnect(transport)(connection.id);
|
|
200
|
+
},
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
catch (error) {
|
|
204
|
+
logger.debug({ error }, 'Error establishing connection');
|
|
205
|
+
container.dispose();
|
|
206
|
+
throw error;
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
onDisconnect(transport) {
|
|
211
|
+
const logger = this.logger.child({ transport });
|
|
212
|
+
return async (connectionId) => {
|
|
213
|
+
logger.debug({ connectionId }, 'Disconnecting connection');
|
|
214
|
+
await this.closeConnection(connectionId);
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
onMessage(transport) {
|
|
218
|
+
const _logger = this.logger.child({ transport });
|
|
219
|
+
return async ({ connectionId, data }, ...injections) => {
|
|
220
|
+
const logger = _logger.child({ connectionId });
|
|
221
|
+
try {
|
|
222
|
+
const connection = this.connections.get(connectionId);
|
|
223
|
+
const messageContext = this.createMessageContext(connection, transport);
|
|
224
|
+
const message = connection.protocol.decodeMessage(messageContext, Buffer.from(data));
|
|
225
|
+
logger.trace(message, 'Received message');
|
|
226
|
+
switch (message.type) {
|
|
227
|
+
case ClientMessageType.Rpc: {
|
|
228
|
+
const rpcContext = this.createRpcContext(connection, messageContext, logger, message.rpc);
|
|
229
|
+
try {
|
|
230
|
+
await rpcContext.container.provide([
|
|
231
|
+
...injections,
|
|
232
|
+
provide(injectables.createBlob, this.createBlobFunction(rpcContext)),
|
|
233
|
+
]);
|
|
234
|
+
await this.handleRpcMessage(connection, rpcContext);
|
|
235
|
+
}
|
|
236
|
+
finally {
|
|
237
|
+
await rpcContext[Symbol.asyncDispose]();
|
|
238
|
+
}
|
|
239
|
+
break;
|
|
240
|
+
}
|
|
241
|
+
case ClientMessageType.RpcPull: {
|
|
242
|
+
this.rpcs.releasePull(connectionId, message.callId);
|
|
243
|
+
break;
|
|
244
|
+
}
|
|
245
|
+
case ClientMessageType.RpcAbort: {
|
|
246
|
+
this.rpcs.abort(connectionId, message.callId);
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
case ClientMessageType.ClientStreamAbort: {
|
|
250
|
+
this.blobStreams.abortClientStream(connectionId, message.streamId, message.reason);
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case ClientMessageType.ClientStreamPush: {
|
|
254
|
+
this.blobStreams.pushToClientStream(connectionId, message.streamId, message.chunk);
|
|
255
|
+
break;
|
|
256
|
+
}
|
|
257
|
+
case ClientMessageType.ClientStreamEnd: {
|
|
258
|
+
this.blobStreams.endClientStream(connectionId, message.streamId);
|
|
259
|
+
break;
|
|
260
|
+
}
|
|
261
|
+
case ClientMessageType.ServerStreamAbort: {
|
|
262
|
+
this.blobStreams.abortServerStream(connectionId, message.streamId, message.reason);
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
case ClientMessageType.ServerStreamPull: {
|
|
266
|
+
this.blobStreams.pullServerStream(connectionId, message.streamId);
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
default:
|
|
270
|
+
throw new Error('Unknown message type');
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
catch (error) {
|
|
274
|
+
logger.trace({ error }, 'Error handling message');
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
onRpc(transport) {
|
|
280
|
+
const _logger = this.logger.child({ transport });
|
|
281
|
+
return async (connection, rpc, signal, ...injections) => {
|
|
282
|
+
const logger = _logger.child({ connectionId: connection.id });
|
|
283
|
+
const messageContext = this.createMessageContext(connection, connection.transport);
|
|
284
|
+
const rpcContext = this.createRpcContext(connection, messageContext, logger, rpc, signal);
|
|
285
|
+
try {
|
|
286
|
+
await rpcContext.container.provide([
|
|
287
|
+
...injections,
|
|
288
|
+
provide(injectables.rpcAbortSignal, signal),
|
|
289
|
+
provide(injectables.createBlob, this.createBlobFunction(rpcContext)),
|
|
290
|
+
]);
|
|
291
|
+
const result = await this.options.api.call({
|
|
292
|
+
connection,
|
|
293
|
+
payload: rpc.payload,
|
|
294
|
+
procedure: rpc.procedure,
|
|
295
|
+
metadata: rpc.metadata,
|
|
296
|
+
container: rpcContext.container,
|
|
297
|
+
signal: rpcContext.signal,
|
|
298
|
+
});
|
|
299
|
+
if (typeof result === 'function') {
|
|
300
|
+
return result(async () => {
|
|
301
|
+
await rpcContext[Symbol.asyncDispose]();
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
else {
|
|
305
|
+
await rpcContext[Symbol.asyncDispose]();
|
|
306
|
+
return result;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
await rpcContext[Symbol.asyncDispose]();
|
|
311
|
+
throw error;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
async handleRpcMessage(connection, context) {
|
|
316
|
+
const { container, connectionId, transport, protocol, signal, callId, procedure, payload, encoder, } = context;
|
|
317
|
+
try {
|
|
318
|
+
await container.provide(injectables.rpcAbortSignal, signal);
|
|
319
|
+
const response = await this.options.api.call({
|
|
320
|
+
connection: connection,
|
|
321
|
+
container,
|
|
322
|
+
payload,
|
|
323
|
+
procedure,
|
|
324
|
+
signal,
|
|
325
|
+
});
|
|
326
|
+
if (typeof response === 'function') {
|
|
327
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcStreamResponse, {
|
|
328
|
+
callId,
|
|
329
|
+
}));
|
|
330
|
+
try {
|
|
331
|
+
const consumeTimeoutSignal = this.options.rpcStreamConsumeTimeout
|
|
332
|
+
? AbortSignal.timeout(this.options.rpcStreamConsumeTimeout)
|
|
333
|
+
: undefined;
|
|
334
|
+
const streamSignal = consumeTimeoutSignal
|
|
335
|
+
? anyAbortSignal(signal, consumeTimeoutSignal)
|
|
336
|
+
: signal;
|
|
337
|
+
await this.rpcs.awaitPull(connectionId, callId, streamSignal);
|
|
338
|
+
for await (const chunk of response()) {
|
|
339
|
+
signal.throwIfAborted();
|
|
340
|
+
const chunkEncoded = encoder.encode(chunk);
|
|
341
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcStreamChunk, { callId, chunk: chunkEncoded }));
|
|
342
|
+
await this.rpcs.awaitPull(connectionId, callId);
|
|
343
|
+
}
|
|
344
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcStreamEnd, {
|
|
345
|
+
callId,
|
|
346
|
+
}));
|
|
347
|
+
}
|
|
348
|
+
catch (error) {
|
|
349
|
+
if (!isAbortError(error)) {
|
|
350
|
+
this.logger.error(error);
|
|
351
|
+
}
|
|
352
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcStreamAbort, {
|
|
353
|
+
callId,
|
|
354
|
+
}));
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
const streams = this.blobStreams.getServerStreamsMetadata(connectionId, callId);
|
|
359
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcResponse, {
|
|
360
|
+
callId,
|
|
361
|
+
result: response,
|
|
362
|
+
streams,
|
|
363
|
+
error: null,
|
|
364
|
+
}));
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch (error) {
|
|
368
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.RpcResponse, {
|
|
369
|
+
callId,
|
|
370
|
+
result: null,
|
|
371
|
+
streams: {},
|
|
372
|
+
error,
|
|
373
|
+
}));
|
|
374
|
+
const level = error instanceof ProtocolError ? 'trace' : 'error';
|
|
375
|
+
this.logger[level](error);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
async closeConnection(connectionId) {
|
|
379
|
+
if (this.connections.has(connectionId)) {
|
|
380
|
+
const connection = this.connections.get(connectionId);
|
|
381
|
+
connection.abortController.abort();
|
|
382
|
+
connection.container.dispose();
|
|
383
|
+
}
|
|
384
|
+
this.rpcs.close(connectionId);
|
|
385
|
+
this.blobStreams.cleanupConnection(connectionId);
|
|
386
|
+
this.connections.remove(connectionId);
|
|
387
|
+
}
|
|
388
|
+
createBlobFunction(context) {
|
|
389
|
+
const { streamId: getStreamId, transport, protocol, connectionId, callId, encoder, } = context;
|
|
390
|
+
return (source, metadata) => {
|
|
391
|
+
const streamId = getStreamId();
|
|
392
|
+
const blob = ProtocolBlob.from(source, metadata, () => {
|
|
393
|
+
return encoder.encodeBlob(streamId);
|
|
394
|
+
});
|
|
395
|
+
const stream = this.blobStreams.createServerStream(connectionId, callId, streamId, blob);
|
|
396
|
+
stream.on('data', (chunk) => {
|
|
397
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.ServerStreamPush, {
|
|
398
|
+
streamId: streamId,
|
|
399
|
+
chunk: Buffer.from(chunk),
|
|
400
|
+
}));
|
|
401
|
+
});
|
|
402
|
+
stream.on('error', (error) => {
|
|
403
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.ServerStreamAbort, {
|
|
404
|
+
streamId: streamId,
|
|
405
|
+
reason: error.message,
|
|
406
|
+
}));
|
|
407
|
+
});
|
|
408
|
+
stream.once('finish', () => {
|
|
409
|
+
transport.send(connectionId, protocol.encodeMessage(context, ServerMessageType.ServerStreamEnd, {
|
|
410
|
+
streamId: streamId,
|
|
411
|
+
}));
|
|
412
|
+
});
|
|
413
|
+
stream.once('close', () => {
|
|
414
|
+
this.blobStreams.removeServerStream(connectionId, streamId);
|
|
415
|
+
});
|
|
416
|
+
return blob;
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const gatewayLoggerOptions = {
|
|
421
|
+
serializers: {
|
|
422
|
+
chunk: (chunk) => isTypedArray(chunk) ? `<Buffer length=${chunk.byteLength}>` : chunk,
|
|
423
|
+
payload: (payload) => {
|
|
424
|
+
function traverseObject(obj) {
|
|
425
|
+
if (Array.isArray(obj)) {
|
|
426
|
+
return obj.map(traverseObject);
|
|
427
|
+
}
|
|
428
|
+
else if (isTypedArray(obj)) {
|
|
429
|
+
return `<${obj.constructor.name} length=${obj.byteLength}>`;
|
|
430
|
+
}
|
|
431
|
+
else if (typeof obj === 'object' && obj !== null) {
|
|
432
|
+
const result = {};
|
|
433
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
434
|
+
result[key] = traverseObject(value);
|
|
435
|
+
}
|
|
436
|
+
return result;
|
|
437
|
+
}
|
|
438
|
+
else if (isBlobInterface(obj)) {
|
|
439
|
+
return `<ClientBlobStream metadata=${JSON.stringify(obj.metadata)}>`;
|
|
440
|
+
}
|
|
441
|
+
return obj;
|
|
442
|
+
}
|
|
443
|
+
return traverseObject(payload);
|
|
444
|
+
},
|
|
445
|
+
headers: (value) => {
|
|
446
|
+
if (value instanceof Headers) {
|
|
447
|
+
const obj = {};
|
|
448
|
+
value.forEach((v, k) => {
|
|
449
|
+
obj[k] = v;
|
|
450
|
+
});
|
|
451
|
+
return obj;
|
|
452
|
+
}
|
|
453
|
+
return value;
|
|
454
|
+
},
|
|
455
|
+
},
|
|
456
|
+
};
|
|
457
|
+
//# sourceMappingURL=gateway.js.map
|