@nmtjs/protocol 0.6.0
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/LICENSE.md +7 -0
- package/README.md +9 -0
- package/dist/client/events.js +41 -0
- package/dist/client/events.js.map +1 -0
- package/dist/client/format.js +2 -0
- package/dist/client/format.js.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/protocol.js +311 -0
- package/dist/client/protocol.js.map +1 -0
- package/dist/client/stream.js +99 -0
- package/dist/client/stream.js.map +1 -0
- package/dist/common/binary.js +25 -0
- package/dist/common/binary.js.map +1 -0
- package/dist/common/blob.js +42 -0
- package/dist/common/blob.js.map +1 -0
- package/dist/common/enums.js +44 -0
- package/dist/common/enums.js.map +1 -0
- package/dist/common/index.js +4 -0
- package/dist/common/index.js.map +1 -0
- package/dist/common/types.js +1 -0
- package/dist/common/types.js.map +1 -0
- package/dist/server/api.js +1 -0
- package/dist/server/api.js.map +1 -0
- package/dist/server/connection.js +21 -0
- package/dist/server/connection.js.map +1 -0
- package/dist/server/constants.js +1 -0
- package/dist/server/constants.js.map +1 -0
- package/dist/server/format.js +48 -0
- package/dist/server/format.js.map +1 -0
- package/dist/server/index.js +10 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/injectables.js +22 -0
- package/dist/server/injectables.js.map +1 -0
- package/dist/server/protocol.js +293 -0
- package/dist/server/protocol.js.map +1 -0
- package/dist/server/registry.js +19 -0
- package/dist/server/registry.js.map +1 -0
- package/dist/server/stream.js +30 -0
- package/dist/server/stream.js.map +1 -0
- package/dist/server/transport.js +7 -0
- package/dist/server/transport.js.map +1 -0
- package/dist/server/utils.js +10 -0
- package/dist/server/utils.js.map +1 -0
- package/lib/client/events.ts +66 -0
- package/lib/client/format.ts +22 -0
- package/lib/client/index.ts +4 -0
- package/lib/client/protocol.ts +440 -0
- package/lib/client/stream.ts +116 -0
- package/lib/common/binary.ts +60 -0
- package/lib/common/blob.ts +70 -0
- package/lib/common/enums.ts +46 -0
- package/lib/common/index.ts +4 -0
- package/lib/common/types.ts +64 -0
- package/lib/server/api.ts +47 -0
- package/lib/server/connection.ts +57 -0
- package/lib/server/constants.ts +4 -0
- package/lib/server/format.ts +107 -0
- package/lib/server/index.ts +10 -0
- package/lib/server/injectables.ts +51 -0
- package/lib/server/protocol.ts +422 -0
- package/lib/server/registry.ts +24 -0
- package/lib/server/stream.ts +43 -0
- package/lib/server/transport.ts +36 -0
- package/lib/server/utils.ts +22 -0
- package/package.json +39 -0
- package/tsconfig.json +3 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
import { type Callback, createPromise, defer, throwError } from '@nmtjs/common'
|
|
2
|
+
import { type Container, Hook, type Logger, Scope } from '@nmtjs/core'
|
|
3
|
+
import { concat, decodeNumber, encodeNumber } from '../common/binary.ts'
|
|
4
|
+
import type { ProtocolBlob, ProtocolBlobMetadata } from '../common/blob.ts'
|
|
5
|
+
import { ErrorCode, ServerMessageType } from '../common/enums.ts'
|
|
6
|
+
import type { ProtocolRPC } from '../common/types.ts'
|
|
7
|
+
import type { ProtocolApi, ProtocolApiCallResult } from './api.ts'
|
|
8
|
+
import {
|
|
9
|
+
Connection,
|
|
10
|
+
ConnectionContext,
|
|
11
|
+
type ConnectionOptions,
|
|
12
|
+
} from './connection.ts'
|
|
13
|
+
import type { Format } from './format.ts'
|
|
14
|
+
import type { ProtocolRegistry } from './registry.ts'
|
|
15
|
+
import { ProtocolClientStream, ProtocolServerStream } from './stream.ts'
|
|
16
|
+
import type { Transport } from './transport.ts'
|
|
17
|
+
import { type ResolveFormatParams, getFormat } from './utils.ts'
|
|
18
|
+
|
|
19
|
+
export class ProtocolError extends Error {
|
|
20
|
+
code: string
|
|
21
|
+
data?: any
|
|
22
|
+
|
|
23
|
+
constructor(code: string, message?: string, data?: any) {
|
|
24
|
+
super(message)
|
|
25
|
+
this.code = code
|
|
26
|
+
this.data = data
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
get message() {
|
|
30
|
+
return `${this.code} ${super.message}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
toString() {
|
|
34
|
+
return `${this.code} ${this.message}`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
toJSON() {
|
|
38
|
+
return {
|
|
39
|
+
code: this.code,
|
|
40
|
+
message: this.message,
|
|
41
|
+
data: this.data,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export class ProtocolConnections {
|
|
47
|
+
readonly #collection = new Map<
|
|
48
|
+
string,
|
|
49
|
+
{
|
|
50
|
+
connection: Connection
|
|
51
|
+
context: ConnectionContext
|
|
52
|
+
transport: Transport
|
|
53
|
+
}
|
|
54
|
+
>()
|
|
55
|
+
|
|
56
|
+
constructor(
|
|
57
|
+
private readonly application: {
|
|
58
|
+
logger: Logger
|
|
59
|
+
registry: ProtocolRegistry
|
|
60
|
+
format: Format
|
|
61
|
+
container: Container
|
|
62
|
+
},
|
|
63
|
+
) {}
|
|
64
|
+
|
|
65
|
+
get(connectionId: string) {
|
|
66
|
+
const connection = this.#collection.get(connectionId)
|
|
67
|
+
if (!connection) throwError('Connection not found')
|
|
68
|
+
return connection
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async add<T>(
|
|
72
|
+
transport: Transport<any>,
|
|
73
|
+
options: ConnectionOptions<T>,
|
|
74
|
+
params: ResolveFormatParams,
|
|
75
|
+
) {
|
|
76
|
+
const connection = new Connection(options)
|
|
77
|
+
const format = getFormat(this.application.format, params)
|
|
78
|
+
const container = this.application.container.fork(Scope.Connection)
|
|
79
|
+
const context = new ConnectionContext(container, format)
|
|
80
|
+
|
|
81
|
+
this.#collection.set(connection.id, { connection, context, transport })
|
|
82
|
+
|
|
83
|
+
await this.application.registry.hooks.call(
|
|
84
|
+
Hook.OnConnect,
|
|
85
|
+
{ concurrent: false },
|
|
86
|
+
connection,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
return { connection, context }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async remove(connectionId: string) {
|
|
93
|
+
const { connection, context } = this.get(connectionId)
|
|
94
|
+
|
|
95
|
+
this.application.registry.hooks.call(
|
|
96
|
+
Hook.OnDisconnect,
|
|
97
|
+
{ concurrent: true },
|
|
98
|
+
connection,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
this.#collection.delete(connectionId)
|
|
102
|
+
|
|
103
|
+
const { calls, serverStreams, clientStreams, container } = context
|
|
104
|
+
|
|
105
|
+
for (const call of calls.values()) {
|
|
106
|
+
call.reject(new Error('Connection closed'))
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const stream of clientStreams.values()) {
|
|
110
|
+
stream.destroy(new Error('Connection closed'))
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const stream of serverStreams.values()) {
|
|
114
|
+
stream.destroy(new Error('Connection closed'))
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
await container.dispose()
|
|
119
|
+
} catch (error) {
|
|
120
|
+
this.application.logger.error(
|
|
121
|
+
{ error, connection },
|
|
122
|
+
'Error during closing connection',
|
|
123
|
+
)
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export class ProtocolClientStreams {
|
|
129
|
+
constructor(private readonly connections: ProtocolConnections) {}
|
|
130
|
+
|
|
131
|
+
get(connectionId: string, streamId: number) {
|
|
132
|
+
const { context } = this.connections.get(connectionId)
|
|
133
|
+
const { clientStreams } = context
|
|
134
|
+
const stream = clientStreams.get(streamId) ?? throwError('Stream not found')
|
|
135
|
+
return stream
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
remove(connectionId: string, streamId: number) {
|
|
139
|
+
const { context } = this.connections.get(connectionId)
|
|
140
|
+
const { clientStreams } = context
|
|
141
|
+
clientStreams.get(streamId) || throwError('Stream not found')
|
|
142
|
+
clientStreams.delete(streamId)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
add(
|
|
146
|
+
connectionId: string,
|
|
147
|
+
streamId: number,
|
|
148
|
+
metadata: ProtocolBlobMetadata,
|
|
149
|
+
read: Callback,
|
|
150
|
+
) {
|
|
151
|
+
const { context } = this.connections.get(connectionId)
|
|
152
|
+
const { clientStreams } = context
|
|
153
|
+
const stream = new ProtocolClientStream(streamId, metadata, { read })
|
|
154
|
+
clientStreams.set(streamId, stream)
|
|
155
|
+
return stream
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
push(connectionId: string, streamId: number, chunk: ArrayBuffer) {
|
|
159
|
+
const stream = this.get(connectionId, streamId)
|
|
160
|
+
stream.push(Buffer.from(chunk))
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
end(connectionId: string, streamId: number) {
|
|
164
|
+
const stream = this.get(connectionId, streamId)
|
|
165
|
+
stream.push(null)
|
|
166
|
+
this.remove(connectionId, streamId)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
abort(connectionId: string, streamId: number, error = new Error('Aborted')) {
|
|
170
|
+
const stream = this.get(connectionId, streamId)
|
|
171
|
+
stream.destroy(error)
|
|
172
|
+
this.remove(connectionId, streamId)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export class ProtocolServerStreams {
|
|
177
|
+
constructor(private readonly connections: ProtocolConnections) {}
|
|
178
|
+
|
|
179
|
+
get(connectionId: string, streamId: number) {
|
|
180
|
+
const { context } = this.connections.get(connectionId)
|
|
181
|
+
const { serverStreams } = context
|
|
182
|
+
const stream = serverStreams.get(streamId) ?? throwError('Stream not found')
|
|
183
|
+
return stream
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
add(connectionId: string, streamId: number, blob: ProtocolBlob) {
|
|
187
|
+
const { context } = this.connections.get(connectionId)
|
|
188
|
+
const { serverStreams } = context
|
|
189
|
+
const stream = new ProtocolServerStream(streamId, blob)
|
|
190
|
+
serverStreams.set(streamId, stream)
|
|
191
|
+
return stream
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
remove(connectionId: string, streamId: number) {
|
|
195
|
+
const { context } = this.connections.get(connectionId)
|
|
196
|
+
const { serverStreams } = context
|
|
197
|
+
serverStreams.has(streamId) || throwError('Stream not found')
|
|
198
|
+
serverStreams.delete(streamId)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
pull(connectionId: string, streamId: number) {
|
|
202
|
+
const stream = this.get(connectionId, streamId)
|
|
203
|
+
stream.resume()
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
abort(connectionId: string, streamId: number, error = new Error('Aborted')) {
|
|
207
|
+
const stream = this.get(connectionId, streamId)
|
|
208
|
+
stream.destroy(error)
|
|
209
|
+
this.remove(connectionId, streamId)
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export class Protocol {
|
|
214
|
+
readonly connections: ProtocolConnections
|
|
215
|
+
readonly clientStreams: ProtocolClientStreams
|
|
216
|
+
readonly serverStreams: ProtocolServerStreams
|
|
217
|
+
|
|
218
|
+
constructor(
|
|
219
|
+
protected readonly application: {
|
|
220
|
+
logger: Logger
|
|
221
|
+
format: Format
|
|
222
|
+
container: Container
|
|
223
|
+
registry: ProtocolRegistry
|
|
224
|
+
api: ProtocolApi
|
|
225
|
+
},
|
|
226
|
+
) {
|
|
227
|
+
this.connections = new ProtocolConnections(this.application)
|
|
228
|
+
this.clientStreams = new ProtocolClientStreams(this.connections)
|
|
229
|
+
this.serverStreams = new ProtocolServerStreams(this.connections)
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async call(
|
|
233
|
+
connectionId: string,
|
|
234
|
+
rpc: ProtocolRPC,
|
|
235
|
+
params: { signal?: AbortSignal } = {},
|
|
236
|
+
) {
|
|
237
|
+
const { connection, context, transport } =
|
|
238
|
+
this.connections.get(connectionId)
|
|
239
|
+
const { calls, format } = context
|
|
240
|
+
const { callId, namespace, procedure, payload } = rpc
|
|
241
|
+
const abortController = new AbortController()
|
|
242
|
+
const signal = params.signal
|
|
243
|
+
? AbortSignal.any([params.signal, abortController.signal])
|
|
244
|
+
: abortController.signal
|
|
245
|
+
|
|
246
|
+
const call = Object.assign(createPromise<ProtocolApiCallResult>(), {
|
|
247
|
+
abort: () => abortController.abort(),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
calls.set(callId, call)
|
|
251
|
+
call.promise.finally(() => calls.delete(callId))
|
|
252
|
+
|
|
253
|
+
const callIdEncoded = encodeNumber(callId, 'Uint32')
|
|
254
|
+
const container = context.container.fork(Scope.Call)
|
|
255
|
+
|
|
256
|
+
try {
|
|
257
|
+
const response = await this.application.api.call({
|
|
258
|
+
connection,
|
|
259
|
+
container,
|
|
260
|
+
namespace,
|
|
261
|
+
payload,
|
|
262
|
+
procedure,
|
|
263
|
+
signal,
|
|
264
|
+
})
|
|
265
|
+
|
|
266
|
+
const responseEncoded = format.encoder.encodeRPC(
|
|
267
|
+
{
|
|
268
|
+
callId,
|
|
269
|
+
payload: response.output,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
addStream: (blob) => {
|
|
273
|
+
const id = context.streamId++
|
|
274
|
+
const stream = this.serverStreams.add(connectionId, id, blob)
|
|
275
|
+
stream.on('data', (chunk) => {
|
|
276
|
+
stream.pause()
|
|
277
|
+
transport.send(
|
|
278
|
+
connection,
|
|
279
|
+
ServerMessageType.ServerStreamPush,
|
|
280
|
+
concat(
|
|
281
|
+
encodeNumber(id, 'Uint32'),
|
|
282
|
+
Buffer.from(chunk).buffer as ArrayBuffer,
|
|
283
|
+
),
|
|
284
|
+
)
|
|
285
|
+
})
|
|
286
|
+
stream.on('error', (err) => {
|
|
287
|
+
transport.send(
|
|
288
|
+
connection,
|
|
289
|
+
ServerMessageType.ServerStreamAbort,
|
|
290
|
+
encodeNumber(id, 'Uint32'),
|
|
291
|
+
)
|
|
292
|
+
})
|
|
293
|
+
stream.on('end', () => {
|
|
294
|
+
transport.send(
|
|
295
|
+
connection,
|
|
296
|
+
ServerMessageType.ServerStreamEnd,
|
|
297
|
+
encodeNumber(id, 'Uint32'),
|
|
298
|
+
)
|
|
299
|
+
})
|
|
300
|
+
return stream
|
|
301
|
+
},
|
|
302
|
+
getStream: (id) => {
|
|
303
|
+
return this.clientStreams.get(connectionId, id)
|
|
304
|
+
},
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
if ('subscription' in response) {
|
|
309
|
+
throwError('Unimplemented')
|
|
310
|
+
} else if ('iterable' in response) {
|
|
311
|
+
transport.send(
|
|
312
|
+
connection,
|
|
313
|
+
ServerMessageType.RpcStreamResponse,
|
|
314
|
+
responseEncoded,
|
|
315
|
+
)
|
|
316
|
+
try {
|
|
317
|
+
const iterable =
|
|
318
|
+
typeof response.iterable === 'function'
|
|
319
|
+
? response.iterable()
|
|
320
|
+
: response.iterable
|
|
321
|
+
for await (const chunk of iterable) {
|
|
322
|
+
const chunkEncoded = format.encoder.encode(chunk)
|
|
323
|
+
transport.send(
|
|
324
|
+
connection,
|
|
325
|
+
ServerMessageType.RpcStreamChunk,
|
|
326
|
+
concat(callIdEncoded, chunkEncoded),
|
|
327
|
+
)
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
this.application.logger.error(error)
|
|
331
|
+
transport.send(
|
|
332
|
+
connection,
|
|
333
|
+
ServerMessageType.RpcStreamAbort,
|
|
334
|
+
callIdEncoded,
|
|
335
|
+
)
|
|
336
|
+
}
|
|
337
|
+
} else {
|
|
338
|
+
transport.send(
|
|
339
|
+
connection,
|
|
340
|
+
ServerMessageType.RpcResponse,
|
|
341
|
+
responseEncoded,
|
|
342
|
+
)
|
|
343
|
+
}
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (error instanceof ProtocolError === false) {
|
|
346
|
+
this.application.logger.error(
|
|
347
|
+
{ error, connection },
|
|
348
|
+
'Error during RPC call',
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
// biome-ignore lint/suspicious/noCatchAssign:
|
|
352
|
+
error = new ProtocolError(
|
|
353
|
+
ErrorCode.InternalServerError,
|
|
354
|
+
'Internal server error',
|
|
355
|
+
)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const payload = format.encoder.encodeRPC(
|
|
359
|
+
{ callId, error },
|
|
360
|
+
{
|
|
361
|
+
addStream(blob) {
|
|
362
|
+
throwError('Cannot handle stream for error response')
|
|
363
|
+
},
|
|
364
|
+
getStream(id) {
|
|
365
|
+
throwError('Cannot handle stream for error response')
|
|
366
|
+
},
|
|
367
|
+
},
|
|
368
|
+
)
|
|
369
|
+
transport.send(connection, ServerMessageType.RpcResponse, payload)
|
|
370
|
+
} finally {
|
|
371
|
+
container.dispose().catch((error) => {
|
|
372
|
+
this.application.logger.error(
|
|
373
|
+
{ error, connection },
|
|
374
|
+
"Error during disposing connection's container",
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async callRaw(
|
|
381
|
+
connectionId: string,
|
|
382
|
+
buffer: ArrayBuffer,
|
|
383
|
+
params: { signal?: AbortSignal } = {},
|
|
384
|
+
) {
|
|
385
|
+
const { connection, context, transport } =
|
|
386
|
+
this.connections.get(connectionId)
|
|
387
|
+
|
|
388
|
+
const { format } = context
|
|
389
|
+
|
|
390
|
+
const rpc = format.decoder.decodeRPC(buffer, {
|
|
391
|
+
addStream: (id, metadata) => {
|
|
392
|
+
return this.clientStreams.add(connectionId, id, metadata, (size) => {
|
|
393
|
+
transport.send(
|
|
394
|
+
connection,
|
|
395
|
+
ServerMessageType.ClientStreamPull,
|
|
396
|
+
concat(encodeNumber(id, 'Uint32'), encodeNumber(size, 'Uint32')),
|
|
397
|
+
)
|
|
398
|
+
})
|
|
399
|
+
},
|
|
400
|
+
getStream: (id) => {
|
|
401
|
+
return this.serverStreams.get(connectionId, id)
|
|
402
|
+
},
|
|
403
|
+
})
|
|
404
|
+
|
|
405
|
+
return await this.call(connectionId, rpc, params)
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
callAbort(connectionId: string, callId: number) {
|
|
409
|
+
const { context } = this.connections.get(connectionId)
|
|
410
|
+
const call = context.calls.get(callId) ?? throwError('Call not found')
|
|
411
|
+
call.abort()
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
callAbortRaw(connectionId: string, buffer: ArrayBuffer) {
|
|
415
|
+
const callId = decodeNumber(buffer, 'Uint32')
|
|
416
|
+
return this.callAbort(connectionId, callId)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
notify(connectionId: string, event, payload) {
|
|
420
|
+
throw Error('Unimplemented')
|
|
421
|
+
}
|
|
422
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Registry } from '@nmtjs/core'
|
|
2
|
+
import type { BaseType, BaseTypeAny } from '@nmtjs/type'
|
|
3
|
+
import { type Compiled, compile } from '@nmtjs/type/compiler'
|
|
4
|
+
|
|
5
|
+
export class ProtocolRegistry extends Registry {
|
|
6
|
+
readonly types = new Set<BaseType>()
|
|
7
|
+
readonly compiled = new Map<any, Compiled>()
|
|
8
|
+
|
|
9
|
+
registerType(type: BaseTypeAny) {
|
|
10
|
+
this.types.add(type)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
compile() {
|
|
14
|
+
for (const type of this.types) {
|
|
15
|
+
this.compiled.set(type, compile(type))
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
clear() {
|
|
20
|
+
super.clear()
|
|
21
|
+
this.types.clear()
|
|
22
|
+
this.compiled.clear()
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PassThrough,
|
|
3
|
+
Readable,
|
|
4
|
+
type ReadableOptions,
|
|
5
|
+
type TransformOptions,
|
|
6
|
+
} from 'node:stream'
|
|
7
|
+
import { ReadableStream } from 'node:stream/web'
|
|
8
|
+
import type { ProtocolBlob, ProtocolBlobMetadata } from '../common/blob.ts'
|
|
9
|
+
|
|
10
|
+
export class ProtocolClientStream extends Readable {
|
|
11
|
+
constructor(
|
|
12
|
+
public readonly id: number,
|
|
13
|
+
public readonly metadata: ProtocolBlobMetadata,
|
|
14
|
+
options?: ReadableOptions,
|
|
15
|
+
) {
|
|
16
|
+
super(options)
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ProtocolServerStream extends PassThrough {
|
|
21
|
+
public readonly id: number
|
|
22
|
+
public readonly metadata: ProtocolBlobMetadata
|
|
23
|
+
|
|
24
|
+
constructor(id: number, blob: ProtocolBlob) {
|
|
25
|
+
let readable: Readable
|
|
26
|
+
|
|
27
|
+
if (blob.source instanceof Readable) {
|
|
28
|
+
readable = blob.source
|
|
29
|
+
} else if (blob.source instanceof ReadableStream) {
|
|
30
|
+
readable = Readable.fromWeb(blob.source as ReadableStream)
|
|
31
|
+
} else {
|
|
32
|
+
throw new Error('Invalid source type')
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
super()
|
|
36
|
+
|
|
37
|
+
this.pause()
|
|
38
|
+
readable.pipe(this)
|
|
39
|
+
|
|
40
|
+
this.id = id
|
|
41
|
+
this.metadata = blob.metadata
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Async } from '@nmtjs/common'
|
|
2
|
+
import type { BasePlugin, PluginContext } from '@nmtjs/core'
|
|
3
|
+
import type { ServerMessageType } from '../common/enums.ts'
|
|
4
|
+
import type { Connection } from './connection.ts'
|
|
5
|
+
import { kTransportPlugin } from './constants.ts'
|
|
6
|
+
import type { Protocol } from './protocol.ts'
|
|
7
|
+
import type { ProtocolRegistry } from './registry.ts'
|
|
8
|
+
|
|
9
|
+
export interface Transport<T = unknown> {
|
|
10
|
+
start: () => Promise<void>
|
|
11
|
+
stop: () => Promise<void>
|
|
12
|
+
send: (
|
|
13
|
+
connection: Connection<T>,
|
|
14
|
+
messageType: ServerMessageType,
|
|
15
|
+
buffer: ArrayBuffer,
|
|
16
|
+
) => Async<void>
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface TransportPluginContext extends PluginContext {
|
|
20
|
+
protocol: Protocol
|
|
21
|
+
registry: ProtocolRegistry
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TransportPlugin<Type = unknown, Options = unknown>
|
|
25
|
+
extends BasePlugin<Transport<Type>, Options, TransportPluginContext> {
|
|
26
|
+
[kTransportPlugin]: any
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const createTransport = <Type = unknown, Options = unknown>(
|
|
30
|
+
name: string,
|
|
31
|
+
init: TransportPlugin<Type, Options>['init'],
|
|
32
|
+
): TransportPlugin<Type, Options> => ({ name, init, [kTransportPlugin]: true })
|
|
33
|
+
|
|
34
|
+
export const isTransportPlugin = (
|
|
35
|
+
plugin: BasePlugin<any, any, any>,
|
|
36
|
+
): plugin is TransportPlugin => kTransportPlugin in plugin
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Format } from './format.ts'
|
|
2
|
+
|
|
3
|
+
export type ResolveFormatParams = {
|
|
4
|
+
contentType?: string | null
|
|
5
|
+
acceptType?: string | null
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const getFormat = (
|
|
9
|
+
format: Format,
|
|
10
|
+
{ acceptType, contentType }: ResolveFormatParams,
|
|
11
|
+
) => {
|
|
12
|
+
const encoder = contentType ? format.supportsEncoder(contentType) : undefined
|
|
13
|
+
if (!encoder) throw new Error('Unsupported content-type')
|
|
14
|
+
|
|
15
|
+
const decoder = acceptType ? format.supportsDecoder(acceptType) : undefined
|
|
16
|
+
if (!decoder) throw new Error('Unsupported accept')
|
|
17
|
+
|
|
18
|
+
return {
|
|
19
|
+
encoder,
|
|
20
|
+
decoder,
|
|
21
|
+
}
|
|
22
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nmtjs/protocol",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"exports": {
|
|
5
|
+
"./common": {
|
|
6
|
+
"bun": "./lib/common/index.ts",
|
|
7
|
+
"default": "./dist/common/index.js",
|
|
8
|
+
"types": "./lib/common/index.ts"
|
|
9
|
+
},
|
|
10
|
+
"./server": {
|
|
11
|
+
"bun": "./lib/server/index.ts",
|
|
12
|
+
"default": "./dist/server/index.js",
|
|
13
|
+
"types": "./lib/server/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./client": {
|
|
16
|
+
"bun": "./lib/client/index.ts",
|
|
17
|
+
"default": "./dist/client/index.js",
|
|
18
|
+
"types": "./lib/client/index.ts"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {
|
|
22
|
+
"@nmtjs/type": "0.6.0",
|
|
23
|
+
"@nmtjs/core": "0.6.0",
|
|
24
|
+
"@nmtjs/common": "0.6.0"
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"index.ts",
|
|
28
|
+
"lib",
|
|
29
|
+
"dist",
|
|
30
|
+
"tsconfig.json",
|
|
31
|
+
"LICENSE.md",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"version": "0.6.0",
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "neemata-build --root=./lib './**/*.ts'",
|
|
37
|
+
"type-check": "tsc --noEmit"
|
|
38
|
+
}
|
|
39
|
+
}
|
package/tsconfig.json
ADDED