@nmtjs/protocol 0.15.0-beta.37 → 0.15.0-beta.39

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 (93) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +1 -1
  3. package/dist/client/format.d.ts +18 -13
  4. package/dist/client/format.js +1 -0
  5. package/dist/client/format.js.map +1 -0
  6. package/dist/client/index.d.ts +4 -1
  7. package/dist/client/index.js +7 -4
  8. package/dist/client/index.js.map +1 -0
  9. package/dist/client/protocol.d.ts +102 -138
  10. package/dist/client/protocol.js +7 -336
  11. package/dist/client/protocol.js.map +1 -0
  12. package/dist/client/stream.d.ts +34 -19
  13. package/dist/client/stream.js +138 -46
  14. package/dist/client/stream.js.map +1 -0
  15. package/dist/client/versions/v1.d.ts +108 -0
  16. package/dist/client/versions/v1.js +128 -0
  17. package/dist/client/versions/v1.js.map +1 -0
  18. package/dist/common/binary.d.ts +5 -6
  19. package/dist/common/binary.js +22 -9
  20. package/dist/common/binary.js.map +1 -0
  21. package/dist/common/blob.d.ts +14 -8
  22. package/dist/common/blob.js +55 -30
  23. package/dist/common/blob.js.map +1 -0
  24. package/dist/common/constants.d.ts +2 -0
  25. package/dist/common/constants.js +2 -0
  26. package/dist/common/constants.js.map +1 -0
  27. package/dist/common/enums.d.ts +17 -7
  28. package/dist/common/enums.js +34 -14
  29. package/dist/common/enums.js.map +1 -0
  30. package/dist/common/index.d.ts +2 -0
  31. package/dist/common/index.js +7 -4
  32. package/dist/common/index.js.map +1 -0
  33. package/dist/common/types.d.ts +5 -24
  34. package/dist/common/types.js +1 -0
  35. package/dist/common/types.js.map +1 -0
  36. package/dist/common/utils.d.ts +2 -0
  37. package/dist/common/utils.js +7 -0
  38. package/dist/common/utils.js.map +1 -0
  39. package/dist/server/format.d.ts +17 -23
  40. package/dist/server/format.js +13 -8
  41. package/dist/server/format.js.map +1 -0
  42. package/dist/server/index.d.ts +4 -6
  43. package/dist/server/index.js +9 -11
  44. package/dist/server/index.js.map +1 -0
  45. package/dist/server/protocol.d.ts +88 -114
  46. package/dist/server/protocol.js +5 -354
  47. package/dist/server/protocol.js.map +1 -0
  48. package/dist/server/stream.d.ts +5 -0
  49. package/dist/server/stream.js +31 -2
  50. package/dist/server/stream.js.map +1 -0
  51. package/dist/server/types.d.ts +29 -8
  52. package/dist/server/types.js +1 -0
  53. package/dist/server/types.js.map +1 -0
  54. package/dist/server/utils.d.ts +3 -6
  55. package/dist/server/utils.js +5 -4
  56. package/dist/server/utils.js.map +1 -0
  57. package/dist/server/versions/v1.d.ts +77 -0
  58. package/dist/server/versions/v1.js +119 -0
  59. package/dist/server/versions/v1.js.map +1 -0
  60. package/package.json +18 -24
  61. package/src/client/format.ts +49 -0
  62. package/src/client/index.ts +8 -0
  63. package/src/client/protocol.ts +107 -0
  64. package/src/client/stream.ts +222 -0
  65. package/src/client/versions/v1.ts +205 -0
  66. package/src/common/binary.ts +70 -0
  67. package/src/common/blob.ts +94 -0
  68. package/src/common/constants.ts +2 -0
  69. package/src/common/enums.ts +62 -0
  70. package/src/common/index.ts +6 -0
  71. package/src/common/types.ts +18 -0
  72. package/src/common/utils.ts +12 -0
  73. package/src/server/format.ts +117 -0
  74. package/src/server/index.ts +10 -0
  75. package/src/server/protocol.ts +97 -0
  76. package/src/server/stream.ts +72 -0
  77. package/src/server/types.ts +42 -0
  78. package/src/server/utils.ts +22 -0
  79. package/src/server/versions/v1.ts +198 -0
  80. package/dist/client/events.d.ts +0 -15
  81. package/dist/client/events.js +0 -28
  82. package/dist/server/api.d.ts +0 -33
  83. package/dist/server/api.js +0 -7
  84. package/dist/server/connection.d.ts +0 -25
  85. package/dist/server/connection.js +0 -22
  86. package/dist/server/constants.d.ts +0 -4
  87. package/dist/server/constants.js +0 -2
  88. package/dist/server/injectables.d.ts +0 -14
  89. package/dist/server/injectables.js +0 -22
  90. package/dist/server/registry.d.ts +0 -3
  91. package/dist/server/registry.js +0 -3
  92. package/dist/server/transport.d.ts +0 -23
  93. package/dist/server/transport.js +0 -3
