@rsdk/grpc.server 4.0.0-next.4 → 4.0.0-next.6

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 (40) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/controllers/index.d.ts +2 -0
  3. package/dist/controllers/index.js +2 -0
  4. package/dist/controllers/index.js.map +1 -1
  5. package/dist/controllers/reflection/v1.base.controller.d.ts +32 -0
  6. package/dist/controllers/reflection/v1.base.controller.js +177 -0
  7. package/dist/controllers/reflection/v1.base.controller.js.map +1 -0
  8. package/dist/controllers/reflection/v1.controller.d.ts +10 -0
  9. package/dist/controllers/reflection/v1.controller.js +25 -0
  10. package/dist/controllers/reflection/v1.controller.js.map +1 -0
  11. package/dist/controllers/reflection/v1alpha.controller.d.ts +10 -0
  12. package/dist/controllers/reflection/v1alpha.controller.js +25 -0
  13. package/dist/controllers/reflection/v1alpha.controller.js.map +1 -0
  14. package/dist/grpc.config.d.ts +1 -0
  15. package/dist/grpc.config.js +8 -0
  16. package/dist/grpc.config.js.map +1 -1
  17. package/dist/grpc.transport.d.ts +13 -1
  18. package/dist/grpc.transport.js +40 -9
  19. package/dist/grpc.transport.js.map +1 -1
  20. package/dist/grpc.utils.d.ts +9 -0
  21. package/dist/grpc.utils.js +18 -0
  22. package/dist/grpc.utils.js.map +1 -0
  23. package/dist/reflection.module.d.ts +5 -0
  24. package/dist/reflection.module.js +33 -0
  25. package/dist/reflection.module.js.map +1 -0
  26. package/dist/reflection.service.d.ts +43 -0
  27. package/dist/reflection.service.js +151 -0
  28. package/dist/reflection.service.js.map +1 -0
  29. package/jest.config.js +11 -0
  30. package/package.json +9 -8
  31. package/src/controllers/index.ts +2 -0
  32. package/src/controllers/reflection/v1.base.controller.ts +173 -0
  33. package/src/controllers/reflection/v1.controller.ts +25 -0
  34. package/src/controllers/reflection/v1alpha.controller.ts +25 -0
  35. package/src/grpc.config.ts +7 -0
  36. package/src/grpc.transport.ts +51 -15
  37. package/src/grpc.utils.ts +20 -0
  38. package/src/reflection.module.ts +26 -0
  39. package/src/reflection.service.spec.ts +86 -0
  40. package/src/reflection.service.ts +210 -0
