rocketride 1.0.0

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.
Files changed (166) hide show
  1. package/README.md +476 -0
  2. package/dist/cjs/client.js +1745 -0
  3. package/dist/cjs/client.js.map +1 -0
  4. package/dist/cjs/constants.js +48 -0
  5. package/dist/cjs/constants.js.map +1 -0
  6. package/dist/cjs/core/DAPBase.js +328 -0
  7. package/dist/cjs/core/DAPBase.js.map +1 -0
  8. package/dist/cjs/core/DAPClient.js +226 -0
  9. package/dist/cjs/core/DAPClient.js.map +1 -0
  10. package/dist/cjs/core/TransportBase.js +152 -0
  11. package/dist/cjs/core/TransportBase.js.map +1 -0
  12. package/dist/cjs/core/TransportWebSocket.js +646 -0
  13. package/dist/cjs/core/TransportWebSocket.js.map +1 -0
  14. package/dist/cjs/exceptions/index.js +119 -0
  15. package/dist/cjs/exceptions/index.js.map +1 -0
  16. package/dist/cjs/index.js +56 -0
  17. package/dist/cjs/index.js.map +1 -0
  18. package/dist/cjs/package.json +3 -0
  19. package/dist/cjs/schema/Doc.js +79 -0
  20. package/dist/cjs/schema/Doc.js.map +1 -0
  21. package/dist/cjs/schema/DocFilter.js +133 -0
  22. package/dist/cjs/schema/DocFilter.js.map +1 -0
  23. package/dist/cjs/schema/DocGroup.js +230 -0
  24. package/dist/cjs/schema/DocGroup.js.map +1 -0
  25. package/dist/cjs/schema/DocMetadata.js +58 -0
  26. package/dist/cjs/schema/DocMetadata.js.map +1 -0
  27. package/dist/cjs/schema/Question.js +414 -0
  28. package/dist/cjs/schema/Question.js.map +1 -0
  29. package/dist/cjs/schema/index.js +50 -0
  30. package/dist/cjs/schema/index.js.map +1 -0
  31. package/dist/cjs/types/client.js +26 -0
  32. package/dist/cjs/types/client.js.map +1 -0
  33. package/dist/cjs/types/data.js +26 -0
  34. package/dist/cjs/types/data.js.map +1 -0
  35. package/dist/cjs/types/events.js +119 -0
  36. package/dist/cjs/types/events.js.map +1 -0
  37. package/dist/cjs/types/index.js +50 -0
  38. package/dist/cjs/types/index.js.map +1 -0
  39. package/dist/cjs/types/pipeline.js +26 -0
  40. package/dist/cjs/types/pipeline.js.map +1 -0
  41. package/dist/cjs/types/task.js +115 -0
  42. package/dist/cjs/types/task.js.map +1 -0
  43. package/dist/cli/cli/rocketride.js +1399 -0
  44. package/dist/cli/cli/rocketride.js.map +1 -0
  45. package/dist/cli/client/client.js +1745 -0
  46. package/dist/cli/client/client.js.map +1 -0
  47. package/dist/cli/client/constants.js +48 -0
  48. package/dist/cli/client/constants.js.map +1 -0
  49. package/dist/cli/client/core/DAPBase.js +328 -0
  50. package/dist/cli/client/core/DAPBase.js.map +1 -0
  51. package/dist/cli/client/core/DAPClient.js +226 -0
  52. package/dist/cli/client/core/DAPClient.js.map +1 -0
  53. package/dist/cli/client/core/TransportBase.js +152 -0
  54. package/dist/cli/client/core/TransportBase.js.map +1 -0
  55. package/dist/cli/client/core/TransportWebSocket.js +646 -0
  56. package/dist/cli/client/core/TransportWebSocket.js.map +1 -0
  57. package/dist/cli/client/exceptions/index.js +119 -0
  58. package/dist/cli/client/exceptions/index.js.map +1 -0
  59. package/dist/cli/client/index.js +56 -0
  60. package/dist/cli/client/index.js.map +1 -0
  61. package/dist/cli/client/schema/Doc.js +79 -0
  62. package/dist/cli/client/schema/Doc.js.map +1 -0
  63. package/dist/cli/client/schema/DocFilter.js +133 -0
  64. package/dist/cli/client/schema/DocFilter.js.map +1 -0
  65. package/dist/cli/client/schema/DocGroup.js +230 -0
  66. package/dist/cli/client/schema/DocGroup.js.map +1 -0
  67. package/dist/cli/client/schema/DocMetadata.js +58 -0
  68. package/dist/cli/client/schema/DocMetadata.js.map +1 -0
  69. package/dist/cli/client/schema/Question.js +414 -0
  70. package/dist/cli/client/schema/Question.js.map +1 -0
  71. package/dist/cli/client/schema/index.js +50 -0
  72. package/dist/cli/client/schema/index.js.map +1 -0
  73. package/dist/cli/client/types/client.js +26 -0
  74. package/dist/cli/client/types/client.js.map +1 -0
  75. package/dist/cli/client/types/data.js +26 -0
  76. package/dist/cli/client/types/data.js.map +1 -0
  77. package/dist/cli/client/types/events.js +119 -0
  78. package/dist/cli/client/types/events.js.map +1 -0
  79. package/dist/cli/client/types/index.js +50 -0
  80. package/dist/cli/client/types/index.js.map +1 -0
  81. package/dist/cli/client/types/pipeline.js +26 -0
  82. package/dist/cli/client/types/pipeline.js.map +1 -0
  83. package/dist/cli/client/types/task.js +115 -0
  84. package/dist/cli/client/types/task.js.map +1 -0
  85. package/dist/esm/client.js +1740 -0
  86. package/dist/esm/client.js.map +1 -0
  87. package/dist/esm/constants.js +45 -0
  88. package/dist/esm/constants.js.map +1 -0
  89. package/dist/esm/core/DAPBase.js +324 -0
  90. package/dist/esm/core/DAPBase.js.map +1 -0
  91. package/dist/esm/core/DAPClient.js +222 -0
  92. package/dist/esm/core/DAPClient.js.map +1 -0
  93. package/dist/esm/core/TransportBase.js +148 -0
  94. package/dist/esm/core/TransportBase.js.map +1 -0
  95. package/dist/esm/core/TransportWebSocket.js +609 -0
  96. package/dist/esm/core/TransportWebSocket.js.map +1 -0
  97. package/dist/esm/exceptions/index.js +109 -0
  98. package/dist/esm/exceptions/index.js.map +1 -0
  99. package/dist/esm/index.js +40 -0
  100. package/dist/esm/index.js.map +1 -0
  101. package/dist/esm/package.json +3 -0
  102. package/dist/esm/schema/Doc.js +75 -0
  103. package/dist/esm/schema/Doc.js.map +1 -0
  104. package/dist/esm/schema/DocFilter.js +129 -0
  105. package/dist/esm/schema/DocFilter.js.map +1 -0
  106. package/dist/esm/schema/DocGroup.js +226 -0
  107. package/dist/esm/schema/DocGroup.js.map +1 -0
  108. package/dist/esm/schema/DocMetadata.js +54 -0
  109. package/dist/esm/schema/DocMetadata.js.map +1 -0
  110. package/dist/esm/schema/Question.js +409 -0
  111. package/dist/esm/schema/Question.js.map +1 -0
  112. package/dist/esm/schema/index.js +34 -0
  113. package/dist/esm/schema/index.js.map +1 -0
  114. package/dist/esm/types/client.js +25 -0
  115. package/dist/esm/types/client.js.map +1 -0
  116. package/dist/esm/types/data.js +25 -0
  117. package/dist/esm/types/data.js.map +1 -0
  118. package/dist/esm/types/events.js +116 -0
  119. package/dist/esm/types/events.js.map +1 -0
  120. package/dist/esm/types/index.js +34 -0
  121. package/dist/esm/types/index.js.map +1 -0
  122. package/dist/esm/types/pipeline.js +25 -0
  123. package/dist/esm/types/pipeline.js.map +1 -0
  124. package/dist/esm/types/task.js +112 -0
  125. package/dist/esm/types/task.js.map +1 -0
  126. package/dist/types/client.d.ts +798 -0
  127. package/dist/types/client.d.ts.map +1 -0
  128. package/dist/types/constants.d.ts +45 -0
  129. package/dist/types/constants.d.ts.map +1 -0
  130. package/dist/types/core/DAPBase.d.ts +152 -0
  131. package/dist/types/core/DAPBase.d.ts.map +1 -0
  132. package/dist/types/core/DAPClient.d.ts +93 -0
  133. package/dist/types/core/DAPClient.d.ts.map +1 -0
  134. package/dist/types/core/TransportBase.d.ts +113 -0
  135. package/dist/types/core/TransportBase.d.ts.map +1 -0
  136. package/dist/types/core/TransportWebSocket.d.ts +100 -0
  137. package/dist/types/core/TransportWebSocket.d.ts.map +1 -0
  138. package/dist/types/exceptions/index.d.ts +87 -0
  139. package/dist/types/exceptions/index.d.ts.map +1 -0
  140. package/dist/types/index.d.ts +36 -0
  141. package/dist/types/index.d.ts.map +1 -0
  142. package/dist/types/schema/Doc.d.ts +69 -0
  143. package/dist/types/schema/Doc.d.ts.map +1 -0
  144. package/dist/types/schema/DocFilter.d.ts +101 -0
  145. package/dist/types/schema/DocFilter.d.ts.map +1 -0
  146. package/dist/types/schema/DocGroup.d.ts +113 -0
  147. package/dist/types/schema/DocGroup.d.ts.map +1 -0
  148. package/dist/types/schema/DocMetadata.d.ts +77 -0
  149. package/dist/types/schema/DocMetadata.d.ts.map +1 -0
  150. package/dist/types/schema/Question.d.ts +163 -0
  151. package/dist/types/schema/Question.d.ts.map +1 -0
  152. package/dist/types/schema/index.d.ts +34 -0
  153. package/dist/types/schema/index.d.ts.map +1 -0
  154. package/dist/types/types/client.d.ts +149 -0
  155. package/dist/types/types/client.d.ts.map +1 -0
  156. package/dist/types/types/data.d.ts +95 -0
  157. package/dist/types/types/data.d.ts.map +1 -0
  158. package/dist/types/types/events.d.ts +246 -0
  159. package/dist/types/types/events.d.ts.map +1 -0
  160. package/dist/types/types/index.d.ts +34 -0
  161. package/dist/types/types/index.d.ts.map +1 -0
  162. package/dist/types/types/pipeline.d.ts +83 -0
  163. package/dist/types/types/pipeline.d.ts.map +1 -0
  164. package/dist/types/types/task.d.ts +314 -0
  165. package/dist/types/types/task.d.ts.map +1 -0
  166. package/package.json +61 -0
