aparavi-client 1.0.2
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/LICENSE +21 -0
- package/README.md +523 -0
- package/dist/aparavi-client-typescript-1.0.0.tgz +0 -0
- package/dist/aparavi-client-typescript-1.0.1.tgz +0 -0
- package/dist/cjs/client.js +1002 -0
- package/dist/cjs/client.js.map +1 -0
- package/dist/cjs/constants.js +37 -0
- package/dist/cjs/constants.js.map +1 -0
- package/dist/cjs/core/DAPBase.js +313 -0
- package/dist/cjs/core/DAPBase.js.map +1 -0
- package/dist/cjs/core/DAPClient.js +173 -0
- package/dist/cjs/core/DAPClient.js.map +1 -0
- package/dist/cjs/core/TransportBase.js +131 -0
- package/dist/cjs/core/TransportBase.js.map +1 -0
- package/dist/cjs/core/TransportWebSocket.js +492 -0
- package/dist/cjs/core/TransportWebSocket.js.map +1 -0
- package/dist/cjs/exceptions/index.js +109 -0
- package/dist/cjs/exceptions/index.js.map +1 -0
- package/dist/cjs/index.js +56 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/schema/Doc.js +79 -0
- package/dist/cjs/schema/Doc.js.map +1 -0
- package/dist/cjs/schema/DocFilter.js +133 -0
- package/dist/cjs/schema/DocFilter.js.map +1 -0
- package/dist/cjs/schema/DocGroup.js +235 -0
- package/dist/cjs/schema/DocGroup.js.map +1 -0
- package/dist/cjs/schema/DocMetadata.js +57 -0
- package/dist/cjs/schema/DocMetadata.js.map +1 -0
- package/dist/cjs/schema/Question.js +414 -0
- package/dist/cjs/schema/Question.js.map +1 -0
- package/dist/cjs/schema/index.js +50 -0
- package/dist/cjs/schema/index.js.map +1 -0
- package/dist/cjs/types/client.js +26 -0
- package/dist/cjs/types/client.js.map +1 -0
- package/dist/cjs/types/data.js +26 -0
- package/dist/cjs/types/data.js.map +1 -0
- package/dist/cjs/types/events.js +119 -0
- package/dist/cjs/types/events.js.map +1 -0
- package/dist/cjs/types/index.js +50 -0
- package/dist/cjs/types/index.js.map +1 -0
- package/dist/cjs/types/pipeline.js +26 -0
- package/dist/cjs/types/pipeline.js.map +1 -0
- package/dist/cjs/types/task.js +115 -0
- package/dist/cjs/types/task.js.map +1 -0
- package/dist/cli/cli/aparavi.js +1401 -0
- package/dist/cli/cli/aparavi.js.map +1 -0
- package/dist/cli/src/client.js +1002 -0
- package/dist/cli/src/client.js.map +1 -0
- package/dist/cli/src/constants.js +37 -0
- package/dist/cli/src/constants.js.map +1 -0
- package/dist/cli/src/core/DAPBase.js +313 -0
- package/dist/cli/src/core/DAPBase.js.map +1 -0
- package/dist/cli/src/core/DAPClient.js +173 -0
- package/dist/cli/src/core/DAPClient.js.map +1 -0
- package/dist/cli/src/core/TransportBase.js +131 -0
- package/dist/cli/src/core/TransportBase.js.map +1 -0
- package/dist/cli/src/core/TransportWebSocket.js +492 -0
- package/dist/cli/src/core/TransportWebSocket.js.map +1 -0
- package/dist/cli/src/exceptions/index.js +109 -0
- package/dist/cli/src/exceptions/index.js.map +1 -0
- package/dist/cli/src/index.js +56 -0
- package/dist/cli/src/index.js.map +1 -0
- package/dist/cli/src/schema/Doc.js +79 -0
- package/dist/cli/src/schema/Doc.js.map +1 -0
- package/dist/cli/src/schema/DocFilter.js +133 -0
- package/dist/cli/src/schema/DocFilter.js.map +1 -0
- package/dist/cli/src/schema/DocGroup.js +235 -0
- package/dist/cli/src/schema/DocGroup.js.map +1 -0
- package/dist/cli/src/schema/DocMetadata.js +57 -0
- package/dist/cli/src/schema/DocMetadata.js.map +1 -0
- package/dist/cli/src/schema/Question.js +414 -0
- package/dist/cli/src/schema/Question.js.map +1 -0
- package/dist/cli/src/schema/index.js +50 -0
- package/dist/cli/src/schema/index.js.map +1 -0
- package/dist/cli/src/types/client.js +26 -0
- package/dist/cli/src/types/client.js.map +1 -0
- package/dist/cli/src/types/data.js +26 -0
- package/dist/cli/src/types/data.js.map +1 -0
- package/dist/cli/src/types/events.js +119 -0
- package/dist/cli/src/types/events.js.map +1 -0
- package/dist/cli/src/types/index.js +50 -0
- package/dist/cli/src/types/index.js.map +1 -0
- package/dist/cli/src/types/pipeline.js +26 -0
- package/dist/cli/src/types/pipeline.js.map +1 -0
- package/dist/cli/src/types/task.js +115 -0
- package/dist/cli/src/types/task.js.map +1 -0
- package/dist/esm/client.js +997 -0
- package/dist/esm/client.js.map +1 -0
- package/dist/esm/constants.js +34 -0
- package/dist/esm/constants.js.map +1 -0
- package/dist/esm/core/DAPBase.js +309 -0
- package/dist/esm/core/DAPBase.js.map +1 -0
- package/dist/esm/core/DAPClient.js +169 -0
- package/dist/esm/core/DAPClient.js.map +1 -0
- package/dist/esm/core/TransportBase.js +127 -0
- package/dist/esm/core/TransportBase.js.map +1 -0
- package/dist/esm/core/TransportWebSocket.js +488 -0
- package/dist/esm/core/TransportWebSocket.js.map +1 -0
- package/dist/esm/exceptions/index.js +100 -0
- package/dist/esm/exceptions/index.js.map +1 -0
- package/dist/esm/index.js +40 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/package.json +3 -0
- package/dist/esm/schema/Doc.js +75 -0
- package/dist/esm/schema/Doc.js.map +1 -0
- package/dist/esm/schema/DocFilter.js +129 -0
- package/dist/esm/schema/DocFilter.js.map +1 -0
- package/dist/esm/schema/DocGroup.js +231 -0
- package/dist/esm/schema/DocGroup.js.map +1 -0
- package/dist/esm/schema/DocMetadata.js +53 -0
- package/dist/esm/schema/DocMetadata.js.map +1 -0
- package/dist/esm/schema/Question.js +409 -0
- package/dist/esm/schema/Question.js.map +1 -0
- package/dist/esm/schema/index.js +34 -0
- package/dist/esm/schema/index.js.map +1 -0
- package/dist/esm/types/client.js +25 -0
- package/dist/esm/types/client.js.map +1 -0
- package/dist/esm/types/data.js +25 -0
- package/dist/esm/types/data.js.map +1 -0
- package/dist/esm/types/events.js +116 -0
- package/dist/esm/types/events.js.map +1 -0
- package/dist/esm/types/index.js +34 -0
- package/dist/esm/types/index.js.map +1 -0
- package/dist/esm/types/pipeline.js +25 -0
- package/dist/esm/types/pipeline.js.map +1 -0
- package/dist/esm/types/task.js +112 -0
- package/dist/esm/types/task.js.map +1 -0
- package/dist/types/client.d.ts +395 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/constants.d.ts +34 -0
- package/dist/types/constants.d.ts.map +1 -0
- package/dist/types/core/DAPBase.d.ts +140 -0
- package/dist/types/core/DAPBase.d.ts.map +1 -0
- package/dist/types/core/DAPClient.d.ts +83 -0
- package/dist/types/core/DAPClient.d.ts.map +1 -0
- package/dist/types/core/TransportBase.d.ts +94 -0
- package/dist/types/core/TransportBase.d.ts.map +1 -0
- package/dist/types/core/TransportWebSocket.d.ts +78 -0
- package/dist/types/core/TransportWebSocket.d.ts.map +1 -0
- package/dist/types/exceptions/index.d.ts +81 -0
- package/dist/types/exceptions/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +36 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/schema/Doc.d.ts +69 -0
- package/dist/types/schema/Doc.d.ts.map +1 -0
- package/dist/types/schema/DocFilter.d.ts +101 -0
- package/dist/types/schema/DocFilter.d.ts.map +1 -0
- package/dist/types/schema/DocGroup.d.ts +113 -0
- package/dist/types/schema/DocGroup.d.ts.map +1 -0
- package/dist/types/schema/DocMetadata.d.ts +61 -0
- package/dist/types/schema/DocMetadata.d.ts.map +1 -0
- package/dist/types/schema/Question.d.ts +163 -0
- package/dist/types/schema/Question.d.ts.map +1 -0
- package/dist/types/schema/index.d.ts +34 -0
- package/dist/types/schema/index.d.ts.map +1 -0
- package/dist/types/types/client.d.ts +140 -0
- package/dist/types/types/client.d.ts.map +1 -0
- package/dist/types/types/data.d.ts +95 -0
- package/dist/types/types/data.d.ts.map +1 -0
- package/dist/types/types/events.d.ts +246 -0
- package/dist/types/types/events.d.ts.map +1 -0
- package/dist/types/types/index.d.ts +34 -0
- package/dist/types/types/index.d.ts.map +1 -0
- package/dist/types/types/pipeline.d.ts +61 -0
- package/dist/types/types/pipeline.d.ts.map +1 -0
- package/dist/types/types/task.d.ts +265 -0
- package/dist/types/types/task.d.ts.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,1002 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MIT License
|
|
4
|
+
*
|
|
5
|
+
* Copyright (c) 2024 Aparavi Development Team
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
15
|
+
* copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
23
|
+
* SOFTWARE.
|
|
24
|
+
*/
|
|
25
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
26
|
+
exports.default = exports.AparaviClient = exports.DataPipe = void 0;
|
|
27
|
+
const TransportWebSocket_1 = require("./core/TransportWebSocket");
|
|
28
|
+
const DAPClient_1 = require("./core/DAPClient");
|
|
29
|
+
const constants_1 = require("./constants");
|
|
30
|
+
// Global counter for generating unique client IDs
|
|
31
|
+
let clientId = 0;
|
|
32
|
+
/**
|
|
33
|
+
* Streaming data pipe for sending large datasets to Aparavi pipelines.
|
|
34
|
+
*
|
|
35
|
+
* DataPipe provides a stream-like interface for uploading data to an Aparavi
|
|
36
|
+
* pipeline. It handles the low-level protocol details of opening, writing to,
|
|
37
|
+
* and closing data pipes on the server.
|
|
38
|
+
*
|
|
39
|
+
* Usage pattern:
|
|
40
|
+
* 1. Create pipe using client.pipe()
|
|
41
|
+
* 2. Call open() to establish the pipe
|
|
42
|
+
* 3. Call write() multiple times with data chunks
|
|
43
|
+
* 4. Call close() to finalize and get results
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```typescript
|
|
47
|
+
* const pipe = await client.pipe(token, { filename: 'data.json' }, 'application/json');
|
|
48
|
+
* await pipe.open();
|
|
49
|
+
* await pipe.write(new TextEncoder().encode('{"data": "value"}'));
|
|
50
|
+
* const result = await pipe.close();
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
class DataPipe {
|
|
54
|
+
/**
|
|
55
|
+
* Creates a new DataPipe instance.
|
|
56
|
+
*
|
|
57
|
+
* @param client - The AparaviClient instance managing this pipe
|
|
58
|
+
* @param token - Task token for the pipeline receiving the data
|
|
59
|
+
* @param objinfo - Metadata about the object being sent (e.g., filename, size)
|
|
60
|
+
* @param mimeType - MIME type of the data being sent (default: 'application/octet-stream')
|
|
61
|
+
* @param provider - Optional provider name for the data source
|
|
62
|
+
*/
|
|
63
|
+
constructor(client, token, objinfo = {}, mimeType = 'application/octet-stream', provider) {
|
|
64
|
+
this._opened = false;
|
|
65
|
+
this._closed = false;
|
|
66
|
+
this._client = client;
|
|
67
|
+
this._token = token;
|
|
68
|
+
this._objinfo = objinfo;
|
|
69
|
+
this._mimeType = mimeType;
|
|
70
|
+
this._provider = provider;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Check if the pipe is currently open for writing.
|
|
74
|
+
*
|
|
75
|
+
* @returns true if the pipe has been opened and not yet closed
|
|
76
|
+
*/
|
|
77
|
+
get isOpened() {
|
|
78
|
+
return this._opened;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the unique ID assigned to this pipe by the server.
|
|
82
|
+
*
|
|
83
|
+
* This ID is assigned when the pipe is opened and is used for subsequent
|
|
84
|
+
* write operations. It remains undefined until open() is called successfully.
|
|
85
|
+
*
|
|
86
|
+
* @returns The server-assigned pipe ID, or undefined if not yet opened
|
|
87
|
+
*/
|
|
88
|
+
get pipeId() {
|
|
89
|
+
return this._pipeId;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Open the pipe for data transmission.
|
|
93
|
+
*
|
|
94
|
+
* Establishes a data pipe on the server for streaming data to the pipeline.
|
|
95
|
+
* Must be called before any write() operations. The server will assign a
|
|
96
|
+
* unique pipe ID that is used for subsequent operations.
|
|
97
|
+
*
|
|
98
|
+
* @returns This DataPipe instance (for method chaining)
|
|
99
|
+
* @throws Error if the pipe is already opened or if the pipeline is not running
|
|
100
|
+
*/
|
|
101
|
+
async open() {
|
|
102
|
+
if (this._opened) {
|
|
103
|
+
throw new Error('Pipe already opened');
|
|
104
|
+
}
|
|
105
|
+
const request = this._client.buildRequest('apaext_process', {
|
|
106
|
+
arguments: {
|
|
107
|
+
subcommand: 'open',
|
|
108
|
+
object: this._objinfo,
|
|
109
|
+
mimeType: this._mimeType,
|
|
110
|
+
provider: this._provider,
|
|
111
|
+
},
|
|
112
|
+
token: this._token,
|
|
113
|
+
});
|
|
114
|
+
const response = await this._client.request(request);
|
|
115
|
+
if (this._client.didFail(response)) {
|
|
116
|
+
throw new Error(response.message || 'Your pipeline is not currently running.');
|
|
117
|
+
}
|
|
118
|
+
this._pipeId = response.body?.pipe_id;
|
|
119
|
+
this._opened = true;
|
|
120
|
+
return this;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Write data to the pipe.
|
|
124
|
+
*
|
|
125
|
+
* Sends a chunk of data through the pipe to the server pipeline. Can be called
|
|
126
|
+
* multiple times to stream large datasets. The pipe must be opened first.
|
|
127
|
+
*
|
|
128
|
+
* @param buffer - Data to write, must be a Uint8Array
|
|
129
|
+
* @throws Error if the pipe is not opened, buffer is invalid, or write fails
|
|
130
|
+
*/
|
|
131
|
+
async write(buffer) {
|
|
132
|
+
if (!this._opened) {
|
|
133
|
+
throw new Error('Pipe not opened');
|
|
134
|
+
}
|
|
135
|
+
if (!(buffer instanceof Uint8Array)) {
|
|
136
|
+
throw new Error('Buffer must be Uint8Array');
|
|
137
|
+
}
|
|
138
|
+
const request = this._client.buildRequest('apaext_process', {
|
|
139
|
+
arguments: {
|
|
140
|
+
subcommand: 'write',
|
|
141
|
+
pipe_id: this._pipeId,
|
|
142
|
+
data: buffer,
|
|
143
|
+
},
|
|
144
|
+
token: this._token,
|
|
145
|
+
});
|
|
146
|
+
const response = await this._client.request(request);
|
|
147
|
+
if (this._client.didFail(response)) {
|
|
148
|
+
throw new Error(response.message || 'Failed to write to pipe');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Close the pipe and get the processing results.
|
|
153
|
+
*
|
|
154
|
+
* Finalizes the data stream and signals the server that no more data will be sent.
|
|
155
|
+
* The server processes any buffered data and returns the final result. After closing,
|
|
156
|
+
* the pipe cannot be reopened or written to again.
|
|
157
|
+
*
|
|
158
|
+
* @returns The processing result from the server, or undefined if already closed
|
|
159
|
+
* @throws Error if closing the pipe fails
|
|
160
|
+
*/
|
|
161
|
+
async close() {
|
|
162
|
+
if (!this._opened || this._closed) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
try {
|
|
166
|
+
const request = this._client.buildRequest('apaext_process', {
|
|
167
|
+
arguments: {
|
|
168
|
+
subcommand: 'close',
|
|
169
|
+
pipe_id: this._pipeId,
|
|
170
|
+
},
|
|
171
|
+
token: this._token,
|
|
172
|
+
});
|
|
173
|
+
const response = await this._client.request(request);
|
|
174
|
+
if (this._client.didFail(response)) {
|
|
175
|
+
throw new Error(response.message || 'Failed to close pipe');
|
|
176
|
+
}
|
|
177
|
+
return response.body;
|
|
178
|
+
}
|
|
179
|
+
finally {
|
|
180
|
+
this._closed = true;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
exports.DataPipe = DataPipe;
|
|
185
|
+
/**
|
|
186
|
+
* Main Aparavi client for connecting to Aparavi servers and services.
|
|
187
|
+
*
|
|
188
|
+
* This client provides a comprehensive API for interacting with Aparavi services,
|
|
189
|
+
* including connection management, pipeline execution, data operations, AI chat,
|
|
190
|
+
* event handling, and server connectivity testing.
|
|
191
|
+
*
|
|
192
|
+
* Key features:
|
|
193
|
+
* - Single shared WebSocket connection for all operations
|
|
194
|
+
* - Connection management (connect/disconnect) with optional persistence
|
|
195
|
+
* - Automatic reconnection when persist mode is enabled
|
|
196
|
+
* - Pipeline execution (use, terminate, getTaskStatus)
|
|
197
|
+
* - Data operations (send, sendFiles, pipe)
|
|
198
|
+
* - AI chat functionality (chat)
|
|
199
|
+
* - Event handling (setEvents, event callbacks)
|
|
200
|
+
* - Server connectivity testing (ping)
|
|
201
|
+
* - Full TypeScript type safety
|
|
202
|
+
*/
|
|
203
|
+
class AparaviClient extends DAPClient_1.DAPClient {
|
|
204
|
+
/**
|
|
205
|
+
* Creates a new AparaviClient instance.
|
|
206
|
+
*
|
|
207
|
+
* Configuration priority (highest to lowest):
|
|
208
|
+
* 1. Values passed in config parameter (auth, uri)
|
|
209
|
+
* 2. Values from env parameter (if provided)
|
|
210
|
+
* 3. Values from .env file (Node.js only)
|
|
211
|
+
* 4. Default values
|
|
212
|
+
*
|
|
213
|
+
* @param config - Configuration options for the client
|
|
214
|
+
* @param config.auth - API key for authentication (required)
|
|
215
|
+
* @param config.uri - Server URI (default: CONST_DEFAULT_SERVICE)
|
|
216
|
+
* @param config.env - Environment variables dictionary for configuration and substitution
|
|
217
|
+
* @param config.onEvent - Callback for server events
|
|
218
|
+
* @param config.onConnected - Callback when connection is established
|
|
219
|
+
* @param config.onDisconnected - Callback when connection is lost
|
|
220
|
+
* @param config.persist - Enable automatic reconnection
|
|
221
|
+
* @param config.reconnectDelay - Delay between reconnection attempts in ms
|
|
222
|
+
* @param config.module - Optional module name for client identification
|
|
223
|
+
*
|
|
224
|
+
* @throws Error if auth is not provided via config, env, or .env file
|
|
225
|
+
*
|
|
226
|
+
* @example
|
|
227
|
+
* ```typescript
|
|
228
|
+
* // Using explicit auth and URI
|
|
229
|
+
* const client = new AparaviClient({
|
|
230
|
+
* auth: 'your-api-key',
|
|
231
|
+
* uri: 'wss://your-server.com',
|
|
232
|
+
* persist: true,
|
|
233
|
+
* onEvent: (event) => console.log('Event:', event)
|
|
234
|
+
* });
|
|
235
|
+
*
|
|
236
|
+
* // Using custom env dictionary
|
|
237
|
+
* const client = new AparaviClient({
|
|
238
|
+
* env: {
|
|
239
|
+
* APARAVI_APIKEY: 'your-api-key',
|
|
240
|
+
* APARAVI_URI: 'wss://your-server.com',
|
|
241
|
+
* APARAVI_PROJECT_ID: 'my-project'
|
|
242
|
+
* }
|
|
243
|
+
* });
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
constructor(config = {}) {
|
|
247
|
+
// Check if we're in Node.js or browser environment
|
|
248
|
+
const isBrowser = typeof window !== 'undefined';
|
|
249
|
+
// Build environment variables dictionary
|
|
250
|
+
// Priority: provided env > .env file
|
|
251
|
+
let clientEnv = {};
|
|
252
|
+
if (config.env) {
|
|
253
|
+
// Use provided env dictionary
|
|
254
|
+
clientEnv = { ...config.env };
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
// Load from .env file only (Node.js only)
|
|
258
|
+
if (!isBrowser) {
|
|
259
|
+
try {
|
|
260
|
+
const fs = require('fs');
|
|
261
|
+
const path = require('path');
|
|
262
|
+
const envPath = path.join(process.cwd(), '.env');
|
|
263
|
+
if (fs.existsSync(envPath)) {
|
|
264
|
+
const content = fs.readFileSync(envPath, 'utf-8');
|
|
265
|
+
// Parse each line
|
|
266
|
+
for (const line of content.split('\n')) {
|
|
267
|
+
const trimmed = line.trim();
|
|
268
|
+
// Skip comments and empty lines
|
|
269
|
+
if (!trimmed || trimmed.startsWith('#')) {
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
// Parse KEY=VALUE format
|
|
273
|
+
const match = trimmed.match(/^([^=]+)=(.*)$/);
|
|
274
|
+
if (match) {
|
|
275
|
+
const key = match[1].trim();
|
|
276
|
+
let value = match[2].trim();
|
|
277
|
+
// Remove quotes if present
|
|
278
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
279
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
280
|
+
value = value.slice(1, -1);
|
|
281
|
+
}
|
|
282
|
+
clientEnv[key] = value;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// File doesn't exist or can't be read - that's okay
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const { auth = config.auth || clientEnv.APARAVI_APIKEY, uri = config.uri || clientEnv.APARAVI_URI || constants_1.CONST_DEFAULT_SERVICE, onEvent, onConnected, onDisconnected, persist, reconnectDelay, module, } = config;
|
|
293
|
+
if (!auth) {
|
|
294
|
+
throw new Error("Authentication key is required. Provide 'auth' parameter, 'env' parameter, or set 'APARAVI_APIKEY' in .env file.");
|
|
295
|
+
}
|
|
296
|
+
// Convert HTTP/HTTPS URI to WS/WSS for WebSocket connections
|
|
297
|
+
const wsUri = AparaviClient._convertToWebSocketUri(uri);
|
|
298
|
+
const fullUri = `${wsUri}/task/service`;
|
|
299
|
+
// Create unique client identifier
|
|
300
|
+
const clientName = module || `CLIENT-${clientId++}`;
|
|
301
|
+
// Set up WebSocket transport for server communication
|
|
302
|
+
const transport = new TransportWebSocket_1.TransportWebSocket(fullUri, auth);
|
|
303
|
+
// Initialize the DAPClient
|
|
304
|
+
super(clientName, transport, config);
|
|
305
|
+
this._dapAttempted = false;
|
|
306
|
+
this._nextChatId = 1;
|
|
307
|
+
// Persistence properties for automatic reconnection
|
|
308
|
+
this._persist = false;
|
|
309
|
+
this._reconnectDelay = 1000; // Default delay in milliseconds
|
|
310
|
+
this._manualDisconnect = false;
|
|
311
|
+
// Store connection details and environment
|
|
312
|
+
this._uri = fullUri;
|
|
313
|
+
this._apikey = auth;
|
|
314
|
+
this._env = clientEnv;
|
|
315
|
+
// Set up callbacks if provided
|
|
316
|
+
if (onEvent)
|
|
317
|
+
this._callerOnEvent = onEvent;
|
|
318
|
+
if (onConnected)
|
|
319
|
+
this._callerOnConnected = onConnected;
|
|
320
|
+
if (onDisconnected)
|
|
321
|
+
this._callerOnDisconnected = onDisconnected;
|
|
322
|
+
// Set up persistence options
|
|
323
|
+
this._persist = persist ?? false;
|
|
324
|
+
this._reconnectDelay = reconnectDelay ?? 1000;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Convert HTTP/HTTPS URI to WebSocket URI
|
|
328
|
+
*/
|
|
329
|
+
static _convertToWebSocketUri(uri) {
|
|
330
|
+
try {
|
|
331
|
+
const url = new URL(uri);
|
|
332
|
+
const wsScheme = url.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
333
|
+
return `${wsScheme}//${url.host}`;
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return uri;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Clear any pending reconnection timeout
|
|
341
|
+
*/
|
|
342
|
+
_clearReconnectTimeout() {
|
|
343
|
+
if (this._reconnectTimeout) {
|
|
344
|
+
clearTimeout(this._reconnectTimeout);
|
|
345
|
+
this._reconnectTimeout = undefined;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Schedule a reconnection attempt after the configured delay
|
|
350
|
+
*/
|
|
351
|
+
_scheduleReconnect() {
|
|
352
|
+
this._clearReconnectTimeout();
|
|
353
|
+
this.debugMessage(`Scheduling reconnection in ${this._reconnectDelay}ms`);
|
|
354
|
+
this._reconnectTimeout = setTimeout(async () => {
|
|
355
|
+
if (this._persist && !this._manualDisconnect) {
|
|
356
|
+
this.debugMessage('Attempting to reconnect...');
|
|
357
|
+
try {
|
|
358
|
+
await super.connect();
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
this.debugMessage(`Reconnection failed: ${error}`);
|
|
362
|
+
// Will schedule another attempt via onDisconnected
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
}, this._reconnectDelay);
|
|
366
|
+
}
|
|
367
|
+
// ============================================================================
|
|
368
|
+
// CONNECTION METHODS
|
|
369
|
+
// ============================================================================
|
|
370
|
+
/**
|
|
371
|
+
* Check if the client is currently connected to the Aparavi server.
|
|
372
|
+
*/
|
|
373
|
+
isConnected() {
|
|
374
|
+
return this._transport?.isConnected() || false;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Connect to the Aparavi server.
|
|
378
|
+
*
|
|
379
|
+
* Must be called before executing pipelines or other operations.
|
|
380
|
+
* In persist mode, enables automatic reconnection on disconnect.
|
|
381
|
+
*/
|
|
382
|
+
async connect() {
|
|
383
|
+
this._manualDisconnect = false;
|
|
384
|
+
// If we are already connected, disconnect first
|
|
385
|
+
if (this.isConnected()) {
|
|
386
|
+
await this.disconnect();
|
|
387
|
+
}
|
|
388
|
+
// Call the DAP client function
|
|
389
|
+
await super.connect();
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Disconnect from the Aparavi server and stop automatic reconnection.
|
|
393
|
+
*
|
|
394
|
+
* Should be called when finished with the client to clean up resources.
|
|
395
|
+
*/
|
|
396
|
+
async disconnect() {
|
|
397
|
+
this._manualDisconnect = true;
|
|
398
|
+
this._clearReconnectTimeout();
|
|
399
|
+
// Call the DAP client function
|
|
400
|
+
await super.disconnect();
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Send a request to the Aparavi server.
|
|
404
|
+
*/
|
|
405
|
+
async request(request) {
|
|
406
|
+
// Delegate to parent class for actual request processing
|
|
407
|
+
return super.request(request);
|
|
408
|
+
}
|
|
409
|
+
// ============================================================================
|
|
410
|
+
// PING METHODS
|
|
411
|
+
// ============================================================================
|
|
412
|
+
/**
|
|
413
|
+
* Test connectivity to the Aparavi server.
|
|
414
|
+
*
|
|
415
|
+
* Sends a lightweight ping request to the server to verify it's responding
|
|
416
|
+
* and reachable. This is useful for connectivity testing, health checks,
|
|
417
|
+
* and measuring response times.
|
|
418
|
+
*/
|
|
419
|
+
async ping(token) {
|
|
420
|
+
// Build ping request
|
|
421
|
+
const request = this.buildRequest('apaext_ping', { token });
|
|
422
|
+
// Send to server and wait for response
|
|
423
|
+
const response = await this.request(request);
|
|
424
|
+
// Check if ping failed
|
|
425
|
+
if (this.didFail(response)) {
|
|
426
|
+
const errorMsg = response.message || 'Ping failed';
|
|
427
|
+
throw new Error(`Ping failed: ${errorMsg}`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
// ============================================================================
|
|
431
|
+
// EXECUTION METHODS
|
|
432
|
+
// ============================================================================
|
|
433
|
+
/**
|
|
434
|
+
* Substitute environment variables in a string.
|
|
435
|
+
* Replaces ${APARAVI_*} patterns with values from client's env dictionary.
|
|
436
|
+
* If variable is not found, leaves it unchanged.
|
|
437
|
+
*/
|
|
438
|
+
substituteEnvVars(value) {
|
|
439
|
+
// Match ${APARAVI_*} patterns
|
|
440
|
+
return value.replace(/\$\{(APARAVI_[^}]+)\}/g, (match, varName) => {
|
|
441
|
+
// Check if variable exists in client's env
|
|
442
|
+
if (varName in this._env) {
|
|
443
|
+
return String(this._env[varName]);
|
|
444
|
+
}
|
|
445
|
+
// If not found, leave as is
|
|
446
|
+
return match;
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Recursively process an object/array to substitute environment variables.
|
|
451
|
+
* Only processes string values, leaving other types unchanged.
|
|
452
|
+
*/
|
|
453
|
+
processEnvSubstitution(obj) {
|
|
454
|
+
if (typeof obj === 'string') {
|
|
455
|
+
// If it's a string, perform substitution
|
|
456
|
+
return this.substituteEnvVars(obj);
|
|
457
|
+
}
|
|
458
|
+
else if (Array.isArray(obj)) {
|
|
459
|
+
// If it's an array, process each element
|
|
460
|
+
return obj.map(item => this.processEnvSubstitution(item));
|
|
461
|
+
}
|
|
462
|
+
else if (obj !== null && typeof obj === 'object') {
|
|
463
|
+
// If it's an object, process each property
|
|
464
|
+
const result = {};
|
|
465
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
466
|
+
result[key] = this.processEnvSubstitution(value);
|
|
467
|
+
}
|
|
468
|
+
return result;
|
|
469
|
+
}
|
|
470
|
+
// For other types (number, boolean, null), return as is
|
|
471
|
+
return obj;
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Start an Aparavi pipeline for processing data.
|
|
475
|
+
*
|
|
476
|
+
* This method loads and executes a pipeline configuration. It automatically performs
|
|
477
|
+
* environment variable substitution on the pipeline config, replacing ${APARAVI_*}
|
|
478
|
+
* placeholders with values from the .env file.
|
|
479
|
+
*
|
|
480
|
+
* @param options - Pipeline execution options
|
|
481
|
+
* @param options.token - Custom token for the pipeline (auto-generated if not provided)
|
|
482
|
+
* @param options.filepath - Path to JSON file containing pipeline configuration
|
|
483
|
+
* @param options.pipeline - Pipeline configuration object (alternative to filepath)
|
|
484
|
+
* @param options.source - Override pipeline source
|
|
485
|
+
* @param options.threads - Number of threads for execution (default: 1)
|
|
486
|
+
* @param options.useExisting - Use existing pipeline instance
|
|
487
|
+
* @param options.args - Command line arguments to pass to pipeline
|
|
488
|
+
*
|
|
489
|
+
* @returns Promise resolving to an object containing the task token and other metadata
|
|
490
|
+
* @throws Error if neither pipeline nor filepath is provided
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* // Using pipeline file
|
|
495
|
+
* const result = await client.use({ filepath: './pipeline.json' });
|
|
496
|
+
*
|
|
497
|
+
* // Using pipeline object
|
|
498
|
+
* const result = await client.use({
|
|
499
|
+
* pipeline: { components: [...], source: 'local', project_id: '123' }
|
|
500
|
+
* });
|
|
501
|
+
*
|
|
502
|
+
* // With environment variable substitution
|
|
503
|
+
* // Pipeline config: { "endpoint": "${APARAVI_URI}/api" }
|
|
504
|
+
* // Will be replaced with actual APARAVI_URI value from .env
|
|
505
|
+
* ```
|
|
506
|
+
*/
|
|
507
|
+
async use(options = {}) {
|
|
508
|
+
const { token, filepath, pipeline, source, threads, useExisting, args } = options;
|
|
509
|
+
// Validate required parameters
|
|
510
|
+
if (!pipeline && !filepath) {
|
|
511
|
+
throw new Error('Pipeline configuration or file path is required and must be specified');
|
|
512
|
+
}
|
|
513
|
+
let pipelineConfig;
|
|
514
|
+
// Load pipeline configuration from file if needed
|
|
515
|
+
if (!pipeline && filepath) {
|
|
516
|
+
// Check if we're in Node.js environment
|
|
517
|
+
if (typeof window !== 'undefined') {
|
|
518
|
+
throw new Error('File loading not available in browser environment. Please provide pipeline object directly.');
|
|
519
|
+
}
|
|
520
|
+
// Load file in Node.js
|
|
521
|
+
const fs = require('fs');
|
|
522
|
+
const fileContent = fs.readFileSync(filepath, 'utf-8');
|
|
523
|
+
pipelineConfig = JSON.parse(fileContent);
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
pipelineConfig = pipeline;
|
|
527
|
+
}
|
|
528
|
+
// Create a deep copy of the pipeline config to avoid modifying the original
|
|
529
|
+
let processedConfig = JSON.parse(JSON.stringify(pipelineConfig));
|
|
530
|
+
// Perform environment variable substitution on the pipeline configuration
|
|
531
|
+
processedConfig = this.processEnvSubstitution(processedConfig);
|
|
532
|
+
// Override source if specified (after substitution)
|
|
533
|
+
if (source !== undefined) {
|
|
534
|
+
processedConfig.pipeline.source = source;
|
|
535
|
+
}
|
|
536
|
+
// Build execution request with all parameters
|
|
537
|
+
const arguments_ = {
|
|
538
|
+
pipeline: processedConfig,
|
|
539
|
+
args: args || [],
|
|
540
|
+
};
|
|
541
|
+
// Add optional parameters if specified
|
|
542
|
+
if (token !== undefined) {
|
|
543
|
+
arguments_.token = token;
|
|
544
|
+
}
|
|
545
|
+
if (threads !== undefined) {
|
|
546
|
+
arguments_.threads = threads;
|
|
547
|
+
}
|
|
548
|
+
if (useExisting !== undefined) {
|
|
549
|
+
arguments_.useExisting = useExisting;
|
|
550
|
+
}
|
|
551
|
+
// Send execution request to server
|
|
552
|
+
const request = this.buildRequest('execute', { arguments: arguments_ });
|
|
553
|
+
const response = await this.request(request);
|
|
554
|
+
// Check for execution errors
|
|
555
|
+
if (this.didFail(response)) {
|
|
556
|
+
const errorMsg = response.message || 'Unknown execution error';
|
|
557
|
+
this.debugMessage(`Pipeline execution failed: ${errorMsg}`);
|
|
558
|
+
throw new Error(errorMsg);
|
|
559
|
+
}
|
|
560
|
+
// Extract and validate response
|
|
561
|
+
const responseBody = response.body || {};
|
|
562
|
+
const taskToken = responseBody.token;
|
|
563
|
+
if (!taskToken) {
|
|
564
|
+
throw new Error('Server did not return a task token in successful response');
|
|
565
|
+
}
|
|
566
|
+
this.debugMessage(`Pipeline execution started successfully, task token: ${taskToken}`);
|
|
567
|
+
// Type assertion to ensure token is present
|
|
568
|
+
return responseBody;
|
|
569
|
+
}
|
|
570
|
+
/**
|
|
571
|
+
* Terminate a running pipeline.
|
|
572
|
+
*/
|
|
573
|
+
async terminate(token) {
|
|
574
|
+
// Send termination request
|
|
575
|
+
const request = this.buildRequest('terminate', { token });
|
|
576
|
+
const response = await this.request(request);
|
|
577
|
+
// Check for termination errors
|
|
578
|
+
if (this.didFail(response)) {
|
|
579
|
+
const errorMsg = response.message || 'Unknown termination error';
|
|
580
|
+
this.debugMessage(`Pipeline termination failed: ${errorMsg}`);
|
|
581
|
+
throw new Error(errorMsg);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Get the current status of a running pipeline.
|
|
586
|
+
*/
|
|
587
|
+
async getTaskStatus(token) {
|
|
588
|
+
// Send status request
|
|
589
|
+
const request = this.buildRequest('apaext_get_task_status', { token });
|
|
590
|
+
const response = await this.request(request);
|
|
591
|
+
// Check for status retrieval errors
|
|
592
|
+
if (this.didFail(response)) {
|
|
593
|
+
const errorMsg = response.message || 'Unknown status retrieval error';
|
|
594
|
+
this.debugMessage(`Pipeline status retrieval failed: ${errorMsg}`);
|
|
595
|
+
throw new Error(errorMsg);
|
|
596
|
+
}
|
|
597
|
+
// Return status information
|
|
598
|
+
return response.body || {};
|
|
599
|
+
}
|
|
600
|
+
// ============================================================================
|
|
601
|
+
// DATA METHODS
|
|
602
|
+
// ============================================================================
|
|
603
|
+
/**
|
|
604
|
+
* Create a data pipe for streaming operations.
|
|
605
|
+
*/
|
|
606
|
+
async pipe(token, objinfo = {}, mimeType, provider) {
|
|
607
|
+
return new DataPipe(this, token, objinfo, mimeType, provider);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Send data to a running pipeline.
|
|
611
|
+
*/
|
|
612
|
+
async send(token, data, objinfo = {}, mimetype) {
|
|
613
|
+
// Convert string to bytes if needed
|
|
614
|
+
let buffer;
|
|
615
|
+
if (typeof data === 'string') {
|
|
616
|
+
buffer = new TextEncoder().encode(data);
|
|
617
|
+
}
|
|
618
|
+
else if (data instanceof Uint8Array) {
|
|
619
|
+
buffer = data;
|
|
620
|
+
}
|
|
621
|
+
else {
|
|
622
|
+
throw new Error('data must be either a string or Uint8Array');
|
|
623
|
+
}
|
|
624
|
+
// Create and use a temporary pipe for the data
|
|
625
|
+
const pipe = await this.pipe(token, objinfo, mimetype);
|
|
626
|
+
try {
|
|
627
|
+
await pipe.open();
|
|
628
|
+
await pipe.write(buffer);
|
|
629
|
+
return await pipe.close();
|
|
630
|
+
}
|
|
631
|
+
catch (error) {
|
|
632
|
+
// Clean up pipe on any error
|
|
633
|
+
if (pipe.isOpened) {
|
|
634
|
+
try {
|
|
635
|
+
await pipe.close();
|
|
636
|
+
}
|
|
637
|
+
catch {
|
|
638
|
+
// Ignore cleanup errors
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Upload multiple files to a pipeline with progress tracking and parallel execution.
|
|
646
|
+
*
|
|
647
|
+
* This method efficiently uploads files in parallel with configurable concurrency control.
|
|
648
|
+
* Each file is streamed through a data pipe, and progress events are emitted through the
|
|
649
|
+
* event system for all subscribers. The order of results matches the input file order.
|
|
650
|
+
*
|
|
651
|
+
* Progress events are sent through the event system as 'apaevt_status_upload' events
|
|
652
|
+
* (matching Python client behavior) rather than through a callback parameter.
|
|
653
|
+
*
|
|
654
|
+
* @param files - Array of file objects with optional metadata and MIME types
|
|
655
|
+
* @param token - Pipeline task token to receive the uploads
|
|
656
|
+
* @param maxConcurrent - Maximum number of concurrent uploads (default: 5)
|
|
657
|
+
*
|
|
658
|
+
* @returns Promise resolving to array of UPLOAD_RESULT objects in the same order as input
|
|
659
|
+
*
|
|
660
|
+
* @example
|
|
661
|
+
* ```typescript
|
|
662
|
+
* // Subscribe to upload events
|
|
663
|
+
* client.on('apaevt_status_upload', (event) => {
|
|
664
|
+
* console.log(`${event.body.filepath}: ${event.body.bytes_sent}/${event.body.file_size}`);
|
|
665
|
+
* });
|
|
666
|
+
*
|
|
667
|
+
* // Upload files
|
|
668
|
+
* const results = await client.sendFiles(
|
|
669
|
+
* [
|
|
670
|
+
* { file: fileObject1 },
|
|
671
|
+
* { file: fileObject2, mimetype: 'application/json' },
|
|
672
|
+
* { file: fileObject3, objinfo: { custom: 'metadata' } }
|
|
673
|
+
* ],
|
|
674
|
+
* 'task-token',
|
|
675
|
+
* 10 // Upload max 10 files concurrently
|
|
676
|
+
* );
|
|
677
|
+
* ```
|
|
678
|
+
*/
|
|
679
|
+
async sendFiles(files, token, maxConcurrent = 5) {
|
|
680
|
+
const results = new Array(files.length);
|
|
681
|
+
/**
|
|
682
|
+
* Helper function to send upload events through the event system.
|
|
683
|
+
* This matches the Python implementation where events are emitted
|
|
684
|
+
* through the event system rather than callbacks.
|
|
685
|
+
*/
|
|
686
|
+
const sendUploadEvent = (body) => {
|
|
687
|
+
const eventMessage = {
|
|
688
|
+
event: 'apaevt_status_upload',
|
|
689
|
+
body: body,
|
|
690
|
+
seq: 0,
|
|
691
|
+
type: 'event'
|
|
692
|
+
};
|
|
693
|
+
// Emit event through your event system
|
|
694
|
+
// Replace this with your actual event emission method
|
|
695
|
+
this.onEvent(eventMessage);
|
|
696
|
+
};
|
|
697
|
+
/**
|
|
698
|
+
* Upload a single file
|
|
699
|
+
*/
|
|
700
|
+
const uploadFile = async (fileData, index) => {
|
|
701
|
+
const { file, objinfo = {}, mimetype } = fileData;
|
|
702
|
+
const startTime = Date.now();
|
|
703
|
+
let bytesUploaded = 0;
|
|
704
|
+
let pipe = null;
|
|
705
|
+
let error;
|
|
706
|
+
let result;
|
|
707
|
+
// Prepare objinfo with file metadata
|
|
708
|
+
const fullObjinfo = {
|
|
709
|
+
name: file.name,
|
|
710
|
+
size: file.size,
|
|
711
|
+
...objinfo,
|
|
712
|
+
};
|
|
713
|
+
const finalMimetype = mimetype || file.type || 'application/octet-stream';
|
|
714
|
+
try {
|
|
715
|
+
const fileSize = file.size;
|
|
716
|
+
// Create and open pipe
|
|
717
|
+
pipe = await this.pipe(token, fullObjinfo, finalMimetype);
|
|
718
|
+
// Open the pipe
|
|
719
|
+
await pipe.open();
|
|
720
|
+
// Send upload start event (action: 'open')
|
|
721
|
+
sendUploadEvent({
|
|
722
|
+
action: 'open',
|
|
723
|
+
filepath: file.name,
|
|
724
|
+
bytes_sent: 0,
|
|
725
|
+
file_size: fileSize,
|
|
726
|
+
upload_time: 0,
|
|
727
|
+
});
|
|
728
|
+
// Upload file in chunks
|
|
729
|
+
const reader = file.stream().getReader();
|
|
730
|
+
try {
|
|
731
|
+
while (true) {
|
|
732
|
+
const { done, value } = await reader.read();
|
|
733
|
+
if (done)
|
|
734
|
+
break;
|
|
735
|
+
// Write chunk
|
|
736
|
+
await pipe.write(value);
|
|
737
|
+
bytesUploaded += value.length;
|
|
738
|
+
// Send progress event for every chunk (action: 'write')
|
|
739
|
+
sendUploadEvent({
|
|
740
|
+
action: 'write',
|
|
741
|
+
filepath: file.name,
|
|
742
|
+
bytes_sent: bytesUploaded,
|
|
743
|
+
file_size: fileSize,
|
|
744
|
+
upload_time: (Date.now() - startTime) / 1000,
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
finally {
|
|
749
|
+
reader.releaseLock();
|
|
750
|
+
}
|
|
751
|
+
// Send close event (action: 'close')
|
|
752
|
+
sendUploadEvent({
|
|
753
|
+
action: 'close',
|
|
754
|
+
filepath: file.name,
|
|
755
|
+
bytes_sent: bytesUploaded,
|
|
756
|
+
file_size: fileSize,
|
|
757
|
+
upload_time: (Date.now() - startTime) / 1000,
|
|
758
|
+
});
|
|
759
|
+
// Close pipe and get result
|
|
760
|
+
result = await pipe.close();
|
|
761
|
+
}
|
|
762
|
+
catch (err) {
|
|
763
|
+
error = err instanceof Error ? err.message : String(err);
|
|
764
|
+
}
|
|
765
|
+
// Create final result
|
|
766
|
+
const uploadTime = (Date.now() - startTime) / 1000;
|
|
767
|
+
const finalResult = {
|
|
768
|
+
action: error ? 'error' : 'complete',
|
|
769
|
+
filepath: file.name,
|
|
770
|
+
bytes_sent: bytesUploaded,
|
|
771
|
+
file_size: file.size,
|
|
772
|
+
upload_time: uploadTime,
|
|
773
|
+
result,
|
|
774
|
+
error,
|
|
775
|
+
};
|
|
776
|
+
// Send final result event (action: 'complete' or 'error')
|
|
777
|
+
sendUploadEvent(finalResult);
|
|
778
|
+
// Store result at the correct index
|
|
779
|
+
results[index] = finalResult;
|
|
780
|
+
};
|
|
781
|
+
// Execute uploads with concurrency limit
|
|
782
|
+
const executing = new Set();
|
|
783
|
+
for (let i = 0; i < files.length; i++) {
|
|
784
|
+
const uploadPromise = uploadFile(files[i], i).then(() => {
|
|
785
|
+
// Remove this promise from the executing set when done
|
|
786
|
+
executing.delete(uploadPromise);
|
|
787
|
+
}).catch(() => {
|
|
788
|
+
// Remove this promise from the executing set even on error
|
|
789
|
+
executing.delete(uploadPromise);
|
|
790
|
+
});
|
|
791
|
+
executing.add(uploadPromise);
|
|
792
|
+
// If we've reached max concurrency, wait for one to complete
|
|
793
|
+
if (executing.size >= maxConcurrent) {
|
|
794
|
+
await Promise.race(executing);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
// Wait for all remaining uploads to complete
|
|
798
|
+
await Promise.all(Array.from(executing));
|
|
799
|
+
return results;
|
|
800
|
+
}
|
|
801
|
+
// ============================================================================
|
|
802
|
+
// CHAT METHODS
|
|
803
|
+
// ============================================================================
|
|
804
|
+
/**
|
|
805
|
+
* Ask a question to Aparavi's AI and get an intelligent response.
|
|
806
|
+
*/
|
|
807
|
+
async chat(options) {
|
|
808
|
+
const { token, question } = options;
|
|
809
|
+
try {
|
|
810
|
+
// Validate that we have a question to ask
|
|
811
|
+
if (!question) {
|
|
812
|
+
throw new Error('Question cannot be empty');
|
|
813
|
+
}
|
|
814
|
+
// Create unique identifier for this chat operation
|
|
815
|
+
const objinfo = { name: `Question ${this._nextChatId}` };
|
|
816
|
+
this._nextChatId += 1;
|
|
817
|
+
// Create pipe instance
|
|
818
|
+
const pipe = new DataPipe(this, token, objinfo, 'application/aparavi-question', 'chat');
|
|
819
|
+
try {
|
|
820
|
+
// Open the communication channel to the AI
|
|
821
|
+
await pipe.open();
|
|
822
|
+
// Send the question as JSON data to the AI system
|
|
823
|
+
const questionJson = JSON.stringify(question.toDict());
|
|
824
|
+
const questionBytes = new TextEncoder().encode(questionJson);
|
|
825
|
+
await pipe.write(questionBytes);
|
|
826
|
+
// Close the pipe and get the AI's response
|
|
827
|
+
const result = await pipe.close();
|
|
828
|
+
// Check it
|
|
829
|
+
if (!result) {
|
|
830
|
+
throw new Error('No response received from AI');
|
|
831
|
+
}
|
|
832
|
+
// Return success response in standard format
|
|
833
|
+
return result;
|
|
834
|
+
}
|
|
835
|
+
finally {
|
|
836
|
+
// Ensure the pipe is properly closed even if errors occur
|
|
837
|
+
if (pipe.isOpened) {
|
|
838
|
+
try {
|
|
839
|
+
await pipe.close();
|
|
840
|
+
}
|
|
841
|
+
catch {
|
|
842
|
+
// Ignore errors during cleanup
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
catch (error) {
|
|
848
|
+
// Return error response in standard format
|
|
849
|
+
throw new Error(error instanceof Error ? error.message : String(error));
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
// ============================================================================
|
|
853
|
+
// EVENT METHODS
|
|
854
|
+
// ============================================================================
|
|
855
|
+
/**
|
|
856
|
+
* Send events to debugging interface if available (for development).
|
|
857
|
+
*/
|
|
858
|
+
_sendVSCodeEvent(eventType, body) {
|
|
859
|
+
// Set up debugging integration on first use
|
|
860
|
+
if (!this._dapAttempted) {
|
|
861
|
+
this._dapAttempted = true;
|
|
862
|
+
try {
|
|
863
|
+
// In browser environment, check for debugging tools
|
|
864
|
+
if (typeof window !== 'undefined' && window.__APARAVI_DEBUG__) {
|
|
865
|
+
this._dapSend = window.__APARAVI_DEBUG__.sendEvent;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
catch {
|
|
869
|
+
// Not in debugging environment - no problem
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
// Send event to debugger if available
|
|
873
|
+
if (this._dapSend) {
|
|
874
|
+
const customEvent = {
|
|
875
|
+
type: 'event',
|
|
876
|
+
event: eventType,
|
|
877
|
+
body: body,
|
|
878
|
+
};
|
|
879
|
+
this._dapSend(customEvent);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
/**
|
|
883
|
+
* Handle incoming events from the Aparavi server.
|
|
884
|
+
*/
|
|
885
|
+
async onEvent(message) {
|
|
886
|
+
// Extract event information
|
|
887
|
+
const eventType = message.event || 'unknown';
|
|
888
|
+
const eventBody = message.body || {};
|
|
889
|
+
const seqNum = message.seq || 0;
|
|
890
|
+
// Forward to debugging interface if available
|
|
891
|
+
this._sendVSCodeEvent(eventType, eventBody);
|
|
892
|
+
// Call user-provided event handler if available
|
|
893
|
+
if (this._callerOnEvent) {
|
|
894
|
+
try {
|
|
895
|
+
await this._callerOnEvent(message);
|
|
896
|
+
}
|
|
897
|
+
catch (error) {
|
|
898
|
+
// Log errors but don't let user code break the connection
|
|
899
|
+
this.debugMessage(`Error in user onEvent handler for ${eventType} (seq ${seqNum}): ${error}`);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Handle connected events from the Aparavi server.
|
|
905
|
+
*/
|
|
906
|
+
async onConnected(connectionInfo) {
|
|
907
|
+
this._manualDisconnect = false;
|
|
908
|
+
this._clearReconnectTimeout();
|
|
909
|
+
// Call user-provided event handler if available
|
|
910
|
+
if (this._callerOnConnected) {
|
|
911
|
+
try {
|
|
912
|
+
await this._callerOnConnected(connectionInfo);
|
|
913
|
+
}
|
|
914
|
+
catch (error) {
|
|
915
|
+
// Log errors but don't let user code break the connection
|
|
916
|
+
this.debugMessage(`Error in user onConnected handler for ${connectionInfo}: ${error}`);
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Handle disconnected events from the Aparavi server.
|
|
922
|
+
*/
|
|
923
|
+
async onDisconnected(reason, hasError) {
|
|
924
|
+
// Call user-provided event handler if available
|
|
925
|
+
if (this._callerOnDisconnected) {
|
|
926
|
+
try {
|
|
927
|
+
await this._callerOnDisconnected(reason, hasError);
|
|
928
|
+
}
|
|
929
|
+
catch (error) {
|
|
930
|
+
// Log errors but don't let user code break the connection
|
|
931
|
+
this.debugMessage(`Error in user onDisconnected handler for ${reason}: ${error}`);
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
// Schedule reconnection if persist is enabled and not a manual disconnect
|
|
935
|
+
if (this._persist && !this._manualDisconnect) {
|
|
936
|
+
this._scheduleReconnect();
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Subscribe to specific types of events from the server.
|
|
941
|
+
*/
|
|
942
|
+
async setEvents(token, eventTypes) {
|
|
943
|
+
// Build event subscription request
|
|
944
|
+
const request = this.buildRequest('apaext_monitor', {
|
|
945
|
+
arguments: { types: eventTypes },
|
|
946
|
+
token,
|
|
947
|
+
});
|
|
948
|
+
// Send to server
|
|
949
|
+
const response = await this.request(request);
|
|
950
|
+
// Check for errors
|
|
951
|
+
if (this.didFail(response)) {
|
|
952
|
+
const errorMsg = response.message || 'Event subscription failed';
|
|
953
|
+
throw new Error(errorMsg);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
// ============================================================================
|
|
957
|
+
// CONTEXT MANAGER SUPPORT - Python-style async context manager
|
|
958
|
+
// ============================================================================
|
|
959
|
+
/**
|
|
960
|
+
* Async disposal support for 'await using' pattern.
|
|
961
|
+
* Equivalent to Python's __aexit__
|
|
962
|
+
*/
|
|
963
|
+
async [Symbol.asyncDispose]() {
|
|
964
|
+
await this.disconnect();
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Static factory method for automatic connection management.
|
|
968
|
+
* Equivalent to Python's async with pattern
|
|
969
|
+
*/
|
|
970
|
+
static async withConnection(config, callback) {
|
|
971
|
+
const client = new AparaviClient(config);
|
|
972
|
+
try {
|
|
973
|
+
await client.connect();
|
|
974
|
+
return await callback(client);
|
|
975
|
+
}
|
|
976
|
+
finally {
|
|
977
|
+
await client.disconnect();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
// ============================================================================
|
|
981
|
+
// ADDITIONAL CONVENIENCE METHODS
|
|
982
|
+
// ============================================================================
|
|
983
|
+
/**
|
|
984
|
+
* Get connection information (TypeScript-specific convenience)
|
|
985
|
+
*/
|
|
986
|
+
getConnectionInfo() {
|
|
987
|
+
return {
|
|
988
|
+
connected: this.isConnected(),
|
|
989
|
+
transport: 'WebSocket',
|
|
990
|
+
uri: this._uri,
|
|
991
|
+
};
|
|
992
|
+
}
|
|
993
|
+
/**
|
|
994
|
+
* Get API key (for debugging/validation)
|
|
995
|
+
*/
|
|
996
|
+
getApiKey() {
|
|
997
|
+
return this._apikey;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
exports.AparaviClient = AparaviClient;
|
|
1001
|
+
exports.default = AparaviClient;
|
|
1002
|
+
//# sourceMappingURL=client.js.map
|