@railtownai/railengine 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +570 -0
- package/dist/cjs/index.js +892 -0
- package/dist/cjs/index.js.map +1 -0
- package/dist/esm/index.js +890 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/types/auth.d.ts +24 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/client.d.ts +196 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/embeddings.d.ts +17 -0
- package/dist/types/embeddings.d.ts.map +1 -0
- package/dist/types/index.d.ts +3 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/indexing.d.ts +17 -0
- package/dist/types/indexing.d.ts.map +1 -0
- package/dist/types/storage.d.ts +63 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/package.json +47 -0
|
@@ -0,0 +1,890 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error class for all Railtown API errors.
|
|
3
|
+
*/
|
|
4
|
+
class RailtownError extends Error {
|
|
5
|
+
constructor(message, statusCode, responseBody) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.statusCode = statusCode;
|
|
8
|
+
this.responseBody = responseBody;
|
|
9
|
+
this.name = "RailtownError";
|
|
10
|
+
Object.setPrototypeOf(this, RailtownError.prototype);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when authentication fails (401).
|
|
15
|
+
*/
|
|
16
|
+
class RailtownUnauthorizedError extends RailtownError {
|
|
17
|
+
constructor(message = "Authentication failed", responseBody) {
|
|
18
|
+
super(message, 401, responseBody);
|
|
19
|
+
this.name = "RailtownUnauthorizedError";
|
|
20
|
+
Object.setPrototypeOf(this, RailtownUnauthorizedError.prototype);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Error thrown when a resource is not found (404).
|
|
25
|
+
*/
|
|
26
|
+
class RailtownNotFoundError extends RailtownError {
|
|
27
|
+
constructor(message = "Resource not found", responseBody) {
|
|
28
|
+
super(message, 404, responseBody);
|
|
29
|
+
this.name = "RailtownNotFoundError";
|
|
30
|
+
Object.setPrototypeOf(this, RailtownNotFoundError.prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when a bad request is made (400).
|
|
35
|
+
*/
|
|
36
|
+
class RailtownBadRequestError extends RailtownError {
|
|
37
|
+
constructor(message = "Bad request", responseBody) {
|
|
38
|
+
super(message, 400, responseBody);
|
|
39
|
+
this.name = "RailtownBadRequestError";
|
|
40
|
+
Object.setPrototypeOf(this, RailtownBadRequestError.prototype);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Error thrown when a conflict occurs (409).
|
|
45
|
+
*/
|
|
46
|
+
class RailtownConflictError extends RailtownError {
|
|
47
|
+
constructor(message = "Conflict", responseBody) {
|
|
48
|
+
super(message, 409, responseBody);
|
|
49
|
+
this.name = "RailtownConflictError";
|
|
50
|
+
Object.setPrototypeOf(this, RailtownConflictError.prototype);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Error thrown when a server error occurs (500+).
|
|
55
|
+
*/
|
|
56
|
+
class RailtownServerError extends RailtownError {
|
|
57
|
+
constructor(message = "Server error", statusCode = 500, responseBody) {
|
|
58
|
+
super(message, statusCode, responseBody);
|
|
59
|
+
this.name = "RailtownServerError";
|
|
60
|
+
Object.setPrototypeOf(this, RailtownServerError.prototype);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Maps HTTP status codes to appropriate Railtown error classes.
|
|
65
|
+
*/
|
|
66
|
+
function createRailtownError(statusCode, message, responseBody) {
|
|
67
|
+
switch (statusCode) {
|
|
68
|
+
case 401:
|
|
69
|
+
return new RailtownUnauthorizedError(message, responseBody);
|
|
70
|
+
case 404:
|
|
71
|
+
return new RailtownNotFoundError(message, responseBody);
|
|
72
|
+
case 400:
|
|
73
|
+
return new RailtownBadRequestError(message, responseBody);
|
|
74
|
+
case 409:
|
|
75
|
+
return new RailtownConflictError(message, responseBody);
|
|
76
|
+
default:
|
|
77
|
+
if (statusCode >= 500) {
|
|
78
|
+
return new RailtownServerError(message, statusCode, responseBody);
|
|
79
|
+
}
|
|
80
|
+
return new RailtownError(message || `HTTP ${statusCode}`, statusCode, responseBody);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Strips the /api suffix from a URL if present.
|
|
85
|
+
*
|
|
86
|
+
* @param url - The URL to process
|
|
87
|
+
* @returns URL without /api suffix
|
|
88
|
+
*/
|
|
89
|
+
function stripApiSuffix(url) {
|
|
90
|
+
return url.replace(/\/api\/?$/, "");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Makes an HTTP request with timeout support.
|
|
95
|
+
*
|
|
96
|
+
* @param url - Request URL
|
|
97
|
+
* @param options - Request options
|
|
98
|
+
* @returns Response object
|
|
99
|
+
* @throws {Error} If request fails or times out
|
|
100
|
+
*/
|
|
101
|
+
async function makeHttpRequest(url, options = {}) {
|
|
102
|
+
const { timeout = 30000, signal, ...fetchOptions } = options;
|
|
103
|
+
const controller = new AbortController();
|
|
104
|
+
const timeoutId = timeout > 0 ? setTimeout(() => controller.abort(), timeout) : null;
|
|
105
|
+
try {
|
|
106
|
+
const response = await fetch(url, {
|
|
107
|
+
...fetchOptions,
|
|
108
|
+
signal: signal || controller.signal
|
|
109
|
+
});
|
|
110
|
+
if (timeoutId)
|
|
111
|
+
clearTimeout(timeoutId);
|
|
112
|
+
return response;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (timeoutId)
|
|
116
|
+
clearTimeout(timeoutId);
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Handles HTTP response errors by creating appropriate RailtownError.
|
|
122
|
+
*
|
|
123
|
+
* @param response - HTTP response object
|
|
124
|
+
* @param defaultMessage - Default error message
|
|
125
|
+
* @returns Never (always throws)
|
|
126
|
+
* @throws {RailtownError} Appropriate error based on status code
|
|
127
|
+
*/
|
|
128
|
+
async function handleHttpError(response, defaultMessage) {
|
|
129
|
+
const responseBody = await getResponseBody(response);
|
|
130
|
+
throw createRailtownError(response.status, defaultMessage, responseBody);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Safely extracts response body as text, returning empty string on error.
|
|
134
|
+
*
|
|
135
|
+
* @param response - HTTP response object
|
|
136
|
+
* @returns Response body as string (empty string if extraction fails)
|
|
137
|
+
*/
|
|
138
|
+
async function getResponseBody(response) {
|
|
139
|
+
return await response.text().catch(() => "");
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Handles request errors (timeout, network, etc.) with consistent error messages.
|
|
143
|
+
*
|
|
144
|
+
* @param error - Caught error
|
|
145
|
+
* @param timeout - Request timeout in milliseconds
|
|
146
|
+
* @param context - Context description for error message
|
|
147
|
+
* @returns Never (always throws)
|
|
148
|
+
* @throws {RailtownError} If error is a RailtownError
|
|
149
|
+
* @throws {Error} For timeout or other errors
|
|
150
|
+
*/
|
|
151
|
+
function handleRequestError(error, timeout, context) {
|
|
152
|
+
if (error instanceof RailtownError) {
|
|
153
|
+
throw error;
|
|
154
|
+
}
|
|
155
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
156
|
+
throw new Error(`Request timeout after ${timeout}ms`);
|
|
157
|
+
}
|
|
158
|
+
throw new Error(`${context}: ${error instanceof Error ? error.message : String(error)}`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Validates and normalizes RailEngine configuration.
|
|
163
|
+
*
|
|
164
|
+
* @param pat - PAT token from options or environment variable
|
|
165
|
+
* @param engineId - Engine ID from options or environment variable
|
|
166
|
+
* @param apiUrl - Base API URL from options or environment variable
|
|
167
|
+
* @returns Validated and normalized configuration
|
|
168
|
+
* @throws {RailtownBadRequestError} If PAT or engineId is missing
|
|
169
|
+
*/
|
|
170
|
+
function validateRailEngineConfig(pat, engineId, apiUrl) {
|
|
171
|
+
// Get PAT from parameter or environment variable
|
|
172
|
+
const resolvedPat = pat ?? process.env.ENGINE_PAT;
|
|
173
|
+
if (!resolvedPat || typeof resolvedPat !== "string" || resolvedPat.trim().length === 0) {
|
|
174
|
+
throw new RailtownBadRequestError("PAT is required. Provide it via constructor parameter (pat) or ENGINE_PAT environment variable.");
|
|
175
|
+
}
|
|
176
|
+
// Get engineId from parameter or environment variable
|
|
177
|
+
const resolvedEngineId = engineId ?? process.env.ENGINE_ID;
|
|
178
|
+
if (!resolvedEngineId || typeof resolvedEngineId !== "string" || resolvedEngineId.trim().length === 0) {
|
|
179
|
+
throw new RailtownBadRequestError("Engine ID is required. Provide it via constructor parameter (engineId) or ENGINE_ID environment variable.");
|
|
180
|
+
}
|
|
181
|
+
// Get apiUrl from parameter or environment variable, default to production
|
|
182
|
+
const resolvedApiUrl = apiUrl ?? process.env.RAILTOWN_API_URL ?? "https://cndr.railtown.ai/api";
|
|
183
|
+
const trimmedApiUrl = resolvedApiUrl.trim();
|
|
184
|
+
// Normalize URL (strip /api suffix if present, it will be added back when constructing endpoints)
|
|
185
|
+
const normalizedApiUrl = stripApiSuffix(trimmedApiUrl);
|
|
186
|
+
return {
|
|
187
|
+
pat: resolvedPat,
|
|
188
|
+
engineId: resolvedEngineId,
|
|
189
|
+
apiUrl: trimmedApiUrl, // Original URL for display
|
|
190
|
+
normalizedApiUrl // Normalized URL for internal use
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Search a vector store using semantic similarity.
|
|
196
|
+
*
|
|
197
|
+
* @param client - RailEngine client instance
|
|
198
|
+
* @param engineId - Engine identifier
|
|
199
|
+
* @param vectorStore - Vector store name (e.g., "VectorStore1", "VectorStore2", "VectorStore3")
|
|
200
|
+
* @param query - Search query string
|
|
201
|
+
* @param options - Optional filter function and schema override
|
|
202
|
+
* @returns Async iterable of search results (validated against schema if provided)
|
|
203
|
+
*/
|
|
204
|
+
async function* searchVectorStore(client, engineId, vectorStore, query, options) {
|
|
205
|
+
try {
|
|
206
|
+
// Determine which schema to use (override or default)
|
|
207
|
+
const schema = options?.schema ?? client._getSchema();
|
|
208
|
+
// Make POST request to search endpoint
|
|
209
|
+
const response = await client._makeRequest("POST", `/Engine/${engineId}/Embeddings/Search`, {
|
|
210
|
+
body: {
|
|
211
|
+
VectorStore: vectorStore,
|
|
212
|
+
Query: query
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
// Parse response
|
|
216
|
+
const data = await response.json().catch(() => null);
|
|
217
|
+
if (!data) {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
// Handle array of results
|
|
221
|
+
const dataRecord = data;
|
|
222
|
+
const results = Array.isArray(data)
|
|
223
|
+
? data
|
|
224
|
+
: (Array.isArray(dataRecord.results)
|
|
225
|
+
? dataRecord.results
|
|
226
|
+
: Array.isArray(dataRecord.items)
|
|
227
|
+
? dataRecord.items
|
|
228
|
+
: []);
|
|
229
|
+
// Process each result
|
|
230
|
+
for (const item of results) {
|
|
231
|
+
if (!item || typeof item !== "object") {
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
let validatedItem = item;
|
|
235
|
+
// Validate against schema if provided
|
|
236
|
+
if (schema) {
|
|
237
|
+
try {
|
|
238
|
+
validatedItem = schema.parse(item);
|
|
239
|
+
}
|
|
240
|
+
catch {
|
|
241
|
+
// Skip invalid items
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// Apply filter if provided
|
|
246
|
+
if (options?.filterFn && !options.filterFn(validatedItem)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
yield validatedItem;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
// Return empty iterable on error (graceful degradation)
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Collects all items from an async iterable into an array.
|
|
260
|
+
*
|
|
261
|
+
* @param iterable - Async iterable to collect
|
|
262
|
+
* @returns Promise that resolves to an array of all items
|
|
263
|
+
*/
|
|
264
|
+
/**
|
|
265
|
+
* Extracts and deserializes the Body field from a storage document.
|
|
266
|
+
* The Body field is a JSON string that contains the actual document data.
|
|
267
|
+
*
|
|
268
|
+
* @param document - Storage document with Body field
|
|
269
|
+
* @param schema - Optional Zod schema to validate the deserialized body
|
|
270
|
+
* @returns Deserialized body object or null if extraction/validation fails
|
|
271
|
+
*/
|
|
272
|
+
function extractAndDeserializeBody(document, schema) {
|
|
273
|
+
// Extract Body or content field (JSON string)
|
|
274
|
+
// API may return either "Body" or "content" field
|
|
275
|
+
const bodyString = (document.Body || document.content);
|
|
276
|
+
if (!bodyString || typeof bodyString !== "string") {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
// Parse JSON string
|
|
280
|
+
let bodyObject;
|
|
281
|
+
try {
|
|
282
|
+
bodyObject = JSON.parse(bodyString);
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
return null;
|
|
286
|
+
}
|
|
287
|
+
// Validate against schema if provided
|
|
288
|
+
if (schema) {
|
|
289
|
+
try {
|
|
290
|
+
return schema.parse(bodyObject);
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
return null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return bodyObject;
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Get a storage document by EventId.
|
|
300
|
+
*
|
|
301
|
+
* @param client - RailEngine client instance
|
|
302
|
+
* @param engineId - Engine identifier
|
|
303
|
+
* @param eventId - Event identifier
|
|
304
|
+
* @param options - Optional filter function and schema override
|
|
305
|
+
* @returns Document or null if not found or filtered out
|
|
306
|
+
*/
|
|
307
|
+
async function getStorageDocumentByEventId(client, engineId, eventId, options) {
|
|
308
|
+
try {
|
|
309
|
+
// Make GET request
|
|
310
|
+
const response = await client._makeRequest("GET", `/api/Engine/${engineId}/Storage`, {
|
|
311
|
+
queryParams: {
|
|
312
|
+
EventId: eventId
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
// Parse response
|
|
316
|
+
const data = await response.json().catch(() => null);
|
|
317
|
+
if (!data) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
// Handle single document or array
|
|
321
|
+
const document = Array.isArray(data) ? data[0] : data;
|
|
322
|
+
if (!document || typeof document !== "object") {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
// If raw mode, return full wrapper object
|
|
326
|
+
if (options?.raw) {
|
|
327
|
+
const schema = options?.schema ?? client._getSchema();
|
|
328
|
+
let validatedDocument = document;
|
|
329
|
+
// Validate against schema if provided
|
|
330
|
+
if (schema) {
|
|
331
|
+
try {
|
|
332
|
+
validatedDocument = schema.parse(document);
|
|
333
|
+
}
|
|
334
|
+
catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
// Apply filter if provided
|
|
339
|
+
if (options?.filterFn && !options.filterFn(validatedDocument)) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
return validatedDocument;
|
|
343
|
+
}
|
|
344
|
+
// Default behavior: Extract and deserialize Body field
|
|
345
|
+
const schema = options?.schema ?? client._getSchema();
|
|
346
|
+
const deserializedBody = extractAndDeserializeBody(document, schema);
|
|
347
|
+
if (deserializedBody === null) {
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
// Apply filter if provided
|
|
351
|
+
if (options?.filterFn && !options.filterFn(deserializedBody)) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
return deserializedBody;
|
|
355
|
+
}
|
|
356
|
+
catch {
|
|
357
|
+
// Return null on error (graceful degradation)
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get storage documents by CustomerKey.
|
|
363
|
+
*
|
|
364
|
+
* @param client - RailEngine client instance
|
|
365
|
+
* @param engineId - Engine identifier
|
|
366
|
+
* @param customerKey - Customer key
|
|
367
|
+
* @param options - Optional pagination, filter function, and schema override
|
|
368
|
+
* @returns Async iterable of documents (paginated results automatically flattened and filtered)
|
|
369
|
+
*/
|
|
370
|
+
async function* getStorageDocumentByCustomerKey(client, engineId, customerKey, options) {
|
|
371
|
+
const pageNumber = options?.pageNumber ?? 1;
|
|
372
|
+
const pageSize = Math.min(options?.pageSize ?? 25, 100); // Max 100
|
|
373
|
+
const schema = options?.schema ?? client._getSchema();
|
|
374
|
+
let currentPage = pageNumber;
|
|
375
|
+
let hasMorePages = true;
|
|
376
|
+
while (hasMorePages) {
|
|
377
|
+
try {
|
|
378
|
+
// Make GET request for current page
|
|
379
|
+
const response = await client._makeRequest("GET", `/api/Engine/${engineId}/Storage`, {
|
|
380
|
+
queryParams: {
|
|
381
|
+
CustomerKey: customerKey,
|
|
382
|
+
PageNumber: currentPage.toString(),
|
|
383
|
+
PageSize: pageSize.toString()
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
// Parse response
|
|
387
|
+
const data = await response.json().catch(() => null);
|
|
388
|
+
if (!data) {
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
// Handle array of documents or paginated response
|
|
392
|
+
const dataRecord = data;
|
|
393
|
+
const documents = Array.isArray(data)
|
|
394
|
+
? data
|
|
395
|
+
: (Array.isArray(dataRecord.results)
|
|
396
|
+
? dataRecord.results
|
|
397
|
+
: Array.isArray(dataRecord.items)
|
|
398
|
+
? dataRecord.items
|
|
399
|
+
: []);
|
|
400
|
+
const totalPages = (typeof dataRecord.totalPages === "number" ? dataRecord.totalPages : undefined) ||
|
|
401
|
+
(typeof dataRecord.pageCount === "number" ? dataRecord.pageCount : undefined) ||
|
|
402
|
+
1;
|
|
403
|
+
// Process each document
|
|
404
|
+
for (const item of documents) {
|
|
405
|
+
if (!item || typeof item !== "object") {
|
|
406
|
+
continue;
|
|
407
|
+
}
|
|
408
|
+
// If raw mode, yield full wrapper object
|
|
409
|
+
if (options?.raw) {
|
|
410
|
+
// In raw mode, skip schema validation since the wrapper structure differs from Body content
|
|
411
|
+
// The schema is meant for deserialized Body, not for the raw wrapper object
|
|
412
|
+
let validatedItem = item;
|
|
413
|
+
// Apply filter if provided
|
|
414
|
+
if (options?.filterFn && !options.filterFn(validatedItem)) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
yield validatedItem;
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
// Default behavior: Extract and deserialize Body field
|
|
421
|
+
const deserializedBody = extractAndDeserializeBody(item, schema);
|
|
422
|
+
if (deserializedBody === null) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
// Apply filter if provided
|
|
426
|
+
if (options?.filterFn && !options.filterFn(deserializedBody)) {
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
yield deserializedBody;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
// Check if there are more pages
|
|
433
|
+
hasMorePages = currentPage < totalPages && documents.length === pageSize;
|
|
434
|
+
currentPage++;
|
|
435
|
+
}
|
|
436
|
+
catch {
|
|
437
|
+
// Stop pagination on error
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
/**
|
|
443
|
+
* Query storage documents using JSONPath.
|
|
444
|
+
*
|
|
445
|
+
* @param client - RailEngine client instance
|
|
446
|
+
* @param engineId - Engine identifier
|
|
447
|
+
* @param jsonPathQuery - JSONPath query string (e.g., "$.direct_report_id:1")
|
|
448
|
+
* @param options - Optional filter function and schema override
|
|
449
|
+
* @returns Async iterable of matching documents
|
|
450
|
+
*/
|
|
451
|
+
async function* queryStorageByJsonPath(client, engineId, jsonPathQuery, options) {
|
|
452
|
+
try {
|
|
453
|
+
const schema = options?.schema ?? client._getSchema();
|
|
454
|
+
// Make GET request
|
|
455
|
+
const response = await client._makeRequest("GET", `/api/Engine/${engineId}/Storage`, {
|
|
456
|
+
queryParams: {
|
|
457
|
+
JsonPathQuery: jsonPathQuery
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
// Parse response
|
|
461
|
+
const data = await response.json().catch(() => null);
|
|
462
|
+
if (!data) {
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
// Handle array of documents or paginated response
|
|
466
|
+
const dataRecord = data;
|
|
467
|
+
const documents = Array.isArray(data)
|
|
468
|
+
? data
|
|
469
|
+
: (Array.isArray(dataRecord.results)
|
|
470
|
+
? dataRecord.results
|
|
471
|
+
: Array.isArray(dataRecord.items)
|
|
472
|
+
? dataRecord.items
|
|
473
|
+
: []);
|
|
474
|
+
// Process each document
|
|
475
|
+
for (const item of documents) {
|
|
476
|
+
if (!item || typeof item !== "object") {
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
479
|
+
// If raw mode, yield full wrapper object
|
|
480
|
+
if (options?.raw) {
|
|
481
|
+
// In raw mode, skip schema validation since the wrapper structure differs from Body content
|
|
482
|
+
// The schema is meant for deserialized Body, not for the raw wrapper object
|
|
483
|
+
let validatedItem = item;
|
|
484
|
+
// Apply filter if provided
|
|
485
|
+
if (options?.filterFn && !options.filterFn(validatedItem)) {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
yield validatedItem;
|
|
489
|
+
}
|
|
490
|
+
else {
|
|
491
|
+
// Default behavior: Extract and deserialize Body field
|
|
492
|
+
const deserializedBody = extractAndDeserializeBody(item, schema);
|
|
493
|
+
if (deserializedBody === null) {
|
|
494
|
+
continue;
|
|
495
|
+
}
|
|
496
|
+
// Apply filter if provided
|
|
497
|
+
if (options?.filterFn && !options.filterFn(deserializedBody)) {
|
|
498
|
+
continue;
|
|
499
|
+
}
|
|
500
|
+
yield deserializedBody;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
catch {
|
|
505
|
+
// Return empty iterable on error (graceful degradation)
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
/**
|
|
510
|
+
* List storage documents.
|
|
511
|
+
*
|
|
512
|
+
* @param client - RailEngine client instance
|
|
513
|
+
* @param engineId - Engine identifier
|
|
514
|
+
* @param options - Optional pagination, customer key filter, filter function, and schema override
|
|
515
|
+
* @returns Async iterable of documents (paginated results automatically flattened and filtered)
|
|
516
|
+
*/
|
|
517
|
+
async function* listStorageDocuments(client, engineId, options) {
|
|
518
|
+
const pageNumber = options?.pageNumber ?? 1;
|
|
519
|
+
const pageSize = Math.min(options?.pageSize ?? 100, 100); // Max 100
|
|
520
|
+
const schema = options?.schema ?? client._getSchema();
|
|
521
|
+
let currentPage = pageNumber;
|
|
522
|
+
let hasMorePages = true;
|
|
523
|
+
while (hasMorePages) {
|
|
524
|
+
try {
|
|
525
|
+
// Build query parameters
|
|
526
|
+
const queryParams = {
|
|
527
|
+
PageNumber: currentPage.toString(),
|
|
528
|
+
PageSize: pageSize.toString()
|
|
529
|
+
};
|
|
530
|
+
if (options?.customerKey) {
|
|
531
|
+
queryParams.CustomerKey = options.customerKey;
|
|
532
|
+
}
|
|
533
|
+
// Make GET request for current page
|
|
534
|
+
const response = await client._makeRequest("GET", `/api/Engine/${engineId}/Storage`, {
|
|
535
|
+
queryParams
|
|
536
|
+
});
|
|
537
|
+
// Parse response
|
|
538
|
+
const data = await response.json().catch(() => null);
|
|
539
|
+
if (!data) {
|
|
540
|
+
break;
|
|
541
|
+
}
|
|
542
|
+
// Handle array of documents or paginated response
|
|
543
|
+
const dataRecord = data;
|
|
544
|
+
const documents = Array.isArray(data)
|
|
545
|
+
? data
|
|
546
|
+
: (Array.isArray(dataRecord.results)
|
|
547
|
+
? dataRecord.results
|
|
548
|
+
: Array.isArray(dataRecord.items)
|
|
549
|
+
? dataRecord.items
|
|
550
|
+
: []);
|
|
551
|
+
const totalPages = (typeof dataRecord.totalPages === "number" ? dataRecord.totalPages : undefined) ||
|
|
552
|
+
(typeof dataRecord.pageCount === "number" ? dataRecord.pageCount : undefined) ||
|
|
553
|
+
1;
|
|
554
|
+
// Process each document
|
|
555
|
+
for (const item of documents) {
|
|
556
|
+
if (!item || typeof item !== "object") {
|
|
557
|
+
continue;
|
|
558
|
+
}
|
|
559
|
+
// If raw mode, yield full wrapper object
|
|
560
|
+
if (options?.raw) {
|
|
561
|
+
// In raw mode, skip schema validation since the wrapper structure differs from Body content
|
|
562
|
+
// The schema is meant for deserialized Body, not for the raw wrapper object
|
|
563
|
+
let validatedItem = item;
|
|
564
|
+
// Apply filter if provided
|
|
565
|
+
if (options?.filterFn && !options.filterFn(validatedItem)) {
|
|
566
|
+
continue;
|
|
567
|
+
}
|
|
568
|
+
yield validatedItem;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
// Default behavior: Extract and deserialize Body field
|
|
572
|
+
const deserializedBody = extractAndDeserializeBody(item, schema);
|
|
573
|
+
if (deserializedBody === null) {
|
|
574
|
+
continue;
|
|
575
|
+
}
|
|
576
|
+
// Apply filter if provided
|
|
577
|
+
if (options?.filterFn && !options.filterFn(deserializedBody)) {
|
|
578
|
+
continue;
|
|
579
|
+
}
|
|
580
|
+
yield deserializedBody;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
// Check if there are more pages
|
|
584
|
+
hasMorePages = currentPage < totalPages && documents.length === pageSize;
|
|
585
|
+
currentPage++;
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
// Stop pagination on error
|
|
589
|
+
break;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Search index using Azure Search.
|
|
596
|
+
*
|
|
597
|
+
* @param client - RailEngine client instance
|
|
598
|
+
* @param projectId - Project identifier
|
|
599
|
+
* @param engineId - Engine identifier
|
|
600
|
+
* @param query - Search query object (e.g., { search: "query" })
|
|
601
|
+
* @param options - Optional filter function and schema override
|
|
602
|
+
* @returns Async iterable of search results (validated against schema if provided)
|
|
603
|
+
*/
|
|
604
|
+
async function* searchIndex(client, projectId, engineId, query, options) {
|
|
605
|
+
try {
|
|
606
|
+
// Determine which schema to use (override or default)
|
|
607
|
+
const schema = options?.schema ?? client._getSchema();
|
|
608
|
+
// Make POST request to search endpoint
|
|
609
|
+
const response = await client._makeRequest("POST", `/Engine/Indexing/Search`, {
|
|
610
|
+
body: {
|
|
611
|
+
ProjectId: projectId,
|
|
612
|
+
EngineId: engineId,
|
|
613
|
+
...query
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
// Parse response
|
|
617
|
+
const data = await response.json().catch(() => null);
|
|
618
|
+
if (!data) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
// Handle array of results or paginated response
|
|
622
|
+
const dataRecord = data;
|
|
623
|
+
const results = Array.isArray(data)
|
|
624
|
+
? data
|
|
625
|
+
: (Array.isArray(dataRecord.results)
|
|
626
|
+
? dataRecord.results
|
|
627
|
+
: Array.isArray(dataRecord.items)
|
|
628
|
+
? dataRecord.items
|
|
629
|
+
: Array.isArray(dataRecord.value)
|
|
630
|
+
? dataRecord.value
|
|
631
|
+
: []);
|
|
632
|
+
// Process each result
|
|
633
|
+
for (const item of results) {
|
|
634
|
+
if (!item || typeof item !== "object") {
|
|
635
|
+
continue;
|
|
636
|
+
}
|
|
637
|
+
let validatedItem = item;
|
|
638
|
+
// Validate against schema if provided
|
|
639
|
+
if (schema) {
|
|
640
|
+
try {
|
|
641
|
+
validatedItem = schema.parse(item);
|
|
642
|
+
}
|
|
643
|
+
catch {
|
|
644
|
+
// Skip invalid items
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// Apply filter if provided
|
|
649
|
+
if (options?.filterFn && !options.filterFn(validatedItem)) {
|
|
650
|
+
continue;
|
|
651
|
+
}
|
|
652
|
+
yield validatedItem;
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
catch {
|
|
656
|
+
// Return empty iterable on error (graceful degradation)
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
/**
|
|
662
|
+
* Client for retrieving and searching data from Rail Engine.
|
|
663
|
+
*/
|
|
664
|
+
class RailEngine {
|
|
665
|
+
/**
|
|
666
|
+
* Initialize the Rail Engine retrieval client.
|
|
667
|
+
*
|
|
668
|
+
* @param options - Configuration options
|
|
669
|
+
* @throws {RailtownBadRequestError} If PAT or engineId is missing
|
|
670
|
+
*/
|
|
671
|
+
constructor(options = {}) {
|
|
672
|
+
const config = validateRailEngineConfig(options.pat, options.engineId, options.apiUrl);
|
|
673
|
+
this.pat = config.pat;
|
|
674
|
+
this.engineId = config.engineId;
|
|
675
|
+
this.apiUrl = config.apiUrl; // Original URL for display
|
|
676
|
+
this.normalizedApiUrl = config.normalizedApiUrl; // Normalized URL for internal use
|
|
677
|
+
this.schema = options.schema;
|
|
678
|
+
this.timeout = options.timeout ?? 30000;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Makes an authenticated HTTP request.
|
|
682
|
+
*
|
|
683
|
+
* @param method - HTTP method
|
|
684
|
+
* @param path - API path (will be appended to base URL)
|
|
685
|
+
* @param options - Request options
|
|
686
|
+
* @returns Response object
|
|
687
|
+
* @throws {RailtownError} If the request fails
|
|
688
|
+
*/
|
|
689
|
+
async _request(method, path, options = {}) {
|
|
690
|
+
// Construct URL using normalized base URL
|
|
691
|
+
const url = new URL(`${this.normalizedApiUrl}${path}`);
|
|
692
|
+
if (options.queryParams) {
|
|
693
|
+
for (const [key, value] of Object.entries(options.queryParams)) {
|
|
694
|
+
url.searchParams.append(key, value);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
// Prepare request body
|
|
698
|
+
let body;
|
|
699
|
+
if (options.body !== undefined) {
|
|
700
|
+
body = JSON.stringify(options.body);
|
|
701
|
+
}
|
|
702
|
+
// Make request
|
|
703
|
+
try {
|
|
704
|
+
const response = await makeHttpRequest(url.toString(), {
|
|
705
|
+
method,
|
|
706
|
+
headers: {
|
|
707
|
+
"Content-Type": "application/json",
|
|
708
|
+
Authorization: this.pat
|
|
709
|
+
},
|
|
710
|
+
body,
|
|
711
|
+
timeout: this.timeout
|
|
712
|
+
});
|
|
713
|
+
// Handle errors (but don't throw - let calling methods handle gracefully)
|
|
714
|
+
if (!response.ok) {
|
|
715
|
+
await handleHttpError(response, `Request failed: ${response.statusText}`);
|
|
716
|
+
}
|
|
717
|
+
return response;
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
return handleRequestError(error, this.timeout, "Request failed");
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Search a vector store using semantic similarity.
|
|
725
|
+
*
|
|
726
|
+
* @param engineId - Engine identifier
|
|
727
|
+
* @param vectorStore - Vector store name (e.g., "VectorStore1")
|
|
728
|
+
* @param query - Search query string
|
|
729
|
+
* @param options - Optional filter function and schema override
|
|
730
|
+
* @returns Async iterable of search results
|
|
731
|
+
*/
|
|
732
|
+
searchVectorStore(engineId, vectorStore, query, options) {
|
|
733
|
+
return searchVectorStore(this, engineId, vectorStore, query, options);
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Get a storage document by EventId.
|
|
737
|
+
*
|
|
738
|
+
* @param params - Parameters object
|
|
739
|
+
* @param params.eventId - Event identifier
|
|
740
|
+
* @param params.engineId - Optional engine identifier (defaults to client.engineId)
|
|
741
|
+
* @param params.filterFn - Optional filter function
|
|
742
|
+
* @param params.schema - Optional schema override
|
|
743
|
+
* @param params.raw - If true, returns full wrapper object with metadata instead of deserialized Body
|
|
744
|
+
* @returns Document or null if not found. When a schema is provided (via constructor or options), returns T | null.
|
|
745
|
+
* When no schema is provided, returns Record<string, unknown> | null.
|
|
746
|
+
*/
|
|
747
|
+
async getStorageDocumentByEventId(params) {
|
|
748
|
+
const engineId = params.engineId ?? this.engineId;
|
|
749
|
+
return getStorageDocumentByEventId(this, engineId, params.eventId, {
|
|
750
|
+
filterFn: params.filterFn,
|
|
751
|
+
schema: params.schema,
|
|
752
|
+
raw: params.raw
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Get storage documents by CustomerKey.
|
|
757
|
+
*
|
|
758
|
+
* @param params - Parameters object
|
|
759
|
+
* @param params.customerKey - Customer key
|
|
760
|
+
* @param params.engineId - Optional engine identifier (defaults to client.engineId)
|
|
761
|
+
* @param params.pageNumber - Optional page number (defaults to 1)
|
|
762
|
+
* @param params.pageSize - Optional page size (defaults to 25, max 100)
|
|
763
|
+
* @param params.filterFn - Optional filter function
|
|
764
|
+
* @param params.schema - Optional schema override
|
|
765
|
+
* @param params.raw - If true, returns full wrapper object with metadata instead of deserialized Body
|
|
766
|
+
* @returns Promise that resolves to an array of documents. Pagination is handled automatically.
|
|
767
|
+
* When a schema is provided (via constructor or options), returns Promise<T[]>.
|
|
768
|
+
*/
|
|
769
|
+
async getStorageDocumentByCustomerKey(params) {
|
|
770
|
+
const engineId = params.engineId ?? this.engineId;
|
|
771
|
+
const iterable = getStorageDocumentByCustomerKey(this, engineId, params.customerKey, {
|
|
772
|
+
pageNumber: params.pageNumber,
|
|
773
|
+
pageSize: params.pageSize,
|
|
774
|
+
filterFn: params.filterFn,
|
|
775
|
+
schema: params.schema,
|
|
776
|
+
raw: params.raw
|
|
777
|
+
});
|
|
778
|
+
const results = [];
|
|
779
|
+
for await (const item of iterable) {
|
|
780
|
+
results.push(item);
|
|
781
|
+
}
|
|
782
|
+
return results;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* Query storage documents using JSONPath.
|
|
786
|
+
*
|
|
787
|
+
* @param params - Parameters object
|
|
788
|
+
* @param params.jsonPathQuery - JSONPath query string (e.g., "$.direct_report_id:1")
|
|
789
|
+
* @param params.engineId - Optional engine identifier (defaults to client.engineId)
|
|
790
|
+
* @param params.filterFn - Optional filter function
|
|
791
|
+
* @param params.schema - Optional schema override
|
|
792
|
+
* @param params.raw - If true, returns full wrapper object with metadata instead of deserialized Body
|
|
793
|
+
* @returns Promise that resolves to an array of matching documents.
|
|
794
|
+
* When a schema is provided (via constructor or options), returns Promise<T[]>.
|
|
795
|
+
*/
|
|
796
|
+
async queryStorageByJsonPath(params) {
|
|
797
|
+
const engineId = params.engineId ?? this.engineId;
|
|
798
|
+
const iterable = queryStorageByJsonPath(this, engineId, params.jsonPathQuery, {
|
|
799
|
+
filterFn: params.filterFn,
|
|
800
|
+
schema: params.schema,
|
|
801
|
+
raw: params.raw
|
|
802
|
+
});
|
|
803
|
+
const results = [];
|
|
804
|
+
for await (const item of iterable) {
|
|
805
|
+
results.push(item);
|
|
806
|
+
}
|
|
807
|
+
return results;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* List storage documents.
|
|
811
|
+
*
|
|
812
|
+
* @param params - Parameters object
|
|
813
|
+
* @param params.engineId - Optional engine identifier (defaults to client.engineId)
|
|
814
|
+
* @param params.customerKey - Optional customer key filter
|
|
815
|
+
* @param params.pageNumber - Optional page number (defaults to 1)
|
|
816
|
+
* @param params.pageSize - Optional page size (defaults to 100, max 100)
|
|
817
|
+
* @param params.filterFn - Optional filter function
|
|
818
|
+
* @param params.schema - Optional schema override
|
|
819
|
+
* @param params.raw - If true, returns full wrapper object with metadata instead of deserialized Body
|
|
820
|
+
* @returns Promise that resolves to an array of documents. Pagination is handled automatically.
|
|
821
|
+
* When a schema is provided (via constructor or options), returns Promise<T[]>.
|
|
822
|
+
*/
|
|
823
|
+
async listStorageDocuments(params) {
|
|
824
|
+
const engineId = params?.engineId ?? this.engineId;
|
|
825
|
+
const iterable = listStorageDocuments(this, engineId, {
|
|
826
|
+
customerKey: params?.customerKey,
|
|
827
|
+
pageNumber: params?.pageNumber,
|
|
828
|
+
pageSize: params?.pageSize,
|
|
829
|
+
filterFn: params?.filterFn,
|
|
830
|
+
schema: params?.schema,
|
|
831
|
+
raw: params?.raw
|
|
832
|
+
});
|
|
833
|
+
const results = [];
|
|
834
|
+
for await (const item of iterable) {
|
|
835
|
+
results.push(item);
|
|
836
|
+
}
|
|
837
|
+
return results;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Search index using Azure Search.
|
|
841
|
+
*
|
|
842
|
+
* @param projectId - Project identifier
|
|
843
|
+
* @param engineId - Engine identifier
|
|
844
|
+
* @param query - Search query object (e.g., { search: "query" })
|
|
845
|
+
* @param options - Optional filter function and schema override
|
|
846
|
+
* @returns Async iterable of search results
|
|
847
|
+
*/
|
|
848
|
+
searchIndex(projectId, engineId, query, options) {
|
|
849
|
+
return searchIndex(this, projectId, engineId, query, options);
|
|
850
|
+
}
|
|
851
|
+
/**
|
|
852
|
+
* Delete an event from the engine.
|
|
853
|
+
*
|
|
854
|
+
* @param params - Parameters object
|
|
855
|
+
* @param params.eventId - Event identifier to delete
|
|
856
|
+
* @param params.engineId - Optional engine identifier (defaults to client.engineId)
|
|
857
|
+
* @returns Promise that resolves when deletion is successful or accepted
|
|
858
|
+
* @throws {RailtownNotFoundError} If engine is not found (404)
|
|
859
|
+
* @throws {RailtownServerError} If service is unavailable (503)
|
|
860
|
+
* @throws {RailtownUnauthorizedError} If authentication fails (401)
|
|
861
|
+
*
|
|
862
|
+
* The operation is idempotent - calling deleteEvent multiple times with the same eventId
|
|
863
|
+
* will succeed. The API returns:
|
|
864
|
+
* - 204: Event deleted immediately or already deleted
|
|
865
|
+
* - 202: Deletion accepted, may take time (eventually consistent)
|
|
866
|
+
*/
|
|
867
|
+
async deleteEvent(params) {
|
|
868
|
+
const engineId = params.engineId ?? this.engineId;
|
|
869
|
+
await this._makeRequest("DELETE", `/api/Engine/${engineId}/Event/${params.eventId}`);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Internal method to make authenticated requests (used by API modules).
|
|
873
|
+
*
|
|
874
|
+
* @internal
|
|
875
|
+
*/
|
|
876
|
+
async _makeRequest(method, path, options = {}) {
|
|
877
|
+
return this._request(method, path, options);
|
|
878
|
+
}
|
|
879
|
+
/**
|
|
880
|
+
* Get the default schema for this client.
|
|
881
|
+
*
|
|
882
|
+
* @internal
|
|
883
|
+
*/
|
|
884
|
+
_getSchema() {
|
|
885
|
+
return this.schema;
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
export { RailEngine };
|
|
890
|
+
//# sourceMappingURL=index.js.map
|