@funduck/connectrpc-fastify 1.0.0 → 1.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/package.json +4 -5
- package/.editorconfig +0 -12
- package/.prettierrc +0 -7
- package/pnpm-workspace.yaml +0 -2
- package/publish.sh +0 -3
- package/src/connectrpc.ts +0 -87
- package/src/execution-context.ts +0 -45
- package/src/fastify-plugin.ts +0 -99
- package/src/guards.ts +0 -130
- package/src/helpers.ts +0 -92
- package/src/index.ts +0 -21
- package/src/interfaces.ts +0 -175
- package/src/middlewares.ts +0 -71
- package/src/stores.ts +0 -180
- package/src/types.ts +0 -24
- package/test/buf.gen.yaml +0 -7
- package/test/buf.lock +0 -6
- package/test/buf.yaml +0 -12
- package/test/controller.ts +0 -73
- package/test/e2e-demo.ts +0 -252
- package/test/gen/buf/validate/validate_pb.ts +0 -4938
- package/test/gen/connectrpc/eliza/v1/eliza_pb.ts +0 -97
- package/test/guards.ts +0 -13
- package/test/middlewares.ts +0 -50
- package/test/proto/connectrpc/eliza/v1/eliza.proto +0 -26
- package/test/server.ts +0 -47
- package/tsconfig.build.json +0 -4
- package/tsconfig.json +0 -26
package/src/middlewares.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import { FastifyInstance } from 'fastify';
|
|
2
|
-
import { convertMiddlewareToHook, logger } from './helpers';
|
|
3
|
-
import { MiddlewareConfigUnion } from './interfaces';
|
|
4
|
-
import { MiddlewareStore } from './stores';
|
|
5
|
-
|
|
6
|
-
export async function initMiddlewares(
|
|
7
|
-
server: FastifyInstance,
|
|
8
|
-
middlewareConfigs: MiddlewareConfigUnion[],
|
|
9
|
-
) {
|
|
10
|
-
for (const config of middlewareConfigs) {
|
|
11
|
-
// Convert method names to set with PascalCase
|
|
12
|
-
const methods = new Set(
|
|
13
|
-
(config.methods || []).map((m) => m[0].toUpperCase() + m.slice(1)),
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
// Get the middleware instance from the store
|
|
17
|
-
const middlewareInstance = MiddlewareStore.getInstance(config.use);
|
|
18
|
-
|
|
19
|
-
if (!middlewareInstance) {
|
|
20
|
-
logger.warn(
|
|
21
|
-
`Middleware ${config.use.name} not found in store. Did you forget to add MiddlewareStore.registerInstance(this) in the constructor? Or did you forget to instantiate the middleware?`,
|
|
22
|
-
);
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (typeof middlewareInstance.use === 'function') {
|
|
27
|
-
const hook = convertMiddlewareToHook(middlewareInstance);
|
|
28
|
-
|
|
29
|
-
// Create a filtered hook that checks service and method
|
|
30
|
-
const filteredHook = async (request: any, reply: any) => {
|
|
31
|
-
const url = request.url as string;
|
|
32
|
-
|
|
33
|
-
// Parse the URL to get service and method
|
|
34
|
-
// Format: /package.ServiceName/MethodName
|
|
35
|
-
const match = url.match(/^\/([^/]+)\/([^/]+)$/);
|
|
36
|
-
|
|
37
|
-
if (!match) {
|
|
38
|
-
// Not a ConnectRPC route, skip
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const [, serviceName, methodName] = match;
|
|
43
|
-
|
|
44
|
-
// Check if middleware should apply to this service
|
|
45
|
-
if (config.on && config.on.typeName !== serviceName) {
|
|
46
|
-
return;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// Check if middleware should apply to this method
|
|
50
|
-
if (methods.size && !methods.has(methodName)) {
|
|
51
|
-
return;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Apply the middleware
|
|
55
|
-
await hook(request, reply);
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
server.addHook('onRequest', filteredHook);
|
|
59
|
-
|
|
60
|
-
const serviceInfo = config.on
|
|
61
|
-
? ` to service ${config.on.typeName}`
|
|
62
|
-
: ' to all services';
|
|
63
|
-
const methodInfo = config.methods
|
|
64
|
-
? ` methods [${config.methods.join(', ')}]`
|
|
65
|
-
: ' all methods';
|
|
66
|
-
logger.log(
|
|
67
|
-
`Applied middleware: ${config.use.name}${serviceInfo}${methodInfo}`,
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
package/src/stores.ts
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import { GenService, GenServiceMethods } from '@bufbuild/protobuf/codegenv2';
|
|
2
|
-
import { Guard, Middleware, Service, Type } from './interfaces';
|
|
3
|
-
|
|
4
|
-
class ControllersStoreClass {
|
|
5
|
-
private controllers = new Map<
|
|
6
|
-
Type<any>,
|
|
7
|
-
{
|
|
8
|
-
instance: any;
|
|
9
|
-
service: GenService<any>;
|
|
10
|
-
}
|
|
11
|
-
>();
|
|
12
|
-
|
|
13
|
-
values() {
|
|
14
|
-
return Array.from(this.controllers.entries()).map(([target, data]) => ({
|
|
15
|
-
target,
|
|
16
|
-
...data,
|
|
17
|
-
}));
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
registerInstance<T extends GenServiceMethods>(
|
|
21
|
-
self: Service<GenService<T>>,
|
|
22
|
-
service: GenService<T>,
|
|
23
|
-
{
|
|
24
|
-
allowMultipleInstances = false,
|
|
25
|
-
}: {
|
|
26
|
-
allowMultipleInstances?: boolean;
|
|
27
|
-
} = {},
|
|
28
|
-
) {
|
|
29
|
-
const controllerClass = self.constructor as Type<any>;
|
|
30
|
-
if (!allowMultipleInstances && this.controllers.has(controllerClass)) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Controller ${controllerClass.name} is already registered! This may happen if you export controller as provider and also register it in some Nest module.`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
this.controllers.set(controllerClass, {
|
|
36
|
-
instance: self,
|
|
37
|
-
service,
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export const ControllersStore = new ControllersStoreClass();
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Store for middleware classes and their instances
|
|
46
|
-
*/
|
|
47
|
-
class MiddlewareStoreClass {
|
|
48
|
-
private middlewares = new Map<Type<Middleware>, Middleware>();
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Register a middleware instance from its constructor
|
|
52
|
-
*/
|
|
53
|
-
registerInstance(
|
|
54
|
-
self: Middleware,
|
|
55
|
-
{
|
|
56
|
-
allowMultipleInstances = false,
|
|
57
|
-
}: {
|
|
58
|
-
allowMultipleInstances?: boolean;
|
|
59
|
-
} = {},
|
|
60
|
-
) {
|
|
61
|
-
const middlewareClass = self.constructor as Type<Middleware>;
|
|
62
|
-
if (!allowMultipleInstances && this.middlewares.has(middlewareClass)) {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`Middleware ${middlewareClass.name} is already registered! This may happen if you export middleware as provider and also register it in some Nest module.`,
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
this.middlewares.set(middlewareClass, self);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Get a middleware instance by its class
|
|
72
|
-
*/
|
|
73
|
-
getInstance(middlewareClass: Type<Middleware>): Middleware | null {
|
|
74
|
-
return this.middlewares.get(middlewareClass) || null;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export const MiddlewareStore = new MiddlewareStoreClass();
|
|
79
|
-
|
|
80
|
-
/**
|
|
81
|
-
* Store for route metadata - maps URL paths to controller class and method info
|
|
82
|
-
*/
|
|
83
|
-
class RouteMetadataStoreClass {
|
|
84
|
-
private routes = new Map<
|
|
85
|
-
string,
|
|
86
|
-
{
|
|
87
|
-
controllerClass: Type<any>;
|
|
88
|
-
controllerMethod: Function;
|
|
89
|
-
controllerMethodName: string;
|
|
90
|
-
instance: any;
|
|
91
|
-
serviceName: string;
|
|
92
|
-
methodName: string;
|
|
93
|
-
}
|
|
94
|
-
>();
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Register route metadata for a specific service method
|
|
98
|
-
* @param serviceName - The full service name (e.g., "connectrpc.eliza.v1.ElizaService")
|
|
99
|
-
* @param methodName - The method name in PascalCase (e.g., "Say")
|
|
100
|
-
* @param controllerClass - The controller class
|
|
101
|
-
* @param controllerMethod - The bound controller method
|
|
102
|
-
* @param controllerMethodName - The name of the controller method
|
|
103
|
-
* @param instance - The controller instance
|
|
104
|
-
*/
|
|
105
|
-
registerRoute(
|
|
106
|
-
serviceName: string,
|
|
107
|
-
methodName: string,
|
|
108
|
-
controllerClass: Type<any>,
|
|
109
|
-
controllerMethod: Function,
|
|
110
|
-
controllerMethodName: string,
|
|
111
|
-
instance: any,
|
|
112
|
-
) {
|
|
113
|
-
const routeKey = `/${serviceName}/${methodName}`;
|
|
114
|
-
this.routes.set(routeKey, {
|
|
115
|
-
controllerClass,
|
|
116
|
-
controllerMethod,
|
|
117
|
-
controllerMethodName,
|
|
118
|
-
instance,
|
|
119
|
-
serviceName,
|
|
120
|
-
methodName,
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Get route metadata by URL path
|
|
126
|
-
*/
|
|
127
|
-
getRouteMetadata(urlPath: string) {
|
|
128
|
-
return this.routes.get(urlPath) || null;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Get all registered routes
|
|
133
|
-
*/
|
|
134
|
-
getAllRoutes() {
|
|
135
|
-
return Array.from(this.routes.entries());
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
export const RouteMetadataStore = new RouteMetadataStoreClass();
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Store for guard classes and their instances
|
|
143
|
-
*/
|
|
144
|
-
class GuardsStoreClass {
|
|
145
|
-
private guards = new Map<Type<Guard>, Guard>();
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Register a guard instance from its constructor
|
|
149
|
-
*/
|
|
150
|
-
registerInstance(
|
|
151
|
-
self: Guard,
|
|
152
|
-
{
|
|
153
|
-
allowMultipleInstances = false,
|
|
154
|
-
}: { allowMultipleInstances?: boolean } = {},
|
|
155
|
-
) {
|
|
156
|
-
const guardClass = self.constructor as Type<Guard>;
|
|
157
|
-
if (!allowMultipleInstances && this.guards.has(guardClass)) {
|
|
158
|
-
throw new Error(
|
|
159
|
-
`Guard ${guardClass.name} is already registered! This may happen if you export guard as provider and also register it in some Nest module.`,
|
|
160
|
-
);
|
|
161
|
-
}
|
|
162
|
-
this.guards.set(guardClass, self);
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Get a guard instance by its class
|
|
167
|
-
*/
|
|
168
|
-
getInstance(guardClass: Type<Guard>): Guard | null {
|
|
169
|
-
return this.guards.get(guardClass) || null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Get all registered guards
|
|
174
|
-
*/
|
|
175
|
-
getAllGuards(): Guard[] {
|
|
176
|
-
return Array.from(this.guards.values());
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export const GuardsStore = new GuardsStoreClass();
|
package/src/types.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { OptionalKeysOf, Primitive, RequiredKeysOf } from 'type-fest';
|
|
2
|
-
|
|
3
|
-
export type OmitFieldsDeep<T, K extends keyof any> = T extends Primitive | Date
|
|
4
|
-
? T
|
|
5
|
-
: T extends Array<any>
|
|
6
|
-
? {
|
|
7
|
-
[P in keyof T]?: OmitFieldsDeep<T[P], K>;
|
|
8
|
-
}
|
|
9
|
-
: T extends object
|
|
10
|
-
? {
|
|
11
|
-
[P in Exclude<RequiredKeysOf<T>, K>]: OmitFieldsDeep<T[P], K>;
|
|
12
|
-
} & {
|
|
13
|
-
[P in Exclude<OptionalKeysOf<T>, K>]?: OmitFieldsDeep<T[P], K>;
|
|
14
|
-
}
|
|
15
|
-
: T;
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Used to simplify types by omitting ConnectRPC specific fields like `$typeName` and `$unknown`
|
|
19
|
-
* The fields are omitted deeply in nested structures.
|
|
20
|
-
*/
|
|
21
|
-
export type OmitConnectrpcFields<T> = OmitFieldsDeep<
|
|
22
|
-
T,
|
|
23
|
-
'$typeName' | '$unknown'
|
|
24
|
-
>;
|
package/test/buf.gen.yaml
DELETED
package/test/buf.lock
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
# Generated by buf. DO NOT EDIT.
|
|
2
|
-
version: v2
|
|
3
|
-
deps:
|
|
4
|
-
- name: buf.build/bufbuild/protovalidate
|
|
5
|
-
commit: 2a1774d888024a9b93ce7eb4b59f6a83
|
|
6
|
-
digest: b5:6b7f9bc919b65e5b79d7b726ffc03d6f815a412d6b792970fa6f065cae162107bd0a9d47272c8ab1a2c9514e87b13d3fbf71df614374d62d2183afb64be2d30a
|
package/test/buf.yaml
DELETED
package/test/controller.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { ConnectRPC, OmitConnectrpcFields, Service } from '../src/index';
|
|
2
|
-
import type {
|
|
3
|
-
SayRequest,
|
|
4
|
-
SayResponse,
|
|
5
|
-
SayResponses,
|
|
6
|
-
} from './gen/connectrpc/eliza/v1/eliza_pb';
|
|
7
|
-
import { ElizaService } from './gen/connectrpc/eliza/v1/eliza_pb';
|
|
8
|
-
|
|
9
|
-
export class ElizaController implements Service<typeof ElizaService> {
|
|
10
|
-
constructor() {
|
|
11
|
-
ConnectRPC.registerController(this, ElizaService);
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Unary RPC: Say
|
|
16
|
-
* Client sends one request, server sends one response
|
|
17
|
-
*
|
|
18
|
-
* For demonstration, this method is decorated with @SkipAuthGuard to bypass authentication.
|
|
19
|
-
*/
|
|
20
|
-
async say(
|
|
21
|
-
request: SayRequest,
|
|
22
|
-
|
|
23
|
-
// You can leave out the return type, it will be inferred from the interface
|
|
24
|
-
) {
|
|
25
|
-
console.log(`Controller received request Say`);
|
|
26
|
-
return {
|
|
27
|
-
sentence: `You said: ${request.sentence}`,
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Client Streaming RPC: SayMany
|
|
33
|
-
* Client sends multiple requests, server sends one response with all collected
|
|
34
|
-
*/
|
|
35
|
-
async sayMany(
|
|
36
|
-
request: AsyncIterable<SayRequest>,
|
|
37
|
-
|
|
38
|
-
// You can specify the return type if you want, but you always need to use OmitConnectrpcFields<> because ConnectRPC adds extra fields internally
|
|
39
|
-
): Promise<OmitConnectrpcFields<SayResponses>> {
|
|
40
|
-
console.log(`Controller received request SayMany`);
|
|
41
|
-
|
|
42
|
-
const responses: OmitConnectrpcFields<SayResponse>[] = [];
|
|
43
|
-
|
|
44
|
-
for await (const req of request) {
|
|
45
|
-
responses.push({
|
|
46
|
-
sentence: `You said: ${req.sentence}`,
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
return {
|
|
51
|
-
responses,
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Server Streaming RPC: ListenMany
|
|
57
|
-
* Client sends one request, server sends multiple responses
|
|
58
|
-
*/
|
|
59
|
-
async *listenMany(request: SayRequest) {
|
|
60
|
-
console.log(`Controller received request ListenMany`);
|
|
61
|
-
|
|
62
|
-
const words = request.sentence.split(' ');
|
|
63
|
-
|
|
64
|
-
for (const word of words) {
|
|
65
|
-
// Simulate some processing delay
|
|
66
|
-
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
67
|
-
|
|
68
|
-
yield {
|
|
69
|
-
sentence: `Echo: ${word}`,
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
package/test/e2e-demo.ts
DELETED
|
@@ -1,252 +0,0 @@
|
|
|
1
|
-
import { create } from '@bufbuild/protobuf';
|
|
2
|
-
import { createClient } from '@connectrpc/connect';
|
|
3
|
-
import { createConnectTransport } from '@connectrpc/connect-node';
|
|
4
|
-
import {
|
|
5
|
-
ElizaService,
|
|
6
|
-
SayRequestSchema,
|
|
7
|
-
} from './gen/connectrpc/eliza/v1/eliza_pb';
|
|
8
|
-
import { TestGuard1 } from './guards';
|
|
9
|
-
import {
|
|
10
|
-
TestMiddleware1,
|
|
11
|
-
TestMiddleware2,
|
|
12
|
-
TestMiddleware3,
|
|
13
|
-
} from './middlewares';
|
|
14
|
-
import { bootstrap } from './server';
|
|
15
|
-
|
|
16
|
-
const transport = createConnectTransport({
|
|
17
|
-
baseUrl: 'http://localhost:3000',
|
|
18
|
-
httpVersion: '1.1',
|
|
19
|
-
});
|
|
20
|
-
|
|
21
|
-
export const client = createClient(ElizaService, transport);
|
|
22
|
-
|
|
23
|
-
const mockAuthorizationToken = 'Bearer mock-token-123';
|
|
24
|
-
|
|
25
|
-
let testMiddlewareCalled = {
|
|
26
|
-
1: false,
|
|
27
|
-
2: false,
|
|
28
|
-
3: false,
|
|
29
|
-
};
|
|
30
|
-
function prepareMiddlewares() {
|
|
31
|
-
testMiddlewareCalled[1] = false;
|
|
32
|
-
testMiddlewareCalled[2] = false;
|
|
33
|
-
testMiddlewareCalled[3] = false;
|
|
34
|
-
|
|
35
|
-
TestMiddleware1.callback = (req, res) => {
|
|
36
|
-
console.log(`Middleware 1 called for request: ${req.url}`);
|
|
37
|
-
testMiddlewareCalled[1] = true;
|
|
38
|
-
return null;
|
|
39
|
-
};
|
|
40
|
-
TestMiddleware2.callback = (req, res) => {
|
|
41
|
-
console.log(`Middleware 2 called for request: ${req.url}`);
|
|
42
|
-
testMiddlewareCalled[2] = true;
|
|
43
|
-
return null;
|
|
44
|
-
};
|
|
45
|
-
TestMiddleware3.callback = (req, res) => {
|
|
46
|
-
console.log(`Middleware 3 called for request: ${req.url}`);
|
|
47
|
-
testMiddlewareCalled[3] = true;
|
|
48
|
-
return null;
|
|
49
|
-
};
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
let testGuardCalled = {
|
|
53
|
-
1: false,
|
|
54
|
-
};
|
|
55
|
-
function prepareGuards() {
|
|
56
|
-
testGuardCalled[1] = false;
|
|
57
|
-
TestGuard1.callback = (context) => {
|
|
58
|
-
console.log(
|
|
59
|
-
`Guard 1 called for request:`,
|
|
60
|
-
context.switchToHttp().getRequest().url,
|
|
61
|
-
);
|
|
62
|
-
testGuardCalled[1] = true;
|
|
63
|
-
return true;
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function testUnary() {
|
|
68
|
-
console.log('\n=== Testing Unary RPC: Say ===');
|
|
69
|
-
const sentence = 'Hello ConnectRPC!';
|
|
70
|
-
console.log(`Request: "${sentence}"`);
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
prepareMiddlewares();
|
|
74
|
-
prepareGuards();
|
|
75
|
-
|
|
76
|
-
const response = await client.say(
|
|
77
|
-
{ sentence },
|
|
78
|
-
{
|
|
79
|
-
headers: {
|
|
80
|
-
Authorization: mockAuthorizationToken,
|
|
81
|
-
'x-request-id': 'unary-test-001',
|
|
82
|
-
},
|
|
83
|
-
},
|
|
84
|
-
);
|
|
85
|
-
console.log(`Response: "${response.sentence}"`);
|
|
86
|
-
|
|
87
|
-
// Check that all middlewares were called
|
|
88
|
-
if (
|
|
89
|
-
!testMiddlewareCalled[1] ||
|
|
90
|
-
!testMiddlewareCalled[2] ||
|
|
91
|
-
!testMiddlewareCalled[3]
|
|
92
|
-
) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`Not all middlewares were called: ${JSON.stringify(
|
|
95
|
-
testMiddlewareCalled,
|
|
96
|
-
)}`,
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Check that the guard was called
|
|
101
|
-
if (!testGuardCalled[1]) {
|
|
102
|
-
throw new Error('Guard 1 was not called');
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
console.log('✅ Unary RPC test passed\n');
|
|
106
|
-
return true;
|
|
107
|
-
} catch (error) {
|
|
108
|
-
console.error('❌ Error calling Say:', error);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async function testClientStreaming() {
|
|
114
|
-
console.log('=== Testing Client Streaming RPC: SayMany ===');
|
|
115
|
-
const sentences = ['First message', 'Second message', 'Third message'];
|
|
116
|
-
console.log('Sending multiple requests:', sentences);
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
prepareMiddlewares();
|
|
120
|
-
prepareGuards();
|
|
121
|
-
|
|
122
|
-
// Create an async generator to send multiple requests
|
|
123
|
-
async function* generateRequests() {
|
|
124
|
-
for (const sentence of sentences) {
|
|
125
|
-
console.log(` Sending: "${sentence}"`);
|
|
126
|
-
yield create(SayRequestSchema, { sentence });
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
const response = await client.sayMany(generateRequests(), {
|
|
131
|
-
headers: {
|
|
132
|
-
Authorization: mockAuthorizationToken,
|
|
133
|
-
'x-request-id': 'client-streaming-test-001',
|
|
134
|
-
},
|
|
135
|
-
});
|
|
136
|
-
console.log(`Received ${response.responses.length} responses:`);
|
|
137
|
-
response.responses.forEach((resp, idx) => {
|
|
138
|
-
console.log(` [${idx + 1}] ${resp.sentence}`);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
// Check that all middlewares were called
|
|
142
|
-
if (!testMiddlewareCalled[1] || !testMiddlewareCalled[2]) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
`Not all middlewares were called: ${JSON.stringify(
|
|
145
|
-
testMiddlewareCalled,
|
|
146
|
-
)}`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
if (testMiddlewareCalled[3]) {
|
|
150
|
-
throw new Error(
|
|
151
|
-
`Middleware 3 should not have been called for SayMany: ${JSON.stringify(
|
|
152
|
-
testMiddlewareCalled,
|
|
153
|
-
)}`,
|
|
154
|
-
);
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Check that the guard was called
|
|
158
|
-
if (!testGuardCalled[1]) {
|
|
159
|
-
throw new Error('Guard 1 was not called');
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
console.log('✅ Client Streaming RPC test passed\n');
|
|
163
|
-
return true;
|
|
164
|
-
} catch (error) {
|
|
165
|
-
console.error('❌ Error calling SayMany:', error);
|
|
166
|
-
return false;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
async function testServerStreaming() {
|
|
171
|
-
console.log('=== Testing Server Streaming RPC: ListenMany ===');
|
|
172
|
-
const sentence = 'Hello Streaming World';
|
|
173
|
-
console.log(`Request: "${sentence}"`);
|
|
174
|
-
console.log('Receiving streamed responses:');
|
|
175
|
-
|
|
176
|
-
try {
|
|
177
|
-
prepareMiddlewares();
|
|
178
|
-
prepareGuards();
|
|
179
|
-
|
|
180
|
-
let count = 0;
|
|
181
|
-
for await (const response of client.listenMany(
|
|
182
|
-
{ sentence },
|
|
183
|
-
{
|
|
184
|
-
headers: {
|
|
185
|
-
Authorization: mockAuthorizationToken,
|
|
186
|
-
'x-request-id': 'server-streaming-test-001',
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
)) {
|
|
190
|
-
count++;
|
|
191
|
-
console.log(` [${count}] ${response.sentence}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Check that all middlewares were called
|
|
195
|
-
if (!testMiddlewareCalled[1] || !testMiddlewareCalled[2]) {
|
|
196
|
-
throw new Error(
|
|
197
|
-
`Not all middlewares were called: ${JSON.stringify(
|
|
198
|
-
testMiddlewareCalled,
|
|
199
|
-
)}`,
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
if (testMiddlewareCalled[3]) {
|
|
203
|
-
throw new Error(
|
|
204
|
-
`Middleware 3 should not have been called for SayMany: ${JSON.stringify(
|
|
205
|
-
testMiddlewareCalled,
|
|
206
|
-
)}`,
|
|
207
|
-
);
|
|
208
|
-
}
|
|
209
|
-
// Check that the guard was called
|
|
210
|
-
if (!testGuardCalled[1]) {
|
|
211
|
-
throw new Error('Guard 1 was not called');
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
console.log(
|
|
215
|
-
`✅ Server Streaming RPC test passed (received ${count} responses)\n`,
|
|
216
|
-
);
|
|
217
|
-
return true;
|
|
218
|
-
} catch (error) {
|
|
219
|
-
console.error('❌ Error calling ListenMany:', error);
|
|
220
|
-
return false;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function runAllTests() {
|
|
225
|
-
console.log('🚀 Starting ConnectRPC Tests\n');
|
|
226
|
-
|
|
227
|
-
// Bootstrap the server
|
|
228
|
-
await bootstrap();
|
|
229
|
-
|
|
230
|
-
// Give the server a moment to start
|
|
231
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
232
|
-
|
|
233
|
-
// Run all tests
|
|
234
|
-
const results = [
|
|
235
|
-
await testUnary(),
|
|
236
|
-
await testClientStreaming(),
|
|
237
|
-
await testServerStreaming(),
|
|
238
|
-
];
|
|
239
|
-
|
|
240
|
-
// Check results
|
|
241
|
-
const allPassed = results.every((result) => result === true);
|
|
242
|
-
|
|
243
|
-
if (allPassed) {
|
|
244
|
-
console.log('🎉 All tests passed!');
|
|
245
|
-
process.exit(0);
|
|
246
|
-
} else {
|
|
247
|
-
console.log('❌ Some tests failed');
|
|
248
|
-
process.exit(1);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
runAllTests();
|