@@ -0,0 +1,86 @@
1
+ import { FileDescriptorProto } from '@bufbuild/protobuf';
2
+ import type { PlatformTestingModule } from '@rsdk/testing';
3
+ import { PlatformTest } from '@rsdk/testing';
4
+
5
+ import { ReflectionService } from './reflection.service';
6
+
7
+ describe('ReflectionService', () => {
8
+ let testingModule: PlatformTestingModule;
9
+ let reflectionService: ReflectionService;
10
+
11
+ beforeAll(async () => {
12
+ const platformTesting = await PlatformTest.create({});
13
+
14
+ testingModule = await platformTesting
15
+ .createTestingModule({
16
+ providers: [
17
+ ReflectionService,
18
+ {
19
+ provide: 'GRPC_PACKAGES',
20
+ useValue: [],
21
+ },
22
+ ],
23
+ })
24
+ .compile();
25
+
26
+ reflectionService = testingModule.get(ReflectionService);
27
+
28
+ reflectionService.onModuleInit();
29
+ });
30
+
31
+ afterAll(async () => {
32
+ await testingModule.close();
33
+ });
34
+
35
+ describe('listServices()', () => {
36
+ it('lists all services', () => {
37
+ const services = reflectionService.listServices();
38
+
39
+ expect(services).toHaveLength(4);
40
+ expect(services).toContain('grpc.health.v1.Health');
41
+ expect(services).toContain('grpc.metrics.v1.Metrics');
42
+ expect(services).toContain('grpc.reflection.v1.ServerReflection');
43
+ expect(services).toContain('grpc.reflection.v1alpha.ServerReflection');
44
+ });
45
+ });
46
+
47
+ describe('fileByFilename()', () => {
48
+ it('finds files with no transitive dependencies', () => {
49
+ const descriptor = FileDescriptorProto.fromBinary(
50
+ reflectionService.fileByName('grpc/health/v1/health.proto'),
51
+ );
52
+
53
+ expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
54
+ });
55
+
56
+ it('errors with no file found', () => {
57
+ expect(() => reflectionService.fileByName('nonexistent.proto')).toThrow(
58
+ 'file: nonexistent.proto not found',
59
+ );
60
+ });
61
+ });
62
+
63
+ describe('fileContainingSymbol()', () => {
64
+ it('finds symbols and returns transitive file dependencies', () => {
65
+ const descriptor = FileDescriptorProto.fromBinary(
66
+ reflectionService.fileBySymbol('grpc.health.v1.HealthCheckRequest'),
67
+ );
68
+
69
+ expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
70
+ });
71
+
72
+ it('resolves references to method types', () => {
73
+ const descriptor = FileDescriptorProto.fromBinary(
74
+ reflectionService.fileBySymbol('grpc.health.v1.Health.Check'),
75
+ );
76
+
77
+ expect(descriptor.name).toEqual('grpc/health/v1/health.proto');
78
+ });
79
+
80
+ it('errors with no symbol found', () => {
81
+ expect(() =>
82
+ reflectionService.fileBySymbol('non.existant.symbol'),
83
+ ).toThrow('symbol: non.existant.symbol not found');
84
+ });
85
+ });
86
+ });
@@ -0,0 +1,210 @@
1
+ import type {
2
+ EnumDescriptorProto,
3
+ MethodDescriptorProto,
4
+ } from '@bufbuild/protobuf';
5
+ import {
6
+ DescriptorProto,
7
+ FileDescriptorProto,
8
+ FileDescriptorSet,
9
+ ServiceDescriptorProto,
10
+ } from '@bufbuild/protobuf';
11
+ import type { OnModuleInit } from '@nestjs/common';
12
+ import { Inject, Injectable } from '@nestjs/common';
13
+ import HealthGrpcPackage from '@rsdk/builtin-contract/dist/grpc.health.v1';
14
+ import MetricsGrpcPackage from '@rsdk/builtin-contract/dist/grpc.metrics.v1';
15
+ import ReflectionGrpcPackageV1 from '@rsdk/builtin-contract/dist/grpc.reflection.v1';
16
+ import ReflectionGrpcPackageV1alpha from '@rsdk/builtin-contract/dist/grpc.reflection.v1alpha';
17
+ import type { Package } from '@rsdk/grpc.loader';
18
+ import { LoggerFactory } from '@rsdk/logging';
19
+
20
+ @Injectable()
21
+ export class ReflectionService implements OnModuleInit {
22
+ private readonly services: Set<string> = new Set();
23
+ private readonly files: FileDescriptorProto[] = [];
24
+ private readonly logger = LoggerFactory.create(ReflectionService);
25
+
26
+ constructor(@Inject('GRPC_PACKAGES') private readonly packages: Package[]) {}
27
+
28
+ onModuleInit(): void {
29
+ const allPackages = [
30
+ ...this.packages,
31
+ HealthGrpcPackage,
32
+ MetricsGrpcPackage,
33
+ ReflectionGrpcPackageV1,
34
+ ReflectionGrpcPackageV1alpha,
35
+ ];
36
+
37
+ const allPackagesNames = new Set(allPackages.map((pkg) => pkg.name));
38
+
39
+ for (const pkg of allPackages) {
40
+ if (pkg.descriptor) {
41
+ for (const service of Object.keys(pkg.definitions['grpc-js'])) {
42
+ this.logger.info(
43
+ `GRPC descriptor loaded for service: ${pkg.name}.${service}`,
44
+ );
45
+ }
46
+
47
+ for (const file of FileDescriptorSet.fromBinary(pkg.descriptor).file) {
48
+ this.files.push(file);
49
+
50
+ if (allPackagesNames.has(`${file.package}`)) {
51
+ for (const service of file.service) {
52
+ this.services.add(`${file.package}.${service.name}`);
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ /**
61
+ * List the full names of registered gRPC services
62
+ *
63
+ * note: the spec is unclear as to what the 'listServices' param can be; most
64
+ * clients seem to only pass '*' but unsure if this should behave like a
65
+ * filter. Until we know how this should behave with different inputs this
66
+ * just always returns *all* services.
67
+ *
68
+ * @returns full-qualified service names (eg. 'sample.SampleService')
69
+ */
70
+ listServices(): string[] {
71
+ return [...this.services];
72
+ }
73
+
74
+ /**
75
+ * Find the proto file(s) that declares the given fully-qualified symbol name
76
+ *
77
+ * @param symbol fully-qualified name of the symbol to lookup
78
+ * (e.g. package.service[.method] or package.type)
79
+ *
80
+ * @returns descriptors of the file which contains this symbol and its imports
81
+ */
82
+ fileBySymbol(symbol: string): Buffer {
83
+ const file = this.files.find((file) => this.findSymbol(symbol, file));
84
+
85
+ if (file) {
86
+ return Buffer.from(file.toBinary());
87
+ }
88
+
89
+ throw new Error(`symbol: ${symbol} not found`);
90
+ }
91
+
92
+ /**
93
+ * Find a proto file by the file name
94
+ * @returns descriptors of the file which contains this symbol and its imports
95
+ */
96
+ fileByName(filename: string): Buffer {
97
+ const file = this.files.find((file) => file.name === filename);
98
+
99
+ if (file) {
100
+ return Buffer.from(file.toBinary());
101
+ }
102
+
103
+ throw new Error(`file: ${filename} not found`);
104
+ }
105
+
106
+ /**
107
+ * Find a proto file containing an extension to a message type
108
+ * @returns descriptors of the file which contains this symbol and its imports
109
+ */
110
+ fileContainingExtension(
111
+ containingType: string,
112
+ extensionNumber: number,
113
+ ): Buffer {
114
+ const extension = this.files.find((file) => {
115
+ const descriptor = this.findSymbol(containingType, file);
116
+
117
+ return (
118
+ descriptor instanceof DescriptorProto &&
119
+ descriptor.extension.some(
120
+ (extension) => extensionNumber === extension.number,
121
+ )
122
+ );
123
+ });
124
+
125
+ if (extension) {
126
+ return Buffer.from(extension.toBinary());
127
+ }
128
+
129
+ throw new Error(
130
+ `containingType: ${containingType}, extensionNumber: ${extensionNumber} not found`,
131
+ );
132
+ }
133
+
134
+ allExtensionNumbersOfType(symbol: string): number[] {
135
+ return [
136
+ ...new Set(
137
+ this.files.flatMap((file) =>
138
+ file.extension.flatMap((_fqn, _file, ext) =>
139
+ ext
140
+ .filter((e) => e.extendee === symbol)
141
+ .flatMap((e) => Number(e.number)),
142
+ ),
143
+ ),
144
+ ),
145
+ ];
146
+ }
147
+
148
+ private findSymbol(
149
+ symbol: string,
150
+ descriptor:
151
+ | FileDescriptorProto
152
+ | DescriptorProto
153
+ | EnumDescriptorProto
154
+ | ServiceDescriptorProto
155
+ | MethodDescriptorProto,
156
+ prefix = '',
157
+ ):
158
+ | DescriptorProto
159
+ | EnumDescriptorProto
160
+ | ServiceDescriptorProto
161
+ | MethodDescriptorProto
162
+ | undefined {
163
+ if (descriptor instanceof FileDescriptorProto) {
164
+ const packageName = descriptor.package;
165
+
166
+ const packagePrefix = packageName ? `${packageName}.` : '';
167
+
168
+ return (
169
+ descriptor.messageType.find((type) =>
170
+ this.findSymbol(symbol, type, packagePrefix),
171
+ ) ??
172
+ descriptor.enumType.find((type) =>
173
+ this.findSymbol(symbol, type, packagePrefix),
174
+ ) ??
175
+ descriptor.service.find((type) =>
176
+ this.findSymbol(symbol, type, packagePrefix),
177
+ )
178
+ );
179
+ }
180
+
181
+ const fullName = prefix + descriptor.name;
182
+
183
+ if (symbol === fullName) {
184
+ return descriptor;
185
+ }
186
+
187
+ if (descriptor instanceof DescriptorProto) {
188
+ const messagePrefix = `${fullName}.`;
189
+
190
+ return (
191
+ descriptor.nestedType.find((type) =>
192
+ this.findSymbol(symbol, type, messagePrefix),
193
+ ) ??
194
+ descriptor.enumType.find((type) =>
195
+ this.findSymbol(symbol, type, messagePrefix),
196
+ )
197
+ );
198
+ }
199
+
200
+ if (descriptor instanceof ServiceDescriptorProto) {
201
+ const servicePrefix = `${fullName}.`;
202
+
203
+ return descriptor.method.find((method) =>
204
+ this.findSymbol(symbol, method, servicePrefix),
205
+ );
206
+ }
207
+
208
+ return undefined;
209
+ }
210
+ }