@@ -0,0 +1,205 @@
1
+ import type { BaseProtocolError } from '../../common/types.ts'
2
+ import type { ClientMessageTypePayload, MessageContext } from '../protocol.ts'
3
+ import {
4
+ decodeNumber,
5
+ decodeText,
6
+ encodeNumber,
7
+ encodeText,
8
+ } from '../../common/binary.ts'
9
+ import {
10
+ ClientMessageType,
11
+ MessageByteLength,
12
+ ProtocolVersion,
13
+ ServerMessageType,
14
+ } from '../../common/enums.ts'
15
+ import { ProtocolVersionInterface } from '../protocol.ts'
16
+
17
+ export class ProtocolVersion1 extends ProtocolVersionInterface {
18
+ version = ProtocolVersion.v1
19
+
20
+ decodeMessage(context: MessageContext, buffer: Uint8Array) {
21
+ const messageType = decodeNumber(buffer, 'Uint8')
22
+ const payload = buffer.subarray(MessageByteLength.MessageType)
23
+ switch (messageType) {
24
+ // case ServerMessageType.Event: {
25
+ // const { event, data } = context.decoder.decode(payload)
26
+ // return { type: messageType, event, data }
27
+ // }
28
+ case ServerMessageType.RpcResponse: {
29
+ const callId = decodeNumber(payload, 'Uint32')
30
+ const isError = decodeNumber(payload, 'Uint8', MessageByteLength.CallId)
31
+ const dataPayload = payload.subarray(
32
+ MessageByteLength.CallId + MessageByteLength.MessageError,
33
+ )
34
+
35
+ if (isError) {
36
+ const error = context.decoder.decode(dataPayload) as BaseProtocolError
37
+ return { type: messageType, callId, error }
38
+ } else {
39
+ const result = context.decoder.decodeRPC(dataPayload, {
40
+ addStream: (streamId, metadata) => {
41
+ return context.addServerStream(streamId, metadata)
42
+ },
43
+ })
44
+ return { type: messageType, callId, result }
45
+ }
46
+ }
47
+ case ServerMessageType.RpcStreamResponse: {
48
+ const callId = decodeNumber(payload, 'Uint32')
49
+ const errorPayload = payload.subarray(MessageByteLength.CallId)
50
+ const error =
51
+ errorPayload.byteLength > 0
52
+ ? (context.decoder.decode(errorPayload) as
53
+ | BaseProtocolError
54
+ | undefined)
55
+ : undefined
56
+ return { type: messageType, callId, error }
57
+ }
58
+ case ServerMessageType.RpcStreamChunk: {
59
+ const callId = decodeNumber(payload, 'Uint32')
60
+ const chunk = payload.subarray(MessageByteLength.CallId)
61
+ return { type: messageType, callId, chunk }
62
+ }
63
+ case ServerMessageType.RpcStreamEnd: {
64
+ const callId = decodeNumber(payload, 'Uint32')
65
+ return { type: messageType, callId }
66
+ }
67
+ case ServerMessageType.RpcStreamAbort: {
68
+ const callId = decodeNumber(payload, 'Uint32')
69
+ const reasonPayload = payload.subarray(MessageByteLength.CallId)
70
+ const reason =
71
+ reasonPayload.byteLength > 0 ? decodeText(reasonPayload) : undefined
72
+ return { type: messageType, callId, reason }
73
+ }
74
+ case ServerMessageType.ClientStreamPull: {
75
+ const streamId = decodeNumber(payload, 'Uint32')
76
+ const size = decodeNumber(payload, 'Uint32', MessageByteLength.StreamId)
77
+ return { type: messageType, streamId, size }
78
+ }
79
+ case ServerMessageType.ClientStreamAbort: {
80
+ const streamId = decodeNumber(payload, 'Uint32')
81
+ const reasonPayload = payload.subarray(MessageByteLength.StreamId)
82
+ const reason =
83
+ reasonPayload.byteLength > 0 ? decodeText(reasonPayload) : undefined
84
+
85
+ return { type: messageType, streamId, reason }
86
+ }
87
+ case ServerMessageType.ServerStreamPush: {
88
+ const streamId = decodeNumber(payload, 'Uint32')
89
+ const chunk = payload.subarray(MessageByteLength.StreamId)
90
+ return { type: messageType, streamId, chunk }
91
+ }
92
+ case ServerMessageType.ServerStreamEnd: {
93
+ const streamId = decodeNumber(payload, 'Uint32')
94
+ const reasonPayload = payload.subarray(MessageByteLength.StreamId)
95
+ const reason =
96
+ reasonPayload.byteLength > 0 ? decodeText(reasonPayload) : undefined
97
+
98
+ return { type: messageType, streamId, reason }
99
+ }
100
+ case ServerMessageType.ServerStreamAbort: {
101
+ const streamId = decodeNumber(payload, 'Uint32')
102
+ const reasonPayload = payload.subarray(MessageByteLength.StreamId)
103
+ const reason =
104
+ reasonPayload.byteLength > 0 ? decodeText(reasonPayload) : undefined
105
+
106
+ return { type: messageType, streamId, reason }
107
+ }
108
+
109
+ default:
110
+ throw new Error(`Unsupported message type: ${messageType}`)
111
+ }
112
+ }
113
+
114
+ encodeMessage<T extends ClientMessageType>(
115
+ context: MessageContext,
116
+ messageType: T,
117
+ payload: ClientMessageTypePayload[T],
118
+ ) {
119
+ switch (messageType) {
120
+ case ClientMessageType.Rpc: {
121
+ const {
122
+ callId,
123
+ procedure,
124
+ payload: rpcPayload,
125
+ } = payload as ClientMessageTypePayload[ClientMessageType.Rpc]
126
+ const procedureBuffer = encodeText(procedure)
127
+ return this.encode(
128
+ encodeNumber(messageType, 'Uint8'),
129
+ encodeNumber(callId, 'Uint32'),
130
+ encodeNumber(procedureBuffer.byteLength, 'Uint16'),
131
+ procedureBuffer,
132
+ context.encoder.encodeRPC(rpcPayload, {
133
+ addStream: (blob) => context.addClientStream(blob),
134
+ }),
135
+ )
136
+ }
137
+ case ClientMessageType.RpcAbort: {
138
+ const { callId, reason } =
139
+ payload as ClientMessageTypePayload[ClientMessageType.RpcAbort]
140
+
141
+ return this.encode(
142
+ encodeNumber(messageType, 'Uint8'),
143
+ encodeNumber(callId, 'Uint32'),
144
+ reason ? encodeText(reason) : new Uint8Array(0),
145
+ )
146
+ }
147
+ case ClientMessageType.RpcPull: {
148
+ const { callId } =
149
+ payload as ClientMessageTypePayload[ClientMessageType.RpcPull]
150
+
151
+ return this.encode(
152
+ encodeNumber(messageType, 'Uint8'),
153
+ encodeNumber(callId, 'Uint32'),
154
+ )
155
+ }
156
+ case ClientMessageType.ClientStreamPush: {
157
+ const { streamId, chunk } =
158
+ payload as ClientMessageTypePayload[ClientMessageType.ClientStreamPush]
159
+ return this.encode(
160
+ encodeNumber(messageType, 'Uint8'),
161
+ encodeNumber(streamId, 'Uint32'),
162
+ chunk,
163
+ )
164
+ }
165
+ case ClientMessageType.ClientStreamEnd: {
166
+ const { streamId } =
167
+ payload as ClientMessageTypePayload[ClientMessageType.ClientStreamEnd]
168
+ return this.encode(
169
+ encodeNumber(messageType, 'Uint8'),
170
+ encodeNumber(streamId, 'Uint32'),
171
+ )
172
+ }
173
+ case ClientMessageType.ClientStreamAbort: {
174
+ const { streamId, reason } =
175
+ payload as ClientMessageTypePayload[ClientMessageType.ClientStreamAbort]
176
+ return this.encode(
177
+ encodeNumber(messageType, 'Uint8'),
178
+ encodeNumber(streamId, 'Uint32'),
179
+ reason ? encodeText(reason) : new Uint8Array(0),
180
+ )
181
+ }
182
+ case ClientMessageType.ServerStreamPull: {
183
+ const { streamId, size } =
184
+ payload as ClientMessageTypePayload[ClientMessageType.ServerStreamPull]
185
+ return this.encode(
186
+ encodeNumber(messageType, 'Uint8'),
187
+ encodeNumber(streamId, 'Uint32'),
188
+ encodeNumber(size, 'Uint32'),
189
+ )
190
+ }
191
+ case ClientMessageType.ServerStreamAbort: {
192
+ const { streamId, reason } =
193
+ payload as ClientMessageTypePayload[ClientMessageType.ServerStreamAbort]
194
+ return this.encode(
195
+ encodeNumber(messageType, 'Uint8'),
196
+ encodeNumber(streamId, 'Uint32'),
197
+ reason ? encodeText(reason) : new Uint8Array(0),
198
+ )
199
+ }
200
+
201
+ default:
202
+ throw new Error(`Unsupported message type: ${messageType}`)
203
+ }
204
+ }
205
+ }
@@ -0,0 +1,70 @@
1
+ // TODO: get rid of lib DOM somehow...
2
+ /// <reference lib="dom" />
3
+
4
+ const utf8decoder = new TextDecoder()
5
+ const utf8encoder = new TextEncoder()
6
+
7
+ export type BinaryTypes = {
8
+ Int8: number
9
+ Int16: number
10
+ Int32: number
11
+ Uint8: number
12
+ Uint16: number
13
+ Uint32: number
14
+ Float32: number
15
+ Float64: number
16
+ BigInt64: bigint
17
+ BigUint64: bigint
18
+ }
19
+
20
+ export const encodeNumber = <T extends keyof BinaryTypes>(
21
+ value: BinaryTypes[T],
22
+ type: T,
23
+ littleEndian = true,
24
+ ) => {
25
+ const bytesNeeded = globalThis[`${type}Array`].BYTES_PER_ELEMENT
26
+ const ab = new ArrayBuffer(bytesNeeded)
27
+ const dv = new DataView(ab)
28
+ dv[`set${type}`](0, value as never, littleEndian)
29
+ return ab
30
+ }
31
+
32
+ export const decodeNumber = <T extends keyof BinaryTypes>(
33
+ buffer: ArrayBuffer | ArrayBufferView,
34
+ type: T,
35
+ offset = 0,
36
+ littleEndian = true,
37
+ ): BinaryTypes[T] => {
38
+ const ab = buffer instanceof ArrayBuffer ? buffer : buffer.buffer
39
+ const baseOffset = buffer instanceof ArrayBuffer ? 0 : buffer.byteOffset
40
+ const view = new DataView(ab)
41
+ return view[`get${type}`](baseOffset + offset, littleEndian) as BinaryTypes[T]
42
+ }
43
+
44
+ export const encodeText = (text: string) => utf8encoder.encode(text)
45
+
46
+ export const decodeText = (buffer: Parameters<typeof utf8decoder.decode>[0]) =>
47
+ utf8decoder.decode(buffer)
48
+
49
+ export const concat = (...buffers: (ArrayBuffer | ArrayBufferView)[]) => {
50
+ let totalLength = 0
51
+ for (const buffer of buffers) totalLength += buffer.byteLength
52
+ const view = new Uint8Array(totalLength)
53
+ let offset = 0
54
+ for (const buffer of buffers) {
55
+ const chunk =
56
+ buffer instanceof ArrayBuffer
57
+ ? new Uint8Array(buffer)
58
+ : new Uint8Array(buffer.buffer, buffer.byteOffset, buffer.byteLength)
59
+ view.set(chunk, offset)
60
+ offset += chunk.byteLength
61
+ }
62
+ return view
63
+ }
64
+
65
+ export const UTF8Transform = () =>
66
+ new TransformStream<string, Uint8Array>({
67
+ transform(chunk, controller) {
68
+ controller.enqueue(encodeText(chunk))
69
+ },
70
+ })
@@ -0,0 +1,94 @@
1
+ import { kBlobKey } from './constants.ts'
2
+
3
+ export type ProtocolBlobMetadata = {
4
+ type: string
5
+ size?: number | undefined
6
+ filename?: string | undefined
7
+ }
8
+
9
+ export interface ProtocolBlobInterface {
10
+ readonly metadata: ProtocolBlobMetadata
11
+ readonly [kBlobKey]: any
12
+ }
13
+
14
+ export class ProtocolBlob implements ProtocolBlobInterface {
15
+ [kBlobKey]: true = true
16
+
17
+ public readonly source: any
18
+ public readonly metadata: ProtocolBlobMetadata
19
+ public readonly encode?: (metadata: ProtocolBlobMetadata) => unknown
20
+ public readonly toJSON?: () => unknown
21
+
22
+ constructor({
23
+ source,
24
+ encode,
25
+ size,
26
+ type = 'application/octet-stream',
27
+ filename,
28
+ }: {
29
+ source: any
30
+ encode?: () => unknown
31
+ size?: number
32
+ type?: string
33
+ filename?: string
34
+ }) {
35
+ if (typeof size !== 'undefined' && size <= 0)
36
+ throw new Error('Blob size is invalid')
37
+
38
+ this.encode = encode
39
+ this.source = source
40
+ this.metadata = { size, type, filename }
41
+ if (encode) {
42
+ Object.defineProperty(this, 'toJSON', {
43
+ configurable: false,
44
+ enumerable: false,
45
+ writable: false,
46
+ value: encode,
47
+ })
48
+ }
49
+ }
50
+
51
+ static from(
52
+ _source: any,
53
+ _metadata: { size?: number; type?: string; filename?: string } = {},
54
+ _encode?: (metadata: ProtocolBlobMetadata) => unknown,
55
+ ) {
56
+ let source: any
57
+ const metadata = { type: 'application/octet-stream', ..._metadata }
58
+
59
+ if (_source instanceof globalThis.ReadableStream) {
60
+ source = _source
61
+ } else if ('File' in globalThis && _source instanceof globalThis.File) {
62
+ source = _source.stream()
63
+ metadata.size ??= _source.size
64
+ metadata.filename ??= _source.name
65
+ } else if (_source instanceof globalThis.Blob) {
66
+ source = _source.stream()
67
+ metadata.size ??= _source.size
68
+ metadata.type ??= _source.type
69
+ } else if (typeof _source === 'string') {
70
+ const blob = new Blob([_source])
71
+ source = blob.stream()
72
+ metadata.size ??= blob.size
73
+ metadata.type ??= 'text/plain'
74
+ } else if (globalThis.ArrayBuffer.isView(_source)) {
75
+ const blob = new Blob([_source as ArrayBufferView<ArrayBuffer>])
76
+ source = blob.stream()
77
+ metadata.size ??= blob.size
78
+ } else if (_source instanceof globalThis.ArrayBuffer) {
79
+ const blob = new Blob([_source])
80
+ source = blob.stream()
81
+ metadata.size ??= blob.size
82
+ } else {
83
+ source = _source
84
+ }
85
+
86
+ return new ProtocolBlob({
87
+ source,
88
+ encode: _encode?.bind(null, metadata),
89
+ size: metadata.size,
90
+ type: metadata.type,
91
+ filename: metadata.filename,
92
+ })
93
+ }
94
+ }
@@ -0,0 +1,2 @@
1
+ export const kBlobKey: unique symbol = Symbol.for('neemata:blobKey')
2
+ export type kBlobKey = typeof kBlobKey
@@ -0,0 +1,62 @@
1
+ export enum ProtocolVersion {
2
+ v1 = 1,
3
+ }
4
+
5
+ export enum ClientMessageType {
6
+ Rpc = 10,
7
+ RpcAbort = 11,
8
+ RpcPull = 12,
9
+
10
+ ClientStreamPush = 20,
11
+ ClientStreamEnd = 21,
12
+ ClientStreamAbort = 22,
13
+
14
+ ServerStreamAbort = 33,
15
+ ServerStreamPull = 34,
16
+ }
17
+
18
+ export enum ServerMessageType {
19
+ // Event = 1,
20
+
21
+ RpcResponse = 10,
22
+ RpcStreamResponse = 11,
23
+ RpcStreamChunk = 12,
24
+ RpcStreamEnd = 13,
25
+ RpcStreamAbort = 14,
26
+
27
+ ServerStreamPush = 20,
28
+ ServerStreamEnd = 21,
29
+ ServerStreamAbort = 22,
30
+
31
+ ClientStreamAbort = 33,
32
+ ClientStreamPull = 34,
33
+ }
34
+
35
+ export enum ConnectionType {
36
+ Bidirectional = 'Bidirectional',
37
+ Unidirectional = 'Unidirectional',
38
+ }
39
+
40
+ export enum ErrorCode {
41
+ ValidationError = 'ValidationError',
42
+ BadRequest = 'BadRequest',
43
+ NotFound = 'NotFound',
44
+ Forbidden = 'Forbidden',
45
+ Unauthorized = 'Unauthorized',
46
+ InternalServerError = 'InternalServerError',
47
+ NotAcceptable = 'NotAcceptable',
48
+ RequestTimeout = 'RequestTimeout',
49
+ GatewayTimeout = 'GatewayTimeout',
50
+ ServiceUnavailable = 'ServiceUnavailable',
51
+ ClientRequestError = 'ClientRequestError',
52
+ ConnectionError = 'ConnectionError',
53
+ }
54
+
55
+ export enum MessageByteLength {
56
+ MessageType = 1,
57
+ MessageError = 1,
58
+ ProcedureLength = 2,
59
+ CallId = 4,
60
+ StreamId = 4,
61
+ ChunkSize = 4,
62
+ }
@@ -0,0 +1,6 @@
1
+ export * from './binary.ts'
2
+ export * from './blob.ts'
3
+ export * from './constants.ts'
4
+ export * from './enums.ts'
5
+ export * from './types.ts'
6
+ export * from './utils.ts'
@@ -0,0 +1,18 @@
1
+ import type { ProtocolBlobMetadata } from './blob.ts'
2
+
3
+ type Stream = any
4
+
5
+ export interface BaseProtocolError {
6
+ code: string
7
+ message: string
8
+ data?: any
9
+ }
10
+
11
+ export type ProtocolRPCPayload = unknown
12
+ export type ProtocolRPCResponse = unknown
13
+
14
+ export type EncodeRPCStreams = Record<number, ProtocolBlobMetadata>
15
+
16
+ export interface DecodeRPCContext<T = Stream> {
17
+ addStream: (id: number, metadata: ProtocolBlobMetadata) => T
18
+ }
@@ -0,0 +1,12 @@
1
+ import type { ProtocolBlobInterface } from './blob.ts'
2
+ import { kBlobKey } from './constants.ts'
3
+
4
+ export const isBlobInterface = <T extends ProtocolBlobInterface>(
5
+ value: any,
6
+ ): value is T => {
7
+ return (
8
+ value &&
9
+ (typeof value === 'object' || typeof value === 'function') &&
10
+ kBlobKey in value
11
+ )
12
+ }
@@ -0,0 +1,117 @@
1
+ import type { Pattern } from '@nmtjs/common'
2
+ import { match } from '@nmtjs/common'
3
+
4
+ import type { ProtocolBlobMetadata } from '../common/blob.ts'
5
+ import type {
6
+ DecodeRPCContext,
7
+ EncodeRPCStreams,
8
+ ProtocolRPCPayload,
9
+ } from '../common/types.ts'
10
+ import type { ProtocolClientStream } from './stream.ts'
11
+
12
+ export interface BaseServerDecoder {
13
+ accept: Pattern[]
14
+ decode(buffer: ArrayBufferView): unknown
15
+ decodeRPC(
16
+ buffer: ArrayBufferView,
17
+ context: DecodeRPCContext<() => ProtocolClientStream>,
18
+ ): ProtocolRPCPayload
19
+ }
20
+
21
+ export interface BaseServerEncoder {
22
+ contentType: string
23
+ encode(data: unknown): ArrayBufferView
24
+ encodeRPC(data: unknown, streams: EncodeRPCStreams): ArrayBufferView
25
+ encodeBlob(streamId: number, metadata: ProtocolBlobMetadata): unknown
26
+ }
27
+
28
+ export abstract class BaseServerFormat
29
+ implements BaseServerDecoder, BaseServerEncoder
30
+ {
31
+ abstract accept: Pattern[]
32
+ abstract contentType: string
33
+
34
+ abstract encode(data: unknown): ArrayBufferView
35
+ abstract encodeRPC(data: unknown, streams: EncodeRPCStreams): ArrayBufferView
36
+ abstract encodeBlob(streamId: number, metadata: ProtocolBlobMetadata): unknown
37
+ abstract decode(buffer: ArrayBufferView): any
38
+ abstract decodeRPC(
39
+ buffer: ArrayBufferView,
40
+ context: DecodeRPCContext<() => ProtocolClientStream>,
41
+ ): ProtocolRPCPayload
42
+ }
43
+
44
+ export const parseContentTypes = (types: string) => {
45
+ const normalized = types.trim()
46
+ if (normalized === '*/*') return ['*/*']
47
+ return normalized
48
+ .split(',')
49
+ .map((t) => t.trim())
50
+ .map((t) => {
51
+ const [rawType, ...rest] = t.split(';')
52
+ const params = new Map(
53
+ rest.map((p) =>
54
+ p
55
+ .trim()
56
+ .split('=')
57
+ .slice(0, 2)
58
+ .map((part) => part.trim()),
59
+ ) as [string, string][],
60
+ )
61
+ return {
62
+ type: rawType.trim(),
63
+ q: params.has('q') ? Number.parseFloat(params.get('q')!) : 1,
64
+ }
65
+ })
66
+ .sort((a, b) => {
67
+ if (a.type === '*/*') return 1
68
+ if (b.type === '*/*') return -1
69
+ return b.q - a.q
70
+ })
71
+ .map((t) => t.type)
72
+ }
73
+
74
+ export class ProtocolFormats {
75
+ decoders = new Map<Pattern, BaseServerDecoder>()
76
+ encoders = new Map<Pattern, BaseServerEncoder>()
77
+
78
+ default: BaseServerFormat
79
+
80
+ constructor(formats: [BaseServerFormat, ...BaseServerFormat[]]) {
81
+ this.default = formats[0]
82
+ for (const format of formats) {
83
+ this.encoders.set(format.contentType, format)
84
+ for (const acceptType of format.accept) {
85
+ this.decoders.set(acceptType, format)
86
+ }
87
+ }
88
+ }
89
+
90
+ supportsDecoder(contentType: string, throwIfUnsupported = false) {
91
+ return this.supports(this.decoders, contentType, throwIfUnsupported)
92
+ }
93
+
94
+ supportsEncoder(contentType: string, throwIfUnsupported = false) {
95
+ return this.supports(this.encoders, contentType, throwIfUnsupported)
96
+ }
97
+
98
+ private supports<T extends BaseServerEncoder | BaseServerDecoder>(
99
+ formats: Map<Pattern, T>,
100
+ contentType: string,
101
+ throwIfUnsupported = false,
102
+ ): T | null {
103
+ // TODO: Use node:utils.MIMEType (not implemented yet in Deno and Bun yet)
104
+ const types = parseContentTypes(contentType)
105
+
106
+ for (const type of types) {
107
+ for (const [pattern, format] of formats) {
108
+ if (type === '*/*' || match(type, pattern)) return format
109
+ }
110
+ }
111
+
112
+ if (throwIfUnsupported)
113
+ throw new Error(`No supported format found: ${contentType}`)
114
+
115
+ return null
116
+ }
117
+ }
@@ -0,0 +1,10 @@
1
+ export * from './format.ts'
2
+ export * from './protocol.ts'
3
+ export * from './stream.ts'
4
+ export * from './types.ts'
5
+ export * from './utils.ts'
6
+
7
+ import { ProtocolVersion } from '../common/enums.ts'
8
+ import { ProtocolVersion1 } from './versions/v1.ts'
9
+
10
+ export const versions = { [ProtocolVersion.v1]: new ProtocolVersion1() }
@@ -0,0 +1,97 @@
1
+ import type {
2
+ ClientMessageType,
3
+ ProtocolVersion,
4
+ ServerMessageType,
5
+ } from '../common/enums.ts'
6
+ import type { BaseProtocolError, EncodeRPCStreams } from '../common/types.ts'
7
+ import type { MessageContext } from './types.ts'
8
+ import { concat } from '../common/binary.ts'
9
+
10
+ export class ProtocolError extends Error implements BaseProtocolError {
11
+ code: string
12
+ data?: any
13
+
14
+ constructor(code: string, message?: string, data?: any) {
15
+ super(message)
16
+ this.code = code
17
+ this.data = data
18
+ }
19
+
20
+ get message() {
21
+ return `${this.code} ${super.message}`
22
+ }
23
+
24
+ toString() {
25
+ return `${this.code} ${this.message}`
26
+ }
27
+
28
+ toJSON() {
29
+ return { code: this.code, message: this.message, data: this.data }
30
+ }
31
+ }
32
+
33
+ export abstract class ProtocolVersionInterface {
34
+ abstract version: ProtocolVersion
35
+ abstract decodeMessage(
36
+ context: MessageContext,
37
+ buffer: ArrayBufferView,
38
+ ): {
39
+ [K in keyof ClientMessageTypePayload]: {
40
+ type: K
41
+ } & ClientMessageTypePayload[K]
42
+ }[keyof ClientMessageTypePayload]
43
+ abstract encodeMessage<T extends ServerMessageType = ServerMessageType>(
44
+ context: MessageContext,
45
+ messageType: T,
46
+ payload: ServerMessageTypePayload[T],
47
+ ): ArrayBufferView
48
+
49
+ protected encode(
50
+ ...chunks: (ArrayBuffer | ArrayBufferView)[]
51
+ ): ArrayBufferView {
52
+ return concat(...chunks)
53
+ }
54
+ }
55
+
56
+ export type ServerMessageTypePayload = {
57
+ // [ServerMessageType.Event]: { event: string; data: any }
58
+ [ServerMessageType.RpcResponse]: {
59
+ callId: number
60
+ result: any
61
+ streams: EncodeRPCStreams
62
+ error: any | null
63
+ }
64
+ [ServerMessageType.RpcStreamAbort]: { callId: number; reason?: string }
65
+ [ServerMessageType.RpcStreamEnd]: { callId: number }
66
+ [ServerMessageType.RpcStreamChunk]: { callId: number; chunk: ArrayBufferView }
67
+ [ServerMessageType.RpcStreamResponse]: { callId: number }
68
+ [ServerMessageType.ClientStreamAbort]: { streamId: number; reason?: string }
69
+ [ServerMessageType.ClientStreamPull]: { streamId: number; size: number }
70
+ [ServerMessageType.ServerStreamAbort]: { streamId: number; reason?: string }
71
+ [ServerMessageType.ServerStreamEnd]: { streamId: number }
72
+ [ServerMessageType.ServerStreamPush]: {
73
+ streamId: number
74
+ chunk: ArrayBufferView
75
+ }
76
+ }
77
+
78
+ export type ClientMessageTypePayload = {
79
+ [ClientMessageType.Rpc]: {
80
+ rpc: {
81
+ callId: number
82
+ procedure: string
83
+ payload: unknown
84
+ streams?: EncodeRPCStreams
85
+ }
86
+ }
87
+ [ClientMessageType.RpcPull]: { callId: number }
88
+ [ClientMessageType.RpcAbort]: { callId: number; reason?: string }
89
+ [ClientMessageType.ClientStreamPush]: {
90
+ streamId: number
91
+ chunk: ArrayBufferView
92
+ }
93
+ [ClientMessageType.ClientStreamEnd]: { streamId: number }
94
+ [ClientMessageType.ClientStreamAbort]: { streamId: number; reason?: string }
95
+ [ClientMessageType.ServerStreamPull]: { streamId: number; size: number }
96
+ [ClientMessageType.ServerStreamAbort]: { streamId: number; reason?: string }
97
+ }