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