@retrivora-ai/rag-engine 0.1.5 → 0.1.7

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 (41) hide show
  1. package/dist/{DocumentChunker-cfaMidtA.d.mts → DocumentChunker-BEyzadsv.d.mts} +2 -2
  2. package/dist/{DocumentChunker-cfaMidtA.d.ts → DocumentChunker-BEyzadsv.d.ts} +2 -2
  3. package/dist/{PineconeProvider-NJ675H7U.mjs → PineconeProvider-ZRAFNFEC.mjs} +1 -1
  4. package/dist/{PostgreSQLProvider-ISNMD3BE.mjs → PostgreSQLProvider-ZNXA67IM.mjs} +1 -1
  5. package/dist/{QdrantProvider-LJWOIGES.mjs → QdrantProvider-VAED5VA7.mjs} +1 -1
  6. package/dist/{RagConfig-DG_0f8ka.d.mts → RagConfig-hBGXJmSx.d.mts} +3 -3
  7. package/dist/{RagConfig-DG_0f8ka.d.ts → RagConfig-hBGXJmSx.d.ts} +3 -3
  8. package/dist/chunk-7YQWGERZ.mjs +1764 -0
  9. package/dist/chunk-CWQQHAF6.mjs +157 -0
  10. package/dist/{chunk-S5DRHETN.mjs → chunk-IUTAZ7QR.mjs} +31 -2
  11. package/dist/{chunk-AALIF3AL.mjs → chunk-ZM6TYIDH.mjs} +3 -3
  12. package/dist/handlers/index.d.mts +3 -44
  13. package/dist/handlers/index.d.ts +3 -44
  14. package/dist/handlers/index.js +1371 -60
  15. package/dist/handlers/index.mjs +1 -1
  16. package/dist/index-Bx182KKn.d.ts +64 -0
  17. package/dist/index-Ck2pt7-8.d.mts +64 -0
  18. package/dist/index.d.mts +4 -4
  19. package/dist/index.d.ts +4 -4
  20. package/dist/server.d.mts +74 -18
  21. package/dist/server.d.ts +74 -18
  22. package/dist/server.js +1371 -60
  23. package/dist/server.mjs +4 -4
  24. package/package.json +2 -1
  25. package/src/config/serverConfig.ts +4 -0
  26. package/src/core/BatchProcessor.ts +347 -0
  27. package/src/core/ConfigValidator.ts +568 -0
  28. package/src/core/Pipeline.ts +89 -39
  29. package/src/core/ProviderHealthCheck.ts +568 -0
  30. package/src/core/VectorPlugin.ts +49 -8
  31. package/src/handlers/index.ts +2 -2
  32. package/src/providers/vectordb/BaseVectorProvider.ts +1 -1
  33. package/src/providers/vectordb/MilvusProvider.ts +1 -1
  34. package/src/providers/vectordb/MongoDBProvider.ts +1 -1
  35. package/src/providers/vectordb/PineconeProvider.ts +4 -4
  36. package/src/providers/vectordb/PostgreSQLProvider.ts +35 -3
  37. package/src/providers/vectordb/QdrantProvider.ts +81 -4
  38. package/src/rag/DocumentChunker.ts +2 -2
  39. package/src/types/index.ts +3 -3
  40. package/dist/chunk-6FODXNUF.mjs +0 -91
  41. package/dist/chunk-BP4U4TT5.mjs +0 -548
