@nmtjs/http-client 0.15.0-beta.5 → 0.15.0-beta.50
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/README.md +31 -1
- package/dist/http-stream-parser.d.ts +8 -0
- package/dist/http-stream-parser.js +75 -0
- package/dist/http-stream-parser.js.map +1 -0
- package/dist/index.d.ts +12 -5
- package/dist/index.js +81 -26
- package/dist/index.js.map +1 -1
- package/package.json +9 -11
- package/src/http-stream-parser.ts +91 -0
- package/src/index.ts +111 -31
package/README.md
CHANGED
|
@@ -6,4 +6,34 @@
|
|
|
6
6
|
- binary data streaming and event subscriptions
|
|
7
7
|
- contract-based API
|
|
8
8
|
- end-to-end type safety
|
|
9
|
-
- CPU-intensive task execution on separate workers
|
|
9
|
+
- CPU-intensive task execution on separate workers
|
|
10
|
+
|
|
11
|
+
## Jobs E2E (Local Docker)
|
|
12
|
+
|
|
13
|
+
Prerequisites:
|
|
14
|
+
- Docker Desktop (or Docker Engine) with `docker compose` available
|
|
15
|
+
|
|
16
|
+
No host ports are exposed in this setup. Tests run fully inside Docker containers.
|
|
17
|
+
The test image is optimized for Docker layer caching with `pnpm fetch` + offline install.
|
|
18
|
+
|
|
19
|
+
Run both backends with one command:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm run test:e2e:jobs:docker
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
This command rebuilds the test image on each run and executes Redis + Valkey suites in one pass.
|
|
26
|
+
|
|
27
|
+
Run Jobs E2E directly in Docker:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
docker compose -f docker-compose.jobs-e2e.yml run --rm --build test-jobs
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Manual Docker control:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
docker compose -f docker-compose.jobs-e2e.yml up -d --wait redis valkey
|
|
37
|
+
docker compose -f docker-compose.jobs-e2e.yml run --rm --build test-jobs
|
|
38
|
+
docker compose -f docker-compose.jobs-e2e.yml down -v --remove-orphans
|
|
39
|
+
```
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
export class HttpStreamParser {
|
|
2
|
+
pending = '';
|
|
3
|
+
push(chunk, emit) {
|
|
4
|
+
this.pending += chunk;
|
|
5
|
+
this.pending = this.parse(this.pending, emit);
|
|
6
|
+
}
|
|
7
|
+
finish(emit) {
|
|
8
|
+
this.pending = this.parse(this.pending, emit);
|
|
9
|
+
if (this.pending.trim().length > 0) {
|
|
10
|
+
throw new Error('Malformed stream response frame');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
parse(pending, emit) {
|
|
14
|
+
let cursor = 0;
|
|
15
|
+
while (true) {
|
|
16
|
+
const separator = this.findNextEventSeparator(pending, cursor);
|
|
17
|
+
if (!separator)
|
|
18
|
+
break;
|
|
19
|
+
this.emitEventDataFrame(pending, cursor, separator.index, emit);
|
|
20
|
+
cursor = separator.index + separator.length;
|
|
21
|
+
}
|
|
22
|
+
if (cursor === 0)
|
|
23
|
+
return pending;
|
|
24
|
+
return pending.slice(cursor);
|
|
25
|
+
}
|
|
26
|
+
findNextEventSeparator(source, fromIndex) {
|
|
27
|
+
const lf = source.indexOf('\n\n', fromIndex);
|
|
28
|
+
const crlf = source.indexOf('\r\n\r\n', fromIndex);
|
|
29
|
+
if (lf < 0 && crlf < 0)
|
|
30
|
+
return null;
|
|
31
|
+
if (lf < 0)
|
|
32
|
+
return { index: crlf, length: 4 };
|
|
33
|
+
if (crlf < 0)
|
|
34
|
+
return { index: lf, length: 2 };
|
|
35
|
+
return lf < crlf ? { index: lf, length: 2 } : { index: crlf, length: 4 };
|
|
36
|
+
}
|
|
37
|
+
emitEventDataFrame(source, start, end, emit) {
|
|
38
|
+
const chunks = [];
|
|
39
|
+
let lineStart = start;
|
|
40
|
+
while (lineStart <= end) {
|
|
41
|
+
let lineEnd = source.indexOf('\n', lineStart);
|
|
42
|
+
if (lineEnd < 0 || lineEnd > end)
|
|
43
|
+
lineEnd = end;
|
|
44
|
+
let contentEnd = lineEnd;
|
|
45
|
+
if (contentEnd > lineStart &&
|
|
46
|
+
source.charCodeAt(contentEnd - 1) === 13 /* \r */) {
|
|
47
|
+
contentEnd -= 1;
|
|
48
|
+
}
|
|
49
|
+
if (contentEnd - lineStart >= 5 &&
|
|
50
|
+
source.charCodeAt(lineStart) === 100 /* d */ &&
|
|
51
|
+
source.charCodeAt(lineStart + 1) === 97 /* a */ &&
|
|
52
|
+
source.charCodeAt(lineStart + 2) === 116 /* t */ &&
|
|
53
|
+
source.charCodeAt(lineStart + 3) === 97 /* a */ &&
|
|
54
|
+
source.charCodeAt(lineStart + 4) === 58 /* : */) {
|
|
55
|
+
let dataStart = lineStart + 5;
|
|
56
|
+
while (dataStart < contentEnd) {
|
|
57
|
+
const code = source.charCodeAt(dataStart);
|
|
58
|
+
if (code !== 32 /* space */ && code !== 9 /* tab */)
|
|
59
|
+
break;
|
|
60
|
+
dataStart += 1;
|
|
61
|
+
}
|
|
62
|
+
chunks.push(source.slice(dataStart, contentEnd));
|
|
63
|
+
}
|
|
64
|
+
if (lineEnd >= end)
|
|
65
|
+
break;
|
|
66
|
+
lineStart = lineEnd + 1;
|
|
67
|
+
}
|
|
68
|
+
if (!chunks.length)
|
|
69
|
+
return;
|
|
70
|
+
const data = chunks.join('\n');
|
|
71
|
+
if (data.length > 0)
|
|
72
|
+
emit(data);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=http-stream-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-stream-parser.js","sourceRoot":"","sources":["../src/http-stream-parser.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type HttpClientTransportOptions = {
|
|
|
11
11
|
url: string;
|
|
12
12
|
debug?: boolean;
|
|
13
13
|
EventSource?: typeof EventSource;
|
|
14
|
+
fetch?: typeof fetch;
|
|
14
15
|
decodeBase64?: DecodeBase64Function;
|
|
15
16
|
};
|
|
16
17
|
export declare class HttpTransportClient implements ClientTransport<ConnectionType.Unidirectional> {
|
|
@@ -20,7 +21,8 @@ export declare class HttpTransportClient implements ClientTransport<ConnectionTy
|
|
|
20
21
|
type: ConnectionType.Unidirectional;
|
|
21
22
|
decodeBase64: DecodeBase64Function;
|
|
22
23
|
constructor(format: BaseClientFormat, protocol: ProtocolVersion, options: HttpClientTransportOptions);
|
|
23
|
-
|
|
24
|
+
private getFetch;
|
|
25
|
+
url({ procedure, application, payload }: {
|
|
24
26
|
procedure: string;
|
|
25
27
|
application?: string;
|
|
26
28
|
payload?: unknown;
|
|
@@ -31,21 +33,26 @@ export declare class HttpTransportClient implements ClientTransport<ConnectionTy
|
|
|
31
33
|
payload: unknown;
|
|
32
34
|
}, options: ClientTransportMessageOptions): Promise<{
|
|
33
35
|
type: "rpc_stream";
|
|
34
|
-
stream: ReadableStream<ArrayBufferView
|
|
36
|
+
stream: ReadableStream<ArrayBufferView<ArrayBufferLike>>;
|
|
37
|
+
metadata?: undefined;
|
|
38
|
+
source?: undefined;
|
|
39
|
+
result?: undefined;
|
|
35
40
|
} | {
|
|
41
|
+
stream?: undefined;
|
|
36
42
|
type: "blob";
|
|
37
43
|
metadata: {
|
|
38
44
|
type: string;
|
|
39
45
|
size: number | undefined;
|
|
40
46
|
filename: string | undefined;
|
|
41
47
|
};
|
|
42
|
-
source:
|
|
48
|
+
source: ReadableStream<Uint8Array<ArrayBuffer>>;
|
|
43
49
|
result?: undefined;
|
|
44
50
|
} | {
|
|
45
|
-
|
|
46
|
-
result: Uint8Array<ArrayBuffer>;
|
|
51
|
+
stream?: undefined;
|
|
47
52
|
metadata?: undefined;
|
|
48
53
|
source?: undefined;
|
|
54
|
+
type: "rpc";
|
|
55
|
+
result: Uint8Array<ArrayBuffer>;
|
|
49
56
|
}>;
|
|
50
57
|
}
|
|
51
58
|
export type HttpTransportFactory = ClientTransportFactory<ConnectionType.Unidirectional, HttpClientTransportOptions, HttpTransportClient>;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ConnectionType, ErrorCode, ProtocolBlob } from '@nmtjs/protocol';
|
|
2
|
+
import { ProtocolError } from '@nmtjs/protocol/client';
|
|
3
|
+
import { HttpStreamParser } from './http-stream-parser.js';
|
|
3
4
|
const createDecodeBase64 = (customFn) => {
|
|
4
5
|
return (string) => {
|
|
5
6
|
if ('fromBase64' in Uint8Array &&
|
|
@@ -31,6 +32,13 @@ export class HttpTransportClient {
|
|
|
31
32
|
this.options = { debug: false, ...options };
|
|
32
33
|
this.decodeBase64 = createDecodeBase64(options.decodeBase64);
|
|
33
34
|
}
|
|
35
|
+
getFetch() {
|
|
36
|
+
const implementation = this.options.fetch ?? globalThis.fetch;
|
|
37
|
+
if (!implementation) {
|
|
38
|
+
throw new Error('Fetch API is not available. Provide HttpClientTransportOptions.fetch');
|
|
39
|
+
}
|
|
40
|
+
return implementation;
|
|
41
|
+
}
|
|
34
42
|
url({ procedure, application, payload, }) {
|
|
35
43
|
const base = application ? `/${application}/${procedure}` : `/${procedure}`;
|
|
36
44
|
const url = new URL(base, this.options.url);
|
|
@@ -41,9 +49,11 @@ export class HttpTransportClient {
|
|
|
41
49
|
async call(client, rpc, options) {
|
|
42
50
|
const { procedure, payload } = rpc;
|
|
43
51
|
const requestHeaders = new Headers();
|
|
52
|
+
const fetchImpl = this.getFetch();
|
|
44
53
|
const url = this.url({ application: client.application, procedure });
|
|
45
54
|
if (client.auth)
|
|
46
55
|
requestHeaders.set('Authorization', client.auth);
|
|
56
|
+
requestHeaders.set('Accept', client.format.contentType);
|
|
47
57
|
let body;
|
|
48
58
|
if (payload instanceof ProtocolBlob) {
|
|
49
59
|
requestHeaders.set('Content-Type', payload.metadata.type);
|
|
@@ -55,34 +65,71 @@ export class HttpTransportClient {
|
|
|
55
65
|
body = buffer;
|
|
56
66
|
}
|
|
57
67
|
if (options._stream_response) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
:
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
source.addEventListener('open', () => future.resolve({ type: 'rpc_stream', stream: readable }));
|
|
66
|
-
source.addEventListener('close', () => writable.close());
|
|
67
|
-
source.addEventListener('error', (event) => {
|
|
68
|
-
const error = new Error('Stream error', { cause: event });
|
|
69
|
-
future.reject(error);
|
|
70
|
-
writable.abort(error);
|
|
68
|
+
const response = await fetchImpl(url.toString(), {
|
|
69
|
+
body,
|
|
70
|
+
method: 'POST',
|
|
71
|
+
headers: requestHeaders,
|
|
72
|
+
signal: options.signal,
|
|
73
|
+
credentials: 'include',
|
|
74
|
+
keepalive: true,
|
|
71
75
|
});
|
|
72
|
-
|
|
76
|
+
if (!response.ok) {
|
|
73
77
|
try {
|
|
74
|
-
const buffer =
|
|
75
|
-
|
|
78
|
+
const buffer = await response.bytes();
|
|
79
|
+
const error = client.format.decode(buffer);
|
|
80
|
+
throw new ProtocolError(error.code || ErrorCode.ClientRequestError, error.message || response.statusText, error.data);
|
|
76
81
|
}
|
|
77
82
|
catch (cause) {
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
if (cause instanceof ProtocolError)
|
|
84
|
+
throw cause;
|
|
85
|
+
throw new ProtocolError(ErrorCode.ClientRequestError, `HTTP ${response.status}: ${response.statusText}`);
|
|
80
86
|
}
|
|
87
|
+
}
|
|
88
|
+
if (!response.body) {
|
|
89
|
+
throw new ProtocolError(ErrorCode.ClientRequestError, 'Empty stream response body');
|
|
90
|
+
}
|
|
91
|
+
const stream = new ReadableStream({
|
|
92
|
+
start: async (controller) => {
|
|
93
|
+
const reader = response.body.getReader();
|
|
94
|
+
const decoder = new TextDecoder();
|
|
95
|
+
const parser = new HttpStreamParser();
|
|
96
|
+
try {
|
|
97
|
+
while (true) {
|
|
98
|
+
const { done, value } = await reader.read();
|
|
99
|
+
if (done)
|
|
100
|
+
break;
|
|
101
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
102
|
+
parser.push(chunk, (eventData) => {
|
|
103
|
+
controller.enqueue(this.decodeBase64(eventData));
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const tail = decoder.decode();
|
|
107
|
+
parser.push(tail, (eventData) => {
|
|
108
|
+
controller.enqueue(this.decodeBase64(eventData));
|
|
109
|
+
});
|
|
110
|
+
parser.finish((eventData) => {
|
|
111
|
+
controller.enqueue(this.decodeBase64(eventData));
|
|
112
|
+
});
|
|
113
|
+
controller.close();
|
|
114
|
+
}
|
|
115
|
+
catch (cause) {
|
|
116
|
+
controller.error(new Error('Stream error', { cause }));
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
reader.releaseLock();
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
cancel: async () => {
|
|
123
|
+
try {
|
|
124
|
+
await response.body?.cancel();
|
|
125
|
+
}
|
|
126
|
+
catch { }
|
|
127
|
+
},
|
|
81
128
|
});
|
|
82
|
-
return
|
|
129
|
+
return { type: 'rpc_stream', stream };
|
|
83
130
|
}
|
|
84
131
|
else {
|
|
85
|
-
const response = await
|
|
132
|
+
const response = await fetchImpl(url.toString(), {
|
|
86
133
|
body,
|
|
87
134
|
method: 'POST',
|
|
88
135
|
headers: requestHeaders,
|
|
@@ -106,7 +153,7 @@ export class HttpTransportClient {
|
|
|
106
153
|
return {
|
|
107
154
|
type: 'blob',
|
|
108
155
|
metadata: { type, size, filename },
|
|
109
|
-
source: body,
|
|
156
|
+
source: response.body,
|
|
110
157
|
};
|
|
111
158
|
}
|
|
112
159
|
else {
|
|
@@ -114,9 +161,17 @@ export class HttpTransportClient {
|
|
|
114
161
|
}
|
|
115
162
|
}
|
|
116
163
|
else {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
164
|
+
try {
|
|
165
|
+
const buffer = await response.bytes();
|
|
166
|
+
const error = client.format.decode(buffer);
|
|
167
|
+
throw new ProtocolError(error.code || ErrorCode.ClientRequestError, error.message || response.statusText, error.data);
|
|
168
|
+
}
|
|
169
|
+
catch (cause) {
|
|
170
|
+
if (cause instanceof ProtocolError)
|
|
171
|
+
throw cause;
|
|
172
|
+
// If decoding fails, throw generic error with status info
|
|
173
|
+
throw new ProtocolError(ErrorCode.ClientRequestError, `HTTP ${response.status}: ${response.statusText}`);
|
|
174
|
+
}
|
|
120
175
|
}
|
|
121
176
|
}
|
|
122
177
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,YAAY,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nmtjs/http-client",
|
|
3
|
+
"description": "HTTP client transport for Neemata.",
|
|
3
4
|
"type": "module",
|
|
4
5
|
"exports": {
|
|
5
6
|
".": {
|
|
@@ -9,14 +10,14 @@
|
|
|
9
10
|
}
|
|
10
11
|
},
|
|
11
12
|
"dependencies": {
|
|
12
|
-
"@nmtjs/client": "0.15.0-beta.
|
|
13
|
-
"@nmtjs/
|
|
14
|
-
"@nmtjs/
|
|
13
|
+
"@nmtjs/client": "0.15.0-beta.50",
|
|
14
|
+
"@nmtjs/common": "0.15.0-beta.50",
|
|
15
|
+
"@nmtjs/protocol": "0.15.0-beta.50"
|
|
15
16
|
},
|
|
16
17
|
"peerDependencies": {
|
|
17
|
-
"@nmtjs/
|
|
18
|
-
"@nmtjs/
|
|
19
|
-
"@nmtjs/
|
|
18
|
+
"@nmtjs/client": "0.15.0-beta.50",
|
|
19
|
+
"@nmtjs/protocol": "0.15.0-beta.50",
|
|
20
|
+
"@nmtjs/common": "0.15.0-beta.50"
|
|
20
21
|
},
|
|
21
22
|
"files": [
|
|
22
23
|
"dist",
|
|
@@ -24,11 +25,8 @@
|
|
|
24
25
|
"LICENSE.md",
|
|
25
26
|
"README.md"
|
|
26
27
|
],
|
|
27
|
-
"version": "0.15.0-beta.
|
|
28
|
+
"version": "0.15.0-beta.50",
|
|
28
29
|
"scripts": {
|
|
29
|
-
"clean-build": "rm -rf ./dist"
|
|
30
|
-
"build": "tsc --declaration --sourcemap",
|
|
31
|
-
"dev": "tsc --watch",
|
|
32
|
-
"type-check": "tsc --noEmit"
|
|
30
|
+
"clean-build": "rm -rf ./dist"
|
|
33
31
|
}
|
|
34
32
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export class HttpStreamParser {
|
|
2
|
+
private pending = ''
|
|
3
|
+
|
|
4
|
+
push(chunk: string, emit: (data: string) => void) {
|
|
5
|
+
this.pending += chunk
|
|
6
|
+
this.pending = this.parse(this.pending, emit)
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
finish(emit: (data: string) => void) {
|
|
10
|
+
this.pending = this.parse(this.pending, emit)
|
|
11
|
+
|
|
12
|
+
if (this.pending.trim().length > 0) {
|
|
13
|
+
throw new Error('Malformed stream response frame')
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
private parse(pending: string, emit: (data: string) => void): string {
|
|
18
|
+
let cursor = 0
|
|
19
|
+
|
|
20
|
+
while (true) {
|
|
21
|
+
const separator = this.findNextEventSeparator(pending, cursor)
|
|
22
|
+
if (!separator) break
|
|
23
|
+
|
|
24
|
+
this.emitEventDataFrame(pending, cursor, separator.index, emit)
|
|
25
|
+
cursor = separator.index + separator.length
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (cursor === 0) return pending
|
|
29
|
+
return pending.slice(cursor)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
private findNextEventSeparator(
|
|
33
|
+
source: string,
|
|
34
|
+
fromIndex: number,
|
|
35
|
+
): { index: number; length: number } | null {
|
|
36
|
+
const lf = source.indexOf('\n\n', fromIndex)
|
|
37
|
+
const crlf = source.indexOf('\r\n\r\n', fromIndex)
|
|
38
|
+
|
|
39
|
+
if (lf < 0 && crlf < 0) return null
|
|
40
|
+
if (lf < 0) return { index: crlf, length: 4 }
|
|
41
|
+
if (crlf < 0) return { index: lf, length: 2 }
|
|
42
|
+
return lf < crlf ? { index: lf, length: 2 } : { index: crlf, length: 4 }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private emitEventDataFrame(
|
|
46
|
+
source: string,
|
|
47
|
+
start: number,
|
|
48
|
+
end: number,
|
|
49
|
+
emit: (data: string) => void,
|
|
50
|
+
) {
|
|
51
|
+
const chunks: string[] = []
|
|
52
|
+
let lineStart = start
|
|
53
|
+
|
|
54
|
+
while (lineStart <= end) {
|
|
55
|
+
let lineEnd = source.indexOf('\n', lineStart)
|
|
56
|
+
if (lineEnd < 0 || lineEnd > end) lineEnd = end
|
|
57
|
+
|
|
58
|
+
let contentEnd = lineEnd
|
|
59
|
+
if (
|
|
60
|
+
contentEnd > lineStart &&
|
|
61
|
+
source.charCodeAt(contentEnd - 1) === 13 /* \r */
|
|
62
|
+
) {
|
|
63
|
+
contentEnd -= 1
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
contentEnd - lineStart >= 5 &&
|
|
68
|
+
source.charCodeAt(lineStart) === 100 /* d */ &&
|
|
69
|
+
source.charCodeAt(lineStart + 1) === 97 /* a */ &&
|
|
70
|
+
source.charCodeAt(lineStart + 2) === 116 /* t */ &&
|
|
71
|
+
source.charCodeAt(lineStart + 3) === 97 /* a */ &&
|
|
72
|
+
source.charCodeAt(lineStart + 4) === 58 /* : */
|
|
73
|
+
) {
|
|
74
|
+
let dataStart = lineStart + 5
|
|
75
|
+
while (dataStart < contentEnd) {
|
|
76
|
+
const code = source.charCodeAt(dataStart)
|
|
77
|
+
if (code !== 32 /* space */ && code !== 9 /* tab */) break
|
|
78
|
+
dataStart += 1
|
|
79
|
+
}
|
|
80
|
+
chunks.push(source.slice(dataStart, contentEnd))
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (lineEnd >= end) break
|
|
84
|
+
lineStart = lineEnd + 1
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (!chunks.length) return
|
|
88
|
+
const data = chunks.join('\n')
|
|
89
|
+
if (data.length > 0) emit(data)
|
|
90
|
+
}
|
|
91
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,8 +6,10 @@ import type {
|
|
|
6
6
|
} from '@nmtjs/client'
|
|
7
7
|
import type { ProtocolVersion } from '@nmtjs/protocol'
|
|
8
8
|
import type { BaseClientFormat } from '@nmtjs/protocol/client'
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
9
|
+
import { ConnectionType, ErrorCode, ProtocolBlob } from '@nmtjs/protocol'
|
|
10
|
+
import { ProtocolError } from '@nmtjs/protocol/client'
|
|
11
|
+
|
|
12
|
+
import { HttpStreamParser } from './http-stream-parser.ts'
|
|
11
13
|
|
|
12
14
|
type DecodeBase64Function = (data: string) => ArrayBufferView
|
|
13
15
|
|
|
@@ -40,6 +42,7 @@ export type HttpClientTransportOptions = {
|
|
|
40
42
|
url: string
|
|
41
43
|
debug?: boolean
|
|
42
44
|
EventSource?: typeof EventSource
|
|
45
|
+
fetch?: typeof fetch
|
|
43
46
|
decodeBase64?: DecodeBase64Function
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -58,6 +61,16 @@ export class HttpTransportClient
|
|
|
58
61
|
this.decodeBase64 = createDecodeBase64(options.decodeBase64)
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
private getFetch(): typeof fetch {
|
|
65
|
+
const implementation = this.options.fetch ?? globalThis.fetch
|
|
66
|
+
if (!implementation) {
|
|
67
|
+
throw new Error(
|
|
68
|
+
'Fetch API is not available. Provide HttpClientTransportOptions.fetch',
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
return implementation
|
|
72
|
+
}
|
|
73
|
+
|
|
61
74
|
url({
|
|
62
75
|
procedure,
|
|
63
76
|
application,
|
|
@@ -80,10 +93,12 @@ export class HttpTransportClient
|
|
|
80
93
|
) {
|
|
81
94
|
const { procedure, payload } = rpc
|
|
82
95
|
const requestHeaders = new Headers()
|
|
96
|
+
const fetchImpl = this.getFetch()
|
|
83
97
|
|
|
84
98
|
const url = this.url({ application: client.application, procedure })
|
|
85
99
|
|
|
86
100
|
if (client.auth) requestHeaders.set('Authorization', client.auth)
|
|
101
|
+
requestHeaders.set('Accept', client.format.contentType)
|
|
87
102
|
|
|
88
103
|
let body: any
|
|
89
104
|
|
|
@@ -97,37 +112,85 @@ export class HttpTransportClient
|
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
if (options._stream_response) {
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}>()
|
|
108
|
-
const { readable, writable } = new TransformStream()
|
|
109
|
-
const writer = writable.getWriter()
|
|
110
|
-
source.addEventListener('open', () =>
|
|
111
|
-
future.resolve({ type: 'rpc_stream', stream: readable }),
|
|
112
|
-
)
|
|
113
|
-
source.addEventListener('close', () => writable.close())
|
|
114
|
-
source.addEventListener('error', (event) => {
|
|
115
|
-
const error = new Error('Stream error', { cause: event })
|
|
116
|
-
future.reject(error)
|
|
117
|
-
writable.abort(error)
|
|
115
|
+
const response = await fetchImpl(url.toString(), {
|
|
116
|
+
body,
|
|
117
|
+
method: 'POST',
|
|
118
|
+
headers: requestHeaders,
|
|
119
|
+
signal: options.signal,
|
|
120
|
+
credentials: 'include',
|
|
121
|
+
keepalive: true,
|
|
118
122
|
})
|
|
119
|
-
|
|
123
|
+
|
|
124
|
+
if (!response.ok) {
|
|
120
125
|
try {
|
|
121
|
-
const buffer =
|
|
122
|
-
|
|
126
|
+
const buffer = await response.bytes()
|
|
127
|
+
const error = client.format.decode(buffer) as {
|
|
128
|
+
code?: string
|
|
129
|
+
message?: string
|
|
130
|
+
data?: unknown
|
|
131
|
+
}
|
|
132
|
+
throw new ProtocolError(
|
|
133
|
+
error.code || ErrorCode.ClientRequestError,
|
|
134
|
+
error.message || response.statusText,
|
|
135
|
+
error.data,
|
|
136
|
+
)
|
|
123
137
|
} catch (cause) {
|
|
124
|
-
|
|
125
|
-
|
|
138
|
+
if (cause instanceof ProtocolError) throw cause
|
|
139
|
+
throw new ProtocolError(
|
|
140
|
+
ErrorCode.ClientRequestError,
|
|
141
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
142
|
+
)
|
|
126
143
|
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!response.body) {
|
|
147
|
+
throw new ProtocolError(
|
|
148
|
+
ErrorCode.ClientRequestError,
|
|
149
|
+
'Empty stream response body',
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const stream = new ReadableStream<ArrayBufferView>({
|
|
154
|
+
start: async (controller) => {
|
|
155
|
+
const reader = response.body!.getReader()
|
|
156
|
+
const decoder = new TextDecoder()
|
|
157
|
+
const parser = new HttpStreamParser()
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
while (true) {
|
|
161
|
+
const { done, value } = await reader.read()
|
|
162
|
+
if (done) break
|
|
163
|
+
const chunk = decoder.decode(value, { stream: true })
|
|
164
|
+
parser.push(chunk, (eventData) => {
|
|
165
|
+
controller.enqueue(this.decodeBase64(eventData))
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const tail = decoder.decode()
|
|
170
|
+
parser.push(tail, (eventData) => {
|
|
171
|
+
controller.enqueue(this.decodeBase64(eventData))
|
|
172
|
+
})
|
|
173
|
+
parser.finish((eventData) => {
|
|
174
|
+
controller.enqueue(this.decodeBase64(eventData))
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
controller.close()
|
|
178
|
+
} catch (cause) {
|
|
179
|
+
controller.error(new Error('Stream error', { cause }))
|
|
180
|
+
} finally {
|
|
181
|
+
reader.releaseLock()
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
cancel: async () => {
|
|
185
|
+
try {
|
|
186
|
+
await response.body?.cancel()
|
|
187
|
+
} catch {}
|
|
188
|
+
},
|
|
127
189
|
})
|
|
128
|
-
|
|
190
|
+
|
|
191
|
+
return { type: 'rpc_stream' as const, stream }
|
|
129
192
|
} else {
|
|
130
|
-
const response = await
|
|
193
|
+
const response = await fetchImpl(url.toString(), {
|
|
131
194
|
body,
|
|
132
195
|
method: 'POST',
|
|
133
196
|
headers: requestHeaders,
|
|
@@ -153,15 +216,32 @@ export class HttpTransportClient
|
|
|
153
216
|
return {
|
|
154
217
|
type: 'blob' as const,
|
|
155
218
|
metadata: { type, size, filename },
|
|
156
|
-
source: body
|
|
219
|
+
source: response.body!,
|
|
157
220
|
}
|
|
158
221
|
} else {
|
|
159
222
|
return { type: 'rpc' as const, result: await response.bytes() }
|
|
160
223
|
}
|
|
161
224
|
} else {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
225
|
+
try {
|
|
226
|
+
const buffer = await response.bytes()
|
|
227
|
+
const error = client.format.decode(buffer) as {
|
|
228
|
+
code?: string
|
|
229
|
+
message?: string
|
|
230
|
+
data?: unknown
|
|
231
|
+
}
|
|
232
|
+
throw new ProtocolError(
|
|
233
|
+
error.code || ErrorCode.ClientRequestError,
|
|
234
|
+
error.message || response.statusText,
|
|
235
|
+
error.data,
|
|
236
|
+
)
|
|
237
|
+
} catch (cause) {
|
|
238
|
+
if (cause instanceof ProtocolError) throw cause
|
|
239
|
+
// If decoding fails, throw generic error with status info
|
|
240
|
+
throw new ProtocolError(
|
|
241
|
+
ErrorCode.ClientRequestError,
|
|
242
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
243
|
+
)
|
|
244
|
+
}
|
|
165
245
|
}
|
|
166
246
|
}
|
|
167
247
|
}
|