@xxxaz/json-rpc-schema-typing 0.10.20 → 0.10.21
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/cjs/JsonSchemaValidator.js +1 -1
- package/cjs/JsonSchemaValidator.js.map +1 -1
- package/esm/JsonSchemaValidator.js +1 -1
- package/esm/JsonSchemaValidator.js.map +1 -1
- package/package.json +2 -1
- package/src/JsonRpcException.ts +135 -0
- package/src/JsonRpcMethod.ts +111 -0
- package/src/JsonRpcValidator.ts +41 -0
- package/src/JsonSchemaValidator.ts +195 -0
- package/src/WebSocketWrapper.ts +130 -0
- package/src/client/JsonRpcClient.ts +269 -0
- package/src/client/JsonRpcHttp2Client.ts +114 -0
- package/src/client/JsonRpcHttpClient.ts +71 -0
- package/src/client/JsonRpcMessagePortClient.ts +121 -0
- package/src/client/JsonRpcWebSocketClient.ts +143 -0
- package/src/client/index.ts +3 -0
- package/src/index.ts +11 -0
- package/src/router/FileSystemRouter.ts +78 -0
- package/src/router/JsonRpcRouter.ts +62 -0
- package/src/router/StaticRouter.ts +51 -0
- package/src/router/hashObject.ts +31 -0
- package/src/router/index.ts +3 -0
- package/src/schemas/Complex.ts +92 -0
- package/src/schemas/Primitive.ts +21 -0
- package/src/schemas/Structure.ts +42 -0
- package/src/schemas/index.ts +3 -0
- package/src/server/JsonRpcHttpReceiver.ts +126 -0
- package/src/server/JsonRpcMessagePortReceiver.ts +72 -0
- package/src/server/JsonRpcServer.ts +101 -0
- package/src/server/JsonRpcWebSocketReceiver.ts +49 -0
- package/src/server/index.ts +2 -0
- package/src/types.ts +81 -0
- package/src/utility.ts +54 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import http2 from 'http2';
|
|
3
|
+
import stream from 'stream';
|
|
4
|
+
|
|
5
|
+
type HttpServerRequest = http.IncomingMessage | http2.Http2ServerRequest;
|
|
6
|
+
type HttpServerResponse = http.ServerResponse | http2.Http2ServerResponse;
|
|
7
|
+
type OutgoingHttpHeaders = http.OutgoingHttpHeaders | http2.OutgoingHttpHeaders;
|
|
8
|
+
|
|
9
|
+
import { JsonStreamingParser, ParsingJsonArray, ParsingJsonTypes, StringifyingJsonArray } from '@xxxaz/stream-api-json';
|
|
10
|
+
import { JsonRpcServer } from './JsonRpcServer.js';
|
|
11
|
+
import { InternalError, JsonRpcException } from '../JsonRpcException.js';
|
|
12
|
+
import { JsonRpcRouter } from '../router/JsonRpcRouter.js';
|
|
13
|
+
import { isRpcRequest, stringifyStream, readStreamAll } from '../utility.js';
|
|
14
|
+
import { JsonRpcRequest } from '../types.js';
|
|
15
|
+
|
|
16
|
+
type HttpServeOptions = {
|
|
17
|
+
requestConverter?: (request: ReadableStream<string|Uint8Array>) => ReadableStream<string>;
|
|
18
|
+
responseConverter?: (response: ReadableStream<string>) => ReadableStream<Uint8Array|string>;
|
|
19
|
+
headers?: OutgoingHttpHeaders;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export class JsonRpcHttpReceiver<Ctx> extends JsonRpcServer<Ctx> {
|
|
23
|
+
constructor(router: JsonRpcRouter<Ctx>, options: HttpServeOptions = {}) {
|
|
24
|
+
super(router);
|
|
25
|
+
this.#requestConverter = options.requestConverter;
|
|
26
|
+
this.#responseConverter = options.responseConverter;
|
|
27
|
+
this.#headers = {
|
|
28
|
+
'Content-Type': 'application/json',
|
|
29
|
+
...options.headers,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
readonly #requestConverter?: (request: ReadableStream<string|Uint8Array>) => ReadableStream<string>;
|
|
33
|
+
readonly #responseConverter?: (response: ReadableStream<string>) => ReadableStream<Uint8Array|string>;
|
|
34
|
+
readonly #headers: OutgoingHttpHeaders;
|
|
35
|
+
|
|
36
|
+
#convertRequesrt(request: HttpServerRequest) {
|
|
37
|
+
const stream = new ReadableStream({
|
|
38
|
+
start(controller) {
|
|
39
|
+
request.on('error', (error) => controller.error(error));
|
|
40
|
+
request.on('data', (chunk) => controller.enqueue(chunk));
|
|
41
|
+
request.on('end', () => controller.close());
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
return this.#requestConverter?.(stream) ?? stream;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
#convertResponse(response: ReadableStream<string>) {
|
|
48
|
+
if (!this.#responseConverter) return response;
|
|
49
|
+
return this.#responseConverter(response);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async serve(context: Ctx, request: HttpServerRequest, response: HttpServerResponse): Promise<void> {
|
|
53
|
+
try {
|
|
54
|
+
const contentLegth = request.headers['content-length'];
|
|
55
|
+
const reqStream = this.#convertRequesrt(request);
|
|
56
|
+
const root = contentLegth
|
|
57
|
+
? JSON.parse(await (await readStreamAll(reqStream)).text())
|
|
58
|
+
: await JsonStreamingParser.readFrom(reqStream).root();
|
|
59
|
+
const { status, stream } = await this.#invoke(context, root);
|
|
60
|
+
const nodeStream = toNodeReadable(this.#convertResponse(stream));
|
|
61
|
+
const headers = response instanceof http.ServerResponse
|
|
62
|
+
? {
|
|
63
|
+
...this.#headers,
|
|
64
|
+
'Transfer-Encoding': 'chunked',
|
|
65
|
+
}
|
|
66
|
+
: this.#headers;
|
|
67
|
+
response.writeHead(status, headers);
|
|
68
|
+
nodeStream.pipe(response);
|
|
69
|
+
|
|
70
|
+
} catch (err: unknown) {
|
|
71
|
+
response.writeHead(500);
|
|
72
|
+
response.end(JSON.stringify({ jsonrpc: "2.0", error: new InternalError(String(err)).serialize() }));
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async #invoke(context: Ctx, req: JsonRpcRequest|JsonRpcRequest[]|ParsingJsonTypes) {
|
|
77
|
+
try {
|
|
78
|
+
if (isRpcRequest(req)) {
|
|
79
|
+
const result = await this.call(context, req);
|
|
80
|
+
return { status: 200, stream: stringifyStream(result) };
|
|
81
|
+
}
|
|
82
|
+
if (req instanceof Array) {
|
|
83
|
+
const result = this.batch(context, req);
|
|
84
|
+
const stream = new StringifyingJsonArray(result);
|
|
85
|
+
return { status: 200, stream };
|
|
86
|
+
}
|
|
87
|
+
if(req instanceof ParsingJsonArray) {
|
|
88
|
+
async function * iterateReq() {
|
|
89
|
+
for await (const m of req as ParsingJsonArray<any>) {
|
|
90
|
+
yield await m.all();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const result = this.batch(context, iterateReq());
|
|
94
|
+
const stream = new StringifyingJsonArray(result);
|
|
95
|
+
return { status: 200, stream };
|
|
96
|
+
}
|
|
97
|
+
const result = await this.call(context, await req.all());
|
|
98
|
+
return { status: 200, stream: stringifyStream(result) };
|
|
99
|
+
} catch (err: unknown) {
|
|
100
|
+
if(err instanceof JsonRpcException) {
|
|
101
|
+
const status = err instanceof InternalError ? 500 : 400;
|
|
102
|
+
const result = { jsonrpc: "2.0", error: err.serialize() };
|
|
103
|
+
return { status, stream: stringifyStream(result) };
|
|
104
|
+
}
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function toNodeReadable<T>(source: ReadableStream<T>) : stream.Readable {
|
|
111
|
+
const reader = source.getReader();
|
|
112
|
+
return new stream.Readable({
|
|
113
|
+
async destroy(error, callback) {
|
|
114
|
+
if (error) await reader.cancel(error);
|
|
115
|
+
callback(error);
|
|
116
|
+
},
|
|
117
|
+
async read() {
|
|
118
|
+
const { done, value } = await reader.read();
|
|
119
|
+
if (done) {
|
|
120
|
+
this.push(null);
|
|
121
|
+
} else {
|
|
122
|
+
this.push(value);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { JsonRpcServer } from './JsonRpcServer.js';
|
|
2
|
+
import { JsonRpcRouter } from '../router/JsonRpcRouter.js';
|
|
3
|
+
import { JsonRpcRequest, MessageInput, MessageListener, MessageOutput } from '../types.js';
|
|
4
|
+
import { isRpcRequest } from '../utility.js';
|
|
5
|
+
|
|
6
|
+
type MessagePortReceiverOprions = {
|
|
7
|
+
validator?: (event: MessageEvent) => boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export class JsonRpcMessagePortReceiver<Ctx> extends JsonRpcServer<Ctx> {
|
|
11
|
+
readonly #input: MessageInput;
|
|
12
|
+
readonly #validator: (event: MessageEvent) => boolean;
|
|
13
|
+
|
|
14
|
+
constructor(router: JsonRpcRouter<Ctx>, messageInput: MessageInput, options: MessagePortReceiverOprions = {}) {
|
|
15
|
+
super(router);
|
|
16
|
+
this.#input = messageInput;
|
|
17
|
+
this.#validator = options.validator ?? defaultValidator;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async #onMessage(context: Ctx, output: MessageOutput, event: MessageEvent) {
|
|
21
|
+
if (event.source && event.source !== output) return;
|
|
22
|
+
const { data } = event;
|
|
23
|
+
const request
|
|
24
|
+
= (data instanceof Array && data.some(isRpcRequest))
|
|
25
|
+
? data as JsonRpcRequest[]
|
|
26
|
+
: isRpcRequest(data)
|
|
27
|
+
? data
|
|
28
|
+
: null;
|
|
29
|
+
|
|
30
|
+
if (!request) {
|
|
31
|
+
console.debug('message is not JsonRpcRequest', request);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (!this.#validator(event)) {
|
|
35
|
+
console.warn('Invalid Message:', event);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const response
|
|
40
|
+
= request instanceof Array
|
|
41
|
+
? await Promise.all(request.map(req => this.call(context, req)))
|
|
42
|
+
: await this.call(context, request);
|
|
43
|
+
|
|
44
|
+
if (event.source instanceof Window) {
|
|
45
|
+
event.source.postMessage(response, event.origin);
|
|
46
|
+
} else {
|
|
47
|
+
(event.source ?? output).postMessage(response);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
readonly #listeners = new WeakMap<MessageOutput, MessageListener>();
|
|
52
|
+
serve(context: Ctx, output: MessageOutput) {
|
|
53
|
+
if (this.#listeners.has(output)) return;
|
|
54
|
+
const listener = this.#onMessage.bind(this, context, output);
|
|
55
|
+
this.#listeners.set(output, listener);
|
|
56
|
+
this.#input.addEventListener('message', listener);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
unservce(output: MessageOutput) {
|
|
60
|
+
const listener = this.#listeners.get(output);
|
|
61
|
+
if (!listener) return;
|
|
62
|
+
this.#input.removeEventListener('message', listener);
|
|
63
|
+
this.#listeners.delete(output);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function defaultValidator(event: MessageEvent) {
|
|
68
|
+
if (event.source instanceof Window) {
|
|
69
|
+
return event.origin === globalThis.origin;
|
|
70
|
+
}
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { LazyResolvers } from "@xxxaz/stream-api-json/utility";
|
|
2
|
+
import { JsonRpcException, MethodNotFound, InternalError, InvalidRequest } from "../JsonRpcException.js";
|
|
3
|
+
import { JsonRpcMethodDefinition } from "../JsonRpcMethod.js";
|
|
4
|
+
import { type JsonRpcRouter } from "../router/JsonRpcRouter.js";
|
|
5
|
+
import { type JsonRpcRequest, type JsonRpcResponse } from "../types.js";
|
|
6
|
+
import { JsonRpcValidator } from "../JsonRpcValidator.js";
|
|
7
|
+
|
|
8
|
+
type MethodDef<Context> = JsonRpcMethodDefinition<Context, any, any>;
|
|
9
|
+
|
|
10
|
+
export class JsonRpcServer<Context> {
|
|
11
|
+
constructor(readonly router: JsonRpcRouter<Context>) {
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
private validateRequest(request: JsonRpcRequest): void {
|
|
15
|
+
const { jsonrpc, method, params } = request;
|
|
16
|
+
if(jsonrpc as string !== '2.0') throw new InvalidRequest('jsonrpc must be "2.0"');
|
|
17
|
+
if(!method) throw new InvalidRequest('method is required.');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
private async pickMethodDefine(methodPath: string): Promise<MethodDef<Context>> {
|
|
21
|
+
const picked = await this.router.resolve(methodPath) as MethodDef<Context>|null;
|
|
22
|
+
if (!picked) {
|
|
23
|
+
throw new MethodNotFound(`Definition of method ${methodPath} is not Found.`);
|
|
24
|
+
}
|
|
25
|
+
const methodKey = (picked.constructor as typeof JsonRpcMethodDefinition).method as keyof MethodDef<Context>;
|
|
26
|
+
if (picked[methodKey] instanceof Function) {
|
|
27
|
+
return picked;
|
|
28
|
+
}
|
|
29
|
+
throw new MethodNotFound(`Definition ${methodPath} dose not have method.`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private async execute(ctx: Context, request: JsonRpcRequest) {
|
|
33
|
+
try {
|
|
34
|
+
this.validateRequest(request);
|
|
35
|
+
const { method, params } = request;
|
|
36
|
+
|
|
37
|
+
const picked = await this.pickMethodDefine(method);
|
|
38
|
+
const validator = new JsonRpcValidator(picked);
|
|
39
|
+
|
|
40
|
+
validator.validateParams(params);
|
|
41
|
+
const result = await picked.$apply(ctx, params);
|
|
42
|
+
try {
|
|
43
|
+
validator.validateReturn(result);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
console.warn(`Invalid Result: ${method}`, e);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
} catch (err: unknown) {
|
|
49
|
+
console.error(err);
|
|
50
|
+
if(err instanceof JsonRpcException) throw err;
|
|
51
|
+
throw new InternalError(String(err));
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async call(ctx: Context, request: JsonRpcRequest): Promise<JsonRpcResponse<any>> {
|
|
56
|
+
const jsonrpc = '2.0';
|
|
57
|
+
const id = request.id! ?? null;
|
|
58
|
+
try {
|
|
59
|
+
const result = await this.execute(ctx, request);
|
|
60
|
+
return { jsonrpc, id, result };
|
|
61
|
+
} catch (err: unknown) {
|
|
62
|
+
const error = err instanceof JsonRpcException
|
|
63
|
+
? err.serialize()
|
|
64
|
+
: new InternalError(String(err)).serialize();
|
|
65
|
+
return { jsonrpc, id, error };
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
async * batch(ctx: Context, requests: AsyncIterable<JsonRpcRequest>|Iterable<JsonRpcRequest>) {
|
|
70
|
+
const promises: { [id: number|string]: Promise<JsonRpcResponse<any>>} = {};
|
|
71
|
+
let loading: LazyResolvers<void> = new LazyResolvers();
|
|
72
|
+
let loadedAll = false;
|
|
73
|
+
(async () => {
|
|
74
|
+
for await (const request of requests) {
|
|
75
|
+
const id = request.id;
|
|
76
|
+
if (id == null) {
|
|
77
|
+
this.execute(ctx, request);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
promises[id] = this.call(ctx, request);
|
|
81
|
+
loading.resolve();
|
|
82
|
+
loading = new LazyResolvers();
|
|
83
|
+
}
|
|
84
|
+
loadedAll = true;
|
|
85
|
+
loading.resolve();
|
|
86
|
+
})();
|
|
87
|
+
|
|
88
|
+
while(true) {
|
|
89
|
+
const list = Object.values(promises);
|
|
90
|
+
if(!list.length) {
|
|
91
|
+
if (loadedAll) break;
|
|
92
|
+
await loading.promise;
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const response = await Promise.race(list);
|
|
96
|
+
yield response;
|
|
97
|
+
delete promises[response.id!];
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { JsonRpcServer } from './JsonRpcServer.js';
|
|
2
|
+
import { JsonRpcRequest } from '../types.js';
|
|
3
|
+
import { isRpcRequest } from '../utility.js';
|
|
4
|
+
import { WebSocketWrapper, WrapableWebSocket, wrapWebSocket } from '../WebSocketWrapper.js';
|
|
5
|
+
import { type JsonSerializable } from '@xxxaz/stream-api-json';
|
|
6
|
+
|
|
7
|
+
export class JsonRpcWebSocketReceiver<Ctx> extends JsonRpcServer<Ctx> {
|
|
8
|
+
async #onMessage(context: Ctx, socket: WebSocketWrapper, data: JsonSerializable) {
|
|
9
|
+
const request
|
|
10
|
+
= (data instanceof Array && data.some(isRpcRequest))
|
|
11
|
+
? data as JsonRpcRequest[]
|
|
12
|
+
: isRpcRequest(data)
|
|
13
|
+
? data
|
|
14
|
+
: null;
|
|
15
|
+
|
|
16
|
+
if (!request) {
|
|
17
|
+
console.debug('message is not JsonRpcRequest', request);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const response
|
|
22
|
+
= request instanceof Array
|
|
23
|
+
? await Promise.all(request.map(req => this.call(context, req)))
|
|
24
|
+
: await this.call(context, request);
|
|
25
|
+
|
|
26
|
+
socket.send(response);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
readonly #sockets = new Map<WrapableWebSocket, WebSocketWrapper>();
|
|
30
|
+
serve(context: Ctx, socket: WrapableWebSocket) {
|
|
31
|
+
if (this.#sockets.has(socket)) return;
|
|
32
|
+
const listener = (data: JsonSerializable) => this.#onMessage(context, wrapper, data);
|
|
33
|
+
const wrapper = wrapWebSocket(
|
|
34
|
+
socket,
|
|
35
|
+
listener,
|
|
36
|
+
(close) => {
|
|
37
|
+
console.debug('socket closed', close);
|
|
38
|
+
this.unservce(socket);
|
|
39
|
+
}
|
|
40
|
+
);
|
|
41
|
+
this.#sockets.set(socket, wrapper);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
unservce(socket: WrapableWebSocket) {
|
|
45
|
+
const wrapper = this.#sockets.get(socket);
|
|
46
|
+
wrapper?.close();
|
|
47
|
+
this.#sockets.delete(socket);
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { FromSchema as ToTsFromSchema, type JSONSchema as ToTsSchema } from 'json-schema-to-ts';
|
|
2
|
+
import { type JsonSerializable } from "@xxxaz/stream-api-json";
|
|
3
|
+
|
|
4
|
+
export type TupleSchema<T extends JSONSchema[]> = {
|
|
5
|
+
type: 'array';
|
|
6
|
+
items: T;
|
|
7
|
+
minItems: number;
|
|
8
|
+
maxItems: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type IsOptionalSchema<Sch, T, F> = false extends (Sch extends { oneOf: readonly ToTsSchema[] } ? Sch['oneOf'][number] : never) ? T : F;
|
|
12
|
+
type FromTuples<Schemas> =
|
|
13
|
+
Schemas extends [infer Head extends JSONSchema, ...infer Tail]
|
|
14
|
+
? (
|
|
15
|
+
IsOptionalSchema<Head,
|
|
16
|
+
[t?: ToTsFromSchema<Head>, ...FromTuples<Tail>],
|
|
17
|
+
[ToTsFromSchema<Head>, ...FromTuples<Tail>]
|
|
18
|
+
>
|
|
19
|
+
) : [];
|
|
20
|
+
|
|
21
|
+
export type FromSchema<T extends JSONSchema>
|
|
22
|
+
= (
|
|
23
|
+
T extends TupleSchema<infer U>
|
|
24
|
+
? FromTuples<U>
|
|
25
|
+
: ToTsFromSchema<T>
|
|
26
|
+
) | IsOptionalSchema<T, undefined, never>;
|
|
27
|
+
|
|
28
|
+
type SchemaObject = ToTsSchema & object;
|
|
29
|
+
type SchemaTypeSymbol = Exclude<keyof SchemaObject, string|number>;
|
|
30
|
+
export type JSONSchema = Omit<SchemaObject, SchemaTypeSymbol>;
|
|
31
|
+
|
|
32
|
+
export type JsonRpcRequest = {
|
|
33
|
+
jsonrpc: '2.0';
|
|
34
|
+
id?: number|string;
|
|
35
|
+
method: string;
|
|
36
|
+
params: any;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
export type JsonRpcError = Readonly<{
|
|
41
|
+
code: number;
|
|
42
|
+
message?: string;
|
|
43
|
+
data?: JsonSerializable;
|
|
44
|
+
}>;
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
export type JsonRpcResponse<Result extends JsonSerializable> = {
|
|
48
|
+
readonly jsonrpc: '2.0';
|
|
49
|
+
readonly id: number|string;
|
|
50
|
+
readonly result: Result;
|
|
51
|
+
}|{
|
|
52
|
+
readonly jsonrpc: '2.0';
|
|
53
|
+
readonly id: number|string|null;
|
|
54
|
+
readonly error: JsonRpcError;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
export type MessageListener = (ev: MessageEvent) => void;
|
|
59
|
+
export type MessageInput = {
|
|
60
|
+
addEventListener(type: 'message', listener: MessageListener): void;
|
|
61
|
+
removeEventListener(type: 'message', listener: MessageListener): void;
|
|
62
|
+
};
|
|
63
|
+
export type MessageOutput = {
|
|
64
|
+
postMessage(message: any): void;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type FitMin<N extends number, C extends void[] = []> = C['length'] extends N ? C : FitMin<N, [void, ...C]>;
|
|
68
|
+
type FitMax<N extends number, C extends void[] = FitMin<N>> = [void, ...C]['length'] extends N ? FitMax<N, [void, ...C]> : C;
|
|
69
|
+
|
|
70
|
+
export type Min<NumLiteral extends number> = FitMin<NumLiteral> extends any[] ? FitMin<NumLiteral>['length']: never;
|
|
71
|
+
export type Max<NumLiteral extends number> = FitMax<NumLiteral> extends any[] ? FitMax<NumLiteral>['length'] : never;
|
|
72
|
+
|
|
73
|
+
export type IsOptional<A, T, F = never> = A extends { oneOf: readonly [...any, false, ...any] } ? T : A|undefined extends A ? T : F;
|
|
74
|
+
export type IsNever<A, T, F = never> = [A] extends [never] ? T : F;
|
|
75
|
+
export type PerfectMatch<A, B, T, F = never> = [A] extends [B] ? ([B] extends [A] ? T : F) : F;
|
|
76
|
+
|
|
77
|
+
export type OptionalKeys<T> = Exclude<{ [K in keyof T]: IsOptional<T[K], K, never> }[keyof T], undefined>;
|
|
78
|
+
export type RequiredKeys<T> = Exclude<keyof T, OptionalKeys<T>>;
|
|
79
|
+
|
|
80
|
+
declare const InvalidRef: unique symbol;
|
|
81
|
+
export type InvalidRef<Msg> = Omit<Msg&[never], keyof Msg|keyof [never]>;
|
package/src/utility.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { type JsonSerializable } from "@xxxaz/stream-api-json";
|
|
2
|
+
import { JsonRpcRequest, JsonRpcResponse } from "./types.js";
|
|
3
|
+
|
|
4
|
+
export function toStream<T>(src: T) {
|
|
5
|
+
return new ReadableStream<T>({
|
|
6
|
+
start(controller) {
|
|
7
|
+
controller.enqueue(src);
|
|
8
|
+
controller.close();
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function stringifyStream(json: JsonSerializable) {
|
|
14
|
+
return toStream(JSON.stringify(json));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function emptyStrem() {
|
|
18
|
+
return new ReadableStream({ start: c => c.close() });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function readStreamAll(stream: ReadableStream<string|ArrayBuffer>) : Promise<Blob> {
|
|
22
|
+
const reader = stream.getReader();
|
|
23
|
+
const result = [] as (ArrayBuffer|string)[];
|
|
24
|
+
while (true) {
|
|
25
|
+
const { done, value } = await reader.read();
|
|
26
|
+
if (value) result.push(value);
|
|
27
|
+
if (done) break;
|
|
28
|
+
}
|
|
29
|
+
return new Blob(result);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isRpcRequest(data: any): data is JsonRpcRequest {
|
|
33
|
+
return (
|
|
34
|
+
data instanceof Object
|
|
35
|
+
&&
|
|
36
|
+
data.jsonrpc === '2.0'
|
|
37
|
+
&&
|
|
38
|
+
typeof data.method === 'string'
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function isRpcResponse(data: any): data is JsonRpcResponse<any> {
|
|
43
|
+
return (
|
|
44
|
+
data instanceof Object
|
|
45
|
+
&&
|
|
46
|
+
data.jsonrpc === '2.0'
|
|
47
|
+
&&
|
|
48
|
+
(
|
|
49
|
+
'id' in data
|
|
50
|
+
||
|
|
51
|
+
'error' in data
|
|
52
|
+
)
|
|
53
|
+
);
|
|
54
|
+
}
|