@inferencesh/sdk 0.1.0 → 0.1.1
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/CHANGELOG.md +14 -1
- package/README.md +4 -0
- package/dist/client.d.ts +2 -0
- package/dist/client.js +8 -1
- package/dist/client.test.d.ts +1 -0
- package/dist/client.test.js +121 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +4 -1
- package/dist/stream.d.ts +8 -1
- package/dist/stream.js +27 -2
- package/dist/stream.test.d.ts +1 -0
- package/dist/stream.test.js +168 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,7 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [0.1.
|
|
10
|
+
## [0.1.1] - 2024-11-30
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Partial data handling for streaming updates (matches Python SDK behavior)
|
|
15
|
+
- `onPartialUpdate` callback option to receive list of changed fields
|
|
16
|
+
- Export `StreamManager` and `PartialDataWrapper` types
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Stream updates now properly extract data from server's partial update wrapper
|
|
21
|
+
- Removed unused `onYield` callback
|
|
22
|
+
|
|
23
|
+
## [0.1.0] - 2024-11-30
|
|
11
24
|
|
|
12
25
|
### Added
|
|
13
26
|
|
package/README.md
CHANGED
package/dist/client.d.ts
CHANGED
|
@@ -14,6 +14,8 @@ export interface InferenceConfig {
|
|
|
14
14
|
export interface RunOptions {
|
|
15
15
|
/** Callback for real-time status updates */
|
|
16
16
|
onUpdate?: (update: Task) => void;
|
|
17
|
+
/** Callback for partial updates with list of changed fields */
|
|
18
|
+
onPartialUpdate?: (update: Task, fields: string[]) => void;
|
|
17
19
|
/** Wait for task completion (default: true) */
|
|
18
20
|
wait?: boolean;
|
|
19
21
|
/** Auto-reconnect on connection loss (default: true) */
|
package/dist/client.js
CHANGED
|
@@ -128,7 +128,7 @@ class Inference {
|
|
|
128
128
|
* ```
|
|
129
129
|
*/
|
|
130
130
|
async run(params, options = {}) {
|
|
131
|
-
const { onUpdate, wait = true, autoReconnect = true, maxReconnects = 5, reconnectDelayMs = 1000, } = options;
|
|
131
|
+
const { onUpdate, onPartialUpdate, wait = true, autoReconnect = true, maxReconnects = 5, reconnectDelayMs = 1000, } = options;
|
|
132
132
|
// Process input data and upload any files
|
|
133
133
|
const processedInput = await this.processInputData(params.input);
|
|
134
134
|
const task = await this.request("post", "/run", {
|
|
@@ -165,6 +165,13 @@ class Inference {
|
|
|
165
165
|
reject(new Error("task cancelled"));
|
|
166
166
|
}
|
|
167
167
|
},
|
|
168
|
+
onPartialData: (data, fields) => {
|
|
169
|
+
// Call onPartialUpdate if provided
|
|
170
|
+
if (onPartialUpdate) {
|
|
171
|
+
const stripped = this._stripTask(data);
|
|
172
|
+
onPartialUpdate(stripped, fields);
|
|
173
|
+
}
|
|
174
|
+
},
|
|
168
175
|
onError: (error) => {
|
|
169
176
|
reject(error);
|
|
170
177
|
streamManager.stop();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const client_1 = require("./client");
|
|
4
|
+
// Mock fetch globally
|
|
5
|
+
const mockFetch = jest.fn();
|
|
6
|
+
global.fetch = mockFetch;
|
|
7
|
+
describe('Inference', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
jest.clearAllMocks();
|
|
10
|
+
});
|
|
11
|
+
describe('constructor', () => {
|
|
12
|
+
it('should create an instance with valid config', () => {
|
|
13
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
14
|
+
expect(client).toBeInstanceOf(client_1.Inference);
|
|
15
|
+
});
|
|
16
|
+
it('should throw error when apiKey is missing', () => {
|
|
17
|
+
expect(() => new client_1.Inference({ apiKey: '' })).toThrow('API key is required');
|
|
18
|
+
expect(() => new client_1.Inference({})).toThrow('API key is required');
|
|
19
|
+
});
|
|
20
|
+
it('should use default baseUrl when not provided', () => {
|
|
21
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
22
|
+
expect(client).toBeDefined();
|
|
23
|
+
});
|
|
24
|
+
it('should accept custom baseUrl', () => {
|
|
25
|
+
const client = new client_1.Inference({
|
|
26
|
+
apiKey: 'test-api-key',
|
|
27
|
+
baseUrl: 'https://custom-api.example.com',
|
|
28
|
+
});
|
|
29
|
+
expect(client).toBeDefined();
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
describe('run', () => {
|
|
33
|
+
it('should make a POST request to /run', async () => {
|
|
34
|
+
const mockTask = {
|
|
35
|
+
id: 'task-123',
|
|
36
|
+
status: 9, // TaskStatusCompleted
|
|
37
|
+
created_at: new Date().toISOString(),
|
|
38
|
+
updated_at: new Date().toISOString(),
|
|
39
|
+
input: { message: 'hello world' },
|
|
40
|
+
output: { result: 'success' },
|
|
41
|
+
};
|
|
42
|
+
mockFetch.mockResolvedValueOnce({
|
|
43
|
+
json: () => Promise.resolve({ success: true, data: mockTask }),
|
|
44
|
+
});
|
|
45
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
46
|
+
// Use input that won't trigger base64 detection (contains spaces/special chars)
|
|
47
|
+
const result = await client.run({ app: 'test-app', input: { message: 'hello world!' } }, { wait: false });
|
|
48
|
+
expect(result.id).toBe('task-123');
|
|
49
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/run'), expect.objectContaining({
|
|
50
|
+
method: 'POST',
|
|
51
|
+
headers: expect.objectContaining({
|
|
52
|
+
Authorization: 'Bearer test-api-key',
|
|
53
|
+
'Content-Type': 'application/json',
|
|
54
|
+
}),
|
|
55
|
+
}));
|
|
56
|
+
});
|
|
57
|
+
it('should throw error on API failure', async () => {
|
|
58
|
+
mockFetch.mockResolvedValueOnce({
|
|
59
|
+
json: () => Promise.resolve({ success: false, error: { message: 'Invalid app' } }),
|
|
60
|
+
});
|
|
61
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
62
|
+
await expect(client.run({ app: 'invalid-app', input: { message: 'test!' } }, { wait: false })).rejects.toThrow('Invalid app');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('cancel', () => {
|
|
66
|
+
it('should make a POST request to cancel endpoint', async () => {
|
|
67
|
+
mockFetch.mockResolvedValueOnce({
|
|
68
|
+
json: () => Promise.resolve({ success: true, data: null }),
|
|
69
|
+
});
|
|
70
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
71
|
+
await client.cancel('task-123');
|
|
72
|
+
expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining('/tasks/task-123/cancel'), expect.objectContaining({ method: 'POST' }));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
describe('backward compatibility', () => {
|
|
76
|
+
it('should export lowercase inference as alias', () => {
|
|
77
|
+
expect(client_1.inference).toBe(client_1.Inference);
|
|
78
|
+
});
|
|
79
|
+
it('should work with lowercase inference', () => {
|
|
80
|
+
const client = new client_1.inference({ apiKey: 'test-api-key' });
|
|
81
|
+
expect(client).toBeInstanceOf(client_1.Inference);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
describe('uploadFile', () => {
|
|
86
|
+
beforeEach(() => {
|
|
87
|
+
jest.clearAllMocks();
|
|
88
|
+
});
|
|
89
|
+
it('should upload a base64 string', async () => {
|
|
90
|
+
const mockFile = {
|
|
91
|
+
id: 'file-123',
|
|
92
|
+
uri: 'https://example.com/file.png',
|
|
93
|
+
upload_url: 'https://upload.example.com/signed-url',
|
|
94
|
+
};
|
|
95
|
+
mockFetch
|
|
96
|
+
.mockResolvedValueOnce({
|
|
97
|
+
json: () => Promise.resolve({ success: true, data: [mockFile] }),
|
|
98
|
+
})
|
|
99
|
+
.mockResolvedValueOnce({ ok: true });
|
|
100
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
101
|
+
// Use valid base64 that won't be mistaken for regular text
|
|
102
|
+
const result = await client.uploadFile('SGVsbG8gV29ybGQh', {
|
|
103
|
+
filename: 'test.txt',
|
|
104
|
+
contentType: 'text/plain',
|
|
105
|
+
});
|
|
106
|
+
expect(result.uri).toBe('https://example.com/file.png');
|
|
107
|
+
expect(mockFetch).toHaveBeenCalledTimes(2);
|
|
108
|
+
});
|
|
109
|
+
it('should throw error when no upload URL provided', async () => {
|
|
110
|
+
const mockFile = {
|
|
111
|
+
id: 'file-123',
|
|
112
|
+
uri: 'https://example.com/file.png',
|
|
113
|
+
// Missing upload_url
|
|
114
|
+
};
|
|
115
|
+
mockFetch.mockResolvedValueOnce({
|
|
116
|
+
json: () => Promise.resolve({ success: true, data: [mockFile] }),
|
|
117
|
+
});
|
|
118
|
+
const client = new client_1.Inference({ apiKey: 'test-api-key' });
|
|
119
|
+
await expect(client.uploadFile('SGVsbG8gV29ybGQh', { filename: 'test.txt' })).rejects.toThrow('No upload URL provided by the server');
|
|
120
|
+
});
|
|
121
|
+
});
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -14,10 +14,13 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
exports.inference = exports.Inference = void 0;
|
|
17
|
+
exports.StreamManager = exports.inference = exports.Inference = void 0;
|
|
18
18
|
// Main client export
|
|
19
19
|
var client_1 = require("./client");
|
|
20
20
|
Object.defineProperty(exports, "Inference", { enumerable: true, get: function () { return client_1.Inference; } });
|
|
21
21
|
Object.defineProperty(exports, "inference", { enumerable: true, get: function () { return client_1.inference; } });
|
|
22
|
+
// Stream utilities
|
|
23
|
+
var stream_1 = require("./stream");
|
|
24
|
+
Object.defineProperty(exports, "StreamManager", { enumerable: true, get: function () { return stream_1.StreamManager; } });
|
|
22
25
|
// Types - includes TaskStatus constants and all DTOs
|
|
23
26
|
__exportStar(require("./types"), exports);
|
package/dist/stream.d.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
/** Partial data structure from server (contains data and list of updated fields) */
|
|
2
|
+
export interface PartialDataWrapper<T> {
|
|
3
|
+
data: T;
|
|
4
|
+
fields: string[];
|
|
5
|
+
}
|
|
1
6
|
export interface StreamManagerOptions<T> {
|
|
2
7
|
createEventSource: () => Promise<EventSource | null>;
|
|
3
8
|
autoReconnect?: boolean;
|
|
@@ -6,8 +11,10 @@ export interface StreamManagerOptions<T> {
|
|
|
6
11
|
onError?: (error: Error) => void;
|
|
7
12
|
onStart?: () => void;
|
|
8
13
|
onStop?: () => void;
|
|
14
|
+
/** Called with the extracted data (handles both full and partial data) */
|
|
9
15
|
onData?: (data: T) => void;
|
|
10
|
-
|
|
16
|
+
/** Called specifically for partial updates with data and the list of updated fields */
|
|
17
|
+
onPartialData?: (data: T, fields: string[]) => void;
|
|
11
18
|
}
|
|
12
19
|
export declare class StreamManager<T> {
|
|
13
20
|
private options;
|
package/dist/stream.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StreamManager = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Check if the parsed data is a partial data wrapper from the server.
|
|
6
|
+
* The server sends partial updates in the format: { data: T, fields: string[] }
|
|
7
|
+
*/
|
|
8
|
+
function isPartialDataWrapper(parsed) {
|
|
9
|
+
return (typeof parsed === 'object' &&
|
|
10
|
+
parsed !== null &&
|
|
11
|
+
'data' in parsed &&
|
|
12
|
+
'fields' in parsed &&
|
|
13
|
+
Array.isArray(parsed.fields));
|
|
14
|
+
}
|
|
4
15
|
class StreamManager {
|
|
5
16
|
constructor(options) {
|
|
6
17
|
this.eventSource = null;
|
|
@@ -87,8 +98,22 @@ class StreamManager {
|
|
|
87
98
|
return;
|
|
88
99
|
try {
|
|
89
100
|
const parsed = JSON.parse(e.data);
|
|
90
|
-
this
|
|
91
|
-
|
|
101
|
+
// Check if this is a partial data wrapper from the server
|
|
102
|
+
if (isPartialDataWrapper(parsed)) {
|
|
103
|
+
// Extract the actual data from the wrapper
|
|
104
|
+
const actualData = parsed.data;
|
|
105
|
+
const fields = parsed.fields;
|
|
106
|
+
// Call onPartialData if provided
|
|
107
|
+
if (this.options.onPartialData) {
|
|
108
|
+
this.options.onPartialData(actualData, fields);
|
|
109
|
+
}
|
|
110
|
+
// Always call onData with the extracted data
|
|
111
|
+
this.options.onData?.(actualData);
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
// Not a partial wrapper, treat as full data
|
|
115
|
+
this.options.onData?.(parsed);
|
|
116
|
+
}
|
|
92
117
|
}
|
|
93
118
|
catch (err) {
|
|
94
119
|
const error = err instanceof Error ? err : new Error("Invalid JSON");
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const stream_1 = require("./stream");
|
|
4
|
+
describe('StreamManager', () => {
|
|
5
|
+
let mockEventSource;
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
mockEventSource = {
|
|
8
|
+
onmessage: null,
|
|
9
|
+
onerror: null,
|
|
10
|
+
close: jest.fn(),
|
|
11
|
+
};
|
|
12
|
+
});
|
|
13
|
+
describe('constructor', () => {
|
|
14
|
+
it('should create instance with default options', () => {
|
|
15
|
+
const manager = new stream_1.StreamManager({
|
|
16
|
+
createEventSource: async () => mockEventSource,
|
|
17
|
+
});
|
|
18
|
+
expect(manager).toBeDefined();
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
describe('connect', () => {
|
|
22
|
+
it('should call createEventSource', async () => {
|
|
23
|
+
const createEventSource = jest
|
|
24
|
+
.fn()
|
|
25
|
+
.mockResolvedValue(mockEventSource);
|
|
26
|
+
const manager = new stream_1.StreamManager({ createEventSource });
|
|
27
|
+
await manager.connect();
|
|
28
|
+
expect(createEventSource).toHaveBeenCalled();
|
|
29
|
+
});
|
|
30
|
+
it('should call onStart when connected', async () => {
|
|
31
|
+
const onStart = jest.fn();
|
|
32
|
+
const manager = new stream_1.StreamManager({
|
|
33
|
+
createEventSource: async () => mockEventSource,
|
|
34
|
+
onStart,
|
|
35
|
+
});
|
|
36
|
+
await manager.connect();
|
|
37
|
+
expect(onStart).toHaveBeenCalled();
|
|
38
|
+
});
|
|
39
|
+
it('should parse JSON messages and call onData', async () => {
|
|
40
|
+
const onData = jest.fn();
|
|
41
|
+
const manager = new stream_1.StreamManager({
|
|
42
|
+
createEventSource: async () => mockEventSource,
|
|
43
|
+
onData,
|
|
44
|
+
});
|
|
45
|
+
await manager.connect();
|
|
46
|
+
// Simulate receiving a message
|
|
47
|
+
const testData = { status: 'running', id: 'task-123' };
|
|
48
|
+
mockEventSource.onmessage?.({ data: JSON.stringify(testData) });
|
|
49
|
+
expect(onData).toHaveBeenCalledWith(testData);
|
|
50
|
+
});
|
|
51
|
+
it('should extract data from partial data wrapper and call onData', async () => {
|
|
52
|
+
const onData = jest.fn();
|
|
53
|
+
const manager = new stream_1.StreamManager({
|
|
54
|
+
createEventSource: async () => mockEventSource,
|
|
55
|
+
onData,
|
|
56
|
+
});
|
|
57
|
+
await manager.connect();
|
|
58
|
+
// Simulate receiving a partial data wrapper from server
|
|
59
|
+
const innerData = { status: 7, id: 'task-123', logs: ['log1'] };
|
|
60
|
+
const partialWrapper = { data: innerData, fields: ['status', 'logs'] };
|
|
61
|
+
mockEventSource.onmessage?.({ data: JSON.stringify(partialWrapper) });
|
|
62
|
+
// onData should receive the extracted inner data, not the wrapper
|
|
63
|
+
expect(onData).toHaveBeenCalledWith(innerData);
|
|
64
|
+
});
|
|
65
|
+
it('should call onPartialData with data and fields for partial updates', async () => {
|
|
66
|
+
const onData = jest.fn();
|
|
67
|
+
const onPartialData = jest.fn();
|
|
68
|
+
const manager = new stream_1.StreamManager({
|
|
69
|
+
createEventSource: async () => mockEventSource,
|
|
70
|
+
onData,
|
|
71
|
+
onPartialData,
|
|
72
|
+
});
|
|
73
|
+
await manager.connect();
|
|
74
|
+
// Simulate receiving a partial data wrapper
|
|
75
|
+
const innerData = { status: 7, id: 'task-123' };
|
|
76
|
+
const fields = ['status'];
|
|
77
|
+
const partialWrapper = { data: innerData, fields };
|
|
78
|
+
mockEventSource.onmessage?.({ data: JSON.stringify(partialWrapper) });
|
|
79
|
+
expect(onPartialData).toHaveBeenCalledWith(innerData, fields);
|
|
80
|
+
expect(onData).toHaveBeenCalledWith(innerData);
|
|
81
|
+
});
|
|
82
|
+
it('should not call onPartialData for non-partial data', async () => {
|
|
83
|
+
const onData = jest.fn();
|
|
84
|
+
const onPartialData = jest.fn();
|
|
85
|
+
const manager = new stream_1.StreamManager({
|
|
86
|
+
createEventSource: async () => mockEventSource,
|
|
87
|
+
onData,
|
|
88
|
+
onPartialData,
|
|
89
|
+
});
|
|
90
|
+
await manager.connect();
|
|
91
|
+
// Regular data without partial wrapper
|
|
92
|
+
const regularData = { status: 9, id: 'task-123' };
|
|
93
|
+
mockEventSource.onmessage?.({ data: JSON.stringify(regularData) });
|
|
94
|
+
expect(onPartialData).not.toHaveBeenCalled();
|
|
95
|
+
expect(onData).toHaveBeenCalledWith(regularData);
|
|
96
|
+
});
|
|
97
|
+
it('should call onError for invalid JSON', async () => {
|
|
98
|
+
const onError = jest.fn();
|
|
99
|
+
const manager = new stream_1.StreamManager({
|
|
100
|
+
createEventSource: async () => mockEventSource,
|
|
101
|
+
onError,
|
|
102
|
+
});
|
|
103
|
+
await manager.connect();
|
|
104
|
+
mockEventSource.onmessage?.({ data: 'invalid json' });
|
|
105
|
+
expect(onError).toHaveBeenCalled();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
describe('stop', () => {
|
|
109
|
+
it('should close the event source', async () => {
|
|
110
|
+
const onStop = jest.fn();
|
|
111
|
+
const manager = new stream_1.StreamManager({
|
|
112
|
+
createEventSource: async () => mockEventSource,
|
|
113
|
+
onStop,
|
|
114
|
+
});
|
|
115
|
+
await manager.connect();
|
|
116
|
+
manager.stop();
|
|
117
|
+
expect(mockEventSource.close).toHaveBeenCalled();
|
|
118
|
+
expect(onStop).toHaveBeenCalled();
|
|
119
|
+
});
|
|
120
|
+
it('should not process messages after stop', async () => {
|
|
121
|
+
const onData = jest.fn();
|
|
122
|
+
const manager = new stream_1.StreamManager({
|
|
123
|
+
createEventSource: async () => mockEventSource,
|
|
124
|
+
onData,
|
|
125
|
+
});
|
|
126
|
+
await manager.connect();
|
|
127
|
+
manager.stop();
|
|
128
|
+
mockEventSource.onmessage?.({ data: '{"status":"running"}' });
|
|
129
|
+
expect(onData).not.toHaveBeenCalled();
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
describe('reconnection', () => {
|
|
133
|
+
it('should attempt reconnection on error when autoReconnect is true', async () => {
|
|
134
|
+
jest.useFakeTimers();
|
|
135
|
+
const createEventSource = jest
|
|
136
|
+
.fn()
|
|
137
|
+
.mockResolvedValue(mockEventSource);
|
|
138
|
+
const manager = new stream_1.StreamManager({
|
|
139
|
+
createEventSource,
|
|
140
|
+
autoReconnect: true,
|
|
141
|
+
reconnectDelayMs: 100,
|
|
142
|
+
});
|
|
143
|
+
await manager.connect();
|
|
144
|
+
expect(createEventSource).toHaveBeenCalledTimes(1);
|
|
145
|
+
// Simulate error
|
|
146
|
+
mockEventSource.onerror?.({});
|
|
147
|
+
// Fast-forward past reconnect delay
|
|
148
|
+
jest.advanceTimersByTime(100);
|
|
149
|
+
await Promise.resolve(); // Flush promises
|
|
150
|
+
expect(createEventSource).toHaveBeenCalledTimes(2);
|
|
151
|
+
jest.useRealTimers();
|
|
152
|
+
});
|
|
153
|
+
it('should not reconnect when autoReconnect is false', async () => {
|
|
154
|
+
const createEventSource = jest
|
|
155
|
+
.fn()
|
|
156
|
+
.mockResolvedValue(mockEventSource);
|
|
157
|
+
const manager = new stream_1.StreamManager({
|
|
158
|
+
createEventSource,
|
|
159
|
+
autoReconnect: false,
|
|
160
|
+
});
|
|
161
|
+
await manager.connect();
|
|
162
|
+
mockEventSource.onerror?.({});
|
|
163
|
+
// Wait a bit
|
|
164
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
165
|
+
expect(createEventSource).toHaveBeenCalledTimes(1);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
168
|
+
});
|