@nmtjs/client 0.15.0-beta.1 → 0.15.0-beta.10

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/src/events.ts ADDED
@@ -0,0 +1,70 @@
1
+ import type { Callback } from '@nmtjs/common'
2
+
3
+ export type EventMap = { [K: string]: any[] }
4
+
5
+ // TODO: add errors and promise rejections handling
6
+ /**
7
+ * Thin node-like event emitter wrapper around EventTarget
8
+ */
9
+ export class EventEmitter<
10
+ Events extends EventMap = EventMap,
11
+ EventName extends Extract<keyof Events, string> = Extract<
12
+ keyof Events,
13
+ string
14
+ >,
15
+ > {
16
+ static once<
17
+ T extends EventEmitter,
18
+ E extends T extends EventEmitter<any, infer Event> ? Event : never,
19
+ >(ee: T, event: E) {
20
+ return new Promise((resolve) => ee.once(event, resolve))
21
+ }
22
+
23
+ #target = new EventTarget()
24
+ #listeners = new Map<Callback, Callback>()
25
+
26
+ on<E extends EventName>(
27
+ event: E | (Object & string),
28
+ listener: (...args: Events[E]) => void,
29
+ options?: AddEventListenerOptions,
30
+ ) {
31
+ const wrapper = (event) => listener(...event.detail)
32
+ this.#listeners.set(listener, wrapper)
33
+ this.#target.addEventListener(event, wrapper, { ...options, once: false })
34
+ return () => this.#target.removeEventListener(event, wrapper)
35
+ }
36
+
37
+ once<E extends EventName>(
38
+ event: E | (Object & string),
39
+ listener: (...args: Events[E]) => void,
40
+ options?: AddEventListenerOptions,
41
+ ) {
42
+ return this.on(event, listener, { ...options, once: true })
43
+ }
44
+
45
+ off(event: EventName | (Object & string), listener: Callback) {
46
+ const wrapper = this.#listeners.get(listener)
47
+ if (wrapper) this.#target.removeEventListener(event, wrapper)
48
+ }
49
+
50
+ emit<E extends EventName | (Object & string)>(
51
+ event: E,
52
+ ...args: E extends EventName ? Events[E] : any[]
53
+ ) {
54
+ return this.#target.dispatchEvent(new CustomEvent(event, { detail: args }))
55
+ }
56
+ }
57
+
58
+ export const once = <
59
+ T extends EventEmitter,
60
+ EventMap extends T extends EventEmitter<infer E, any> ? E : never,
61
+ EventName extends T extends EventEmitter<any, infer N> ? N : never,
62
+ >(
63
+ ee: T,
64
+ event: EventName,
65
+ signal?: AbortSignal,
66
+ ) => {
67
+ return new Promise<EventMap[EventName]>((resolve) => {
68
+ ee.once(event, resolve, { signal })
69
+ })
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './core.ts'
2
+ export * from './events.ts'
3
+ export * from './transformers.ts'
4
+ export * from './transport.ts'
5
+ export * from './types.ts'
package/src/streams.ts ADDED
@@ -0,0 +1,131 @@
1
+ import type { ProtocolBlobMetadata } from '@nmtjs/protocol'
2
+ import type { ProtocolServerStreamInterface } from '@nmtjs/protocol/client'
3
+ import { ProtocolClientBlobStream } from '@nmtjs/protocol/client'
4
+
5
+ export class ClientStreams {
6
+ readonly #collection = new Map<number, ProtocolClientBlobStream>()
7
+
8
+ get size() {
9
+ return this.#collection.size
10
+ }
11
+
12
+ get(streamId: number) {
13
+ const stream = this.#collection.get(streamId)
14
+ if (!stream) throw new Error('Stream not found')
15
+ return stream
16
+ }
17
+
18
+ add(
19
+ source: ReadableStream,
20
+ streamId: number,
21
+ metadata: ProtocolBlobMetadata,
22
+ ) {
23
+ const stream = new ProtocolClientBlobStream(source, streamId, metadata)
24
+ this.#collection.set(streamId, stream)
25
+ return stream
26
+ }
27
+
28
+ remove(streamId: number) {
29
+ this.#collection.delete(streamId)
30
+ }
31
+
32
+ async abort(streamId: number, reason?: string) {
33
+ const stream = this.#collection.get(streamId)
34
+ if (!stream) return // Stream already cleaned up
35
+ await stream.abort(reason)
36
+ this.remove(streamId)
37
+ }
38
+
39
+ pull(streamId: number, size: number) {
40
+ const stream = this.get(streamId)
41
+ return stream.read(size)
42
+ }
43
+
44
+ async end(streamId: number) {
45
+ await this.get(streamId).end()
46
+ this.remove(streamId)
47
+ }
48
+
49
+ async clear(reason?: string) {
50
+ if (reason) {
51
+ const abortPromises = [...this.#collection.values()].map((stream) =>
52
+ stream.abort(reason),
53
+ )
54
+ await Promise.all(abortPromises)
55
+ }
56
+ this.#collection.clear()
57
+ }
58
+ }
59
+
60
+ export class ServerStreams<
61
+ T extends ProtocolServerStreamInterface = ProtocolServerStreamInterface,
62
+ > {
63
+ readonly #collection = new Map<number, T>()
64
+ readonly #writers = new Map<number, WritableStreamDefaultWriter>()
65
+
66
+ get size() {
67
+ return this.#collection.size
68
+ }
69
+
70
+ has(streamId: number) {
71
+ return this.#collection.has(streamId)
72
+ }
73
+
74
+ get(streamId: number) {
75
+ const stream = this.#collection.get(streamId)
76
+ if (!stream) throw new Error('Stream not found')
77
+ return stream
78
+ }
79
+
80
+ add(streamId: number, stream: T) {
81
+ this.#collection.set(streamId, stream)
82
+ this.#writers.set(
83
+ streamId,
84
+ stream.writable.getWriter() as WritableStreamDefaultWriter,
85
+ )
86
+ return stream
87
+ }
88
+
89
+ remove(streamId: number) {
90
+ this.#collection.delete(streamId)
91
+ this.#writers.delete(streamId)
92
+ }
93
+
94
+ async abort(streamId: number) {
95
+ if (this.has(streamId)) {
96
+ const writer = this.#writers.get(streamId)
97
+ if (writer) {
98
+ await writer.abort()
99
+ writer.releaseLock()
100
+ }
101
+ this.remove(streamId)
102
+ }
103
+ }
104
+
105
+ async push(streamId: number, chunk: ArrayBufferView) {
106
+ const writer = this.#writers.get(streamId)
107
+ if (writer) {
108
+ return await writer.write(chunk)
109
+ }
110
+ }
111
+
112
+ async end(streamId: number) {
113
+ const writer = this.#writers.get(streamId)
114
+ if (writer) {
115
+ await writer.close()
116
+ writer.releaseLock()
117
+ }
118
+ this.remove(streamId)
119
+ }
120
+
121
+ async clear(reason?: string) {
122
+ if (reason) {
123
+ const abortPromises = [...this.#writers.values()].map((writer) =>
124
+ writer.abort(reason).finally(() => writer.releaseLock()),
125
+ )
126
+ await Promise.allSettled(abortPromises)
127
+ }
128
+ this.#collection.clear()
129
+ this.#writers.clear()
130
+ }
131
+ }
@@ -0,0 +1,8 @@
1
+ export class BaseClientTransformer {
2
+ encode(_procedure: string, payload: any) {
3
+ return payload
4
+ }
5
+ decode(_procedure: string, payload: any) {
6
+ return payload
7
+ }
8
+ }
@@ -0,0 +1,71 @@
1
+ import type {
2
+ ConnectionType,
3
+ ProtocolBlobMetadata,
4
+ ProtocolVersion,
5
+ } from '@nmtjs/protocol'
6
+ import type { BaseClientFormat } from '@nmtjs/protocol/client'
7
+
8
+ export type ClientTransportMessageOptions = {
9
+ signal?: AbortSignal
10
+ _stream_response?: boolean
11
+ }
12
+
13
+ export interface ClientTransportStartParams {
14
+ auth?: string
15
+ application?: string
16
+ onMessage: (message: ArrayBufferView) => any
17
+ onConnect: () => any
18
+ onDisconnect: (reason: 'client' | 'server' | (string & {})) => any
19
+ }
20
+
21
+ export interface ClientTransportRpcParams {
22
+ format: BaseClientFormat
23
+ auth?: string
24
+ application?: string
25
+ }
26
+
27
+ export type ClientCallResponse =
28
+ | { type: 'rpc'; result: ArrayBufferView }
29
+ | { type: 'rpc_stream'; stream: ReadableStream<ArrayBufferView> }
30
+ | {
31
+ type: 'blob'
32
+ metadata: ProtocolBlobMetadata
33
+ source: ReadableStream<ArrayBufferView>
34
+ }
35
+
36
+ export type ClientTransport<T extends ConnectionType = ConnectionType> =
37
+ T extends ConnectionType.Bidirectional
38
+ ? {
39
+ type: ConnectionType.Bidirectional
40
+ connect(params: ClientTransportStartParams): Promise<void>
41
+ disconnect(): Promise<void>
42
+ send(
43
+ message: ArrayBufferView,
44
+ options: ClientTransportMessageOptions,
45
+ ): Promise<void>
46
+ }
47
+ : {
48
+ type: ConnectionType.Unidirectional
49
+ connect?(params: ClientTransportStartParams): Promise<void>
50
+ disconnect?(): Promise<void>
51
+ call(
52
+ client: {
53
+ format: BaseClientFormat
54
+ auth?: string
55
+ application?: string
56
+ },
57
+ rpc: { callId: number; procedure: string; payload: any },
58
+ options: ClientTransportMessageOptions,
59
+ ): Promise<ClientCallResponse>
60
+ }
61
+
62
+ export interface ClientTransportParams {
63
+ protocol: ProtocolVersion
64
+ format: BaseClientFormat
65
+ }
66
+
67
+ export type ClientTransportFactory<
68
+ Type extends ConnectionType,
69
+ Options = unknown,
70
+ Transport extends ClientTransport<Type> = ClientTransport<Type>,
71
+ > = (params: ClientTransportParams, options: Options) => Transport
package/src/types.ts ADDED
@@ -0,0 +1,145 @@
1
+ import type { CallTypeProvider, OneOf, TypeProvider } from '@nmtjs/common'
2
+ import type { TAnyProcedureContract, TAnyRouterContract } from '@nmtjs/contract'
3
+ import type { ProtocolBlobInterface } from '@nmtjs/protocol'
4
+ import type {
5
+ ProtocolError,
6
+ ProtocolServerBlobStream,
7
+ ProtocolServerStreamInterface,
8
+ } from '@nmtjs/protocol/client'
9
+ import type { BaseTypeAny, PlainType, t } from '@nmtjs/type'
10
+
11
+ export const ResolvedType: unique symbol = Symbol('ResolvedType')
12
+ export type ResolvedType = typeof ResolvedType
13
+
14
+ export type ClientCallOptions = {
15
+ timeout?: number
16
+ signal?: AbortSignal
17
+ /**
18
+ * @internal
19
+ */
20
+ _stream_response?: boolean
21
+ }
22
+
23
+ export type ClientOutputType<T> = T extends ProtocolBlobInterface
24
+ ? (options?: { signal?: AbortSignal }) => ProtocolServerBlobStream
25
+ : T extends { [PlainType]?: true }
26
+ ? { [K in keyof Omit<T, PlainType>]: ClientOutputType<T[K]> }
27
+ : T
28
+
29
+ export interface StaticInputContractTypeProvider extends TypeProvider {
30
+ output: this['input'] extends BaseTypeAny
31
+ ? t.infer.decode.input<this['input']>
32
+ : never
33
+ }
34
+
35
+ export interface RuntimeInputContractTypeProvider extends TypeProvider {
36
+ output: this['input'] extends BaseTypeAny
37
+ ? t.infer.encode.input<this['input']>
38
+ : never
39
+ }
40
+
41
+ export interface StaticOutputContractTypeProvider extends TypeProvider {
42
+ output: this['input'] extends BaseTypeAny
43
+ ? ClientOutputType<t.infer.encodeRaw.output<this['input']>>
44
+ : never
45
+ }
46
+
47
+ export interface RuntimeOutputContractTypeProvider extends TypeProvider {
48
+ output: this['input'] extends BaseTypeAny
49
+ ? ClientOutputType<t.infer.decodeRaw.output<this['input']>>
50
+ : never
51
+ }
52
+
53
+ export type AnyResolvedContractProcedure = {
54
+ [ResolvedType]: 'procedure'
55
+ contract: TAnyProcedureContract
56
+ stream: boolean
57
+ input: any
58
+ output: any
59
+ }
60
+
61
+ export type AnyResolvedContractRouter = {
62
+ [ResolvedType]: 'router'
63
+ [key: string]:
64
+ | AnyResolvedContractProcedure
65
+ | { [ResolvedType]: 'router'; [key: string]: AnyResolvedContractProcedure }
66
+ }
67
+
68
+ export type ResolveAPIRouterRoutes<
69
+ T extends TAnyRouterContract,
70
+ InputTypeProvider extends TypeProvider = TypeProvider,
71
+ OutputTypeProvider extends TypeProvider = TypeProvider,
72
+ > = { [ResolvedType]: 'router' } & {
73
+ [K in keyof T['routes']]: T['routes'][K] extends TAnyProcedureContract
74
+ ? {
75
+ [ResolvedType]: 'procedure'
76
+ contract: T['routes'][K]
77
+ stream: T['routes'][K]['stream'] extends true ? true : false
78
+ input: CallTypeProvider<InputTypeProvider, T['routes'][K]['input']>
79
+ output: T['routes'][K]['stream'] extends true
80
+ ? AsyncIterable<
81
+ CallTypeProvider<OutputTypeProvider, T['routes'][K]['output']>
82
+ >
83
+ : CallTypeProvider<OutputTypeProvider, T['routes'][K]['output']>
84
+ }
85
+ : T['routes'][K] extends TAnyRouterContract
86
+ ? ResolveAPIRouterRoutes<
87
+ T['routes'][K],
88
+ InputTypeProvider,
89
+ OutputTypeProvider
90
+ >
91
+ : never
92
+ }
93
+
94
+ export type ResolveContract<
95
+ C extends TAnyRouterContract = TAnyRouterContract,
96
+ InputTypeProvider extends TypeProvider = TypeProvider,
97
+ OutputTypeProvider extends TypeProvider = TypeProvider,
98
+ > = ResolveAPIRouterRoutes<C, InputTypeProvider, OutputTypeProvider>
99
+
100
+ export type ClientCaller<
101
+ Procedure extends AnyResolvedContractProcedure,
102
+ SafeCall extends boolean,
103
+ > = (
104
+ ...args: Procedure['input'] extends t.NeverType
105
+ ? [data?: undefined, options?: Partial<ClientCallOptions>]
106
+ : undefined extends t.infer.encode.input<Procedure['contract']['input']>
107
+ ? [data?: Procedure['input'], options?: Partial<ClientCallOptions>]
108
+ : [data: Procedure['input'], options?: Partial<ClientCallOptions>]
109
+ ) => SafeCall extends true
110
+ ? Promise<OneOf<[{ result: Procedure['output'] }, { error: ProtocolError }]>>
111
+ : Promise<Procedure['output']>
112
+
113
+ type OmitType<T extends object, E> = {
114
+ [K in keyof T as T[K] extends E ? never : K]: T[K]
115
+ }
116
+
117
+ // export type FilterResolvedContractRouter<
118
+ // Resolved extends AnyResolvedContractRouter,
119
+ // Stream extends boolean,
120
+ // > = {
121
+ // [K in keyof Resolved]: Resolved[K] extends AnyResolvedContractProcedure
122
+ // ? Resolved[K]['stream'] extends Stream
123
+ // ? Resolved[K]
124
+ // : never
125
+ // : Resolved[K] extends AnyResolvedContractRouter
126
+ // ? FilterResolvedContractRouter<Resolved[K], Stream>
127
+ // : never
128
+ // }
129
+
130
+ export type ClientCallers<
131
+ Resolved extends AnyResolvedContractRouter,
132
+ SafeCall extends boolean,
133
+ Stream extends boolean,
134
+ > = OmitType<
135
+ {
136
+ [K in keyof Resolved]: Resolved[K] extends AnyResolvedContractProcedure
137
+ ? Stream extends (Resolved[K]['stream'] extends true ? true : false)
138
+ ? ClientCaller<Resolved[K], SafeCall>
139
+ : never
140
+ : Resolved[K] extends AnyResolvedContractRouter
141
+ ? ClientCallers<Resolved[K], SafeCall, Stream>
142
+ : never
143
+ },
144
+ never
145
+ >