@nmtjs/protocol 0.12.6 → 0.12.7

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.
@@ -1,601 +0,0 @@
1
- import { type Callback, defer, isAbortError, throwError } from '@nmtjs/common'
2
- import {
3
- type AnyInjectable,
4
- type Container,
5
- Hook,
6
- type Logger,
7
- Scope,
8
- } from '@nmtjs/core'
9
- import { concat, decodeNumber, encodeNumber } from '../common/binary.ts'
10
- import type { ProtocolBlob, ProtocolBlobMetadata } from '../common/blob.ts'
11
- import { ErrorCode, ServerMessageType } from '../common/enums.ts'
12
- import type { ProtocolRPC } from '../common/types.ts'
13
- import {
14
- isIterableResult,
15
- type ProtocolApi,
16
- type ProtocolApiCallOptions,
17
- } from './api.ts'
18
- import {
19
- Connection,
20
- ConnectionContext,
21
- type ConnectionOptions,
22
- } from './connection.ts'
23
- import type { Format } from './format.ts'
24
- import { ProtocolInjectables } from './injectables.ts'
25
- import type { ProtocolRegistry } from './registry.ts'
26
- import { ProtocolClientStream, ProtocolServerStream } from './stream.ts'
27
- import type { Transport } from './transport.ts'
28
- import { getFormat, type ResolveFormatParams } from './utils.ts'
29
-
30
- export class ProtocolError extends Error {
31
- code: string
32
- data?: any
33
-
34
- constructor(code: string, message?: string, data?: any) {
35
- super(message)
36
- this.code = code
37
- this.data = data
38
- }
39
-
40
- get message() {
41
- return `${this.code} ${super.message}`
42
- }
43
-
44
- toString() {
45
- return `${this.code} ${this.message}`
46
- }
47
-
48
- toJSON() {
49
- return {
50
- code: this.code,
51
- message: this.message,
52
- data: this.data,
53
- }
54
- }
55
- }
56
-
57
- export type ProtocolConnectionTransport = {
58
- send: Transport<any>['send']
59
- }
60
-
61
- export class ProtocolConnections {
62
- readonly #collection = new Map<
63
- string,
64
- {
65
- connection: Connection
66
- context: ConnectionContext
67
- transport: ProtocolConnectionTransport
68
- }
69
- >()
70
-
71
- constructor(
72
- private readonly application: {
73
- logger: Logger
74
- registry: ProtocolRegistry
75
- format: Format
76
- container: Container
77
- },
78
- ) {}
79
-
80
- get(connectionId: string) {
81
- const connection = this.#collection.get(connectionId)
82
- if (!connection) throwError('Connection not found')
83
- return connection
84
- }
85
-
86
- async add<T>(
87
- transport: ProtocolConnectionTransport,
88
- options: ConnectionOptions<T>,
89
- params: ResolveFormatParams,
90
- ) {
91
- const connection = new Connection(options)
92
- const format = getFormat(this.application.format, params)
93
- const container = this.application.container.fork(Scope.Connection)
94
- const context = new ConnectionContext(container, format)
95
- container.provide(ProtocolInjectables.connection, connection)
96
- try {
97
- await this.initialize(connection)
98
- this.#collection.set(connection.id, { connection, context, transport })
99
- return { connection, context }
100
- } catch (error) {
101
- container.dispose().catch((error) => {
102
- this.application.logger.error(
103
- { error, connection },
104
- 'Error during disposing connection',
105
- )
106
- })
107
- throw error
108
- }
109
- }
110
-
111
- async remove(connectionId: string) {
112
- const { connection, context } = this.get(connectionId)
113
-
114
- this.application.registry.hooks.call(
115
- Hook.OnDisconnect,
116
- { concurrent: true },
117
- connection,
118
- )
119
-
120
- this.#collection.delete(connectionId)
121
-
122
- const { rpcs, serverStreams, clientStreams, rpcStreams, container } =
123
- context
124
-
125
- for (const call of rpcs.values()) {
126
- call.abort(new Error('Connection closed'))
127
- }
128
-
129
- for (const stream of clientStreams.values()) {
130
- stream.destroy(new Error('Connection closed'))
131
- }
132
-
133
- for (const stream of serverStreams.values()) {
134
- stream.destroy(new Error('Connection closed'))
135
- }
136
-
137
- for (const stream of rpcStreams.values()) {
138
- stream.abort(new Error('Connection closed'))
139
- }
140
-
141
- try {
142
- await container.dispose()
143
- } catch (error) {
144
- this.application.logger.error(
145
- { error, connection },
146
- 'Error during closing connection',
147
- )
148
- }
149
- }
150
-
151
- async initialize(connection: Connection) {
152
- await this.application.registry.hooks.call(
153
- Hook.OnConnect,
154
- { concurrent: false },
155
- connection,
156
- )
157
- }
158
- }
159
-
160
- export class ProtocolClientStreams {
161
- constructor(private readonly connections: ProtocolConnections) {}
162
-
163
- get(connectionId: string, streamId: number) {
164
- const { context } = this.connections.get(connectionId)
165
- const { clientStreams } = context
166
- const stream = clientStreams.get(streamId) ?? throwError('Stream not found')
167
- return stream
168
- }
169
-
170
- remove(connectionId: string, streamId: number) {
171
- const { context } = this.connections.get(connectionId)
172
- const { clientStreams } = context
173
- clientStreams.get(streamId) || throwError('Stream not found')
174
- clientStreams.delete(streamId)
175
- }
176
-
177
- add(
178
- connectionId: string,
179
- streamId: number,
180
- metadata: ProtocolBlobMetadata,
181
- read: Callback,
182
- ) {
183
- const { context } = this.connections.get(connectionId)
184
- const { clientStreams } = context
185
- const stream = new ProtocolClientStream(streamId, metadata, { read })
186
- clientStreams.set(streamId, stream)
187
- return stream
188
- }
189
-
190
- push(connectionId: string, streamId: number, chunk: ArrayBuffer) {
191
- const stream = this.get(connectionId, streamId)
192
- stream.write(Buffer.from(chunk))
193
- }
194
-
195
- end(connectionId: string, streamId: number) {
196
- const stream = this.get(connectionId, streamId)
197
- stream.end(null)
198
- this.remove(connectionId, streamId)
199
- }
200
-
201
- abort(connectionId: string, streamId: number, error = new Error('Aborted')) {
202
- const stream = this.get(connectionId, streamId)
203
- stream.destroy(error)
204
- this.remove(connectionId, streamId)
205
- }
206
- }
207
-
208
- export class ProtocolServerStreams {
209
- constructor(private readonly connections: ProtocolConnections) {}
210
-
211
- get(connectionId: string, streamId: number) {
212
- const { context } = this.connections.get(connectionId)
213
- const { serverStreams } = context
214
- const stream = serverStreams.get(streamId) ?? throwError('Stream not found')
215
- return stream
216
- }
217
-
218
- add(connectionId: string, streamId: number, blob: ProtocolBlob) {
219
- const { context } = this.connections.get(connectionId)
220
- const { serverStreams } = context
221
- const stream = new ProtocolServerStream(streamId, blob)
222
- serverStreams.set(streamId, stream)
223
- return stream
224
- }
225
-
226
- remove(connectionId: string, streamId: number) {
227
- const { context } = this.connections.get(connectionId)
228
- const { serverStreams } = context
229
- serverStreams.has(streamId) || throwError('Stream not found')
230
- serverStreams.delete(streamId)
231
- }
232
-
233
- pull(connectionId: string, streamId: number) {
234
- const stream = this.get(connectionId, streamId)
235
- stream.resume()
236
- }
237
-
238
- abort(connectionId: string, streamId: number, error = new Error('Aborted')) {
239
- const stream = this.get(connectionId, streamId)
240
- stream.destroy(error)
241
- this.remove(connectionId, streamId)
242
- }
243
- }
244
-
245
- export type ProtocolRPCOptions = {
246
- signal?: AbortSignal
247
- provides?: [AnyInjectable, any][]
248
- metadata?: ProtocolApiCallOptions['metadata']
249
- }
250
-
251
- export class Protocol {
252
- #connections: ProtocolConnections
253
- #clientStreams: ProtocolClientStreams
254
- #serverStreams: ProtocolServerStreams
255
-
256
- constructor(
257
- protected readonly application: {
258
- logger: Logger
259
- format: Format
260
- container: Container
261
- registry: ProtocolRegistry
262
- api: ProtocolApi
263
- },
264
- ) {
265
- this.#connections = new ProtocolConnections(this.application)
266
- this.#clientStreams = new ProtocolClientStreams(this.#connections)
267
- this.#serverStreams = new ProtocolServerStreams(this.#connections)
268
- }
269
-
270
- async call(options: ProtocolApiCallOptions) {
271
- const { container, connection } = options
272
- try {
273
- return await this.application.api.call(options)
274
- } catch (error) {
275
- if (error instanceof ProtocolError === false) {
276
- this.application.logger.error(
277
- { error, connection },
278
- 'Error during RPC call',
279
- )
280
- throw new ProtocolError(
281
- ErrorCode.InternalServerError,
282
- 'Internal server error',
283
- )
284
- }
285
- throw error
286
- } finally {
287
- container.dispose().catch((error) => {
288
- this.application.logger.error(
289
- { error, connection },
290
- "Error during disposing connection's container",
291
- )
292
- })
293
- }
294
- }
295
-
296
- async rpc(
297
- connectionId: string,
298
- rpc: ProtocolRPC,
299
- params: ProtocolRPCOptions = {},
300
- ) {
301
- const { connection, context, transport } =
302
- this.#connections.get(connectionId)
303
- const { rpcs, format } = context
304
- const { callId, namespace, procedure, payload } = rpc
305
- const abortController = new AbortController()
306
- const signal = params.signal
307
- ? AbortSignal.any([params.signal, abortController.signal])
308
- : abortController.signal
309
-
310
- rpcs.set(callId, abortController)
311
-
312
- const callIdEncoded = encodeNumber(callId, 'Uint32')
313
- const container = context.container.fork(Scope.Call)
314
-
315
- if (params.provides) {
316
- for (const [key, value] of params.provides) {
317
- container.provide(key, value)
318
- }
319
- }
320
-
321
- try {
322
- const response = await this.call({
323
- connection,
324
- container,
325
- namespace,
326
- payload,
327
- procedure,
328
- signal,
329
- metadata: params.metadata,
330
- })
331
-
332
- const responseEncoded = format.encoder.encodeRPC(
333
- {
334
- callId,
335
- result: response.output,
336
- },
337
- {
338
- addStream: (blob) => {
339
- const streamId = context.streamId++
340
- const stream = this.#serverStreams.add(connectionId, streamId, blob)
341
- stream.on('data', (chunk) => {
342
- stream.pause()
343
- const buf = Buffer.from(chunk)
344
- transport.send(
345
- connection,
346
- ServerMessageType.ServerStreamPush,
347
- concat(
348
- encodeNumber(streamId, 'Uint32'),
349
- (buf.buffer as ArrayBuffer).slice(
350
- buf.byteOffset,
351
- buf.byteOffset + buf.byteLength,
352
- ),
353
- ),
354
- { callId, streamId },
355
- )
356
- })
357
- stream.on('error', (err) => {
358
- transport.send(
359
- connection,
360
- ServerMessageType.ServerStreamAbort,
361
- encodeNumber(streamId, 'Uint32'),
362
- { callId, streamId },
363
- )
364
- })
365
- stream.on('end', () => {
366
- transport.send(
367
- connection,
368
- ServerMessageType.ServerStreamEnd,
369
- encodeNumber(streamId, 'Uint32'),
370
- { callId, streamId },
371
- )
372
- })
373
- return stream
374
- },
375
- getStream: (id) => {
376
- return this.#serverStreams.get(connectionId, id)
377
- },
378
- },
379
- )
380
-
381
- if (isIterableResult(response)) {
382
- transport.send(
383
- connection,
384
- ServerMessageType.RpcStreamResponse,
385
- responseEncoded,
386
- { callId },
387
- )
388
- try {
389
- const controller = new AbortController()
390
- context.rpcStreams.set(callId, controller)
391
- const iterable =
392
- typeof response.iterable === 'function'
393
- ? await response.iterable(controller.signal)
394
- : response.iterable
395
- try {
396
- for await (const chunk of iterable) {
397
- controller.signal.throwIfAborted()
398
- const chunkEncoded = format.encoder.encode(chunk)
399
- transport.send(
400
- connection,
401
- ServerMessageType.RpcStreamChunk,
402
- concat(callIdEncoded, chunkEncoded),
403
- { callId },
404
- )
405
- }
406
- transport.send(
407
- connection,
408
- ServerMessageType.RpcStreamEnd,
409
- callIdEncoded,
410
- { callId },
411
- )
412
- } catch (error) {
413
- // do not re-throw AbortError errors, they are expected
414
- if (!isAbortError(error)) {
415
- throw error
416
- }
417
- }
418
- } catch (error) {
419
- this.application.logger.error(error)
420
- transport.send(
421
- connection,
422
- ServerMessageType.RpcStreamAbort,
423
- callIdEncoded,
424
- { callId },
425
- )
426
- } finally {
427
- context.rpcStreams.delete(callId)
428
- response.onFinish && defer(response.onFinish)
429
- }
430
- } else {
431
- transport.send(
432
- connection,
433
- ServerMessageType.RpcResponse,
434
- responseEncoded,
435
- { callId },
436
- )
437
- }
438
- } catch (error) {
439
- const payload = format.encoder.encodeRPC(
440
- { callId, error },
441
- {
442
- addStream(blob) {
443
- throwError('Cannot handle stream for error response')
444
- },
445
- getStream(id) {
446
- throwError('Cannot handle stream for error response')
447
- },
448
- },
449
- )
450
-
451
- transport.send(connection, ServerMessageType.RpcResponse, payload, {
452
- error,
453
- callId,
454
- })
455
- } finally {
456
- rpcs.delete(callId)
457
- container.dispose().catch((error) => {
458
- this.application.logger.error(
459
- { error, connection },
460
- 'Error during disposing connection',
461
- )
462
- })
463
- }
464
- }
465
-
466
- async rpcRaw(
467
- connectionId: string,
468
- buffer: ArrayBuffer,
469
- params: ProtocolRPCOptions = {},
470
- ) {
471
- const { connection, context, transport } =
472
- this.#connections.get(connectionId)
473
-
474
- const { format } = context
475
-
476
- const rpc = format.decoder.decodeRPC(buffer, {
477
- addStream: (streamId, callId, metadata) => {
478
- return this.#clientStreams.add(
479
- connectionId,
480
- streamId,
481
- metadata,
482
- (size) => {
483
- transport.send(
484
- connection,
485
- ServerMessageType.ClientStreamPull,
486
- concat(
487
- encodeNumber(streamId, 'Uint32'),
488
- encodeNumber(size, 'Uint32'),
489
- ),
490
- { callId, streamId },
491
- )
492
- },
493
- )
494
- },
495
- getStream: (id) => {
496
- return this.#clientStreams.get(connectionId, id)
497
- },
498
- })
499
-
500
- return await this.rpc(connectionId, rpc, params)
501
- }
502
-
503
- rpcAbort(connectionId: string, callId: number) {
504
- const { context } = this.#connections.get(connectionId)
505
- const call = context.rpcs.get(callId) ?? throwError('Call not found')
506
- call.abort()
507
- }
508
-
509
- rpcAbortRaw(connectionId: string, buffer: ArrayBuffer) {
510
- const callId = decodeNumber(buffer, 'Uint32')
511
- return this.rpcAbort(connectionId, callId)
512
- }
513
-
514
- rpcStreamAbort(connectionId: string, callId: number) {
515
- const { context } = this.#connections.get(connectionId)
516
- const ab =
517
- context.rpcStreams.get(callId) ?? throwError('Call stream not found')
518
- ab.abort()
519
- }
520
-
521
- rpcStreamAbortRaw(connectionId: string, buffer: ArrayBuffer) {
522
- const callId = decodeNumber(buffer, 'Uint32')
523
- return this.rpcStreamAbort(connectionId, callId)
524
- }
525
-
526
- notify(connectionId: string, event, payload) {
527
- throw Error('Unimplemented')
528
- }
529
-
530
- addConnection(
531
- transport: ProtocolConnectionTransport,
532
- options: ConnectionOptions,
533
- params: ResolveFormatParams,
534
- ) {
535
- return this.#connections.add(transport, options, params)
536
- }
537
-
538
- removeConnection(connectionId: string) {
539
- return this.#connections.remove(connectionId)
540
- }
541
-
542
- getConnection(connectionId: string) {
543
- return this.#connections.get(connectionId)
544
- }
545
-
546
- initializeConnection(connection: Connection) {
547
- return this.#connections.initialize(connection)
548
- }
549
-
550
- getClientStream(
551
- connectionId: string,
552
- streamId: number,
553
- ): ProtocolClientStream {
554
- return this.#clientStreams.get(connectionId, streamId)
555
- }
556
-
557
- addClientStream(
558
- connectionId: string,
559
- streamId: number,
560
- metadata: ProtocolBlobMetadata,
561
- read: Callback,
562
- ) {
563
- return this.#clientStreams.add(connectionId, streamId, metadata, read)
564
- }
565
-
566
- removeClientStream(connectionId: string, streamId: number) {
567
- return this.#clientStreams.remove(connectionId, streamId)
568
- }
569
-
570
- pushClientStream(connectionId: string, streamId: number, chunk: ArrayBuffer) {
571
- return this.#clientStreams.push(connectionId, streamId, chunk)
572
- }
573
-
574
- endClientStream(connectionId: string, streamId: number) {
575
- return this.#clientStreams.end(connectionId, streamId)
576
- }
577
-
578
- abortClientStream(connectionId: string, streamId: number, error?: Error) {
579
- return this.#clientStreams.abort(connectionId, streamId, error)
580
- }
581
-
582
- getServerStream(connectionId: string, streamId: number) {
583
- return this.#serverStreams.get(connectionId, streamId)
584
- }
585
-
586
- addServerStream(connectionId: string, streamId: number, blob: ProtocolBlob) {
587
- return this.#serverStreams.add(connectionId, streamId, blob)
588
- }
589
-
590
- removeServerStream(connectionId: string, streamId: number) {
591
- return this.#serverStreams.remove(connectionId, streamId)
592
- }
593
-
594
- pullServerStream(connectionId: string, streamId: number) {
595
- return this.#serverStreams.pull(connectionId, streamId)
596
- }
597
-
598
- abortServerStream(connectionId: string, streamId: number, error?: Error) {
599
- return this.#serverStreams.abort(connectionId, streamId, error)
600
- }
601
- }
@@ -1,3 +0,0 @@
1
- import { Registry } from '@nmtjs/core'
2
-
3
- export class ProtocolRegistry extends Registry {}
@@ -1,38 +0,0 @@
1
- import { PassThrough, Readable, type ReadableOptions } from 'node:stream'
2
- import { ReadableStream } from 'node:stream/web'
3
- import type { ProtocolBlob, ProtocolBlobMetadata } from '../common/blob.ts'
4
-
5
- export class ProtocolClientStream extends PassThrough {
6
- constructor(
7
- public readonly id: number,
8
- public readonly metadata: ProtocolBlobMetadata,
9
- options?: ReadableOptions,
10
- ) {
11
- super(options)
12
- }
13
- }
14
-
15
- export class ProtocolServerStream extends PassThrough {
16
- public readonly id: number
17
- public readonly metadata: ProtocolBlobMetadata
18
-
19
- constructor(id: number, blob: ProtocolBlob) {
20
- let readable: Readable
21
-
22
- if (blob.source instanceof Readable) {
23
- readable = blob.source
24
- } else if (blob.source instanceof ReadableStream) {
25
- readable = Readable.fromWeb(blob.source as ReadableStream)
26
- } else {
27
- throw new Error('Invalid source type')
28
- }
29
-
30
- super()
31
-
32
- this.pause()
33
- readable.pipe(this)
34
-
35
- this.id = id
36
- this.metadata = blob.metadata
37
- }
38
- }
@@ -1,39 +0,0 @@
1
- import type { BasePlugin, PluginContext } from '@nmtjs/core'
2
- import type { ServerMessageType } from '../common/enums.ts'
3
- import type { Connection } from './connection.ts'
4
- import { kTransportPlugin } from './constants.ts'
5
- import type { Format } from './format.ts'
6
- import type { Protocol } from './protocol.ts'
7
- import type { ProtocolRegistry } from './registry.ts'
8
- import type { ProtocolSendMetadata } from './types.ts'
9
-
10
- export interface Transport<T = unknown> {
11
- start: () => Promise<void>
12
- stop: () => Promise<void>
13
- send: (
14
- connection: Connection<T>,
15
- messageType: ServerMessageType,
16
- buffer: ArrayBuffer,
17
- metadata: ProtocolSendMetadata,
18
- ) => any
19
- }
20
-
21
- export interface TransportPluginContext extends PluginContext {
22
- protocol: Protocol
23
- registry: ProtocolRegistry
24
- format: Format
25
- }
26
-
27
- export interface TransportPlugin<Type = unknown, Options = unknown>
28
- extends BasePlugin<Transport<Type>, Options, TransportPluginContext> {
29
- [kTransportPlugin]: any
30
- }
31
-
32
- export const createTransport = <Type = unknown, Options = unknown>(
33
- name: string,
34
- init: TransportPlugin<Type, Options>['init'],
35
- ): TransportPlugin<Type, Options> => ({ name, init, [kTransportPlugin]: true })
36
-
37
- export const isTransportPlugin = (
38
- plugin: BasePlugin<any, any, any>,
39
- ): plugin is TransportPlugin => kTransportPlugin in plugin
@@ -1,20 +0,0 @@
1
- import type { ProtocolBlob, ProtocolBlobInterface } from '../common/blob.ts'
2
- import type { ProtocolClientStream } from './stream.ts'
3
-
4
- export type InputType<T> = T extends ProtocolBlobInterface
5
- ? ProtocolClientStream
6
- : T extends object
7
- ? { [K in keyof T]: InputType<T[K]> }
8
- : T
9
-
10
- export type OutputType<T> = T extends ProtocolBlobInterface
11
- ? ProtocolBlob
12
- : T extends object
13
- ? { [K in keyof T]: OutputType<T[K]> }
14
- : T
15
-
16
- export type ProtocolSendMetadata = {
17
- streamId?: number
18
- callId?: number
19
- error?: any
20
- }