package/dist/server.js CHANGED
@@ -557,7 +557,7 @@ var init_PineconeProvider = __esm({
557
557
  var _a;
558
558
  await this.index(namespace).upsert({
559
559
  records: [{
560
- id: doc.id,
560
+ id: String(doc.id),
561
561
  values: doc.vector,
562
562
  metadata: __spreadValues({ content: doc.content }, (_a = doc.metadata) != null ? _a : {})
563
563
  }]
@@ -569,7 +569,7 @@ var init_PineconeProvider = __esm({
569
569
  const records = docs.slice(i, i + BATCH).map((d) => {
570
570
  var _a;
571
571
  return {
572
- id: d.id,
572
+ id: String(d.id),
573
573
  values: d.vector,
574
574
  metadata: __spreadValues({ content: d.content }, (_a = d.metadata) != null ? _a : {})
575
575
  };
@@ -595,7 +595,7 @@ var init_PineconeProvider = __esm({
595
595
  });
596
596
  }
597
597
  async delete(id, namespace) {
598
- await this.index(namespace).deleteOne({ id });
598
+ await this.index(namespace).deleteOne({ id: String(id) });
599
599
  }
600
600
  async deleteNamespace(namespace) {
601
601
  await this.client.index(this.indexName).namespace(namespace).deleteAll();
@@ -673,8 +673,37 @@ var init_PostgreSQLProvider = __esm({
673
673
  );
674
674
  }
675
675
  async batchUpsert(docs, namespace = "") {
676
- for (const doc of docs) {
677
- await this.upsert(doc, namespace);
676
+ if (docs.length === 0) return;
677
+ const client = await this.pool.connect();
678
+ try {
679
+ await client.query("BEGIN");
680
+ const BATCH_SIZE = 50;
681
+ for (let i = 0; i < docs.length; i += BATCH_SIZE) {
682
+ const batch = docs.slice(i, i + BATCH_SIZE);
683
+ const values = [];
684
+ const valuePlaceholders = batch.map((doc, idx) => {
685
+ var _a;
686
+ const offset = idx * 5;
687
+ values.push(doc.id, namespace, doc.content, JSON.stringify((_a = doc.metadata) != null ? _a : {}), `[${doc.vector.join(",")}]`);
688
+ return `($${offset + 1}, $${offset + 2}, $${offset + 3}, $${offset + 4}, $${offset + 5}::vector)`;
689
+ }).join(", ");
690
+ const query = `
691
+ INSERT INTO ${this.tableName} (id, namespace, content, metadata, embedding)
692
+ VALUES ${valuePlaceholders}
693
+ ON CONFLICT (id) DO UPDATE
694
+ SET namespace = EXCLUDED.namespace,
695
+ content = EXCLUDED.content,
696
+ metadata = EXCLUDED.metadata,
697
+ embedding = EXCLUDED.embedding
698
+ `;
699
+ await client.query(query, values);
700
+ }
701
+ await client.query("COMMIT");
702
+ } catch (error) {
703
+ await client.query("ROLLBACK");
704
+ throw error;
705
+ } finally {
706
+ client.release();
678
707
  }
679
708
  }
680
709
  async query(vector, topK, namespace, filter) {
@@ -926,11 +955,12 @@ var QdrantProvider_exports = {};
926
955
  __export(QdrantProvider_exports, {
927
956
  QdrantProvider: () => QdrantProvider
928
957
  });
929
- var import_axios4, QdrantProvider;
958
+ var import_axios4, import_crypto, QdrantProvider;
930
959
  var init_QdrantProvider = __esm({
931
960
  "src/providers/vectordb/QdrantProvider.ts"() {
932
961
  "use strict";
933
962
  import_axios4 = __toESM(require("axios"));
963
+ import_crypto = __toESM(require("crypto"));
934
964
  init_BaseVectorProvider();
935
965
  QdrantProvider = class extends BaseVectorProvider {
936
966
  constructor(config) {
@@ -947,6 +977,61 @@ var init_QdrantProvider = __esm({
947
977
  }
948
978
  async initialize() {
949
979
  await this.ping();
980
+ await this.ensureCollection();
981
+ await this.ensureIndex();
982
+ }
983
+ /**
984
+ * Ensures the collection exists. Creates it if missing.
985
+ */
986
+ async ensureCollection() {
987
+ var _a;
988
+ try {
989
+ const opts = this.config.options;
990
+ const dimensions = opts.dimensions || 1536;
991
+ await this.http.get(`/collections/${this.indexName}`);
992
+ console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
993
+ } catch (err) {
994
+ if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
995
+ const opts = this.config.options;
996
+ const dimensions = opts.dimensions || 1536;
997
+ console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
998
+ await this.http.put(`/collections/${this.indexName}`, {
999
+ vectors: {
1000
+ size: dimensions,
1001
+ distance: "Cosine"
1002
+ }
1003
+ });
1004
+ console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
1005
+ } else {
1006
+ throw err;
1007
+ }
1008
+ }
1009
+ }
1010
+ /**
1011
+ * Ensures that the 'namespace' field has a keyword index for efficient filtering.
1012
+ * Qdrant requires this for search filters to work in many configurations.
1013
+ */
1014
+ async ensureIndex() {
1015
+ var _a, _b;
1016
+ try {
1017
+ await this.http.put(`/collections/${this.indexName}/index`, {
1018
+ field_name: "namespace",
1019
+ field_schema: "keyword"
1020
+ });
1021
+ console.log(`[QdrantProvider] \u2705 Ensured keyword index for "namespace" on collection "${this.indexName}"`);
1022
+ } catch (err) {
1023
+ let status;
1024
+ let data;
1025
+ if (import_axios4.default.isAxiosError(err)) {
1026
+ status = (_a = err.response) == null ? void 0 : _a.status;
1027
+ data = (_b = err.response) == null ? void 0 : _b.data;
1028
+ }
1029
+ if (status === 409) {
1030
+ return;
1031
+ }
1032
+ const errorMessage = err instanceof Error ? err.message : String(err);
1033
+ console.warn(`[QdrantProvider] \u26A0\uFE0F Could not ensure namespace index (Status: ${status}):`, JSON.stringify(data || errorMessage));
1034
+ }
950
1035
  }
951
1036
  async upsert(doc, namespace) {
952
1037
  await this.batchUpsert([doc], namespace);
@@ -954,7 +1039,7 @@ var init_QdrantProvider = __esm({
954
1039
  async batchUpsert(docs, namespace) {
955
1040
  const payload = {
956
1041
  points: docs.map((doc) => ({
957
- id: doc.id,
1042
+ id: this.normalizeId(doc.id),
958
1043
  vector: doc.vector,
959
1044
  payload: __spreadValues({
960
1045
  content: doc.content,
@@ -988,10 +1073,9 @@ var init_QdrantProvider = __esm({
988
1073
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
989
1074
  async delete(id, _namespace) {
990
1075
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
991
- points: [id]
1076
+ points: [this.normalizeId(id)]
992
1077
  });
993
1078
  }
994
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
995
1079
  async deleteNamespace(_namespace) {
996
1080
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
997
1081
  filter: {
@@ -1007,6 +1091,17 @@ var init_QdrantProvider = __esm({
1007
1091
  return false;
1008
1092
  }
1009
1093
  }
1094
+ /**
1095
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
1096
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
1097
+ */
1098
+ normalizeId(id) {
1099
+ if (typeof id === "number") return id;
1100
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1101
+ if (uuidRegex.test(id)) return id.toLowerCase();
1102
+ const hash = import_crypto.default.createHash("md5").update(id).digest("hex");
1103
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
1104
+ }
1010
1105
  async disconnect() {
1011
1106
  }
1012
1107
  };
@@ -1353,7 +1448,7 @@ function readEnum(env, name, fallback, allowed) {
1353
1448
  throw new Error(`[getRagConfig] ${name} must be one of: ${allowed.join(", ")}`);
1354
1449
  }
1355
1450
  function getRagConfig(env = process.env) {
1356
- var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J;
1451
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K;
1357
1452
  const projectId = (_b = (_a = readString(env, "RAG_PROJECT_ID")) != null ? _a : readString(env, "NEXT_PUBLIC_PROJECT_ID")) != null ? _b : "__default__";
1358
1453
  const vectorProvider = readEnum(env, "VECTOR_DB_PROVIDER", "pinecone", VECTOR_DB_PROVIDERS);
1359
1454
  const llmProvider = readEnum(env, "LLM_PROVIDER", "openai", LLM_PROVIDERS);
@@ -1378,6 +1473,10 @@ function getRagConfig(env = process.env) {
1378
1473
  vectorDbOptions.database = (_j = readString(env, "MONGODB_DB")) != null ? _j : "";
1379
1474
  vectorDbOptions.collection = (_k = readString(env, "MONGODB_COLLECTION")) != null ? _k : "";
1380
1475
  vectorDbOptions.indexName = (_l = readString(env, "MONGODB_INDEX_NAME")) != null ? _l : "vector_index";
1476
+ } else if (vectorProvider === "qdrant") {
1477
+ vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
1478
+ vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
1479
+ vectorDbOptions.dimensions = embeddingDimensions;
1381
1480
  }
1382
1481
  const llmApiKeyByProvider = {
1383
1482
  openai: readString(env, "OPENAI_API_KEY"),
@@ -1388,19 +1487,19 @@ function getRagConfig(env = process.env) {
1388
1487
  const embeddingApiKeyByProvider = {
1389
1488
  openai: readString(env, "OPENAI_API_KEY"),
1390
1489
  ollama: void 0,
1391
- custom: (_m = readString(env, "EMBEDDING_API_KEY")) != null ? _m : readString(env, "OPENAI_API_KEY")
1490
+ custom: (_n = readString(env, "EMBEDDING_API_KEY")) != null ? _n : readString(env, "OPENAI_API_KEY")
1392
1491
  };
1393
1492
  return {
1394
1493
  projectId,
1395
1494
  vectorDb: {
1396
1495
  provider: vectorProvider,
1397
- indexName: (_n = readString(env, "VECTOR_DB_INDEX")) != null ? _n : "rag-index",
1496
+ indexName: (_o = readString(env, "VECTOR_DB_INDEX")) != null ? _o : "rag-index",
1398
1497
  options: vectorDbOptions
1399
1498
  },
1400
1499
  llm: {
1401
1500
  provider: llmProvider,
1402
- model: (_o = readString(env, "LLM_MODEL")) != null ? _o : "gpt-4o",
1403
- apiKey: (_p = llmApiKeyByProvider[llmProvider]) != null ? _p : "",
1501
+ model: (_p = readString(env, "LLM_MODEL")) != null ? _p : "gpt-4o",
1502
+ apiKey: (_q = llmApiKeyByProvider[llmProvider]) != null ? _q : "",
1404
1503
  baseUrl: readString(env, "LLM_BASE_URL"),
1405
1504
  systemPrompt: readString(env, "LLM_SYSTEM_PROMPT"),
1406
1505
  maxTokens: readNumber(env, "LLM_MAX_TOKENS", 1024),
@@ -1411,7 +1510,7 @@ function getRagConfig(env = process.env) {
1411
1510
  },
1412
1511
  embedding: {
1413
1512
  provider: embeddingProvider,
1414
- model: (_q = readString(env, "EMBEDDING_MODEL")) != null ? _q : "text-embedding-3-small",
1513
+ model: (_r = readString(env, "EMBEDDING_MODEL")) != null ? _r : "text-embedding-3-small",
1415
1514
  apiKey: embeddingApiKeyByProvider[embeddingProvider],
1416
1515
  baseUrl: readString(env, "EMBEDDING_BASE_URL"),
1417
1516
  dimensions: embeddingDimensions,
@@ -1420,16 +1519,16 @@ function getRagConfig(env = process.env) {
1420
1519
  }
1421
1520
  },
1422
1521
  ui: {
1423
- title: (_s = (_r = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _r : readString(env, "UI_TITLE")) != null ? _s : "AI Assistant",
1424
- subtitle: (_u = (_t = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _t : readString(env, "UI_SUBTITLE")) != null ? _u : "Powered by RAG",
1425
- primaryColor: (_w = (_v = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _v : readString(env, "UI_PRIMARY_COLOR")) != null ? _w : "#10b981",
1426
- accentColor: (_y = (_x = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _x : readString(env, "UI_ACCENT_COLOR")) != null ? _y : "#3b82f6",
1427
- logoUrl: (_z = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _z : readString(env, "UI_LOGO_URL"),
1428
- placeholder: (_B = (_A = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _A : readString(env, "UI_PLACEHOLDER")) != null ? _B : "Ask me anything\u2026",
1429
- showSources: ((_D = (_C = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _C : readString(env, "UI_SHOW_SOURCES")) != null ? _D : "true") !== "false",
1430
- welcomeMessage: (_F = (_E = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _E : readString(env, "UI_WELCOME_MESSAGE")) != null ? _F : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1431
- visualStyle: (_H = (_G = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _G : readString(env, "UI_VISUAL_STYLE")) != null ? _H : "glass",
1432
- borderRadius: (_J = (_I = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _I : readString(env, "UI_BORDER_RADIUS")) != null ? _J : "xl"
1522
+ title: (_t = (_s = readString(env, "NEXT_PUBLIC_UI_TITLE")) != null ? _s : readString(env, "UI_TITLE")) != null ? _t : "AI Assistant",
1523
+ subtitle: (_v = (_u = readString(env, "NEXT_PUBLIC_UI_SUBTITLE")) != null ? _u : readString(env, "UI_SUBTITLE")) != null ? _v : "Powered by RAG",
1524
+ primaryColor: (_x = (_w = readString(env, "NEXT_PUBLIC_PRIMARY_COLOR")) != null ? _w : readString(env, "UI_PRIMARY_COLOR")) != null ? _x : "#10b981",
1525
+ accentColor: (_z = (_y = readString(env, "NEXT_PUBLIC_ACCENT_COLOR")) != null ? _y : readString(env, "UI_ACCENT_COLOR")) != null ? _z : "#3b82f6",
1526
+ logoUrl: (_A = readString(env, "NEXT_PUBLIC_LOGO_URL")) != null ? _A : readString(env, "UI_LOGO_URL"),
1527
+ placeholder: (_C = (_B = readString(env, "NEXT_PUBLIC_PLACEHOLDER")) != null ? _B : readString(env, "UI_PLACEHOLDER")) != null ? _C : "Ask me anything\u2026",
1528
+ showSources: ((_E = (_D = readString(env, "NEXT_PUBLIC_SHOW_SOURCES")) != null ? _D : readString(env, "UI_SHOW_SOURCES")) != null ? _E : "true") !== "false",
1529
+ welcomeMessage: (_G = (_F = readString(env, "NEXT_PUBLIC_WELCOME_MESSAGE")) != null ? _F : readString(env, "UI_WELCOME_MESSAGE")) != null ? _G : "Hello! I'm your AI assistant. Ask me anything about your documents.",
1530
+ visualStyle: (_I = (_H = readString(env, "NEXT_PUBLIC_UI_VISUAL_STYLE")) != null ? _H : readString(env, "UI_VISUAL_STYLE")) != null ? _I : "glass",
1531
+ borderRadius: (_K = (_J = readString(env, "NEXT_PUBLIC_UI_BORDER_RADIUS")) != null ? _J : readString(env, "UI_BORDER_RADIUS")) != null ? _K : "xl"
1433
1532
  },
1434
1533
  rag: {
1435
1534
  topK: readNumber(env, "RAG_TOP_K", 5),
@@ -1482,6 +1581,467 @@ var ConfigResolver = class {
1482
1581
  }
1483
1582
  };
1484
1583
 
1584
+ // src/core/ConfigValidator.ts
1585
+ var ConfigValidator = class {
1586
+ /**
1587
+ * Validates the entire RagConfig object.
1588
+ * Returns an array of validation errors. Empty array = valid config.
1589
+ */
1590
+ static validate(config) {
1591
+ const errors = [];
1592
+ if (!config.projectId) {
1593
+ errors.push({
1594
+ field: "projectId",
1595
+ message: "projectId is required",
1596
+ severity: "error"
1597
+ });
1598
+ }
1599
+ errors.push(...this.validateVectorDbConfig(config.vectorDb));
1600
+ errors.push(...this.validateLLMConfig(config.llm));
1601
+ if (config.embedding) {
1602
+ errors.push(...this.validateEmbeddingConfig(config.embedding));
1603
+ } else if (config.llm.provider === "anthropic") {
1604
+ errors.push({
1605
+ field: "embedding",
1606
+ message: "Embedding config is required when using Anthropic LLM",
1607
+ suggestion: 'Provide embedding config with provider (e.g., "openai")',
1608
+ severity: "error"
1609
+ });
1610
+ }
1611
+ if (config.ui) {
1612
+ errors.push(...this.validateUIConfig(config.ui));
1613
+ }
1614
+ if (config.rag) {
1615
+ errors.push(...this.validateRAGConfig(config.rag));
1616
+ }
1617
+ return errors;
1618
+ }
1619
+ static validateVectorDbConfig(config) {
1620
+ const errors = [];
1621
+ if (!config.provider) {
1622
+ errors.push({
1623
+ field: "vectorDb.provider",
1624
+ message: "Vector database provider is required",
1625
+ severity: "error"
1626
+ });
1627
+ return errors;
1628
+ }
1629
+ if (!config.indexName) {
1630
+ errors.push({
1631
+ field: "vectorDb.indexName",
1632
+ message: "Vector database index name is required",
1633
+ severity: "error"
1634
+ });
1635
+ }
1636
+ switch (config.provider) {
1637
+ case "pinecone":
1638
+ errors.push(...this.validatePineconeConfig(config));
1639
+ break;
1640
+ case "pgvector":
1641
+ case "postgresql":
1642
+ errors.push(...this.validatePostgresConfig(config));
1643
+ break;
1644
+ case "mongodb":
1645
+ errors.push(...this.validateMongoDBConfig(config));
1646
+ break;
1647
+ case "milvus":
1648
+ errors.push(...this.validateMilvusConfig(config));
1649
+ break;
1650
+ case "qdrant":
1651
+ errors.push(...this.validateQdrantConfig(config));
1652
+ break;
1653
+ case "chromadb":
1654
+ errors.push(...this.validateChromaDBConfig(config));
1655
+ break;
1656
+ case "redis":
1657
+ errors.push(...this.validateRedisConfig(config));
1658
+ break;
1659
+ case "weaviate":
1660
+ errors.push(...this.validateWeaviateConfig(config));
1661
+ break;
1662
+ case "universal_rest":
1663
+ case "rest":
1664
+ errors.push(...this.validateRestConfig(config));
1665
+ break;
1666
+ }
1667
+ return errors;
1668
+ }
1669
+ static validatePineconeConfig(config) {
1670
+ const errors = [];
1671
+ const opts = config.options;
1672
+ if (!opts.apiKey) {
1673
+ errors.push({
1674
+ field: "vectorDb.options.apiKey",
1675
+ message: "Pinecone API key is required",
1676
+ suggestion: "Set PINECONE_API_KEY environment variable",
1677
+ severity: "error"
1678
+ });
1679
+ }
1680
+ if (!opts.environment) {
1681
+ errors.push({
1682
+ field: "vectorDb.options.environment",
1683
+ message: "Pinecone environment is required",
1684
+ suggestion: 'Set PINECONE_ENVIRONMENT environment variable (e.g., "gcp-starter")',
1685
+ severity: "error"
1686
+ });
1687
+ }
1688
+ return errors;
1689
+ }
1690
+ static validatePostgresConfig(config) {
1691
+ const errors = [];
1692
+ const opts = config.options;
1693
+ if (!opts.connectionString) {
1694
+ errors.push({
1695
+ field: "vectorDb.options.connectionString",
1696
+ message: "PostgreSQL connection string is required",
1697
+ suggestion: "Set PGVECTOR_CONNECTION_STRING environment variable",
1698
+ severity: "error"
1699
+ });
1700
+ }
1701
+ return errors;
1702
+ }
1703
+ static validateMongoDBConfig(config) {
1704
+ const errors = [];
1705
+ const opts = config.options;
1706
+ if (!opts.uri) {
1707
+ errors.push({
1708
+ field: "vectorDb.options.uri",
1709
+ message: "MongoDB connection URI is required",
1710
+ suggestion: "Set MONGODB_URI environment variable",
1711
+ severity: "error"
1712
+ });
1713
+ }
1714
+ if (!opts.database) {
1715
+ errors.push({
1716
+ field: "vectorDb.options.database",
1717
+ message: "MongoDB database name is required",
1718
+ suggestion: "Set MONGODB_DB environment variable",
1719
+ severity: "error"
1720
+ });
1721
+ }
1722
+ if (!opts.collection) {
1723
+ errors.push({
1724
+ field: "vectorDb.options.collection",
1725
+ message: "MongoDB collection name is required",
1726
+ suggestion: "Set MONGODB_COLLECTION environment variable",
1727
+ severity: "error"
1728
+ });
1729
+ }
1730
+ return errors;
1731
+ }
1732
+ static validateMilvusConfig(config) {
1733
+ const errors = [];
1734
+ const opts = config.options;
1735
+ if (!opts.host) {
1736
+ errors.push({
1737
+ field: "vectorDb.options.host",
1738
+ message: "Milvus host is required",
1739
+ suggestion: "Set MILVUS_HOST environment variable",
1740
+ severity: "error"
1741
+ });
1742
+ }
1743
+ if (!opts.port) {
1744
+ errors.push({
1745
+ field: "vectorDb.options.port",
1746
+ message: "Milvus port is required",
1747
+ suggestion: "Set MILVUS_PORT environment variable (default: 19530)",
1748
+ severity: "error"
1749
+ });
1750
+ }
1751
+ return errors;
1752
+ }
1753
+ static validateQdrantConfig(config) {
1754
+ const errors = [];
1755
+ const opts = config.options;
1756
+ if (!opts.baseUrl && !opts.url) {
1757
+ errors.push({
1758
+ field: "vectorDb.options.baseUrl",
1759
+ message: "Qdrant base URL is required",
1760
+ suggestion: 'Set QDRANT_URL environment variable (e.g., "http://localhost:6333")',
1761
+ severity: "error"
1762
+ });
1763
+ }
1764
+ return errors;
1765
+ }
1766
+ static validateChromaDBConfig(config) {
1767
+ const errors = [];
1768
+ const opts = config.options;
1769
+ if (!opts.host && !opts.path) {
1770
+ errors.push({
1771
+ field: "vectorDb.options.host",
1772
+ message: "ChromaDB host or path is required",
1773
+ suggestion: "Set CHROMADB_HOST or CHROMADB_PATH environment variable",
1774
+ severity: "error"
1775
+ });
1776
+ }
1777
+ return errors;
1778
+ }
1779
+ static validateRedisConfig(config) {
1780
+ const errors = [];
1781
+ const opts = config.options;
1782
+ if (!opts.url && (!opts.host || !opts.port)) {
1783
+ errors.push({
1784
+ field: "vectorDb.options.url",
1785
+ message: "Redis connection URL or host:port is required",
1786
+ suggestion: "Set REDIS_URL or REDIS_HOST/REDIS_PORT environment variables",
1787
+ severity: "error"
1788
+ });
1789
+ }
1790
+ return errors;
1791
+ }
1792
+ static validateWeaviateConfig(config) {
1793
+ const errors = [];
1794
+ const opts = config.options;
1795
+ if (!opts.url) {
1796
+ errors.push({
1797
+ field: "vectorDb.options.url",
1798
+ message: "Weaviate instance URL is required",
1799
+ suggestion: "Set WEAVIATE_URL environment variable",
1800
+ severity: "error"
1801
+ });
1802
+ }
1803
+ return errors;
1804
+ }
1805
+ static validateRestConfig(config) {
1806
+ const errors = [];
1807
+ const opts = config.options;
1808
+ if (!opts.baseUrl) {
1809
+ errors.push({
1810
+ field: "vectorDb.options.baseUrl",
1811
+ message: "REST API base URL is required",
1812
+ suggestion: "Set VECTOR_BASE_URL environment variable",
1813
+ severity: "error"
1814
+ });
1815
+ }
1816
+ return errors;
1817
+ }
1818
+ static validateLLMConfig(config) {
1819
+ var _a;
1820
+ const errors = [];
1821
+ if (!config.provider) {
1822
+ errors.push({
1823
+ field: "llm.provider",
1824
+ message: "LLM provider is required",
1825
+ severity: "error"
1826
+ });
1827
+ return errors;
1828
+ }
1829
+ if (!config.model) {
1830
+ errors.push({
1831
+ field: "llm.model",
1832
+ message: "LLM model name is required",
1833
+ suggestion: `e.g., "gpt-4o" for OpenAI, "claude-3-opus" for Anthropic`,
1834
+ severity: "error"
1835
+ });
1836
+ }
1837
+ switch (config.provider) {
1838
+ case "openai":
1839
+ if (!config.apiKey) {
1840
+ errors.push({
1841
+ field: "llm.apiKey",
1842
+ message: "OpenAI API key is required",
1843
+ suggestion: "Set OPENAI_API_KEY environment variable",
1844
+ severity: "error"
1845
+ });
1846
+ }
1847
+ break;
1848
+ case "anthropic":
1849
+ if (!config.apiKey) {
1850
+ errors.push({
1851
+ field: "llm.apiKey",
1852
+ message: "Anthropic API key is required",
1853
+ suggestion: "Set ANTHROPIC_API_KEY environment variable",
1854
+ severity: "error"
1855
+ });
1856
+ }
1857
+ break;
1858
+ case "ollama":
1859
+ if (!config.baseUrl) {
1860
+ errors.push({
1861
+ field: "llm.baseUrl",
1862
+ message: "Ollama base URL is required",
1863
+ suggestion: 'Set LLM_BASE_URL environment variable (e.g., "http://localhost:11434")',
1864
+ severity: "error"
1865
+ });
1866
+ }
1867
+ break;
1868
+ case "rest":
1869
+ case "universal_rest":
1870
+ if (!config.baseUrl && !((_a = config.options) == null ? void 0 : _a.baseUrl)) {
1871
+ errors.push({
1872
+ field: "llm.baseUrl",
1873
+ message: "REST API base URL is required",
1874
+ suggestion: "Set LLM_BASE_URL environment variable",
1875
+ severity: "error"
1876
+ });
1877
+ }
1878
+ break;
1879
+ }
1880
+ if (config.temperature !== void 0) {
1881
+ if (config.temperature < 0 || config.temperature > 2) {
1882
+ errors.push({
1883
+ field: "llm.temperature",
1884
+ message: "Temperature must be between 0 and 2",
1885
+ severity: "error"
1886
+ });
1887
+ }
1888
+ }
1889
+ if (config.maxTokens !== void 0 && config.maxTokens <= 0) {
1890
+ errors.push({
1891
+ field: "llm.maxTokens",
1892
+ message: "maxTokens must be greater than 0",
1893
+ severity: "error"
1894
+ });
1895
+ }
1896
+ return errors;
1897
+ }
1898
+ static validateEmbeddingConfig(config) {
1899
+ const errors = [];
1900
+ if (!config.provider) {
1901
+ errors.push({
1902
+ field: "embedding.provider",
1903
+ message: "Embedding provider is required",
1904
+ severity: "error"
1905
+ });
1906
+ return errors;
1907
+ }
1908
+ if (!config.model) {
1909
+ errors.push({
1910
+ field: "embedding.model",
1911
+ message: "Embedding model name is required",
1912
+ suggestion: 'e.g., "text-embedding-3-small" for OpenAI',
1913
+ severity: "error"
1914
+ });
1915
+ }
1916
+ if (config.provider === "openai" && !config.apiKey) {
1917
+ errors.push({
1918
+ field: "embedding.apiKey",
1919
+ message: "OpenAI API key is required for embedding",
1920
+ suggestion: "Set OPENAI_API_KEY environment variable",
1921
+ severity: "error"
1922
+ });
1923
+ }
1924
+ if (config.provider === "ollama" && !config.baseUrl) {
1925
+ errors.push({
1926
+ field: "embedding.baseUrl",
1927
+ message: "Ollama base URL is required",
1928
+ suggestion: "Set EMBEDDING_BASE_URL environment variable",
1929
+ severity: "error"
1930
+ });
1931
+ }
1932
+ if (config.dimensions !== void 0 && config.dimensions <= 0) {
1933
+ errors.push({
1934
+ field: "embedding.dimensions",
1935
+ message: "Embedding dimensions must be greater than 0",
1936
+ severity: "error"
1937
+ });
1938
+ }
1939
+ return errors;
1940
+ }
1941
+ static validateUIConfig(config) {
1942
+ const errors = [];
1943
+ if (config.primaryColor) {
1944
+ if (!this.isValidCSSColor(config.primaryColor)) {
1945
+ errors.push({
1946
+ field: "ui.primaryColor",
1947
+ message: "Invalid CSS color format",
1948
+ suggestion: "Use valid CSS colors: hex (#FF0000), rgb, hsl, or named colors",
1949
+ severity: "warning"
1950
+ });
1951
+ }
1952
+ }
1953
+ if (config.borderRadius) {
1954
+ const validValues = ["none", "sm", "md", "lg", "xl", "full"];
1955
+ if (!validValues.includes(config.borderRadius)) {
1956
+ errors.push({
1957
+ field: "ui.borderRadius",
1958
+ message: `borderRadius must be one of: ${validValues.join(", ")}`,
1959
+ severity: "warning"
1960
+ });
1961
+ }
1962
+ }
1963
+ if (config.visualStyle) {
1964
+ const validValues = ["glass", "solid"];
1965
+ if (!validValues.includes(config.visualStyle)) {
1966
+ errors.push({
1967
+ field: "ui.visualStyle",
1968
+ message: `visualStyle must be one of: ${validValues.join(", ")}`,
1969
+ severity: "warning"
1970
+ });
1971
+ }
1972
+ }
1973
+ return errors;
1974
+ }
1975
+ static validateRAGConfig(config) {
1976
+ const errors = [];
1977
+ if (config.topK !== void 0) {
1978
+ if (typeof config.topK !== "number" || config.topK <= 0) {
1979
+ errors.push({
1980
+ field: "rag.topK",
1981
+ message: "topK must be a positive integer",
1982
+ severity: "error"
1983
+ });
1984
+ }
1985
+ }
1986
+ if (config.scoreThreshold !== void 0) {
1987
+ if (typeof config.scoreThreshold !== "number" || config.scoreThreshold < 0 || config.scoreThreshold > 1) {
1988
+ errors.push({
1989
+ field: "rag.scoreThreshold",
1990
+ message: "scoreThreshold must be between 0 and 1",
1991
+ severity: "error"
1992
+ });
1993
+ }
1994
+ }
1995
+ if (config.chunkSize !== void 0) {
1996
+ if (typeof config.chunkSize !== "number" || config.chunkSize <= 0) {
1997
+ errors.push({
1998
+ field: "rag.chunkSize",
1999
+ message: "chunkSize must be a positive integer",
2000
+ severity: "error"
2001
+ });
2002
+ }
2003
+ }
2004
+ if (config.chunkOverlap !== void 0) {
2005
+ if (typeof config.chunkOverlap !== "number" || config.chunkOverlap < 0) {
2006
+ errors.push({
2007
+ field: "rag.chunkOverlap",
2008
+ message: "chunkOverlap must be a non-negative integer",
2009
+ severity: "error"
2010
+ });
2011
+ }
2012
+ }
2013
+ return errors;
2014
+ }
2015
+ static isValidCSSColor(color) {
2016
+ const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
2017
+ const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2018
+ const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2019
+ const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
2020
+ return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
2021
+ }
2022
+ /**
2023
+ * Throws if there are error-level validation issues.
2024
+ * Logs warnings to console.
2025
+ */
2026
+ static validateAndThrow(config) {
2027
+ const errors = this.validate(config);
2028
+ const errorItems = errors.filter((e) => e.severity === "error");
2029
+ const warnings = errors.filter((e) => e.severity === "warning");
2030
+ if (warnings.length > 0) {
2031
+ console.warn("[ConfigValidator] Configuration warnings:");
2032
+ warnings.forEach((w) => {
2033
+ console.warn(` ${w.field}: ${w.message}`);
2034
+ if (w.suggestion) console.warn(` Suggestion: ${w.suggestion}`);
2035
+ });
2036
+ }
2037
+ if (errorItems.length > 0) {
2038
+ const message = errorItems.map((e) => `${e.field}: ${e.message}${e.suggestion ? ` (${e.suggestion})` : ""}`).join("\n ");
2039
+ throw new Error(`[ConfigValidator] Configuration validation failed:
2040
+ ${message}`);
2041
+ }
2042
+ }
2043
+ };
2044
+
1485
2045
  // src/rag/DocumentChunker.ts
1486
2046
  var DocumentChunker = class {
1487
2047
  constructor(chunkSize = 1e3, chunkOverlap = 200) {
@@ -1597,6 +2157,230 @@ var ProviderRegistry = class {
1597
2157
  };
1598
2158
  ProviderRegistry.vectorProviders = {};
1599
2159
 
2160
+ // src/core/BatchProcessor.ts
2161
+ function isTransientError(error) {
2162
+ if (!(error instanceof Error)) return false;
2163
+ const message = error.message.toLowerCase();
2164
+ if (message.includes("econnrefused") || message.includes("econnreset") || message.includes("timeout") || message.includes("network") || message.includes("socket")) {
2165
+ return true;
2166
+ }
2167
+ if (message.includes("429") || message.includes("503") || message.includes("502") || message.includes("rate limit") || message.includes("too many requests")) {
2168
+ return true;
2169
+ }
2170
+ return false;
2171
+ }
2172
+ function calculateBackoffDelay(attempt, initialDelayMs, maxDelayMs, multiplier) {
2173
+ const exponentialDelay = Math.min(
2174
+ initialDelayMs * Math.pow(multiplier, attempt),
2175
+ maxDelayMs
2176
+ );
2177
+ const jitter = exponentialDelay * 0.1 * (Math.random() * 2 - 1);
2178
+ return Math.max(0, exponentialDelay + jitter);
2179
+ }
2180
+ function sleep(ms) {
2181
+ return new Promise((resolve) => setTimeout(resolve, ms));
2182
+ }
2183
+ var BatchProcessor = class {
2184
+ /**
2185
+ * Processes an array of items in configurable batches with retry logic.
2186
+ *
2187
+ * @param items - Items to process
2188
+ * @param processor - Async function that processes a batch of items
2189
+ * @param options - Configuration for batch size, retries, etc.
2190
+ * @returns Object with results, errors, and statistics
2191
+ *
2192
+ * @example
2193
+ * const docs = [...];
2194
+ * const result = await BatchProcessor.processBatch(
2195
+ * docs,
2196
+ * (batch) => vectorDB.batchUpsert(batch),
2197
+ * { batchSize: 100, maxRetries: 3 }
2198
+ * );
2199
+ */
2200
+ static async processBatch(items, processor, options) {
2201
+ const {
2202
+ batchSize = 100,
2203
+ maxRetries = 3,
2204
+ initialDelayMs = 100,
2205
+ maxDelayMs = 1e4,
2206
+ backoffMultiplier = 2,
2207
+ throwOnPartialFailure = false
2208
+ } = options != null ? options : {};
2209
+ const results = [];
2210
+ const errors = [];
2211
+ const batches = [];
2212
+ for (let i = 0; i < items.length; i += batchSize) {
2213
+ batches.push(items.slice(i, i + batchSize));
2214
+ }
2215
+ if (batches.length === 0) {
2216
+ return { results: [], errors: [], totalProcessed: 0, totalFailed: 0 };
2217
+ }
2218
+ for (let batchIndex = 0; batchIndex < batches.length; batchIndex++) {
2219
+ const batch = batches[batchIndex];
2220
+ let success = false;
2221
+ let lastError;
2222
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2223
+ try {
2224
+ const result = await processor(batch);
2225
+ results.push(result);
2226
+ success = true;
2227
+ break;
2228
+ } catch (err) {
2229
+ lastError = err instanceof Error ? err : new Error(String(err));
2230
+ if (!isTransientError(err) || attempt === maxRetries) {
2231
+ break;
2232
+ }
2233
+ const delay = calculateBackoffDelay(
2234
+ attempt,
2235
+ initialDelayMs,
2236
+ maxDelayMs,
2237
+ backoffMultiplier
2238
+ );
2239
+ await sleep(delay);
2240
+ }
2241
+ }
2242
+ if (!success && lastError) {
2243
+ errors.push({
2244
+ index: batchIndex,
2245
+ error: lastError,
2246
+ itemCount: batch.length
2247
+ });
2248
+ }
2249
+ }
2250
+ const totalProcessed = items.length - (errors.length > 0 ? errors.reduce((sum, e) => sum + (e.itemCount || 0), 0) : 0);
2251
+ const totalFailed = errors.reduce((sum, e) => sum + (e.itemCount || 0), 0);
2252
+ if (throwOnPartialFailure && errors.length > 0) {
2253
+ const errorMessages = errors.map((e) => `Batch ${e.index} (${e.itemCount} items): ${e.error.message}`).join("\n");
2254
+ throw new Error(
2255
+ `[BatchProcessor] Batch processing failed:
2256
+ ${errorMessages}`
2257
+ );
2258
+ }
2259
+ return { results, errors, totalProcessed, totalFailed };
2260
+ }
2261
+ /**
2262
+ * Processes items sequentially (one at a time) with retry logic.
2263
+ * Useful for operations that don't support batching or when granular
2264
+ * error tracking is needed.
2265
+ */
2266
+ static async processSequential(items, processor, options) {
2267
+ const {
2268
+ maxRetries = 3,
2269
+ initialDelayMs = 100,
2270
+ maxDelayMs = 1e4,
2271
+ backoffMultiplier = 2,
2272
+ throwOnPartialFailure = false
2273
+ } = options != null ? options : {};
2274
+ const results = [];
2275
+ const errors = [];
2276
+ for (let itemIndex = 0; itemIndex < items.length; itemIndex++) {
2277
+ const item = items[itemIndex];
2278
+ let success = false;
2279
+ let lastError;
2280
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
2281
+ try {
2282
+ const result = await processor(item);
2283
+ results.push(result);
2284
+ success = true;
2285
+ break;
2286
+ } catch (err) {
2287
+ lastError = err instanceof Error ? err : new Error(String(err));
2288
+ if (!isTransientError(err) || attempt === maxRetries) {
2289
+ break;
2290
+ }
2291
+ const delay = calculateBackoffDelay(
2292
+ attempt,
2293
+ initialDelayMs,
2294
+ maxDelayMs,
2295
+ backoffMultiplier
2296
+ );
2297
+ await sleep(delay);
2298
+ }
2299
+ }
2300
+ if (!success && lastError) {
2301
+ errors.push({ index: itemIndex, error: lastError, itemCount: 1 });
2302
+ }
2303
+ }
2304
+ const totalProcessed = items.length - errors.length;
2305
+ const totalFailed = errors.length;
2306
+ if (throwOnPartialFailure && errors.length > 0) {
2307
+ const errorMessages = errors.map((e) => `Item ${e.index}: ${e.error.message}`).join("\n");
2308
+ throw new Error(
2309
+ `[BatchProcessor] Sequential processing failed:
2310
+ ${errorMessages}`
2311
+ );
2312
+ }
2313
+ return { results, errors, totalProcessed, totalFailed };
2314
+ }
2315
+ /**
2316
+ * Maps over items with retry logic, returning results in order.
2317
+ * Like Array.map() but with async processing and automatic retries.
2318
+ */
2319
+ static async mapWithRetry(items, mapper, options) {
2320
+ const result = await this.processSequential(items, mapper, options);
2321
+ if (result.errors.length > 0) {
2322
+ console.warn(
2323
+ `[BatchProcessor] mapWithRetry: ${result.errors.length} items failed`
2324
+ );
2325
+ }
2326
+ const orderedResults = new Array(items.length);
2327
+ let resultIndex = 0;
2328
+ let errorIndex = 0;
2329
+ for (let i = 0; i < items.length; i++) {
2330
+ if (errorIndex < result.errors.length && result.errors[errorIndex].index === i) {
2331
+ orderedResults[i] = void 0;
2332
+ errorIndex++;
2333
+ } else {
2334
+ orderedResults[i] = result.results[resultIndex];
2335
+ resultIndex++;
2336
+ }
2337
+ }
2338
+ return orderedResults.filter((r) => r !== void 0);
2339
+ }
2340
+ /**
2341
+ * Parallel processor with concurrency limit.
2342
+ * Processes up to `concurrency` items at the same time.
2343
+ */
2344
+ static async processConcurrent(items, processor, concurrency = 5, options) {
2345
+ const { throwOnPartialFailure = false } = options != null ? options : {};
2346
+ const results = [];
2347
+ const errors = [];
2348
+ for (let i = 0; i < items.length; i += concurrency) {
2349
+ const chunk = items.slice(i, i + concurrency).map((item, idx) => ({
2350
+ item,
2351
+ originalIndex: i + idx
2352
+ }));
2353
+ const chunkPromises = chunk.map(async ({ item, originalIndex }) => {
2354
+ try {
2355
+ const result = await processor(item);
2356
+ return { success: true, result, index: originalIndex };
2357
+ } catch (err) {
2358
+ const error = err instanceof Error ? err : new Error(String(err));
2359
+ return { success: false, error, index: originalIndex };
2360
+ }
2361
+ });
2362
+ const chunkResults = await Promise.all(chunkPromises);
2363
+ for (const { success, result, error, index } of chunkResults) {
2364
+ if (success) {
2365
+ results.push(result);
2366
+ } else {
2367
+ errors.push({ index, error, itemCount: 1 });
2368
+ }
2369
+ }
2370
+ }
2371
+ const totalProcessed = items.length - errors.length;
2372
+ const totalFailed = errors.length;
2373
+ if (throwOnPartialFailure && errors.length > 0) {
2374
+ const errorMessages = errors.map((e) => `Item ${e.index}: ${e.error.message}`).join("\n");
2375
+ throw new Error(
2376
+ `[BatchProcessor] Concurrent processing failed:
2377
+ ${errorMessages}`
2378
+ );
2379
+ }
2380
+ return { results, errors, totalProcessed, totalFailed };
2381
+ }
2382
+ };
2383
+
1600
2384
  // src/core/Pipeline.ts
1601
2385
  var Pipeline = class {
1602
2386
  constructor(config) {
@@ -1621,24 +2405,64 @@ var Pipeline = class {
1621
2405
  await this.vectorDB.initialize();
1622
2406
  this.initialised = true;
1623
2407
  }
2408
+ /**
2409
+ * Ingest documents with automatic chunking, embedding, and batch processing.
2410
+ * Handles retries for transient failures.
2411
+ */
1624
2412
  async ingest(documents, namespace) {
1625
2413
  await this.initialize();
1626
2414
  const ns = namespace != null ? namespace : this.config.projectId;
1627
2415
  const results = [];
1628
2416
  for (const doc of documents) {
1629
- const chunks = this.chunker.chunk(doc.content, {
1630
- docId: doc.docId,
1631
- metadata: doc.metadata
1632
- });
1633
- const vectors = await this.embeddingProvider.batchEmbed(chunks.map((c) => c.content));
1634
- const upsertDocs = chunks.map((chunk, i) => ({
1635
- id: chunk.id,
1636
- vector: vectors[i],
1637
- content: chunk.content,
1638
- metadata: chunk.metadata
1639
- }));
1640
- await this.vectorDB.batchUpsert(upsertDocs, ns);
1641
- results.push({ docId: doc.docId, chunksIngested: chunks.length });
2417
+ try {
2418
+ const chunks = this.chunker.chunk(doc.content, {
2419
+ docId: doc.docId,
2420
+ metadata: doc.metadata
2421
+ });
2422
+ const batchOptions = {
2423
+ batchSize: 50,
2424
+ // Embedding batch size
2425
+ maxRetries: 3,
2426
+ initialDelayMs: 100
2427
+ };
2428
+ const vectors = await BatchProcessor.mapWithRetry(
2429
+ chunks.map((c) => c.content),
2430
+ (text) => this.embeddingProvider.embed(text),
2431
+ batchOptions
2432
+ );
2433
+ if (vectors.length !== chunks.length) {
2434
+ throw new Error(`Embedding failed: got ${vectors.length} vectors for ${chunks.length} chunks`);
2435
+ }
2436
+ const upsertDocs = chunks.map((chunk, i) => ({
2437
+ id: chunk.id,
2438
+ vector: vectors[i],
2439
+ content: chunk.content,
2440
+ metadata: chunk.metadata
2441
+ }));
2442
+ const upsertBatchOptions = {
2443
+ batchSize: 100,
2444
+ maxRetries: 3,
2445
+ initialDelayMs: 100
2446
+ };
2447
+ const upsertResult = await BatchProcessor.processBatch(
2448
+ upsertDocs,
2449
+ (batch) => this.vectorDB.batchUpsert(batch, ns),
2450
+ upsertBatchOptions
2451
+ );
2452
+ if (upsertResult.errors.length > 0) {
2453
+ console.warn(`[Pipeline] Ingestion: ${upsertResult.errors.length} batches failed for doc ${doc.docId}`);
2454
+ }
2455
+ results.push({
2456
+ docId: doc.docId,
2457
+ chunksIngested: upsertResult.totalProcessed
2458
+ });
2459
+ } catch (error) {
2460
+ console.error(`[Pipeline] Failed to ingest document ${doc.docId}:`, error);
2461
+ results.push({
2462
+ docId: doc.docId,
2463
+ chunksIngested: 0
2464
+ });
2465
+ }
1642
2466
  }
1643
2467
  return results;
1644
2468
  }
@@ -1648,22 +2472,486 @@ var Pipeline = class {
1648
2472
  const ns = namespace != null ? namespace : this.config.projectId;
1649
2473
  const topK = (_b = (_a = this.config.rag) == null ? void 0 : _a.topK) != null ? _b : 5;
1650
2474
  const scoreThreshold = (_d = (_c = this.config.rag) == null ? void 0 : _c.scoreThreshold) != null ? _d : 0;
1651
- const queryVector = await this.embeddingProvider.embed(question);
1652
- const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
1653
- const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
1654
- const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
2475
+ try {
2476
+ const queryVector = await this.embeddingProvider.embed(question);
2477
+ const rawMatches = await this.vectorDB.query(queryVector, topK, ns);
2478
+ const sources = rawMatches.filter((m) => m.score >= scoreThreshold);
2479
+ const context = sources.length ? sources.map((m, i) => `[Source ${i + 1}]
1655
2480
  ${m.content}`).join("\n\n---\n\n") : "No relevant context found.";
1656
- const messages = [...history, { role: "user", content: question }];
1657
- const reply = await this.llmProvider.chat(messages, context);
1658
- return { reply, sources };
2481
+ const messages = [...history, { role: "user", content: question }];
2482
+ const reply = await this.llmProvider.chat(messages, context);
2483
+ return { reply, sources };
2484
+ } catch (error) {
2485
+ throw new Error(`[Pipeline] Chat failed: ${error instanceof Error ? error.message : String(error)}`);
2486
+ }
1659
2487
  }
1660
- async health() {
1661
- await this.initialize();
1662
- const [vectorDB, llm] = await Promise.all([
1663
- this.vectorDB.ping().catch(() => false),
1664
- this.llmProvider.ping().catch(() => false)
2488
+ };
2489
+
2490
+ // src/core/ProviderHealthCheck.ts
2491
+ var ProviderHealthCheck = class {
2492
+ /**
2493
+ * Validates vector database configuration before initialization.
2494
+ * Performs connectivity checks and verifies required resources exist.
2495
+ */
2496
+ static async checkVectorProvider(config) {
2497
+ const timestamp = Date.now();
2498
+ const configErrors = ConfigValidator.validate({
2499
+ projectId: "health-check",
2500
+ vectorDb: config,
2501
+ llm: { provider: "openai", model: "gpt-4o" },
2502
+ // dummy
2503
+ embedding: { provider: "openai", model: "text-embedding-3-small" }
2504
+ });
2505
+ const vectorDbErrors = configErrors.filter((e) => e.field.startsWith("vectorDb"));
2506
+ if (vectorDbErrors.length > 0) {
2507
+ return {
2508
+ healthy: false,
2509
+ provider: config.provider,
2510
+ error: `Configuration validation failed: ${vectorDbErrors.map((e) => e.message).join("; ")}`,
2511
+ timestamp
2512
+ };
2513
+ }
2514
+ try {
2515
+ switch (config.provider) {
2516
+ case "pinecone":
2517
+ return await this.checkPinecone(config, timestamp);
2518
+ case "pgvector":
2519
+ case "postgresql":
2520
+ return await this.checkPostgres(config, timestamp);
2521
+ case "mongodb":
2522
+ return await this.checkMongoDB(config, timestamp);
2523
+ case "milvus":
2524
+ return await this.checkMilvus(config, timestamp);
2525
+ case "qdrant":
2526
+ return await this.checkQdrant(config, timestamp);
2527
+ case "chromadb":
2528
+ return await this.checkChromaDB(config, timestamp);
2529
+ case "redis":
2530
+ return await this.checkRedis(config, timestamp);
2531
+ case "weaviate":
2532
+ return await this.checkWeaviate(config, timestamp);
2533
+ case "rest":
2534
+ case "universal_rest":
2535
+ return await this.checkRestAPI(config, timestamp);
2536
+ default:
2537
+ return {
2538
+ healthy: false,
2539
+ provider: config.provider,
2540
+ error: `Unsupported provider: ${config.provider}`,
2541
+ timestamp
2542
+ };
2543
+ }
2544
+ } catch (error) {
2545
+ return {
2546
+ healthy: false,
2547
+ provider: config.provider,
2548
+ error: error instanceof Error ? error.message : String(error),
2549
+ timestamp
2550
+ };
2551
+ }
2552
+ }
2553
+ static async checkPinecone(config, timestamp) {
2554
+ var _a, _b;
2555
+ const opts = config.options;
2556
+ try {
2557
+ const { Pinecone: Pinecone2 } = await import("@pinecone-database/pinecone");
2558
+ const client = new Pinecone2({ apiKey: opts.apiKey });
2559
+ const indexes = await client.listIndexes();
2560
+ const indexNames = (_b = (_a = indexes.indexes) == null ? void 0 : _a.map((i) => i.name)) != null ? _b : [];
2561
+ if (!indexNames.includes(config.indexName)) {
2562
+ return {
2563
+ healthy: false,
2564
+ provider: "pinecone",
2565
+ error: `Index "${config.indexName}" not found. Available: ${indexNames.join(", ")}`,
2566
+ timestamp
2567
+ };
2568
+ }
2569
+ return {
2570
+ healthy: true,
2571
+ provider: "pinecone",
2572
+ capabilities: {
2573
+ indexes: indexNames.length,
2574
+ targetIndex: config.indexName
2575
+ },
2576
+ timestamp
2577
+ };
2578
+ } catch (error) {
2579
+ return {
2580
+ healthy: false,
2581
+ provider: "pinecone",
2582
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2583
+ timestamp
2584
+ };
2585
+ }
2586
+ }
2587
+ static async checkPostgres(config, timestamp) {
2588
+ const opts = config.options;
2589
+ try {
2590
+ const { Client } = await import("pg");
2591
+ const client = new Client({ connectionString: opts.connectionString });
2592
+ await client.connect();
2593
+ const result = await client.query(`
2594
+ SELECT EXISTS(
2595
+ SELECT 1 FROM pg_extension WHERE extname = 'vector'
2596
+ );
2597
+ `);
2598
+ const hasVector = result.rows[0].exists;
2599
+ await client.end();
2600
+ return {
2601
+ healthy: true,
2602
+ provider: "postgresql",
2603
+ capabilities: { pgvectorInstalled: hasVector },
2604
+ timestamp
2605
+ };
2606
+ } catch (error) {
2607
+ return {
2608
+ healthy: false,
2609
+ provider: "postgresql",
2610
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2611
+ timestamp
2612
+ };
2613
+ }
2614
+ }
2615
+ static async checkMongoDB(config, timestamp) {
2616
+ const opts = config.options;
2617
+ try {
2618
+ const { MongoClient: MongoClient2 } = await import("mongodb");
2619
+ const client = new MongoClient2(opts.uri);
2620
+ await client.connect();
2621
+ const db = client.db(opts.database);
2622
+ const collections = await db.listCollections().toArray();
2623
+ const collectionNames = collections.map((c) => c.name);
2624
+ const hasCollection = collectionNames.includes(opts.collection);
2625
+ await client.close();
2626
+ return {
2627
+ healthy: true,
2628
+ provider: "mongodb",
2629
+ capabilities: {
2630
+ collections: collectionNames.length,
2631
+ targetCollection: hasCollection ? opts.collection : "NOT FOUND"
2632
+ },
2633
+ timestamp
2634
+ };
2635
+ } catch (error) {
2636
+ return {
2637
+ healthy: false,
2638
+ provider: "mongodb",
2639
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2640
+ timestamp
2641
+ };
2642
+ }
2643
+ }
2644
+ static async checkMilvus(config, timestamp) {
2645
+ const opts = config.options;
2646
+ const host = opts.host || "localhost";
2647
+ const port = opts.port || 19530;
2648
+ try {
2649
+ const controller = new AbortController();
2650
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
2651
+ const response = await fetch(`http://${host}:${port}/healthz`, {
2652
+ signal: controller.signal
2653
+ });
2654
+ clearTimeout(timeoutId);
2655
+ if (!response.ok) {
2656
+ throw new Error(`Health check returned ${response.status}`);
2657
+ }
2658
+ return {
2659
+ healthy: true,
2660
+ provider: "milvus",
2661
+ capabilities: { endpoint: `${host}:${port}` },
2662
+ timestamp
2663
+ };
2664
+ } catch (error) {
2665
+ return {
2666
+ healthy: false,
2667
+ provider: "milvus",
2668
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2669
+ timestamp
2670
+ };
2671
+ }
2672
+ }
2673
+ static async checkQdrant(config, timestamp) {
2674
+ const opts = config.options;
2675
+ const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
2676
+ try {
2677
+ const apiKey = opts.apiKey;
2678
+ const response = await fetch(`${baseUrl}/`, {
2679
+ headers: apiKey ? { "api-key": apiKey } : {}
2680
+ });
2681
+ if (!response.ok) {
2682
+ throw new Error(`Health check returned ${response.status}`);
2683
+ }
2684
+ const health = await response.json();
2685
+ return {
2686
+ healthy: true,
2687
+ provider: "qdrant",
2688
+ capabilities: health,
2689
+ timestamp
2690
+ };
2691
+ } catch (error) {
2692
+ return {
2693
+ healthy: false,
2694
+ provider: "qdrant",
2695
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2696
+ timestamp
2697
+ };
2698
+ }
2699
+ }
2700
+ static async checkChromaDB(config, timestamp) {
2701
+ const opts = config.options;
2702
+ const host = opts.host || "localhost";
2703
+ const port = opts.port || 8e3;
2704
+ try {
2705
+ const response = await fetch(`http://${host}:${port}/api/v1/heartbeat`);
2706
+ if (!response.ok) {
2707
+ throw new Error(`Health check returned ${response.status}`);
2708
+ }
2709
+ return {
2710
+ healthy: true,
2711
+ provider: "chromadb",
2712
+ capabilities: { endpoint: `${host}:${port}` },
2713
+ timestamp
2714
+ };
2715
+ } catch (error) {
2716
+ return {
2717
+ healthy: false,
2718
+ provider: "chromadb",
2719
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2720
+ timestamp
2721
+ };
2722
+ }
2723
+ }
2724
+ static async checkRedis(config, timestamp) {
2725
+ const opts = config.options;
2726
+ const url = opts.url || `redis://${opts.host}:${opts.port}`;
2727
+ try {
2728
+ await fetch(url);
2729
+ return {
2730
+ healthy: true,
2731
+ provider: "redis",
2732
+ capabilities: { endpoint: url },
2733
+ timestamp
2734
+ };
2735
+ } catch (error) {
2736
+ return {
2737
+ healthy: false,
2738
+ provider: "redis",
2739
+ error: `Connection check failed: ${error instanceof Error ? error.message : String(error)}. Ensure Redis is running at ${url}`,
2740
+ timestamp
2741
+ };
2742
+ }
2743
+ }
2744
+ static async checkWeaviate(config, timestamp) {
2745
+ const opts = config.options;
2746
+ const url = (opts.url || "http://localhost:8080").replace(/\/$/, "");
2747
+ try {
2748
+ const response = await fetch(`${url}/.well-known/ready`);
2749
+ if (!response.ok) {
2750
+ throw new Error(`Health check returned ${response.status}`);
2751
+ }
2752
+ return {
2753
+ healthy: true,
2754
+ provider: "weaviate",
2755
+ capabilities: { endpoint: url },
2756
+ timestamp
2757
+ };
2758
+ } catch (error) {
2759
+ return {
2760
+ healthy: false,
2761
+ provider: "weaviate",
2762
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2763
+ timestamp
2764
+ };
2765
+ }
2766
+ }
2767
+ static async checkRestAPI(config, timestamp) {
2768
+ const opts = config.options;
2769
+ const baseUrl = (opts.baseUrl || "").replace(/\/$/, "");
2770
+ if (!baseUrl) {
2771
+ return {
2772
+ healthy: false,
2773
+ provider: "rest",
2774
+ error: "baseUrl is required",
2775
+ timestamp
2776
+ };
2777
+ }
2778
+ try {
2779
+ const response = await fetch(`${baseUrl}/health`, {
2780
+ headers: opts.headers ? JSON.parse(opts.headers) : {}
2781
+ });
2782
+ return {
2783
+ healthy: response.ok,
2784
+ provider: "rest",
2785
+ error: response.ok ? void 0 : `Health check returned ${response.status}`,
2786
+ timestamp
2787
+ };
2788
+ } catch (error) {
2789
+ return {
2790
+ healthy: false,
2791
+ provider: "rest",
2792
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2793
+ timestamp
2794
+ };
2795
+ }
2796
+ }
2797
+ /**
2798
+ * Validates LLM provider configuration.
2799
+ */
2800
+ static async checkLLMProvider(config) {
2801
+ const timestamp = Date.now();
2802
+ try {
2803
+ switch (config.provider) {
2804
+ case "openai":
2805
+ return await this.checkOpenAI(config, timestamp);
2806
+ case "anthropic":
2807
+ return await this.checkAnthropic(config, timestamp);
2808
+ case "ollama":
2809
+ return await this.checkOllama(config, timestamp);
2810
+ case "rest":
2811
+ case "universal_rest":
2812
+ return await this.checkLLMRestAPI(config, timestamp);
2813
+ default:
2814
+ return {
2815
+ healthy: false,
2816
+ provider: config.provider,
2817
+ error: `Unsupported provider: ${config.provider}`,
2818
+ timestamp
2819
+ };
2820
+ }
2821
+ } catch (error) {
2822
+ return {
2823
+ healthy: false,
2824
+ provider: config.provider,
2825
+ error: error instanceof Error ? error.message : String(error),
2826
+ timestamp
2827
+ };
2828
+ }
2829
+ }
2830
+ static async checkOpenAI(config, timestamp) {
2831
+ try {
2832
+ const OpenAI2 = await import("openai");
2833
+ const client = new OpenAI2.default({ apiKey: config.apiKey });
2834
+ const models = await client.models.list();
2835
+ const hasModel = models.data.some((m) => m.id === config.model);
2836
+ return {
2837
+ healthy: true,
2838
+ provider: "openai",
2839
+ capabilities: {
2840
+ model: config.model,
2841
+ available: hasModel,
2842
+ totalModels: models.data.length
2843
+ },
2844
+ timestamp
2845
+ };
2846
+ } catch (error) {
2847
+ return {
2848
+ healthy: false,
2849
+ provider: "openai",
2850
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2851
+ timestamp
2852
+ };
2853
+ }
2854
+ }
2855
+ static async checkAnthropic(config, timestamp) {
2856
+ try {
2857
+ const { default: Anthropic2 } = await import("@anthropic-ai/sdk");
2858
+ const client = new Anthropic2({ apiKey: config.apiKey });
2859
+ await client.messages.create({
2860
+ model: config.model,
2861
+ max_tokens: 10,
2862
+ messages: [{ role: "user", content: "ping" }]
2863
+ });
2864
+ return {
2865
+ healthy: true,
2866
+ provider: "anthropic",
2867
+ capabilities: { model: config.model },
2868
+ timestamp
2869
+ };
2870
+ } catch (error) {
2871
+ return {
2872
+ healthy: false,
2873
+ provider: "anthropic",
2874
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2875
+ timestamp
2876
+ };
2877
+ }
2878
+ }
2879
+ static async checkOllama(config, timestamp) {
2880
+ const baseUrl = (config.baseUrl || "http://localhost:11434").replace(/\/$/, "");
2881
+ try {
2882
+ const response = await fetch(`${baseUrl}/api/tags`);
2883
+ if (!response.ok) {
2884
+ throw new Error(`Health check returned ${response.status}`);
2885
+ }
2886
+ const data = await response.json();
2887
+ const models = data.models || [];
2888
+ const hasModel = models.some((m) => m.name === config.model);
2889
+ return {
2890
+ healthy: true,
2891
+ provider: "ollama",
2892
+ capabilities: {
2893
+ model: config.model,
2894
+ available: hasModel,
2895
+ totalModels: models.length
2896
+ },
2897
+ timestamp
2898
+ };
2899
+ } catch (error) {
2900
+ return {
2901
+ healthy: false,
2902
+ provider: "ollama",
2903
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2904
+ timestamp
2905
+ };
2906
+ }
2907
+ }
2908
+ static async checkLLMRestAPI(config, timestamp) {
2909
+ var _a, _b;
2910
+ const baseUrlRaw = config.baseUrl || ((_a = config.options) == null ? void 0 : _a.baseUrl);
2911
+ const baseUrl = (typeof baseUrlRaw === "string" ? baseUrlRaw : "").replace(/\/$/, "");
2912
+ if (!baseUrl) {
2913
+ return {
2914
+ healthy: false,
2915
+ provider: config.provider,
2916
+ error: "baseUrl is required",
2917
+ timestamp
2918
+ };
2919
+ }
2920
+ try {
2921
+ const headers = (_b = config.options) == null ? void 0 : _b.headers;
2922
+ const response = await fetch(`${baseUrl}/health`, {
2923
+ headers: headers || void 0
2924
+ });
2925
+ return {
2926
+ healthy: response.ok,
2927
+ provider: config.provider,
2928
+ error: response.ok ? void 0 : `Health check returned ${response.status}`,
2929
+ timestamp
2930
+ };
2931
+ } catch (error) {
2932
+ return {
2933
+ healthy: false,
2934
+ provider: config.provider,
2935
+ error: `Connection failed: ${error instanceof Error ? error.message : String(error)}`,
2936
+ timestamp
2937
+ };
2938
+ }
2939
+ }
2940
+ /**
2941
+ * Runs comprehensive health checks on all configured providers.
2942
+ */
2943
+ static async checkAll(vectorDbConfig, llmConfig, embeddingConfig) {
2944
+ const [vectorDb, llm, embedding] = await Promise.all([
2945
+ this.checkVectorProvider(vectorDbConfig),
2946
+ this.checkLLMProvider(llmConfig),
2947
+ embeddingConfig ? this.checkLLMProvider(embeddingConfig) : Promise.resolve(void 0)
1665
2948
  ]);
1666
- return { vectorDB, llm };
2949
+ return {
2950
+ vectorDb,
2951
+ llm,
2952
+ embedding,
2953
+ allHealthy: vectorDb.healthy && llm.healthy && (!embedding || embedding.healthy)
2954
+ };
1667
2955
  }
1668
2956
  };
1669
2957
 
@@ -1672,10 +2960,26 @@ var VectorPlugin = class {
1672
2960
  /**
1673
2961
  * Initializes the plugin with the host configuration.
1674
2962
  * @param hostConfig - Configuration object passed from the host application.
2963
+ * @throws Error if configuration is invalid or providers are unhealthy
2964
+ *
2965
+ * @example
2966
+ * const plugin = new VectorPlugin({
2967
+ * projectId: 'my-app',
2968
+ * vectorDb: {
2969
+ * provider: 'pinecone',
2970
+ * indexName: 'my-index',
2971
+ * options: { apiKey: process.env.PINECONE_API_KEY }
2972
+ * },
2973
+ * llm: {
2974
+ * provider: 'openai',
2975
+ * model: 'gpt-4o',
2976
+ * apiKey: process.env.OPENAI_API_KEY
2977
+ * }
2978
+ * });
1675
2979
  */
1676
2980
  constructor(hostConfig) {
1677
2981
  this.config = ConfigResolver.resolve(hostConfig);
1678
- ConfigResolver.validate(this.config);
2982
+ ConfigValidator.validateAndThrow(this.config);
1679
2983
  this.pipeline = new Pipeline(this.config);
1680
2984
  }
1681
2985
  /**
@@ -1684,6 +2988,19 @@ var VectorPlugin = class {
1684
2988
  getConfig() {
1685
2989
  return this.config;
1686
2990
  }
2991
+ /**
2992
+ * Perform pre-flight health checks on all configured providers.
2993
+ * Useful to verify connectivity before running operations.
2994
+ *
2995
+ * @returns Health status for vector DB, LLM, and embedding providers
2996
+ */
2997
+ async checkHealth() {
2998
+ return ProviderHealthCheck.checkAll(
2999
+ this.config.vectorDb,
3000
+ this.config.llm,
3001
+ this.config.embedding
3002
+ );
3003
+ }
1687
3004
  /**
1688
3005
  * Run a chat query.
1689
3006
  */
@@ -1696,12 +3013,6 @@ var VectorPlugin = class {
1696
3013
  async ingest(documents, namespace) {
1697
3014
  return this.pipeline.ingest(documents, namespace);
1698
3015
  }
1699
- /**
1700
- * Check the health of the connected providers.
1701
- */
1702
- async health() {
1703
- return this.pipeline.health();
1704
- }
1705
3016
  };
1706
3017
 
1707
3018
  // src/server.ts
@@ -1820,8 +3131,8 @@ function createHealthHandler(config) {
1820
3131
  const plugin = new VectorPlugin(config);
1821
3132
  return async function GET() {
1822
3133
  try {
1823
- const health = await plugin.health();
1824
- const status = health.vectorDB && health.llm ? "ok" : "degraded";
3134
+ const health = await plugin.checkHealth();
3135
+ const status = health.allHealthy ? "ok" : "degraded";
1825
3136
  return import_server.NextResponse.json(__spreadProps(__spreadValues({
1826
3137
  status
1827
3138
  }, health), {