@atls/nestjs-connectrpc 0.0.1
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/README.md +1 -0
- package/dist/connectrpc.constants.d.ts +3 -0
- package/dist/connectrpc.constants.js +3 -0
- package/dist/connectrpc.decorators.d.ts +6 -0
- package/dist/connectrpc.decorators.js +41 -0
- package/dist/connectrpc.interfaces.d.ts +59 -0
- package/dist/connectrpc.interfaces.js +12 -0
- package/dist/connectrpc.server.d.ts +20 -0
- package/dist/connectrpc.server.js +88 -0
- package/dist/connectrpc.strategy.d.ts +15 -0
- package/dist/connectrpc.strategy.js +48 -0
- package/dist/custom-metadata.storage.d.ts +9 -0
- package/dist/custom-metadata.storage.js +17 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/utils/async.utils.d.ts +13 -0
- package/dist/utils/async.utils.js +95 -0
- package/dist/utils/router.utils.d.ts +14 -0
- package/dist/utils/router.utils.js +50 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ConnectRpc migration @wolfcoded/nestjs-bufconnect
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ServiceType } from '@bufbuild/protobuf';
|
|
2
|
+
import { MethodType } from './connectrpc.interfaces.js';
|
|
3
|
+
export declare const ConnectRpcService: (serviceName: ServiceType) => ClassDecorator;
|
|
4
|
+
export declare const AddMethodMetadata: (target: object, key: string | symbol, methodType: MethodType, metadataKey: string | symbol) => void;
|
|
5
|
+
export declare const ConnectRpcMethod: () => MethodDecorator;
|
|
6
|
+
export declare const ConnectRpcStreamMethod: () => MethodDecorator;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { MessagePattern } from '@nestjs/microservices';
|
|
2
|
+
import { CONNECTRPC_TRANSPORT } from './connectrpc.constants.js';
|
|
3
|
+
import { METHOD_DECORATOR_KEY } from './connectrpc.constants.js';
|
|
4
|
+
import { STREAM_METHOD_DECORATOR_KEY } from './connectrpc.constants.js';
|
|
5
|
+
import { MethodType } from './connectrpc.interfaces.js';
|
|
6
|
+
import { CustomMetadataStore } from './custom-metadata.storage.js';
|
|
7
|
+
import { createConnectRpcMethodMetadata } from './utils/router.utils.js';
|
|
8
|
+
function isFunctionPropertyDescriptor(descriptor) {
|
|
9
|
+
return descriptor !== undefined && typeof descriptor.value === 'function';
|
|
10
|
+
}
|
|
11
|
+
export const ConnectRpcService = (serviceName) => (target) => {
|
|
12
|
+
const unaryMethodKeys = Reflect.getMetadata(METHOD_DECORATOR_KEY, target) || [];
|
|
13
|
+
const streamMethodKeys = Reflect.getMetadata(STREAM_METHOD_DECORATOR_KEY, target) || [];
|
|
14
|
+
const allMethodKeys = [...unaryMethodKeys, ...streamMethodKeys];
|
|
15
|
+
allMethodKeys.forEach((methodImpl) => {
|
|
16
|
+
const { key: functionName, methodType } = methodImpl;
|
|
17
|
+
const descriptor = Object.getOwnPropertyDescriptor(target.prototype, functionName);
|
|
18
|
+
if (!descriptor || !isFunctionPropertyDescriptor(descriptor))
|
|
19
|
+
return;
|
|
20
|
+
const metadata = createConnectRpcMethodMetadata(descriptor.value, functionName, serviceName.typeName, functionName, methodType);
|
|
21
|
+
CustomMetadataStore.getInstance().set(serviceName.typeName, serviceName);
|
|
22
|
+
MessagePattern(metadata, CONNECTRPC_TRANSPORT)(target.prototype, functionName, descriptor);
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
export const AddMethodMetadata = (target, key, methodType, metadataKey) => {
|
|
26
|
+
const metadata = {
|
|
27
|
+
key: key.toString(),
|
|
28
|
+
methodType,
|
|
29
|
+
};
|
|
30
|
+
const existingMethods = Reflect.getMetadata(metadataKey, target.constructor) || new Set();
|
|
31
|
+
if (existingMethods.has(metadata))
|
|
32
|
+
return;
|
|
33
|
+
existingMethods.add(metadata);
|
|
34
|
+
Reflect.defineMetadata(metadataKey, existingMethods, target.constructor);
|
|
35
|
+
};
|
|
36
|
+
export const ConnectRpcMethod = () => (target, key) => {
|
|
37
|
+
AddMethodMetadata(target, key, MethodType.NO_STREAMING, METHOD_DECORATOR_KEY);
|
|
38
|
+
};
|
|
39
|
+
export const ConnectRpcStreamMethod = () => (target, key) => {
|
|
40
|
+
AddMethodMetadata(target, key, MethodType.RX_STREAMING, STREAM_METHOD_DECORATOR_KEY);
|
|
41
|
+
};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type * as http from 'http';
|
|
2
|
+
import type * as http2 from 'http2';
|
|
3
|
+
import type * as https from 'https';
|
|
4
|
+
import type { ConnectRouterOptions } from '@connectrpc/connect';
|
|
5
|
+
import type { Observable } from 'rxjs';
|
|
6
|
+
export interface ConnectRpcPattern {
|
|
7
|
+
service: string;
|
|
8
|
+
rpc: string;
|
|
9
|
+
streaming: MethodType;
|
|
10
|
+
}
|
|
11
|
+
export declare enum MethodType {
|
|
12
|
+
NO_STREAMING = "no_stream",
|
|
13
|
+
RX_STREAMING = "rx_stream"
|
|
14
|
+
}
|
|
15
|
+
export declare enum ServerProtocol {
|
|
16
|
+
HTTP = "http",
|
|
17
|
+
HTTPS = "https",
|
|
18
|
+
HTTP2_INSECURE = "http2_insecure",
|
|
19
|
+
HTTP2 = "http2"
|
|
20
|
+
}
|
|
21
|
+
export interface BaseServerOptions {
|
|
22
|
+
port: number;
|
|
23
|
+
connectOptions?: ConnectRouterOptions;
|
|
24
|
+
callback?: () => void;
|
|
25
|
+
}
|
|
26
|
+
export interface HttpOptions extends BaseServerOptions {
|
|
27
|
+
protocol: ServerProtocol.HTTP;
|
|
28
|
+
serverOptions?: http.ServerOptions;
|
|
29
|
+
}
|
|
30
|
+
export interface HttpsOptions extends BaseServerOptions {
|
|
31
|
+
protocol: ServerProtocol.HTTPS;
|
|
32
|
+
serverOptions: https.ServerOptions;
|
|
33
|
+
}
|
|
34
|
+
export interface Http2Options extends BaseServerOptions {
|
|
35
|
+
protocol: ServerProtocol.HTTP2;
|
|
36
|
+
serverOptions: http2.SecureServerOptions;
|
|
37
|
+
}
|
|
38
|
+
export interface Http2InsecureOptions extends BaseServerOptions {
|
|
39
|
+
protocol: ServerProtocol.HTTP2_INSECURE;
|
|
40
|
+
serverOptions?: http2.ServerOptions;
|
|
41
|
+
}
|
|
42
|
+
export type ServerTypeOptions = Http2InsecureOptions | Http2Options | HttpOptions | HttpsOptions;
|
|
43
|
+
export type ServerInstance = http.Server | http2.Http2Server | https.Server | null;
|
|
44
|
+
export interface ConstructorWithPrototype {
|
|
45
|
+
prototype: Record<string, PropertyDescriptor>;
|
|
46
|
+
}
|
|
47
|
+
export interface MethodKey {
|
|
48
|
+
key: string;
|
|
49
|
+
methodType: MethodType;
|
|
50
|
+
}
|
|
51
|
+
export type MethodKeys = Array<MethodKey>;
|
|
52
|
+
export interface FunctionPropertyDescriptor extends PropertyDescriptor {
|
|
53
|
+
value: (...arguments_: Array<never>) => never;
|
|
54
|
+
}
|
|
55
|
+
export type ResultOrDeferred<T> = Observable<T> | T | {
|
|
56
|
+
subscribe: () => void;
|
|
57
|
+
} | {
|
|
58
|
+
toPromise: () => Promise<T>;
|
|
59
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export var MethodType;
|
|
2
|
+
(function (MethodType) {
|
|
3
|
+
MethodType["NO_STREAMING"] = "no_stream";
|
|
4
|
+
MethodType["RX_STREAMING"] = "rx_stream";
|
|
5
|
+
})(MethodType || (MethodType = {}));
|
|
6
|
+
export var ServerProtocol;
|
|
7
|
+
(function (ServerProtocol) {
|
|
8
|
+
ServerProtocol["HTTP"] = "http";
|
|
9
|
+
ServerProtocol["HTTPS"] = "https";
|
|
10
|
+
ServerProtocol["HTTP2_INSECURE"] = "http2_insecure";
|
|
11
|
+
ServerProtocol["HTTP2"] = "http2";
|
|
12
|
+
})(ServerProtocol || (ServerProtocol = {}));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { ConnectRouter } from '@connectrpc/connect';
|
|
2
|
+
import type { ServerTypeOptions } from './connectrpc.interfaces.js';
|
|
3
|
+
import * as http from 'http';
|
|
4
|
+
import * as http2 from 'http2';
|
|
5
|
+
import * as https from 'https';
|
|
6
|
+
export declare class HTTPServer {
|
|
7
|
+
private readonly options;
|
|
8
|
+
private readonly router;
|
|
9
|
+
private serverPrivate;
|
|
10
|
+
constructor(options: ServerTypeOptions, router: (router: ConnectRouter) => void);
|
|
11
|
+
set server(value: http.Server | http2.Http2Server | https.Server | null);
|
|
12
|
+
get server(): http.Server | http2.Http2Server | https.Server | null;
|
|
13
|
+
listen(): Promise<void>;
|
|
14
|
+
createHttpServer(): http.Server;
|
|
15
|
+
createHttpsServer(): https.Server;
|
|
16
|
+
createHttp2Server(): http2.Http2Server;
|
|
17
|
+
createHttp2InsecureServer(): http2.Http2Server;
|
|
18
|
+
startServer(): Promise<void>;
|
|
19
|
+
close(callback?: () => void): Promise<void>;
|
|
20
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { connectNodeAdapter } from '@connectrpc/connect-node';
|
|
2
|
+
import * as http from 'http';
|
|
3
|
+
import * as http2 from 'http2';
|
|
4
|
+
import * as https from 'https';
|
|
5
|
+
import { ServerProtocol } from './connectrpc.interfaces.js';
|
|
6
|
+
export class HTTPServer {
|
|
7
|
+
options;
|
|
8
|
+
router;
|
|
9
|
+
serverPrivate = null;
|
|
10
|
+
constructor(options, router) {
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.router = router;
|
|
13
|
+
}
|
|
14
|
+
set server(value) {
|
|
15
|
+
this.serverPrivate = value;
|
|
16
|
+
}
|
|
17
|
+
get server() {
|
|
18
|
+
return this.serverPrivate;
|
|
19
|
+
}
|
|
20
|
+
async listen() {
|
|
21
|
+
await this.startServer();
|
|
22
|
+
}
|
|
23
|
+
createHttpServer() {
|
|
24
|
+
const { serverOptions = {}, connectOptions = {} } = this.options;
|
|
25
|
+
return http.createServer(serverOptions, connectNodeAdapter({
|
|
26
|
+
...connectOptions,
|
|
27
|
+
routes: this.router,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
createHttpsServer() {
|
|
31
|
+
const { serverOptions = {}, connectOptions = {} } = this.options;
|
|
32
|
+
return https.createServer(serverOptions, connectNodeAdapter({ ...connectOptions, routes: this.router }));
|
|
33
|
+
}
|
|
34
|
+
createHttp2Server() {
|
|
35
|
+
const { serverOptions = {}, connectOptions = {} } = this.options;
|
|
36
|
+
return http2.createSecureServer(serverOptions, connectNodeAdapter({ ...connectOptions, routes: this.router }));
|
|
37
|
+
}
|
|
38
|
+
createHttp2InsecureServer() {
|
|
39
|
+
const { serverOptions = {}, connectOptions = {} } = this.options;
|
|
40
|
+
return http2.createServer(serverOptions, connectNodeAdapter({ ...connectOptions, routes: this.router }));
|
|
41
|
+
}
|
|
42
|
+
startServer() {
|
|
43
|
+
return new Promise((resolve, reject) => {
|
|
44
|
+
switch (this.options.protocol) {
|
|
45
|
+
case ServerProtocol.HTTP: {
|
|
46
|
+
this.server = this.createHttpServer();
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case ServerProtocol.HTTPS: {
|
|
50
|
+
this.server = this.createHttpsServer();
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
case ServerProtocol.HTTP2: {
|
|
54
|
+
this.server = this.createHttp2Server();
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
case ServerProtocol.HTTP2_INSECURE: {
|
|
58
|
+
this.server = this.createHttp2InsecureServer();
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
default: {
|
|
62
|
+
reject(new Error('Invalid protocol option'));
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
this.server.listen(this.options.port, () => {
|
|
67
|
+
if (this.options.callback)
|
|
68
|
+
this.options.callback();
|
|
69
|
+
resolve();
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
async close(callback) {
|
|
74
|
+
return new Promise((resolve, reject) => {
|
|
75
|
+
if (this.server === null) {
|
|
76
|
+
reject(new Error('Server is not running'));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
this.server.close(() => {
|
|
80
|
+
this.server = null;
|
|
81
|
+
if (callback)
|
|
82
|
+
callback();
|
|
83
|
+
resolve();
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ConnectRouter } from '@connectrpc/connect';
|
|
2
|
+
import type { CustomTransportStrategy } from '@nestjs/microservices';
|
|
3
|
+
import type { MessageHandler } from '@nestjs/microservices';
|
|
4
|
+
import type { ServerTypeOptions } from './connectrpc.interfaces.js';
|
|
5
|
+
import { Server } from '@nestjs/microservices';
|
|
6
|
+
export declare class ConnectRpcServer extends Server implements CustomTransportStrategy {
|
|
7
|
+
private readonly customMetadataStore;
|
|
8
|
+
private server;
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(options: ServerTypeOptions);
|
|
11
|
+
listen(callback: (error?: unknown, ...optionalParameters: Array<unknown>) => void): Promise<void>;
|
|
12
|
+
close(): Promise<void>;
|
|
13
|
+
addHandler(pattern: unknown, callback: MessageHandler, isEventHandler?: boolean): void;
|
|
14
|
+
buildRouter(): (router: ConnectRouter) => void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Server } from '@nestjs/microservices';
|
|
2
|
+
import { isString } from '@nestjs/common/utils/shared.utils.js';
|
|
3
|
+
import { HTTPServer } from './connectrpc.server.js';
|
|
4
|
+
import { CustomMetadataStore } from './custom-metadata.storage.js';
|
|
5
|
+
import { addServicesToRouter } from './utils/router.utils.js';
|
|
6
|
+
import { createServiceHandlersMap } from './utils/router.utils.js';
|
|
7
|
+
export class ConnectRpcServer extends Server {
|
|
8
|
+
customMetadataStore = null;
|
|
9
|
+
server = null;
|
|
10
|
+
options;
|
|
11
|
+
constructor(options) {
|
|
12
|
+
super();
|
|
13
|
+
this.customMetadataStore = CustomMetadataStore.getInstance();
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
async listen(callback) {
|
|
17
|
+
try {
|
|
18
|
+
const router = this.buildRouter();
|
|
19
|
+
this.server = new HTTPServer(this.options, router);
|
|
20
|
+
await this.server.listen();
|
|
21
|
+
callback();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
callback(error);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
async close() {
|
|
28
|
+
await this.server?.close();
|
|
29
|
+
}
|
|
30
|
+
addHandler(pattern, callback, isEventHandler = false) {
|
|
31
|
+
const route = isString(pattern) ? pattern : JSON.stringify(pattern);
|
|
32
|
+
if (isEventHandler) {
|
|
33
|
+
const modifiedCallback = callback;
|
|
34
|
+
modifiedCallback.isEventHandler = true;
|
|
35
|
+
this.messageHandlers.set(route, modifiedCallback);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
this.messageHandlers.set(route, callback);
|
|
39
|
+
}
|
|
40
|
+
buildRouter() {
|
|
41
|
+
return (router) => {
|
|
42
|
+
if (!this.customMetadataStore)
|
|
43
|
+
return;
|
|
44
|
+
const serviceHandlersMap = createServiceHandlersMap(this.getHandlers(), this.customMetadataStore);
|
|
45
|
+
addServicesToRouter(router, serviceHandlersMap, this.customMetadataStore);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ServiceType } from '@bufbuild/protobuf';
|
|
2
|
+
export declare class CustomMetadataStore {
|
|
3
|
+
private static instance;
|
|
4
|
+
private customMetadata;
|
|
5
|
+
private constructor();
|
|
6
|
+
static getInstance(): CustomMetadataStore;
|
|
7
|
+
set(key: string, value: ServiceType): void;
|
|
8
|
+
get(key: string): ServiceType | undefined;
|
|
9
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class CustomMetadataStore {
|
|
2
|
+
static instance;
|
|
3
|
+
customMetadata = new Map();
|
|
4
|
+
constructor() { }
|
|
5
|
+
static getInstance() {
|
|
6
|
+
if (!CustomMetadataStore.instance) {
|
|
7
|
+
CustomMetadataStore.instance = new CustomMetadataStore();
|
|
8
|
+
}
|
|
9
|
+
return CustomMetadataStore.instance;
|
|
10
|
+
}
|
|
11
|
+
set(key, value) {
|
|
12
|
+
this.customMetadata.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
get(key) {
|
|
15
|
+
return this.customMetadata.get(key) ?? undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ResultOrDeferred } from '../connectrpc.interfaces.js';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
export declare function isAsyncGenerator<T>(input: unknown): input is AsyncGenerator<T>;
|
|
4
|
+
export declare function observableToAsyncGenerator<T>(observable: Observable<T>): AsyncGenerator<T>;
|
|
5
|
+
export declare const isObservable: <T>(object: unknown) => object is Observable<T>;
|
|
6
|
+
export declare const hasSubscribe: (object: unknown) => object is {
|
|
7
|
+
subscribe: () => void;
|
|
8
|
+
};
|
|
9
|
+
export declare const hasToPromise: (object: unknown) => object is {
|
|
10
|
+
toPromise: () => Promise<unknown>;
|
|
11
|
+
};
|
|
12
|
+
export declare const transformToObservable: <T>(resultOrDeferred: ResultOrDeferred<T>) => Observable<T>;
|
|
13
|
+
export declare function toAsyncGenerator<T>(input: AsyncGenerator<T> | Observable<T>): AsyncGenerator<T>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { Observable } from 'rxjs';
|
|
2
|
+
import { Subject } from 'rxjs';
|
|
3
|
+
import { from } from 'rxjs';
|
|
4
|
+
import { lastValueFrom } from 'rxjs';
|
|
5
|
+
export function isAsyncGenerator(input) {
|
|
6
|
+
return typeof input === 'object' && input !== null && Symbol.asyncIterator in input;
|
|
7
|
+
}
|
|
8
|
+
async function* asyncIterator(subject) {
|
|
9
|
+
const nextValue = async () => new Promise((resolve, reject) => {
|
|
10
|
+
subject.subscribe({
|
|
11
|
+
next: (val) => {
|
|
12
|
+
resolve(val);
|
|
13
|
+
},
|
|
14
|
+
error: (err) => {
|
|
15
|
+
reject(err);
|
|
16
|
+
},
|
|
17
|
+
complete: () => {
|
|
18
|
+
resolve(null);
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
while (true) {
|
|
23
|
+
const item = await nextValue();
|
|
24
|
+
if (item === null)
|
|
25
|
+
return;
|
|
26
|
+
yield item;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
export async function* observableToAsyncGenerator(observable) {
|
|
30
|
+
const queue = new Subject();
|
|
31
|
+
const subscriber = observable.subscribe({
|
|
32
|
+
next: (value) => {
|
|
33
|
+
queue.next(value);
|
|
34
|
+
},
|
|
35
|
+
error: (error) => {
|
|
36
|
+
queue.error(error);
|
|
37
|
+
},
|
|
38
|
+
complete: () => {
|
|
39
|
+
queue.complete();
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
try {
|
|
43
|
+
for await (const item of asyncIterator(queue)) {
|
|
44
|
+
yield item;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
finally {
|
|
48
|
+
subscriber.unsubscribe();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export const isObservable = (object) => object instanceof Observable;
|
|
52
|
+
export const hasSubscribe = (object) => typeof object === 'object' &&
|
|
53
|
+
object !== null &&
|
|
54
|
+
typeof object.subscribe === 'function';
|
|
55
|
+
export const hasToPromise = (object) => typeof object === 'object' &&
|
|
56
|
+
object !== null &&
|
|
57
|
+
typeof object.toPromise === 'function';
|
|
58
|
+
export const transformToObservable = (resultOrDeferred) => {
|
|
59
|
+
if (isObservable(resultOrDeferred)) {
|
|
60
|
+
return resultOrDeferred;
|
|
61
|
+
}
|
|
62
|
+
if (hasSubscribe(resultOrDeferred)) {
|
|
63
|
+
return new Observable((subscriber) => {
|
|
64
|
+
resultOrDeferred.subscribe({
|
|
65
|
+
next: (value) => {
|
|
66
|
+
subscriber.next(value);
|
|
67
|
+
},
|
|
68
|
+
error: (error) => {
|
|
69
|
+
subscriber.error(error);
|
|
70
|
+
},
|
|
71
|
+
complete: () => {
|
|
72
|
+
subscriber.complete();
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
if (hasToPromise(resultOrDeferred)) {
|
|
78
|
+
return from(lastValueFrom(resultOrDeferred));
|
|
79
|
+
}
|
|
80
|
+
return new Observable((subscriber) => {
|
|
81
|
+
subscriber.next(resultOrDeferred);
|
|
82
|
+
subscriber.complete();
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
export async function* toAsyncGenerator(input) {
|
|
86
|
+
if (isObservable(input)) {
|
|
87
|
+
yield* observableToAsyncGenerator(input);
|
|
88
|
+
}
|
|
89
|
+
else if (isAsyncGenerator(input)) {
|
|
90
|
+
yield* input;
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new TypeError('Unsupported input type. Expected an Observable or an AsyncGenerator.');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { ServiceType } from '@bufbuild/protobuf';
|
|
2
|
+
import type { ConnectRouter } from '@connectrpc/connect';
|
|
3
|
+
import type { ServiceImpl } from '@connectrpc/connect';
|
|
4
|
+
import type { MessageHandler } from '@nestjs/microservices';
|
|
5
|
+
import type { CustomMetadataStore } from '../custom-metadata.storage.js';
|
|
6
|
+
import { MethodType } from '../connectrpc.interfaces.js';
|
|
7
|
+
export declare const createPattern: (service: string, methodName: string, streaming: MethodType) => string;
|
|
8
|
+
export declare const addServicesToRouter: (router: ConnectRouter, serviceHandlersMap: Record<string, Partial<ServiceImpl<ServiceType>>>, customMetadataStore: CustomMetadataStore) => void;
|
|
9
|
+
export declare const createServiceHandlersMap: (handlers: Map<string, MessageHandler>, customMetadataStore: CustomMetadataStore) => Record<string, Partial<ServiceImpl<ServiceType>>>;
|
|
10
|
+
export declare const createConnectRpcMethodMetadata: (target: object, key: string | symbol, service?: string, method?: string, streaming?: MethodType) => {
|
|
11
|
+
service: string;
|
|
12
|
+
rpc: string;
|
|
13
|
+
streaming: MethodType;
|
|
14
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { lastValueFrom } from 'rxjs';
|
|
2
|
+
import { MethodType } from '../connectrpc.interfaces.js';
|
|
3
|
+
import { toAsyncGenerator } from './async.utils.js';
|
|
4
|
+
import { transformToObservable } from './async.utils.js';
|
|
5
|
+
export const createPattern = (service, methodName, streaming) => JSON.stringify({ service, rpc: methodName, streaming });
|
|
6
|
+
export const addServicesToRouter = (router, serviceHandlersMap, customMetadataStore) => {
|
|
7
|
+
for (const serviceName of Object.keys(serviceHandlersMap)) {
|
|
8
|
+
const service = customMetadataStore.get(serviceName);
|
|
9
|
+
if (!service)
|
|
10
|
+
continue;
|
|
11
|
+
router.service(service, serviceHandlersMap[serviceName]);
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
export const createServiceHandlersMap = (handlers, customMetadataStore) => {
|
|
15
|
+
const serviceHandlersMap = {};
|
|
16
|
+
handlers.forEach((handlerMetadata, pattern) => {
|
|
17
|
+
const parsedPattern = JSON.parse(pattern);
|
|
18
|
+
const { service, rpc, streaming } = parsedPattern;
|
|
19
|
+
const serviceMetadata = customMetadataStore.get(service);
|
|
20
|
+
if (!serviceMetadata)
|
|
21
|
+
return;
|
|
22
|
+
const methodProto = serviceMetadata.methods[rpc];
|
|
23
|
+
if (!methodProto)
|
|
24
|
+
return;
|
|
25
|
+
serviceHandlersMap[service] ??= {};
|
|
26
|
+
switch (streaming) {
|
|
27
|
+
case MethodType.NO_STREAMING:
|
|
28
|
+
serviceHandlersMap[service][rpc] = async (request, context) => {
|
|
29
|
+
const resultOrDeferred = await handlerMetadata(request, context);
|
|
30
|
+
return lastValueFrom(transformToObservable(resultOrDeferred));
|
|
31
|
+
};
|
|
32
|
+
break;
|
|
33
|
+
case MethodType.RX_STREAMING:
|
|
34
|
+
serviceHandlersMap[service][rpc] = async function* handleStream(request, context) {
|
|
35
|
+
const streamOrValue = await handlerMetadata(request, context);
|
|
36
|
+
yield* toAsyncGenerator(streamOrValue);
|
|
37
|
+
};
|
|
38
|
+
break;
|
|
39
|
+
default:
|
|
40
|
+
throw new Error(`Unsupported streaming type: ${streaming}`);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return serviceHandlersMap;
|
|
44
|
+
};
|
|
45
|
+
export const createConnectRpcMethodMetadata = (target, key, service, method, streaming = MethodType.NO_STREAMING) => {
|
|
46
|
+
const capitalizeFirstLetter = (input) => input.charAt(0).toUpperCase() + input.slice(1);
|
|
47
|
+
const serviceName = service ?? target.constructor.name;
|
|
48
|
+
const rpcMethodName = method ?? capitalizeFirstLetter(String(key));
|
|
49
|
+
return { service: serviceName, rpc: rpcMethodName, streaming };
|
|
50
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atls/nestjs-connectrpc",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"license": "BSD-3-Clause",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
"./package.json": "./package.json",
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "yarn library build",
|
|
20
|
+
"prepack": "yarn run build",
|
|
21
|
+
"postpack": "rm -rf dist"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@bufbuild/protobuf": "1.10.0",
|
|
25
|
+
"@connectrpc/connect": "1.6.1",
|
|
26
|
+
"@connectrpc/connect-node": "1.6.1",
|
|
27
|
+
"@nestjs/common": "10.0.5",
|
|
28
|
+
"@nestjs/core": "10.0.5",
|
|
29
|
+
"@nestjs/microservices": "10.2.4",
|
|
30
|
+
"@nestjs/platform-express": "10.2.4",
|
|
31
|
+
"reflect-metadata": "0.2.2",
|
|
32
|
+
"rxjs": "7.8.1"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@bufbuild/protobuf": "^1",
|
|
36
|
+
"@connectrpc/connect": "^1",
|
|
37
|
+
"@connectrpc/connect-node": "^1",
|
|
38
|
+
"@nestjs/common": "^10",
|
|
39
|
+
"@nestjs/core": "^10",
|
|
40
|
+
"@nestjs/microservices": "^10",
|
|
41
|
+
"@nestjs/platform-express": "^10",
|
|
42
|
+
"reflect-metadata": "^0.2",
|
|
43
|
+
"rxjs": "^7"
|
|
44
|
+
},
|
|
45
|
+
"publishConfig": {
|
|
46
|
+
"exports": {
|
|
47
|
+
"./package.json": "./package.json",
|
|
48
|
+
".": {
|
|
49
|
+
"import": "./dist/index.js",
|
|
50
|
+
"types": "./dist/index.d.ts",
|
|
51
|
+
"default": "./dist/index.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
"main": "dist/index.js",
|
|
55
|
+
"typings": "dist/index.d.ts"
|
|
56
|
+
},
|
|
57
|
+
"typings": "dist/index.d.ts"
|
|
58
|
+
}
|