@@ -0,0 +1,1745 @@
1
+ "use strict";
2
+ /**
3
+ * MIT License
4
+ *
5
+ * Copyright (c) 2026 RocketRide, Inc.
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.RocketRideClient = 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
+ const exceptions_1 = require("./exceptions");
31
+ // Global counter for generating unique client IDs
32
+ let clientId = 0;
33
+ /**
34
+ * Streaming data pipe for sending large datasets to RocketRide pipelines.
35
+ *
36
+ * DataPipe provides a stream-like interface for uploading data to an RocketRide
37
+ * pipeline. It handles the low-level protocol details of opening, writing to,
38
+ * and closing data pipes on the server.
39
+ *
40
+ * Usage pattern:
41
+ * 1. Create pipe using client.pipe()
42
+ * 2. Call open() to establish the pipe
43
+ * 3. Call write() multiple times with data chunks
44
+ * 4. Call close() to finalize and get results
45
+ *
46
+ * @example
47
+ * ```typescript
48
+ * const pipe = await client.pipe(token, { filename: 'data.json' }, 'application/json');
49
+ * await pipe.open();
50
+ * await pipe.write(new TextEncoder().encode('{"data": "value"}'));
51
+ * const result = await pipe.close();
52
+ * ```
53
+ */
54
+ class DataPipe {
55
+ /**
56
+ * Creates a new DataPipe instance.
57
+ *
58
+ * @param client - The RocketRideClient instance managing this pipe
59
+ * @param token - Task token for the pipeline receiving the data
60
+ * @param objinfo - Metadata about the object being sent (e.g., filename, size)
61
+ * @param mimeType - MIME type of the data being sent (default: 'application/octet-stream')
62
+ * @param provider - Optional provider name for the data source
63
+ */
64
+ constructor(client, token, objinfo = {}, mimeType = 'application/octet-stream', provider) {
65
+ this._opened = false;
66
+ this._closed = false;
67
+ this._client = client;
68
+ this._token = token;
69
+ this._objinfo = objinfo;
70
+ this._mimeType = mimeType;
71
+ this._provider = provider;
72
+ }
73
+ /**
74
+ * Check if the pipe is currently open for writing.
75
+ *
76
+ * @returns true if the pipe has been opened and not yet closed
77
+ */
78
+ get isOpened() {
79
+ return this._opened;
80
+ }
81
+ /**
82
+ * Get the unique ID assigned to this pipe by the server.
83
+ *
84
+ * This ID is assigned when the pipe is opened and is used for subsequent
85
+ * write operations. It remains undefined until open() is called successfully.
86
+ *
87
+ * @returns The server-assigned pipe ID, or undefined if not yet opened
88
+ */
89
+ get pipeId() {
90
+ return this._pipeId;
91
+ }
92
+ /**
93
+ * Open the pipe for data transmission.
94
+ *
95
+ * Establishes a data pipe on the server for streaming data to the pipeline.
96
+ * Must be called before any write() operations. The server will assign a
97
+ * unique pipe ID that is used for subsequent operations.
98
+ *
99
+ * @returns This DataPipe instance (for method chaining)
100
+ * @throws Error if the pipe is already opened or if the pipeline is not running
101
+ */
102
+ async open() {
103
+ if (this._opened) {
104
+ throw new Error('Pipe already opened');
105
+ }
106
+ const request = this._client.buildRequest('rrext_process', {
107
+ arguments: {
108
+ subcommand: 'open',
109
+ object: this._objinfo,
110
+ mimeType: this._mimeType,
111
+ provider: this._provider,
112
+ },
113
+ token: this._token,
114
+ });
115
+ const response = await this._client.request(request);
116
+ if (this._client.didFail(response)) {
117
+ throw new Error(response.message || 'Your pipeline is not currently running.');
118
+ }
119
+ this._pipeId = response.body?.pipe_id;
120
+ this._opened = true;
121
+ return this;
122
+ }
123
+ /**
124
+ * Write data to the pipe.
125
+ *
126
+ * Sends a chunk of data through the pipe to the server pipeline. Can be called
127
+ * multiple times to stream large datasets. The pipe must be opened first.
128
+ *
129
+ * @param buffer - Data to write, must be a Uint8Array
130
+ * @throws Error if the pipe is not opened, buffer is invalid, or write fails
131
+ */
132
+ async write(buffer) {
133
+ if (!this._opened) {
134
+ throw new Error('Pipe not opened');
135
+ }
136
+ if (!(buffer instanceof Uint8Array)) {
137
+ throw new Error('Buffer must be Uint8Array');
138
+ }
139
+ const request = this._client.buildRequest('rrext_process', {
140
+ arguments: {
141
+ subcommand: 'write',
142
+ pipe_id: this._pipeId,
143
+ data: buffer,
144
+ },
145
+ token: this._token,
146
+ });
147
+ const response = await this._client.request(request);
148
+ if (this._client.didFail(response)) {
149
+ throw new Error(response.message || 'Failed to write to pipe');
150
+ }
151
+ }
152
+ /**
153
+ * Close the pipe and get the processing results.
154
+ *
155
+ * Finalizes the data stream and signals the server that no more data will be sent.
156
+ * The server processes any buffered data and returns the final result. After closing,
157
+ * the pipe cannot be reopened or written to again.
158
+ *
159
+ * @returns The processing result from the server, or undefined if already closed
160
+ * @throws Error if closing the pipe fails
161
+ */
162
+ async close() {
163
+ if (!this._opened || this._closed) {
164
+ return;
165
+ }
166
+ try {
167
+ const request = this._client.buildRequest('rrext_process', {
168
+ arguments: {
169
+ subcommand: 'close',
170
+ pipe_id: this._pipeId,
171
+ },
172
+ token: this._token,
173
+ });
174
+ const response = await this._client.request(request);
175
+ if (this._client.didFail(response)) {
176
+ throw new Error(response.message || 'Failed to close pipe');
177
+ }
178
+ return response.body;
179
+ }
180
+ finally {
181
+ this._closed = true;
182
+ }
183
+ }
184
+ }
185
+ exports.DataPipe = DataPipe;
186
+ /**
187
+ * Main RocketRide client for connecting to RocketRide servers and services.
188
+ *
189
+ * This client provides a comprehensive API for interacting with RocketRide services,
190
+ * including connection management, pipeline execution, data operations, AI chat,
191
+ * event handling, and server connectivity testing.
192
+ *
193
+ * Key features:
194
+ * - Single shared WebSocket connection for all operations
195
+ * - Connection management (connect/disconnect) with optional persistence
196
+ * - Automatic reconnection when persist mode is enabled
197
+ * - Pipeline execution (use, terminate, getTaskStatus)
198
+ * - Data operations (send, sendFiles, pipe)
199
+ * - AI chat functionality (chat)
200
+ * - Event handling (setEvents, event callbacks)
201
+ * - Server connectivity testing (ping)
202
+ * - Full TypeScript type safety
203
+ */
204
+ class RocketRideClient extends DAPClient_1.DAPClient {
205
+ /**
206
+ * Creates a new RocketRideClient instance.
207
+ *
208
+ * Configuration priority (highest to lowest):
209
+ * 1. Values passed in config parameter (auth, uri)
210
+ * 2. Values from env parameter (if provided)
211
+ * 3. Values from .env file (Node.js only)
212
+ * 4. Default values
213
+ *
214
+ * @param config - Configuration options for the client
215
+ * @param config.auth - API key for authentication (required)
216
+ * @param config.uri - Server URI (default: CONST_DEFAULT_SERVICE)
217
+ * @param config.env - Environment variables dictionary for configuration and substitution
218
+ * @param config.onEvent - Callback for server events
219
+ * @param config.onConnected - Callback when connection is established
220
+ * @param config.onDisconnected - Callback when connection is lost
221
+ * @param config.persist - Enable automatic reconnection
222
+ * @param config.requestTimeout - Default timeout in ms for individual requests
223
+ * @param config.maxRetryTime - Max total time in ms to keep retrying connections
224
+ * @param config.module - Optional module name for client identification
225
+ *
226
+ * @throws Error if auth is not provided via config, env, or .env file
227
+ *
228
+ * @example
229
+ * ```typescript
230
+ * // Using explicit auth and URI
231
+ * const client = new RocketRideClient({
232
+ * auth: 'your-api-key',
233
+ * uri: 'wss://your-server.com',
234
+ * persist: true,
235
+ * onEvent: (event) => console.log('Event:', event)
236
+ * });
237
+ *
238
+ * // Using custom env dictionary
239
+ * const client = new RocketRideClient({
240
+ * env: {
241
+ * ROCKETRIDE_APIKEY: 'your-api-key',
242
+ * ROCKETRIDE_URI: 'wss://your-server.com',
243
+ * ROCKETRIDE_PROJECT_ID: 'my-project'
244
+ * }
245
+ * });
246
+ * ```
247
+ */
248
+ constructor(config = {}) {
249
+ // Check if we're in Node.js or browser environment
250
+ const isBrowser = typeof window !== 'undefined';
251
+ // Build environment variables dictionary
252
+ // Priority: provided env > .env file
253
+ let clientEnv = {};
254
+ if (config.env) {
255
+ // Use provided env dictionary
256
+ clientEnv = { ...config.env };
257
+ }
258
+ else {
259
+ // Load from .env file only (Node.js only)
260
+ if (!isBrowser) {
261
+ try {
262
+ const fs = require('fs');
263
+ const path = require('path');
264
+ const envPath = path.join(process.cwd(), '.env');
265
+ if (fs.existsSync(envPath)) {
266
+ const content = fs.readFileSync(envPath, 'utf-8');
267
+ // Parse each line
268
+ for (const line of content.split('\n')) {
269
+ const trimmed = line.trim();
270
+ // Skip comments and empty lines
271
+ if (!trimmed || trimmed.startsWith('#')) {
272
+ continue;
273
+ }
274
+ // Parse KEY=VALUE format
275
+ const match = trimmed.match(/^([^=]+)=(.*)$/);
276
+ if (match) {
277
+ const key = match[1].trim();
278
+ let value = match[2].trim();
279
+ // Remove quotes if present
280
+ if ((value.startsWith('"') && value.endsWith('"')) ||
281
+ (value.startsWith("'") && value.endsWith("'"))) {
282
+ value = value.slice(1, -1);
283
+ }
284
+ clientEnv[key] = value;
285
+ }
286
+ }
287
+ }
288
+ }
289
+ catch {
290
+ // File doesn't exist or can't be read - that's okay
291
+ }
292
+ }
293
+ }
294
+ const { auth = config.auth || clientEnv.ROCKETRIDE_APIKEY, uri = config.uri || clientEnv.ROCKETRIDE_URI || constants_1.CONST_DEFAULT_SERVICE, onEvent, onConnected, onDisconnected, onConnectError, persist, maxRetryTime, module, } = config;
295
+ // Create unique client identifier
296
+ const clientName = module || `CLIENT-${clientId++}`;
297
+ // Initialize the DAPClient without a transport; transport is created in _internalConnect (CONNECTION_LOGIC.md §3)
298
+ super(clientName, undefined, config);
299
+ this._dapAttempted = false;
300
+ this._nextChatId = 1;
301
+ // Persistence properties for automatic reconnection
302
+ this._persist = false;
303
+ this._manualDisconnect = false;
304
+ this._currentReconnectDelay = 250;
305
+ /** True after onConnected has been invoked; used to only invoke onDisconnected when we had a connection. */
306
+ this._didNotifyConnected = false;
307
+ // Store connection details and environment
308
+ this._setUri(uri);
309
+ this._setAuth(auth);
310
+ this._env = clientEnv;
311
+ // Set up callbacks if provided
312
+ if (onEvent)
313
+ this._callerOnEvent = onEvent;
314
+ if (onConnected)
315
+ this._callerOnConnected = onConnected;
316
+ if (onDisconnected)
317
+ this._callerOnDisconnected = onDisconnected;
318
+ if (onConnectError)
319
+ this._callerOnConnectError = onConnectError;
320
+ // Set up persistence options
321
+ this._persist = persist ?? false;
322
+ this._maxRetryTime = maxRetryTime;
323
+ }
324
+ /**
325
+ * Update the server URI (internal). Accepts HTTP/HTTPS/WS/WSS; converts to WebSocket and appends /task/service.
326
+ */
327
+ _setUri(uri) {
328
+ try {
329
+ const url = new URL(uri);
330
+ const wsScheme = url.protocol === 'https:' ? 'wss:' : 'ws:';
331
+ this._uri = `${wsScheme}//${url.host}/task/service`;
332
+ }
333
+ catch {
334
+ this._uri = `${uri}/task/service`;
335
+ }
336
+ }
337
+ /**
338
+ * Update the authentication credential (internal).
339
+ */
340
+ _setAuth(auth) {
341
+ this._apikey = auth;
342
+ }
343
+ /**
344
+ * Clear any pending reconnection timeout.
345
+ */
346
+ _clearReconnectTimeout() {
347
+ if (this._reconnectTimeout) {
348
+ clearTimeout(this._reconnectTimeout);
349
+ this._reconnectTimeout = undefined;
350
+ }
351
+ }
352
+ // ============================================================================
353
+ // CONNECTION METHODS
354
+ // ============================================================================
355
+ /**
356
+ * Single place for physical connection. Creates transport if needed, then
357
+ * calls DAPClient.connect (transport connect + auth handshake + onConnected).
358
+ */
359
+ async _internalConnect(timeout) {
360
+ if (!this._transport) {
361
+ const transport = new TransportWebSocket_1.TransportWebSocket(this._uri, this._apikey);
362
+ this._bindTransport(transport);
363
+ }
364
+ await super.connect(timeout);
365
+ }
366
+ /**
367
+ * Single place for physical disconnect. Closes the transport directly,
368
+ * which triggers onDisconnected via the transport callback.
369
+ */
370
+ async _internalDisconnect(reason, hasError) {
371
+ if (!this._transport)
372
+ return;
373
+ await this._transport.disconnect(reason, hasError);
374
+ }
375
+ /**
376
+ * Try to connect; on auth error notify and stop; on other error notify and
377
+ * reschedule with exponential backoff. Used by persist-mode connect() and
378
+ * by the reconnect timer.
379
+ */
380
+ async _attemptConnection(timeout) {
381
+ try {
382
+ await this._internalConnect(timeout);
383
+ this._reconnectTimeout = undefined;
384
+ this.debugMessage('Connection successful');
385
+ }
386
+ catch (error) {
387
+ const err = error instanceof Error ? error : new Error(String(error));
388
+ this.debugMessage(`Connection failed: ${err}`);
389
+ await this.onConnectError(err);
390
+ if (error instanceof exceptions_1.AuthenticationException) {
391
+ return;
392
+ }
393
+ if (this._retryStartTime === undefined) {
394
+ this._retryStartTime = Date.now();
395
+ }
396
+ if (this._maxRetryTime !== undefined) {
397
+ if (Date.now() - this._retryStartTime >= this._maxRetryTime) {
398
+ return;
399
+ }
400
+ }
401
+ this._currentReconnectDelay = Math.min(this._currentReconnectDelay * 2, 2500);
402
+ this._scheduleReconnect();
403
+ }
404
+ }
405
+ /**
406
+ * Schedule a reconnection attempt with exponential backoff.
407
+ */
408
+ _scheduleReconnect() {
409
+ this._clearReconnectTimeout();
410
+ if (this._maxRetryTime !== undefined && this._retryStartTime !== undefined) {
411
+ if (Date.now() - this._retryStartTime >= this._maxRetryTime) {
412
+ this.onConnectError(new Error('Max retry time exceeded'));
413
+ return;
414
+ }
415
+ }
416
+ this.debugMessage(`Scheduling reconnection in ${this._currentReconnectDelay}ms`);
417
+ this._reconnectTimeout = setTimeout(async () => {
418
+ if (this._persist && !this._manualDisconnect) {
419
+ this.debugMessage('Attempting to reconnect...');
420
+ await this._attemptConnection();
421
+ }
422
+ }, this._currentReconnectDelay);
423
+ }
424
+ /**
425
+ * Check if the client is currently connected to the RocketRide server.
426
+ */
427
+ isConnected() {
428
+ return this._transport?.isConnected() || false;
429
+ }
430
+ /**
431
+ * Connect to the RocketRide server.
432
+ *
433
+ * Must be called before executing pipelines or other operations.
434
+ * In persist mode, enables automatic reconnection on disconnect and on initial failure
435
+ * (calls onConnectError on each failed attempt and keeps retrying).
436
+ * @param timeout - Optional overall timeout in ms for the connect + auth handshake.
437
+ */
438
+ async connect(timeout) {
439
+ this._manualDisconnect = false;
440
+ this._currentReconnectDelay = 250;
441
+ this._retryStartTime = undefined;
442
+ // If already connected, disconnect first without setting _manualDisconnect
443
+ if (this.isConnected()) {
444
+ await this._internalDisconnect();
445
+ }
446
+ if (this._persist) {
447
+ this._clearReconnectTimeout();
448
+ await this._attemptConnection(timeout);
449
+ }
450
+ else {
451
+ await this._internalConnect(timeout);
452
+ }
453
+ }
454
+ /**
455
+ * Disconnect from the RocketRide server and stop automatic reconnection.
456
+ *
457
+ * Should be called when finished with the client to clean up resources.
458
+ */
459
+ async disconnect() {
460
+ this._manualDisconnect = true;
461
+ this._clearReconnectTimeout();
462
+ if (this._transport && this.isConnected()) {
463
+ await this._internalDisconnect();
464
+ }
465
+ }
466
+ /**
467
+ * Update server URI and/or auth at runtime. If currently connected,
468
+ * disconnects and reconnects with the new params. In persist mode,
469
+ * reconnection is scheduled only if we were connected.
470
+ */
471
+ async setConnectionParams(options) {
472
+ if (options.uri !== undefined) {
473
+ this._setUri(options.uri);
474
+ }
475
+ if (options.auth !== undefined) {
476
+ this._setAuth(options.auth);
477
+ }
478
+ const wasAlreadyConnected = this.isConnected();
479
+ this._manualDisconnect = true;
480
+ this._clearReconnectTimeout();
481
+ if (wasAlreadyConnected) {
482
+ await this._internalDisconnect();
483
+ }
484
+ // Destroy transport so next connect() creates a new one with updated uri/auth (CONNECTION_LOGIC.md §2c)
485
+ if (options.uri !== undefined || options.auth !== undefined) {
486
+ this._transport = undefined;
487
+ }
488
+ if (this._persist && wasAlreadyConnected) {
489
+ this._manualDisconnect = false;
490
+ this._scheduleReconnect();
491
+ }
492
+ else if (wasAlreadyConnected) {
493
+ this._manualDisconnect = false;
494
+ await this._internalConnect();
495
+ }
496
+ else {
497
+ this._manualDisconnect = false;
498
+ }
499
+ }
500
+ // ============================================================================
501
+ // PING METHODS
502
+ // ============================================================================
503
+ /**
504
+ * Test connectivity to the RocketRide server.
505
+ *
506
+ * Sends a lightweight ping request to the server to verify it's responding
507
+ * and reachable. This is useful for connectivity testing, health checks,
508
+ * and measuring response times.
509
+ */
510
+ async ping(token) {
511
+ // Build ping request
512
+ const request = this.buildRequest('rrext_ping', { token });
513
+ // Send to server and wait for response
514
+ const response = await this.request(request);
515
+ // Check if ping failed
516
+ if (this.didFail(response)) {
517
+ const errorMsg = response.message || 'Ping failed';
518
+ throw new Error(`Ping failed: ${errorMsg}`);
519
+ }
520
+ }
521
+ // ============================================================================
522
+ // EXECUTION METHODS
523
+ // ============================================================================
524
+ /**
525
+ * Substitute environment variables in a string.
526
+ * Replaces ${ROCKETRIDE_*} patterns with values from client's env dictionary.
527
+ * If variable is not found, leaves it unchanged.
528
+ */
529
+ substituteEnvVars(value) {
530
+ // Match ${ROCKETRIDE_*} patterns
531
+ return value.replace(/\$\{(ROCKETRIDE_[^}]+)\}/g, (match, varName) => {
532
+ // Check if variable exists in client's env
533
+ if (varName in this._env) {
534
+ return String(this._env[varName]);
535
+ }
536
+ // If not found, leave as is
537
+ return match;
538
+ });
539
+ }
540
+ /**
541
+ * Recursively process an object/array to substitute environment variables.
542
+ * Only processes string values, leaving other types unchanged.
543
+ */
544
+ processEnvSubstitution(obj) {
545
+ if (typeof obj === 'string') {
546
+ // If it's a string, perform substitution
547
+ return this.substituteEnvVars(obj);
548
+ }
549
+ else if (Array.isArray(obj)) {
550
+ // If it's an array, process each element
551
+ return obj.map(item => this.processEnvSubstitution(item));
552
+ }
553
+ else if (obj !== null && typeof obj === 'object') {
554
+ // If it's an object, process each property
555
+ const result = {};
556
+ for (const [key, value] of Object.entries(obj)) {
557
+ result[key] = this.processEnvSubstitution(value);
558
+ }
559
+ return result;
560
+ }
561
+ // For other types (number, boolean, null), return as is
562
+ return obj;
563
+ }
564
+ // ============================================================================
565
+ // VALIDATION METHODS
566
+ // ============================================================================
567
+ /**
568
+ * Validate a pipeline configuration.
569
+ *
570
+ * Sends the pipeline to the server for structural validation, checking
571
+ * component compatibility, connection integrity, and the resolved
572
+ * execution chain.
573
+ *
574
+ * Source resolution follows the same logic as {@link use}:
575
+ * 1. Explicit `source` option (if provided)
576
+ * 2. `source` field inside the pipeline config
577
+ * 3. Implied source: the single component whose config.mode is 'Source'
578
+ *
579
+ * @param options.pipeline - Pipeline configuration to validate
580
+ * @param options.source - Optional override for the source component ID
581
+ * @returns Promise resolving to validation result with errors, warnings,
582
+ * resolved component, and execution chain
583
+ * @throws Error if the server returns a validation error
584
+ *
585
+ * @example
586
+ * ```typescript
587
+ * const result = await client.validate({
588
+ * pipeline: { components: [...], project_id: '123' },
589
+ * source: 'webhook_1'
590
+ * });
591
+ * if (result.errors?.length) {
592
+ * console.log('Validation errors:', result.errors);
593
+ * }
594
+ * ```
595
+ */
596
+ async validate(options) {
597
+ const { pipeline, source } = options;
598
+ const arguments_ = { pipeline };
599
+ if (source !== undefined) {
600
+ arguments_.source = source;
601
+ }
602
+ const request = this.buildRequest('rrext_validate', {
603
+ arguments: arguments_
604
+ });
605
+ const response = await this.request(request);
606
+ if (this.didFail(response)) {
607
+ const errorMsg = response.message || 'Validation failed';
608
+ throw new Error(`Pipeline validation failed: ${errorMsg}`);
609
+ }
610
+ return response.body || {};
611
+ }
612
+ // ============================================================================
613
+ // PIPELINE EXECUTION METHODS
614
+ // ============================================================================
615
+ /**
616
+ * Start an RocketRide pipeline for processing data.
617
+ *
618
+ * This method loads and executes a pipeline configuration. It automatically performs
619
+ * environment variable substitution on the pipeline config, replacing ${ROCKETRIDE_*}
620
+ * placeholders with values from the .env file.
621
+ *
622
+ * @param options - Pipeline execution options
623
+ * @param options.token - Custom token for the pipeline (auto-generated if not provided)
624
+ * @param options.filepath - Path to JSON file containing pipeline configuration
625
+ * @param options.pipeline - Pipeline configuration object (alternative to filepath)
626
+ * @param options.source - Override pipeline source
627
+ * @param options.threads - Number of threads for execution (default: 1)
628
+ * @param options.useExisting - Use existing pipeline instance
629
+ * @param options.args - Command line arguments to pass to pipeline
630
+ * @param options.ttl - Time-to-live in seconds for idle pipelines (optional, server default if not provided; use 0 for no timeout)
631
+ *
632
+ * @returns Promise resolving to an object containing the task token and other metadata
633
+ * @throws Error if neither pipeline nor filepath is provided
634
+ *
635
+ * @example
636
+ * ```typescript
637
+ * // Using pipeline file
638
+ * const result = await client.use({ filepath: './pipeline.json' });
639
+ *
640
+ * // Using pipeline object
641
+ * const result = await client.use({
642
+ * pipeline: { name: 'My Pipeline', components: [...], source: 'local', project_id: '123' }
643
+ * });
644
+ *
645
+ * // With environment variable substitution
646
+ * // Pipeline config: { "endpoint": "${ROCKETRIDE_URI}/api" }
647
+ * // Will be replaced with actual ROCKETRIDE_URI value from .env
648
+ * ```
649
+ */
650
+ async use(options = {}) {
651
+ const { token, filepath, pipeline, source, threads, useExisting, args, ttl } = options;
652
+ // Validate required parameters
653
+ if (!pipeline && !filepath) {
654
+ throw new Error('Pipeline configuration or file path is required and must be specified');
655
+ }
656
+ let pipelineConfig;
657
+ // Load pipeline configuration from file if needed
658
+ if (!pipeline && filepath) {
659
+ // Check if we're in Node.js environment
660
+ if (typeof window !== 'undefined') {
661
+ throw new Error('File loading not available in browser environment. Please provide pipeline object directly.');
662
+ }
663
+ // Load file in Node.js
664
+ const fs = require('fs');
665
+ const fileContent = fs.readFileSync(filepath, 'utf-8');
666
+ pipelineConfig = JSON.parse(fileContent);
667
+ }
668
+ else {
669
+ pipelineConfig = pipeline;
670
+ }
671
+ // Create a deep copy of the pipeline config to avoid modifying the original
672
+ let processedConfig = JSON.parse(JSON.stringify(pipelineConfig));
673
+ // Perform environment variable substitution on the pipeline configuration
674
+ processedConfig = this.processEnvSubstitution(processedConfig);
675
+ // Override source if specified (after substitution)
676
+ if (source !== undefined) {
677
+ processedConfig.source = source;
678
+ }
679
+ // Build execution request with all parameters
680
+ const arguments_ = {
681
+ pipeline: processedConfig,
682
+ args: args || [],
683
+ };
684
+ // Add TTL if provided (server uses its default if not specified)
685
+ if (ttl !== undefined) {
686
+ arguments_.ttl = ttl;
687
+ }
688
+ // Add optional parameters if specified
689
+ if (token !== undefined) {
690
+ arguments_.token = token;
691
+ }
692
+ if (threads !== undefined) {
693
+ arguments_.threads = threads;
694
+ }
695
+ if (useExisting !== undefined) {
696
+ arguments_.useExisting = useExisting;
697
+ }
698
+ // Send execution request to server
699
+ const request = this.buildRequest('execute', { arguments: arguments_ });
700
+ const response = await this.request(request);
701
+ // Check for execution errors
702
+ if (this.didFail(response)) {
703
+ const errorMsg = response.message || 'Unknown execution error';
704
+ this.debugMessage(`Pipeline execution failed: ${errorMsg}`);
705
+ throw new Error(errorMsg);
706
+ }
707
+ // Extract and validate response
708
+ const responseBody = response.body || {};
709
+ const taskToken = responseBody.token;
710
+ if (!taskToken) {
711
+ throw new Error('Server did not return a task token in successful response');
712
+ }
713
+ this.debugMessage(`Pipeline execution started successfully, task token: ${taskToken}`);
714
+ // Type assertion to ensure token is present
715
+ return responseBody;
716
+ }
717
+ /**
718
+ * Terminate a running pipeline.
719
+ */
720
+ async terminate(token) {
721
+ // Send termination request
722
+ const request = this.buildRequest('terminate', { token });
723
+ const response = await this.request(request);
724
+ // Check for termination errors
725
+ if (this.didFail(response)) {
726
+ const errorMsg = response.message || 'Unknown termination error';
727
+ this.debugMessage(`Pipeline termination failed: ${errorMsg}`);
728
+ throw new Error(errorMsg);
729
+ }
730
+ }
731
+ /**
732
+ * Get the current status of a running pipeline.
733
+ */
734
+ async getTaskStatus(token) {
735
+ // Send status request
736
+ const request = this.buildRequest('rrext_get_task_status', { token });
737
+ const response = await this.request(request);
738
+ // Check for status retrieval errors
739
+ if (this.didFail(response)) {
740
+ const errorMsg = response.message || 'Unknown status retrieval error';
741
+ this.debugMessage(`Pipeline status retrieval failed: ${errorMsg}`);
742
+ throw new Error(errorMsg);
743
+ }
744
+ // Return status information
745
+ return response.body || {};
746
+ }
747
+ // ============================================================================
748
+ // DATA METHODS
749
+ // ============================================================================
750
+ /** Return objinfo with size set; never 0 (parse filter skips "empty"). */
751
+ _objinfoWithSize(objinfo, size) {
752
+ return { ...objinfo, size: size || 1 };
753
+ }
754
+ /**
755
+ * Create a data pipe for streaming operations.
756
+ */
757
+ async pipe(token, objinfo = {}, mimeType, provider) {
758
+ return new DataPipe(this, token, objinfo, mimeType, provider);
759
+ }
760
+ /**
761
+ * Send data to a running pipeline.
762
+ */
763
+ async send(token, data, objinfo = {}, mimetype) {
764
+ // Convert string to bytes if needed
765
+ let buffer;
766
+ if (typeof data === 'string') {
767
+ buffer = new TextEncoder().encode(data);
768
+ }
769
+ else if (data instanceof Uint8Array) {
770
+ buffer = data;
771
+ }
772
+ else {
773
+ throw new Error('data must be either a string or Uint8Array');
774
+ }
775
+ // Create and use a temporary pipe for the data
776
+ const pipe = await this.pipe(token, this._objinfoWithSize(objinfo, buffer.length), mimetype);
777
+ try {
778
+ await pipe.open();
779
+ await pipe.write(buffer);
780
+ return await pipe.close();
781
+ }
782
+ catch (error) {
783
+ // Clean up pipe on any error
784
+ if (pipe.isOpened) {
785
+ try {
786
+ await pipe.close();
787
+ }
788
+ catch {
789
+ // Ignore cleanup errors
790
+ }
791
+ }
792
+ throw error;
793
+ }
794
+ }
795
+ /**
796
+ * Upload multiple files to a pipeline with progress tracking and parallel execution.
797
+ *
798
+ * This method efficiently uploads files in parallel with configurable concurrency control.
799
+ * Each file is streamed through a data pipe, and progress events are emitted through the
800
+ * event system for all subscribers. The order of results matches the input file order.
801
+ *
802
+ * Progress events are sent through the event system as 'apaevt_status_upload' events
803
+ * (matching Python client behavior) rather than through a callback parameter.
804
+ *
805
+ * @param files - Array of file objects with optional metadata and MIME types
806
+ * @param token - Pipeline task token to receive the uploads
807
+ * @param maxConcurrent - Maximum number of concurrent uploads (default: 5)
808
+ *
809
+ * @returns Promise resolving to array of UPLOAD_RESULT objects in the same order as input
810
+ *
811
+ * @example
812
+ * ```typescript
813
+ * // Subscribe to upload events
814
+ * client.on('apaevt_status_upload', (event) => {
815
+ * console.log(`${event.body.filepath}: ${event.body.bytes_sent}/${event.body.file_size}`);
816
+ * });
817
+ *
818
+ * // Upload files
819
+ * const results = await client.sendFiles(
820
+ * [
821
+ * { file: fileObject1 },
822
+ * { file: fileObject2, mimetype: 'application/json' },
823
+ * { file: fileObject3, objinfo: { custom: 'metadata' } }
824
+ * ],
825
+ * 'task-token',
826
+ * 10 // Upload max 10 files concurrently
827
+ * );
828
+ * ```
829
+ */
830
+ async sendFiles(files, token) {
831
+ const results = new Array(files.length);
832
+ /**
833
+ * Helper function to send upload events through the event system.
834
+ */
835
+ const sendUploadEvent = (body) => {
836
+ const eventMessage = {
837
+ event: 'apaevt_status_upload',
838
+ body: body,
839
+ seq: 0,
840
+ type: 'event'
841
+ };
842
+ this.onEvent(eventMessage);
843
+ };
844
+ /**
845
+ * Upload a single file - straightforward linear process:
846
+ * 1. Wait for pipe to become available (server handles queuing)
847
+ * 2. Transfer data
848
+ * 3. Close pipe
849
+ * 4. Send status update
850
+ */
851
+ const uploadFile = async (fileData, index) => {
852
+ const { file, objinfo = {}, mimetype } = fileData;
853
+ const startTime = Date.now();
854
+ let bytesUploaded = 0;
855
+ let pipe = null;
856
+ let error;
857
+ let result;
858
+ // Get file size: from filesystem when filepath in objinfo (Node.js), else file.size (same as Python os.path.getsize)
859
+ let fileSize = file.size;
860
+ if (typeof window === 'undefined' && objinfo?.filepath && typeof objinfo.filepath === 'string') {
861
+ try {
862
+ const fs = require('fs');
863
+ fileSize = fs.statSync(objinfo.filepath).size;
864
+ }
865
+ catch {
866
+ // fallback to file.size
867
+ }
868
+ }
869
+ const finalMimetype = mimetype || file.type || 'application/octet-stream';
870
+ try {
871
+ // Step 1: Create and open pipe (waits for server to allocate)
872
+ pipe = await this.pipe(token, this._objinfoWithSize({ name: file.name, ...objinfo }, fileSize), finalMimetype);
873
+ await pipe.open();
874
+ // Step 2: Send status update AFTER we have the pipe
875
+ sendUploadEvent({
876
+ action: 'open',
877
+ filepath: file.name,
878
+ bytes_sent: 0,
879
+ file_size: fileSize,
880
+ upload_time: 0,
881
+ });
882
+ // Step 3: Transfer data in chunks
883
+ const reader = file.stream().getReader();
884
+ try {
885
+ while (true) {
886
+ const { done, value } = await reader.read();
887
+ if (done)
888
+ break;
889
+ await pipe.write(value);
890
+ bytesUploaded += value.length;
891
+ // Send progress updates during transfer
892
+ sendUploadEvent({
893
+ action: 'write',
894
+ filepath: file.name,
895
+ bytes_sent: bytesUploaded,
896
+ file_size: fileSize,
897
+ upload_time: (Date.now() - startTime) / 1000,
898
+ });
899
+ }
900
+ }
901
+ finally {
902
+ reader.releaseLock();
903
+ }
904
+ // Step 4: Close pipe and get result
905
+ sendUploadEvent({
906
+ action: 'close',
907
+ filepath: file.name,
908
+ bytes_sent: bytesUploaded,
909
+ file_size: fileSize,
910
+ upload_time: (Date.now() - startTime) / 1000,
911
+ });
912
+ result = await pipe.close();
913
+ }
914
+ catch (err) {
915
+ error = err instanceof Error ? err.message : String(err);
916
+ }
917
+ // Send final status
918
+ const uploadTime = (Date.now() - startTime) / 1000;
919
+ const finalResult = {
920
+ action: error ? 'error' : 'complete',
921
+ filepath: file.name,
922
+ bytes_sent: bytesUploaded,
923
+ file_size: fileSize,
924
+ upload_time: uploadTime,
925
+ result,
926
+ error,
927
+ };
928
+ sendUploadEvent(finalResult);
929
+ results[index] = finalResult;
930
+ };
931
+ // Create a promise for every file - let server handle queuing
932
+ const uploadPromises = files.map((fileData, index) => uploadFile(fileData, index).catch(err => {
933
+ // Ensure errors don't kill the whole batch
934
+ console.error(`Upload failed for ${fileData.file.name}:`, err);
935
+ }));
936
+ // Wait for all uploads to complete
937
+ await Promise.all(uploadPromises);
938
+ return results;
939
+ }
940
+ // ============================================================================
941
+ // CHAT METHODS
942
+ // ============================================================================
943
+ /**
944
+ * Ask a question to RocketRide's AI and get an intelligent response.
945
+ */
946
+ async chat(options) {
947
+ const { token, question } = options;
948
+ try {
949
+ // Validate that we have a question to ask
950
+ if (!question) {
951
+ throw new Error('Question cannot be empty');
952
+ }
953
+ // Create unique identifier for this chat operation
954
+ const objinfo = { name: `Question ${this._nextChatId}` };
955
+ this._nextChatId += 1;
956
+ // Create pipe instance
957
+ const pipe = new DataPipe(this, token, objinfo, 'application/rocketride-question', 'chat');
958
+ try {
959
+ // Open the communication channel to the AI
960
+ await pipe.open();
961
+ // Send the question as JSON data to the AI system
962
+ const questionJson = JSON.stringify(question.toDict());
963
+ const questionBytes = new TextEncoder().encode(questionJson);
964
+ await pipe.write(questionBytes);
965
+ // Close the pipe and get the AI's response
966
+ const result = await pipe.close();
967
+ // Check it
968
+ if (!result) {
969
+ throw new Error('No response received from AI');
970
+ }
971
+ // Return success response in standard format
972
+ return result;
973
+ }
974
+ finally {
975
+ // Ensure the pipe is properly closed even if errors occur
976
+ if (pipe.isOpened) {
977
+ try {
978
+ await pipe.close();
979
+ }
980
+ catch {
981
+ // Ignore errors during cleanup
982
+ }
983
+ }
984
+ }
985
+ }
986
+ catch (error) {
987
+ // Return error response in standard format
988
+ throw new Error(error instanceof Error ? error.message : String(error));
989
+ }
990
+ }
991
+ // ============================================================================
992
+ // EVENT METHODS
993
+ // ============================================================================
994
+ /**
995
+ * Send events to debugging interface if available (for development).
996
+ */
997
+ _sendVSCodeEvent(eventType, body) {
998
+ // Set up debugging integration on first use
999
+ if (!this._dapAttempted) {
1000
+ this._dapAttempted = true;
1001
+ try {
1002
+ // In browser environment, check for debugging tools
1003
+ if (typeof window !== 'undefined') {
1004
+ const win = window;
1005
+ if (win.__ROCKETRIDE_DEBUG__) {
1006
+ this._dapSend = win.__ROCKETRIDE_DEBUG__.sendEvent;
1007
+ }
1008
+ }
1009
+ }
1010
+ catch {
1011
+ // Not in debugging environment - no problem
1012
+ }
1013
+ }
1014
+ // Send event to debugger if available
1015
+ if (this._dapSend) {
1016
+ const customEvent = {
1017
+ type: 'event',
1018
+ event: eventType,
1019
+ body: body,
1020
+ };
1021
+ this._dapSend(customEvent);
1022
+ }
1023
+ }
1024
+ /**
1025
+ * Handle incoming events from the RocketRide server.
1026
+ */
1027
+ async onEvent(message) {
1028
+ // Extract event information
1029
+ const eventType = message.event || 'unknown';
1030
+ const eventBody = message.body || {};
1031
+ const seqNum = message.seq || 0;
1032
+ // Forward to debugging interface if available
1033
+ this._sendVSCodeEvent(eventType, eventBody);
1034
+ // Call user-provided event handler if available
1035
+ if (this._callerOnEvent) {
1036
+ try {
1037
+ await this._callerOnEvent(message);
1038
+ }
1039
+ catch (error) {
1040
+ // Log errors but don't let user code break the connection
1041
+ this.debugMessage(`Error in user onEvent handler for ${eventType} (seq ${seqNum}): ${error}`);
1042
+ }
1043
+ }
1044
+ }
1045
+ /**
1046
+ * Handle connection attempt failure.
1047
+ * Calls the user callback and chains to parent.
1048
+ */
1049
+ async onConnectError(error) {
1050
+ if (this._callerOnConnectError) {
1051
+ try {
1052
+ await this._callerOnConnectError(error instanceof Error ? error.message : String(error));
1053
+ }
1054
+ catch (e) {
1055
+ this.debugMessage(`Error in user onConnectError handler: ${e}`);
1056
+ }
1057
+ }
1058
+ await super.onConnectError(error);
1059
+ }
1060
+ /**
1061
+ * Handle connected events from the RocketRide server.
1062
+ */
1063
+ async onConnected(connectionInfo) {
1064
+ this._manualDisconnect = false;
1065
+ this._didNotifyConnected = true;
1066
+ this._clearReconnectTimeout();
1067
+ this._currentReconnectDelay = 250;
1068
+ this._retryStartTime = undefined;
1069
+ // Call user-provided event handler if available
1070
+ if (this._callerOnConnected) {
1071
+ try {
1072
+ await this._callerOnConnected(connectionInfo);
1073
+ }
1074
+ catch (error) {
1075
+ // Log errors but don't let user code break the connection
1076
+ this.debugMessage(`Error in user onConnected handler for ${connectionInfo}: ${error}`);
1077
+ }
1078
+ }
1079
+ await super.onConnected(connectionInfo);
1080
+ }
1081
+ /**
1082
+ * Handle disconnected events from the RocketRide server.
1083
+ * Only invokes the user's onDisconnected if onConnected had previously been called
1084
+ * (so "disconnect without ever connecting" does not fire the user callback).
1085
+ */
1086
+ async onDisconnected(reason, hasError) {
1087
+ if (this._didNotifyConnected) {
1088
+ this._didNotifyConnected = false;
1089
+ if (this._callerOnDisconnected) {
1090
+ try {
1091
+ await this._callerOnDisconnected(reason, hasError);
1092
+ }
1093
+ catch (error) {
1094
+ // Log errors but don't let user code break the connection
1095
+ this.debugMessage(`Error in user onDisconnected handler for ${reason}: ${error}`);
1096
+ }
1097
+ }
1098
+ // Chain to parent to clear pending requests
1099
+ await super.onDisconnected(reason, hasError);
1100
+ }
1101
+ // Schedule reconnection if persist is enabled and not a manual disconnect
1102
+ if (this._persist && !this._manualDisconnect) {
1103
+ this._scheduleReconnect();
1104
+ }
1105
+ }
1106
+ /**
1107
+ * Subscribe to specific types of events from the server.
1108
+ */
1109
+ async setEvents(token, eventTypes) {
1110
+ // Build event subscription request
1111
+ const request = this.buildRequest('rrext_monitor', {
1112
+ arguments: { types: eventTypes },
1113
+ token,
1114
+ });
1115
+ // Send to server
1116
+ const response = await this.request(request);
1117
+ // Check for errors
1118
+ if (this.didFail(response)) {
1119
+ const errorMsg = response.message || 'Event subscription failed';
1120
+ throw new Error(errorMsg);
1121
+ }
1122
+ }
1123
+ // ============================================================================
1124
+ // PROJECT STORAGE MANAGEMENT
1125
+ // ============================================================================
1126
+ /**
1127
+ * Save or update a project pipeline.
1128
+ *
1129
+ * Stores a project pipeline configuration on the server. If the project
1130
+ * already exists, it will be updated. Use expectedVersion to ensure
1131
+ * you're updating the version you expect (prevents conflicts).
1132
+ *
1133
+ * @param options - Save project options
1134
+ * @param options.projectId - Unique identifier for the project
1135
+ * @param options.pipeline - Pipeline configuration object
1136
+ * @param options.expectedVersion - Expected current version for atomic updates (optional)
1137
+ * @returns Promise resolving to save result with success status, projectId, and new version
1138
+ * @throws Error if save fails due to version mismatch, storage error, or invalid input
1139
+ *
1140
+ * @example
1141
+ * ```typescript
1142
+ * // Save a new project
1143
+ * const result = await client.saveProject({
1144
+ * projectId: 'proj-123',
1145
+ * pipeline: {
1146
+ * name: 'Data Processor',
1147
+ * source: 'source_1',
1148
+ * components: [...]
1149
+ * }
1150
+ * });
1151
+ * console.log(`Saved version: ${result.version}`);
1152
+ *
1153
+ * // Update existing project with version check
1154
+ * const existing = await client.getProject({ projectId: 'proj-123' });
1155
+ * existing.name = 'Updated Name';
1156
+ * const updated = await client.saveProject({
1157
+ * projectId: 'proj-123',
1158
+ * pipeline: existing,
1159
+ * expectedVersion: existing.version
1160
+ * });
1161
+ * ```
1162
+ */
1163
+ async saveProject(options) {
1164
+ const { projectId, pipeline, expectedVersion } = options;
1165
+ // Validate inputs
1166
+ if (!projectId) {
1167
+ throw new Error('projectId is required');
1168
+ }
1169
+ if (!pipeline || typeof pipeline !== 'object') {
1170
+ throw new Error('pipeline must be a non-empty object');
1171
+ }
1172
+ // Build request arguments
1173
+ const args = {
1174
+ subcommand: 'save_project',
1175
+ projectId,
1176
+ pipeline,
1177
+ };
1178
+ // Add optional version for atomic updates
1179
+ if (expectedVersion !== undefined) {
1180
+ args.expectedVersion = expectedVersion;
1181
+ }
1182
+ // Send request to server
1183
+ const request = this.buildRequest('rrext_store', { arguments: args });
1184
+ const response = await this.request(request);
1185
+ // Check for errors
1186
+ if (this.didFail(response)) {
1187
+ const errorMsg = response.message || 'Unknown error saving project';
1188
+ this.debugMessage(`Project save failed: ${errorMsg}`);
1189
+ throw new Error(errorMsg);
1190
+ }
1191
+ // Extract and return response
1192
+ this.debugMessage(`Project saved successfully: ${projectId}, version: ${response.body?.version}`);
1193
+ return response.body;
1194
+ }
1195
+ /**
1196
+ * Retrieve a project by its ID.
1197
+ *
1198
+ * Fetches the complete pipeline configuration and current version for
1199
+ * the specified project. Use this before updating to get the current
1200
+ * version for atomic updates.
1201
+ *
1202
+ * @param options - Get project options
1203
+ * @param options.projectId - Unique identifier of the project to retrieve
1204
+ * @returns Promise resolving to project data with success status, pipeline, and version
1205
+ * @throws Error if project doesn't exist or retrieval fails
1206
+ *
1207
+ * @example
1208
+ * ```typescript
1209
+ * // Get a project
1210
+ * try {
1211
+ * const project = await client.getProject({ projectId: 'proj-123' });
1212
+ * console.log(`Project: ${project.name}`);
1213
+ * console.log(`Version: ${project.version}`);
1214
+ * } catch (error) {
1215
+ * if (error.message.includes('NOT_FOUND')) {
1216
+ * console.log("Project doesn't exist");
1217
+ * }
1218
+ * }
1219
+ *
1220
+ * // Before updating - get current version
1221
+ * const project = await client.getProject({ projectId: 'proj-123' });
1222
+ * project.name = 'Updated';
1223
+ * await client.saveProject({
1224
+ * projectId: 'proj-123',
1225
+ * pipeline: project,
1226
+ * expectedVersion: project.version
1227
+ * });
1228
+ * ```
1229
+ */
1230
+ async getProject(options) {
1231
+ const { projectId } = options;
1232
+ // Validate inputs
1233
+ if (!projectId) {
1234
+ throw new Error('projectId is required');
1235
+ }
1236
+ // Build request
1237
+ const args = {
1238
+ subcommand: 'get_project',
1239
+ projectId,
1240
+ };
1241
+ // Send request to server
1242
+ const request = this.buildRequest('rrext_store', { arguments: args });
1243
+ const response = await this.request(request);
1244
+ // Check for errors
1245
+ if (this.didFail(response)) {
1246
+ const errorMsg = response.message || 'Unknown error retrieving project';
1247
+ this.debugMessage(`Project retrieval failed: ${errorMsg}`);
1248
+ throw new Error(errorMsg);
1249
+ }
1250
+ // Extract and return response
1251
+ this.debugMessage(`Project retrieved successfully: ${projectId}`);
1252
+ return response.body;
1253
+ }
1254
+ /**
1255
+ * Delete a project by its ID.
1256
+ *
1257
+ * Permanently removes a project from storage. Optionally verify the
1258
+ * version before deletion to ensure you're deleting the version you
1259
+ * expect (prevents accidental deletion of modified projects).
1260
+ *
1261
+ * @param options - Delete project options
1262
+ * @param options.projectId - Unique identifier of the project to delete
1263
+ * @param options.expectedVersion - Expected current version for atomic deletion (required)
1264
+ * @returns Promise resolving to deletion result with success status and message
1265
+ * @throws Error if project doesn't exist, version mismatch, or deletion fails
1266
+ *
1267
+ * @example
1268
+ * ```typescript
1269
+ * // Safe deletion with version check
1270
+ * const project = await client.getProject({ projectId: 'proj-123' });
1271
+ * try {
1272
+ * const result = await client.deleteProject({
1273
+ * projectId: 'proj-123',
1274
+ * expectedVersion: project.version
1275
+ * });
1276
+ * console.log('Project deleted successfully');
1277
+ * } catch (error) {
1278
+ * if (error.message.includes('CONFLICT')) {
1279
+ * console.log('Project was modified, deletion cancelled');
1280
+ * }
1281
+ * }
1282
+ * ```
1283
+ */
1284
+ async deleteProject(options) {
1285
+ const { projectId, expectedVersion } = options;
1286
+ // Validate inputs
1287
+ if (!projectId) {
1288
+ throw new Error('projectId is required');
1289
+ }
1290
+ // Build request
1291
+ const args = {
1292
+ subcommand: 'delete_project',
1293
+ projectId,
1294
+ };
1295
+ // Add optional version for atomic deletion
1296
+ if (expectedVersion !== undefined) {
1297
+ args.expectedVersion = expectedVersion;
1298
+ }
1299
+ // Send request to server
1300
+ const request = this.buildRequest('rrext_store', { arguments: args });
1301
+ const response = await this.request(request);
1302
+ // Check for errors
1303
+ if (this.didFail(response)) {
1304
+ const errorMsg = response.message || 'Unknown error deleting project';
1305
+ this.debugMessage(`Project deletion failed: ${errorMsg}`);
1306
+ throw new Error(errorMsg);
1307
+ }
1308
+ // Extract and return response
1309
+ this.debugMessage(`Project deleted successfully: ${projectId}`);
1310
+ return response.body;
1311
+ }
1312
+ /**
1313
+ * List all projects for the current user.
1314
+ *
1315
+ * Retrieves a summary of all projects stored for the authenticated user.
1316
+ * Each project summary includes the ID, name, list of data sources, and total component count.
1317
+ *
1318
+ * @returns Promise resolving to list result with success status, projects array, and count
1319
+ * @throws Error if retrieval fails
1320
+ *
1321
+ * @example
1322
+ * ```typescript
1323
+ * // List all projects
1324
+ * const result = await client.getAllProjects();
1325
+ * console.log(`Found ${result.count} projects:`);
1326
+ * for (const project of result.projects) {
1327
+ * console.log(`- ${project.id}: ${project.name} (${project.totalComponents} components)`);
1328
+ * for (const source of project.sources) {
1329
+ * console.log(` * ${source.name} (${source.provider})`);
1330
+ * }
1331
+ * }
1332
+ *
1333
+ * // Find specific project
1334
+ * const result = await client.getAllProjects();
1335
+ * const myProject = result.projects.find(p => p.id === 'proj-123');
1336
+ * ```
1337
+ */
1338
+ async getAllProjects() {
1339
+ // Build request
1340
+ const args = {
1341
+ subcommand: 'get_all_projects',
1342
+ };
1343
+ // Send request to server
1344
+ const request = this.buildRequest('rrext_store', { arguments: args });
1345
+ const response = await this.request(request);
1346
+ // Check for errors
1347
+ if (this.didFail(response)) {
1348
+ const errorMsg = response.message || 'Unknown error listing projects';
1349
+ this.debugMessage(`Project list retrieval failed: ${errorMsg}`);
1350
+ throw new Error(errorMsg);
1351
+ }
1352
+ // Extract and return response
1353
+ const projectCount = response.body?.count || 0;
1354
+ this.debugMessage(`Projects retrieved successfully: ${projectCount} projects`);
1355
+ return response.body;
1356
+ }
1357
+ // ============================================================================
1358
+ // TEMPLATE STORAGE MANAGEMENT (System-wide templates)
1359
+ // ============================================================================
1360
+ /**
1361
+ * Save or update a template pipeline.
1362
+ *
1363
+ * Stores a template pipeline configuration on the server. Templates are system-wide
1364
+ * and accessible to all users. If the template already exists, it will be updated.
1365
+ * Use expectedVersion to ensure you're updating the version you expect.
1366
+ *
1367
+ * @param options - Save template options
1368
+ * @param options.templateId - Unique identifier for the template
1369
+ * @param options.pipeline - Pipeline configuration object
1370
+ * @param options.expectedVersion - Expected current version for atomic updates (optional)
1371
+ * @returns Promise resolving to save result with success status, templateId, and new version
1372
+ * @throws Error if save fails due to version mismatch, storage error, or invalid input
1373
+ */
1374
+ async saveTemplate(options) {
1375
+ const { templateId, pipeline, expectedVersion } = options;
1376
+ // Validate inputs
1377
+ if (!templateId) {
1378
+ throw new Error('templateId is required');
1379
+ }
1380
+ if (!pipeline || typeof pipeline !== 'object') {
1381
+ throw new Error('pipeline must be a non-empty object');
1382
+ }
1383
+ // Build request arguments
1384
+ const args = {
1385
+ subcommand: 'save_template',
1386
+ templateId,
1387
+ pipeline,
1388
+ };
1389
+ // Add optional version for atomic updates
1390
+ if (expectedVersion !== undefined) {
1391
+ args.expectedVersion = expectedVersion;
1392
+ }
1393
+ // Send request to server
1394
+ const request = this.buildRequest('rrext_store', { arguments: args });
1395
+ const response = await this.request(request);
1396
+ // Check for errors
1397
+ if (this.didFail(response)) {
1398
+ const errorMsg = response.message || 'Unknown error saving template';
1399
+ this.debugMessage(`Template save failed: ${errorMsg}`);
1400
+ throw new Error(errorMsg);
1401
+ }
1402
+ // Extract and return response
1403
+ this.debugMessage(`Template saved successfully: ${templateId}, version: ${response.body?.version}`);
1404
+ return response.body;
1405
+ }
1406
+ /**
1407
+ * Retrieve a template by its ID.
1408
+ */
1409
+ async getTemplate(options) {
1410
+ const { templateId } = options;
1411
+ // Validate inputs
1412
+ if (!templateId) {
1413
+ throw new Error('templateId is required');
1414
+ }
1415
+ // Build request
1416
+ const args = {
1417
+ subcommand: 'get_template',
1418
+ templateId,
1419
+ };
1420
+ // Send request to server
1421
+ const request = this.buildRequest('rrext_store', { arguments: args });
1422
+ const response = await this.request(request);
1423
+ // Check for errors
1424
+ if (this.didFail(response)) {
1425
+ const errorMsg = response.message || 'Unknown error retrieving template';
1426
+ this.debugMessage(`Template retrieval failed: ${errorMsg}`);
1427
+ throw new Error(errorMsg);
1428
+ }
1429
+ // Extract and return response
1430
+ this.debugMessage(`Template retrieved successfully: ${templateId}`);
1431
+ return response.body;
1432
+ }
1433
+ /**
1434
+ * Delete a template by its ID.
1435
+ */
1436
+ async deleteTemplate(options) {
1437
+ const { templateId, expectedVersion } = options;
1438
+ // Validate inputs
1439
+ if (!templateId) {
1440
+ throw new Error('templateId is required');
1441
+ }
1442
+ // Build request
1443
+ const args = {
1444
+ subcommand: 'delete_template',
1445
+ templateId,
1446
+ };
1447
+ // Add optional version for atomic deletion
1448
+ if (expectedVersion !== undefined) {
1449
+ args.expectedVersion = expectedVersion;
1450
+ }
1451
+ // Send request to server
1452
+ const request = this.buildRequest('rrext_store', { arguments: args });
1453
+ const response = await this.request(request);
1454
+ // Check for errors
1455
+ if (this.didFail(response)) {
1456
+ const errorMsg = response.message || 'Unknown error deleting template';
1457
+ this.debugMessage(`Template deletion failed: ${errorMsg}`);
1458
+ throw new Error(errorMsg);
1459
+ }
1460
+ // Extract and return response
1461
+ this.debugMessage(`Template deleted successfully: ${templateId}`);
1462
+ return response.body;
1463
+ }
1464
+ /**
1465
+ * List all templates.
1466
+ */
1467
+ async getAllTemplates() {
1468
+ // Build request
1469
+ const args = {
1470
+ subcommand: 'get_all_templates',
1471
+ };
1472
+ // Send request to server
1473
+ const request = this.buildRequest('rrext_store', { arguments: args });
1474
+ const response = await this.request(request);
1475
+ // Check for errors
1476
+ if (this.didFail(response)) {
1477
+ const errorMsg = response.message || 'Unknown error listing templates';
1478
+ this.debugMessage(`Template list retrieval failed: ${errorMsg}`);
1479
+ throw new Error(errorMsg);
1480
+ }
1481
+ // Extract and return response
1482
+ const templateCount = response.body?.count || 0;
1483
+ this.debugMessage(`Templates retrieved successfully: ${templateCount} templates`);
1484
+ return response.body;
1485
+ }
1486
+ // ============================================================================
1487
+ // LOG STORAGE MANAGEMENT (Per-project log files for historical tracking)
1488
+ // ============================================================================
1489
+ /**
1490
+ * Save a log file for a source run.
1491
+ */
1492
+ async saveLog(options) {
1493
+ const { projectId, source, contents } = options;
1494
+ // Validate inputs
1495
+ if (!projectId) {
1496
+ throw new Error('projectId is required');
1497
+ }
1498
+ if (!source) {
1499
+ throw new Error('source is required');
1500
+ }
1501
+ if (!contents || typeof contents !== 'object') {
1502
+ throw new Error('contents must be a non-empty object');
1503
+ }
1504
+ // Build request arguments
1505
+ const args = {
1506
+ subcommand: 'save_log',
1507
+ projectId,
1508
+ source,
1509
+ contents,
1510
+ };
1511
+ // Send request to server
1512
+ const request = this.buildRequest('rrext_store', { arguments: args });
1513
+ const response = await this.request(request);
1514
+ // Check for errors
1515
+ if (this.didFail(response)) {
1516
+ const errorMsg = response.message || 'Unknown error saving log';
1517
+ this.debugMessage(`Log save failed: ${errorMsg}`);
1518
+ throw new Error(errorMsg);
1519
+ }
1520
+ // Extract and return response
1521
+ this.debugMessage(`Log saved successfully: ${response.body?.filename}`);
1522
+ return response.body;
1523
+ }
1524
+ /**
1525
+ * Get a log file by source name and start time.
1526
+ */
1527
+ async getLog(options) {
1528
+ const { projectId, source, startTime } = options;
1529
+ // Validate inputs
1530
+ if (!projectId) {
1531
+ throw new Error('projectId is required');
1532
+ }
1533
+ if (!source) {
1534
+ throw new Error('source is required');
1535
+ }
1536
+ if (startTime === undefined || startTime === null) {
1537
+ throw new Error('startTime is required');
1538
+ }
1539
+ // Build request
1540
+ const args = {
1541
+ subcommand: 'get_log',
1542
+ projectId,
1543
+ source,
1544
+ startTime,
1545
+ };
1546
+ // Send request to server
1547
+ const request = this.buildRequest('rrext_store', { arguments: args });
1548
+ const response = await this.request(request);
1549
+ // Check for errors
1550
+ if (this.didFail(response)) {
1551
+ const errorMsg = response.message || 'Unknown error retrieving log';
1552
+ this.debugMessage(`Log retrieval failed: ${errorMsg}`);
1553
+ throw new Error(errorMsg);
1554
+ }
1555
+ // Extract and return response
1556
+ this.debugMessage(`Log retrieved successfully: ${projectId}/${source}`);
1557
+ return response.body;
1558
+ }
1559
+ /**
1560
+ * List log files for a project.
1561
+ */
1562
+ async listLogs(options) {
1563
+ const { projectId, source, page } = options;
1564
+ // Validate inputs
1565
+ if (!projectId) {
1566
+ throw new Error('projectId is required');
1567
+ }
1568
+ // Build request
1569
+ const args = {
1570
+ subcommand: 'list_logs',
1571
+ projectId,
1572
+ };
1573
+ // Add optional parameters
1574
+ if (source !== undefined) {
1575
+ args.source = source;
1576
+ }
1577
+ if (page !== undefined) {
1578
+ args.page = page;
1579
+ }
1580
+ // Send request to server
1581
+ const request = this.buildRequest('rrext_store', { arguments: args });
1582
+ const response = await this.request(request);
1583
+ // Check for errors
1584
+ if (this.didFail(response)) {
1585
+ const errorMsg = response.message || 'Unknown error listing logs';
1586
+ this.debugMessage(`Log list retrieval failed: ${errorMsg}`);
1587
+ throw new Error(errorMsg);
1588
+ }
1589
+ // Extract and return response
1590
+ const logCount = response.body?.total_count || 0;
1591
+ this.debugMessage(`Logs retrieved successfully: ${logCount} logs`);
1592
+ return response.body;
1593
+ }
1594
+ // ============================================================================
1595
+ // RAW REQUEST METHOD
1596
+ // ============================================================================
1597
+ /**
1598
+ * Send an arbitrary DAP command with command name, arguments, and optional token.
1599
+ *
1600
+ * This is a convenience method for callers that don't want to construct
1601
+ * full DAPMessage objects. It builds the request internally and delegates
1602
+ * to the underlying request() method.
1603
+ *
1604
+ * @param command - The DAP command name (e.g., 'rrext_services', 'rrext_monitor')
1605
+ * @param args - Optional arguments for the command
1606
+ * @param token - Optional task/session token
1607
+ * @param timeout - Optional per-request timeout in ms
1608
+ * @returns The response DAPMessage from the server
1609
+ */
1610
+ async dapRequest(command, args, token, timeout) {
1611
+ const message = this.buildRequest(command, {
1612
+ arguments: args,
1613
+ token,
1614
+ });
1615
+ return await this.request(message, timeout);
1616
+ }
1617
+ // ============================================================================
1618
+ // CONTEXT MANAGER SUPPORT - Python-style async context manager
1619
+ // ============================================================================
1620
+ /**
1621
+ * Async disposal support for 'await using' pattern.
1622
+ * Equivalent to Python's __aexit__
1623
+ */
1624
+ async [Symbol.asyncDispose]() {
1625
+ await this.disconnect();
1626
+ }
1627
+ /**
1628
+ * Static factory method for automatic connection management.
1629
+ * Equivalent to Python's async with pattern
1630
+ */
1631
+ static async withConnection(config, callback) {
1632
+ const client = new RocketRideClient(config);
1633
+ try {
1634
+ await client.connect();
1635
+ return await callback(client);
1636
+ }
1637
+ finally {
1638
+ await client.disconnect();
1639
+ }
1640
+ }
1641
+ // ============================================================================
1642
+ // SERVICES METHODS
1643
+ // ============================================================================
1644
+ /**
1645
+ * Retrieve all available service definitions from the server.
1646
+ *
1647
+ * Returns a dictionary containing all service definitions available on
1648
+ * the connected RocketRide server. Each service definition includes schemas,
1649
+ * UI schemas, and configuration metadata.
1650
+ *
1651
+ * @returns Promise resolving to object mapping service names to their definitions
1652
+ * @throws Error if the request fails or server returns an error
1653
+ *
1654
+ * @example
1655
+ * ```typescript
1656
+ * // Get all available services
1657
+ * const services = await client.getServices();
1658
+ *
1659
+ * // List available service names
1660
+ * for (const name of Object.keys(services)) {
1661
+ * console.log(`Available service: ${name}`);
1662
+ * }
1663
+ *
1664
+ * // Access a specific service's schema
1665
+ * if (services['ocr']) {
1666
+ * console.log('OCR schema:', services['ocr'].schema);
1667
+ * }
1668
+ * ```
1669
+ */
1670
+ async getServices() {
1671
+ // Build services request (no service argument = get all)
1672
+ const request = this.buildRequest('rrext_services', {});
1673
+ // Send to server and wait for response
1674
+ const response = await this.request(request);
1675
+ // Check if request failed
1676
+ if (this.didFail(response)) {
1677
+ const errorMsg = response.message || 'Failed to retrieve services';
1678
+ throw new Error(`Failed to retrieve services: ${errorMsg}`);
1679
+ }
1680
+ // Return the body containing all service definitions
1681
+ return response.body || {};
1682
+ }
1683
+ /**
1684
+ * Retrieve a specific service definition from the server.
1685
+ *
1686
+ * Returns the definition for a specific service (connector) by name.
1687
+ * The definition includes schemas, UI schemas, and configuration metadata.
1688
+ *
1689
+ * @param service - Name of the service to retrieve (e.g., 'ocr', 'embed', 'chat')
1690
+ * @returns Promise resolving to service definition or undefined if not found
1691
+ * @throws Error if the request fails or server returns an error
1692
+ *
1693
+ * @example
1694
+ * ```typescript
1695
+ * // Get OCR service definition
1696
+ * const ocr = await client.getService('ocr');
1697
+ * if (ocr) {
1698
+ * console.log('OCR schema:', ocr.schema);
1699
+ * console.log('OCR UI schema:', ocr.uiSchema);
1700
+ * } else {
1701
+ * console.log('OCR service not available');
1702
+ * }
1703
+ * ```
1704
+ */
1705
+ async getService(service) {
1706
+ if (!service) {
1707
+ throw new Error('Service name is required');
1708
+ }
1709
+ // Build services request with specific service name
1710
+ const request = this.buildRequest('rrext_services', {
1711
+ arguments: { service }
1712
+ });
1713
+ // Send to server and wait for response
1714
+ const response = await this.request(request);
1715
+ // Check if request failed
1716
+ if (this.didFail(response)) {
1717
+ const errorMsg = response.message || `Service '${service}' not found`;
1718
+ throw new Error(`Failed to retrieve service '${service}': ${errorMsg}`);
1719
+ }
1720
+ // Return the body containing the service definition
1721
+ return response.body;
1722
+ }
1723
+ // ============================================================================
1724
+ // ADDITIONAL CONVENIENCE METHODS
1725
+ // ============================================================================
1726
+ /**
1727
+ * Get connection information (TypeScript-specific convenience)
1728
+ */
1729
+ getConnectionInfo() {
1730
+ return {
1731
+ connected: this.isConnected(),
1732
+ transport: 'WebSocket',
1733
+ uri: this._uri,
1734
+ };
1735
+ }
1736
+ /**
1737
+ * Get API key (for debugging/validation)
1738
+ */
1739
+ getApiKey() {
1740
+ return this._apikey;
1741
+ }
1742
+ }
1743
+ exports.RocketRideClient = RocketRideClient;
1744
+ exports.default = RocketRideClient;
1745
+ //# sourceMappingURL=client.js.map