@nmtjs/protocol 0.15.0-beta.3 → 0.15.0-beta.4

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.
@@ -0,0 +1,99 @@
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?: () => 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?: () => unknown,
55
+ ) {
56
+ let source: any
57
+ const metadata = { ..._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,
89
+ size: metadata.size,
90
+ type: metadata.type,
91
+ filename: metadata.filename,
92
+ })
93
+ }
94
+
95
+ // toJSON() {
96
+ // if (!this.encode) throw new Error('Blob format encoder is not defined')
97
+ // return this.encode()
98
+ // }
99
+ }
@@ -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,113 @@
1
+ import type { Pattern } from '@nmtjs/common'
2
+ import { match } from '@nmtjs/common'
3
+
4
+ import type {
5
+ DecodeRPCContext,
6
+ EncodeRPCStreams,
7
+ ProtocolRPCPayload,
8
+ } from '../common/types.ts'
9
+ import type { ProtocolClientStream } from './stream.ts'
10
+
11
+ export interface BaseServerDecoder {
12
+ accept: Pattern[]
13
+ decode(buffer: ArrayBufferView): unknown
14
+ decodeRPC(
15
+ buffer: ArrayBufferView,
16
+ context: DecodeRPCContext<() => ProtocolClientStream>,
17
+ ): ProtocolRPCPayload
18
+ }
19
+
20
+ export interface BaseServerEncoder {
21
+ contentType: string
22
+ encode(data: unknown): ArrayBufferView
23
+ encodeRPC(data: unknown, streams: EncodeRPCStreams): ArrayBufferView
24
+ encodeBlob(streamId: number): unknown
25
+ }
26
+
27
+ export abstract class BaseServerFormat
28
+ implements BaseServerDecoder, BaseServerEncoder
29
+ {
30
+ abstract accept: Pattern[]
31
+ abstract contentType: string
32
+
33
+ abstract encode(data: unknown): ArrayBufferView
34
+ abstract encodeRPC(data: unknown, streams: EncodeRPCStreams): ArrayBufferView
35
+ abstract encodeBlob(streamId: number): unknown
36
+ abstract decode(buffer: ArrayBufferView): any
37
+ abstract decodeRPC(
38
+ buffer: ArrayBufferView,
39
+ context: DecodeRPCContext<() => ProtocolClientStream>,
40
+ ): ProtocolRPCPayload
41
+ }
42
+
43
+ export const parseContentTypes = (types: string) => {
44
+ const normalized = types.trim()
45
+ if (normalized === '*/*') return ['*/*']
46
+ return normalized
47
+ .split(',')
48
+ .map((t) => t.trim())
49
+ .map((t) => {
50
+ const [rawType, ...rest] = t.split(';')
51
+ const params = new Map(
52
+ rest.map((p) =>
53
+ p
54
+ .trim()
55
+ .split('=')
56
+ .slice(0, 2)
57
+ .map((part) => part.trim()),
58
+ ) as [string, string][],
59
+ )
60
+ return {
61
+ type: rawType.trim(),
62
+ q: params.has('q') ? Number.parseFloat(params.get('q')!) : 1,
63
+ }
64
+ })
65
+ .sort((a, b) => {
66
+ if (a.type === '*/*') return 1
67
+ if (b.type === '*/*') return -1
68
+ return b.q - a.q
69
+ })
70
+ .map((t) => t.type)
71
+ }
72
+
73
+ export class ProtocolFormats {
74
+ decoders = new Map<Pattern, BaseServerDecoder>()
75
+ encoders = new Map<Pattern, BaseServerEncoder>()
76
+
77
+ constructor(formats: BaseServerFormat[]) {
78
+ for (const format of formats) {
79
+ this.encoders.set(format.contentType, format)
80
+ for (const acceptType of format.accept) {
81
+ this.decoders.set(acceptType, format)
82
+ }
83
+ }
84
+ }
85
+
86
+ supportsDecoder(contentType: string, throwIfUnsupported = false) {
87
+ return this.supports(this.decoders, contentType, throwIfUnsupported)
88
+ }
89
+
90
+ supportsEncoder(contentType: string, throwIfUnsupported = false) {
91
+ return this.supports(this.encoders, contentType, throwIfUnsupported)
92
+ }
93
+
94
+ private supports<T extends BaseServerEncoder | BaseServerDecoder>(
95
+ formats: Map<Pattern, T>,
96
+ contentType: string,
97
+ throwIfUnsupported = false,
98
+ ): T | null {
99
+ // TODO: Use node:utils.MIMEType (not implemented yet in Deno and Bun yet)
100
+ const types = parseContentTypes(contentType)
101
+
102
+ for (const type of types) {
103
+ for (const [pattern, format] of formats) {
104
+ if (type === '*/*' || match(type, pattern)) return format
105
+ }
106
+ }
107
+
108
+ if (throwIfUnsupported)
109
+ throw new Error(`No supported format found: ${contentType}`)
110
+
111
+ return null
112
+ }
113
+ }
@@ -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
+ }
@@ -0,0 +1,51 @@
1
+ import type { ReadableOptions } from 'node:stream'
2
+ import { PassThrough, Readable } from 'node:stream'
3
+ import { ReadableStream } from 'node:stream/web'
4
+
5
+ import type { ProtocolBlob, ProtocolBlobMetadata } from '../common/blob.ts'
6
+
7
+ export class ProtocolClientStream extends PassThrough {
8
+ readonly #read?: ReadableOptions['read']
9
+
10
+ constructor(
11
+ public readonly id: number,
12
+ public readonly metadata: ProtocolBlobMetadata,
13
+ options?: ReadableOptions,
14
+ ) {
15
+ const { read, ...rest } = options ?? {}
16
+ super(rest)
17
+ this.#read = read
18
+ }
19
+
20
+ override _read(size: number): void {
21
+ if (this.#read) {
22
+ this.#read.call(this, size)
23
+ }
24
+ super._read(size)
25
+ }
26
+ }
27
+
28
+ export class ProtocolServerStream extends PassThrough {
29
+ public readonly id: number
30
+ public readonly metadata: ProtocolBlobMetadata
31
+
32
+ constructor(id: number, blob: ProtocolBlob) {
33
+ let readable: Readable
34
+
35
+ if (blob.source instanceof Readable) {
36
+ readable = blob.source
37
+ } else if (blob.source instanceof ReadableStream) {
38
+ readable = Readable.fromWeb(blob.source as ReadableStream)
39
+ } else {
40
+ throw new Error('Invalid source type')
41
+ }
42
+
43
+ super()
44
+
45
+ this.pause()
46
+ readable.pipe(this)
47
+
48
+ this.id = id
49
+ this.metadata = blob.metadata
50
+ }
51
+ }
@@ -0,0 +1,42 @@
1
+ import type { PlainType } from '@nmtjs/type'
2
+
3
+ import type {
4
+ ProtocolBlobInterface,
5
+ ProtocolBlobMetadata,
6
+ } from '../common/blob.ts'
7
+ import type { kBlobKey } from '../common/constants.ts'
8
+ import type { BaseServerDecoder, BaseServerEncoder } from './format.ts'
9
+ import type { ProtocolVersionInterface } from './protocol.ts'
10
+ import type { ProtocolClientStream } from './stream.ts'
11
+
12
+ export type ClientStreamConsumer = (() => ProtocolClientStream) & {
13
+ readonly [kBlobKey]: any
14
+ readonly metadata: ProtocolBlobMetadata
15
+ }
16
+
17
+ export type MessageContext = {
18
+ protocol: ProtocolVersionInterface
19
+ connectionId: string
20
+ streamId: () => number
21
+ decoder: BaseServerDecoder
22
+ encoder: BaseServerEncoder
23
+ addClientStream: (options: {
24
+ streamId: number
25
+ metadata: ProtocolBlobMetadata
26
+ callId: number
27
+ }) => ClientStreamConsumer
28
+ transport: {
29
+ send?: (connectionId: string, buffer: ArrayBufferView) => boolean | null
30
+ }
31
+ }
32
+
33
+ export type ResolveFormatParams = {
34
+ contentType?: string | null
35
+ accept?: string | null
36
+ }
37
+
38
+ export type InputType<T> = T extends ProtocolBlobInterface
39
+ ? ClientStreamConsumer
40
+ : T extends { [PlainType]?: true }
41
+ ? { [K in keyof Omit<T, PlainType>]: InputType<T[K]> }
42
+ : T
@@ -0,0 +1,22 @@
1
+ import type { ProtocolFormats } from './format.ts'
2
+ import type { ResolveFormatParams } from './types.ts'
3
+
4
+ export class UnsupportedFormatError extends Error {}
5
+
6
+ export class UnsupportedContentTypeError extends UnsupportedFormatError {}
7
+
8
+ export class UnsupportedAcceptTypeError extends UnsupportedFormatError {}
9
+
10
+ export const getFormat = (
11
+ format: ProtocolFormats,
12
+ { accept, contentType }: ResolveFormatParams,
13
+ ) => {
14
+ const encoder = contentType ? format.supportsEncoder(contentType) : undefined
15
+ if (!encoder)
16
+ throw new UnsupportedContentTypeError('Unsupported Content type')
17
+
18
+ const decoder = accept ? format.supportsDecoder(accept) : undefined
19
+ if (!decoder) throw new UnsupportedAcceptTypeError('Unsupported Accept type')
20
+
21
+ return { encoder, decoder }
22
+ }