feat(vector-stores): add S3 Vectors provider to TypeScript OSS SDK (#5822)
Co-authored-by: kartik-mem0 <kartik.labhshetwar@mem0.ai>
This commit is contained in:
@@ -9,15 +9,22 @@ description: "Use Amazon S3 Vectors as a cost-optimized vector storage service i
|
||||
|
||||
S3 Vectors support requires additional dependencies. Install them with:
|
||||
|
||||
```bash
|
||||
<CodeGroup>
|
||||
```bash Python
|
||||
pip install boto3
|
||||
```
|
||||
|
||||
```bash TypeScript
|
||||
npm install @aws-sdk/client-s3vectors
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Usage
|
||||
|
||||
To use Amazon S3 Vectors with Mem0, you need to have an AWS account and the necessary IAM permissions (`s3vectors:*`). Ensure your environment is configured with AWS credentials (e.g., via `~/.aws/credentials` or environment variables).
|
||||
|
||||
```python
|
||||
<CodeGroup>
|
||||
```python Python
|
||||
import os
|
||||
from mem0 import Memory
|
||||
|
||||
@@ -47,6 +54,36 @@ messages = [
|
||||
m.add(messages, user_id="alice", metadata={"category": "movies"})
|
||||
```
|
||||
|
||||
```typescript TypeScript
|
||||
import { Memory } from 'mem0ai/oss';
|
||||
|
||||
// Ensure your AWS credentials are configured in your environment
|
||||
// e.g., by setting AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, and AWS_DEFAULT_REGION
|
||||
|
||||
const config = {
|
||||
vectorStore: {
|
||||
provider: 's3_vectors',
|
||||
config: {
|
||||
vectorBucketName: 'my-mem0-vector-bucket',
|
||||
collectionName: 'my-memories-index',
|
||||
embeddingModelDims: 1536,
|
||||
distanceMetric: 'cosine',
|
||||
region: 'us-east-1',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const memory = new Memory(config);
|
||||
const messages = [
|
||||
{"role": "user", "content": "I'm planning to watch a movie tonight. Any recommendations?"},
|
||||
{"role": "assistant", "content": "How about a thriller movie? They can be quite engaging."},
|
||||
{"role": "user", "content": "I'm not a big fan of thriller movies but I love sci-fi movies."},
|
||||
{"role": "assistant", "content": "Got it! I'll avoid thriller recommendations and suggest sci-fi movies in the future."}
|
||||
]
|
||||
await memory.add(messages, { userId: "alice", metadata: { category: "movies" } });
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Config
|
||||
|
||||
Here are the parameters available for configuring Amazon S3 Vectors:
|
||||
|
||||
@@ -10,7 +10,7 @@ Mem0 includes built-in support for various popular databases. Memory can utilize
|
||||
See the list of supported vector databases below.
|
||||
|
||||
<Note>
|
||||
The following vector databases are supported in the Python implementation. The TypeScript implementation currently supports Qdrant, Redis, PGVector, Supabase, LangChain, Azure AI Search, Vectorize, and an in-memory store.
|
||||
The following vector databases are supported in the Python implementation. The TypeScript implementation currently supports Qdrant, Redis, PGVector, Supabase, LangChain, Azure AI Search, Vectorize, Amazon S3 Vectors, and an in-memory store.
|
||||
</Note>
|
||||
|
||||
<CardGroup cols={3}>
|
||||
|
||||
@@ -107,6 +107,7 @@
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@anthropic-ai/sdk": "^0.40.1",
|
||||
"@aws-sdk/client-s3vectors": "3.967.0",
|
||||
"@azure/identity": "^4.0.0",
|
||||
"@azure/search-documents": "^12.0.0",
|
||||
"@cloudflare/workers-types": "^4.20250504.0",
|
||||
|
||||
Generated
+832
@@ -32,6 +32,9 @@ importers:
|
||||
'@anthropic-ai/sdk':
|
||||
specifier: ^0.40.1
|
||||
version: 0.40.1
|
||||
'@aws-sdk/client-s3vectors':
|
||||
specifier: 3.967.0
|
||||
version: 3.967.0
|
||||
'@azure/identity':
|
||||
specifier: ^4.0.0
|
||||
version: 4.13.1
|
||||
@@ -144,6 +147,131 @@ packages:
|
||||
'@anthropic-ai/sdk@0.40.1':
|
||||
resolution: {integrity: sha512-DJMWm8lTEM9Lk/MSFL+V+ugF7jKOn0M2Ujvb5fN8r2nY14aHbGPZ1k6sgjL+tpJ3VuOGJNG+4R83jEpOuYPv8w==}
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
resolution: {integrity: sha512-nLbCWqQNgUiwwtFsen1AdzAtvuLRsQS8rYgMuxCrdKf9kOssamGLuPwyTY9wyYblNr9+1XM8v6zoDTPPSIeANg==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
resolution: {integrity: sha512-AXfN/lGotSQwu6HNcEsIASo7kWXZ5HYWvfOmSNKDsEqC4OashTp8alTmaz+F7TC2L083SFv5RdB+qU3Vs1kZqw==}
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
resolution: {integrity: sha512-FFQQyu7edu4ufvIZ+OadFpHHOt+eSTBaYaki44c+akjg7qZg9oOQeLlk77F6tSYqjDAFClrHJk9tMf0HdVyOvA==}
|
||||
engines: {node: '>=16.0.0'}
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
resolution: {integrity: sha512-iAvUotm021kM33eCdNfwIN//F77/IADDSs58i+MDaOqFrVjZo9bAal0NK7HurRuWLLpF1iLX7gbWrjHjeo+YFg==}
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==}
|
||||
|
||||
'@aws-sdk/client-s3vectors@3.967.0':
|
||||
resolution: {integrity: sha512-DDmq8Jm/4IwliVEYVUbTub0RhcYJXVexRklhsIOdIWM42zf4ZMWPeeNvHJBSI+DMNbpqRN57TLCfH5tgiPdyVQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/client-sso@3.967.0':
|
||||
resolution: {integrity: sha512-7RgUwHcRMJtWme6kCHGUVT+Rn9GmNH+FHm34N9UgMXzUqQlzFMweE7T5E9O8nv3wIp7xFNB20ADaCw9Xdnox1Q==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/core@3.967.0':
|
||||
resolution: {integrity: sha512-sJmuP7GrVmlbO6DpXkuf9Mbn6jGNNvy6PLawvaxVF150c8bpNk3w39rerRls6q1dot1dBFV2D29hBXMY1agNMg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.967.0':
|
||||
resolution: {integrity: sha512-+XWw0+f/txeMbEVRtTFZhgSw1ymH1ffaVKkdMBSnw48rfSohJElKmitCqdihagRTZpzh7m8qI6tIQ5t3OUqugw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.967.0':
|
||||
resolution: {integrity: sha512-0/GIAEv5pY5htg6IBMuYccBgzz3oS2DqHjHi396ziTrwlhbrCNX96AbNhQhzAx3LBZUk13sPfeapjyQ7G57Ekg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.967.0':
|
||||
resolution: {integrity: sha512-U8dMpaM6Qf6+2Qvp1uG6OcWv1RlrZW7tQkpmzEVWH8HZTGrVHIXXju64NMtIOr7yOnNwd0CKcytuD1QG+phCwQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.967.0':
|
||||
resolution: {integrity: sha512-kbvZsZL6CBlfnb71zuJdJmBUFZN5utNrcziZr/DZ2olEOkA9vlmizE8i9BUIbmS7ptjgvRnmcY1A966yfhiblw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.967.0':
|
||||
resolution: {integrity: sha512-WuNbHs9rfKKSVok4+OBrZf0AHfzDgFYYMxN2G/q6ZfUmY4QmiPyxV5HkNFh1rqDxS9VV6kAZPo0EBmry10idSg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.967.0':
|
||||
resolution: {integrity: sha512-sNCY5JDV0whsfsZ6c2+6eUwH33H7UhKbqvCPbEYlIIa8wkGjCtCyFI3zZIJHVcMKJJ3117vSUFHEkNA7g+8rtw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.967.0':
|
||||
resolution: {integrity: sha512-0K6kITKNytFjk1UYabYUsTThgU6TQkyW6Wmt8S5zd1A/up7NSQGpp58Rpg9GIf4amQDQwb+p9FGG7emmV8FEeA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.967.0':
|
||||
resolution: {integrity: sha512-Vkr7S2ec7q/v8i/MzkHcBEdqqfWz3lyb8FDjb+NjslEwdxC3f6XwADRZzWwV1pChfx6SbsvJXKfkcF/pKAelhA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-host-header@3.965.0':
|
||||
resolution: {integrity: sha512-SfpSYqoPOAmdb3DBsnNsZ0vix+1VAtkUkzXM79JL3R5IfacpyKE2zytOgVAQx/FjhhlpSTwuXd+LRhUEVb3MaA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-logger@3.965.0':
|
||||
resolution: {integrity: sha512-gjUvJRZT1bUABKewnvkj51LAynFrfz2h5DYAg5/2F4Utx6UOGByTSr9Rq8JCLbURvvzAbCtcMkkIJRxw+8Zuzw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-recursion-detection@3.965.0':
|
||||
resolution: {integrity: sha512-6dvD+18Ni14KCRu+tfEoNxq1sIGVp9tvoZDZ7aMvpnA7mDXuRLrOjRQ/TAZqXwr9ENKVGyxcPl0cRK8jk1YWjA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/middleware-user-agent@3.967.0':
|
||||
resolution: {integrity: sha512-2qzJzZj5u+cZiG7kz3XJPaTH4ssUY/aet1kwJsUTFKrWeHUf7mZZkDFfkXP5cOffgiOyR5ZkrmJoLKAde9hshg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/nested-clients@3.967.0':
|
||||
resolution: {integrity: sha512-PYa7V8w0gaNux6Sz/Z7zrHmPloEE+EKpRxQIOG/D0askTr5Yd4oO2KGgcInf65uHK3f0Z9U4CTUGHZvQvABypA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/region-config-resolver@3.965.0':
|
||||
resolution: {integrity: sha512-RoMhu9ly2B0coxn8ctXosPP2WmDD0MkQlZGLjoYHQUOCBmty5qmCxOqBmBDa6wbWbB8xKtMQ/4VXloQOgzjHXg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/token-providers@3.967.0':
|
||||
resolution: {integrity: sha512-Qnd/nJ0CgeUa7zQczgmdQm0vYUF7pD1G0C+dR1T7huHQHRIsgCWIsCV9wNKzOFluqtcr6YAeuTwvY0+l8XWxnA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/types@3.965.0':
|
||||
resolution: {integrity: sha512-jvodoJdMavvg8faN7co58vVJRO5MVep4JFPRzUNCzpJ98BDqWDk/ad045aMJcmxkLzYLS2UAnUmqjJ/tUPNlzQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/types@3.973.13':
|
||||
resolution: {integrity: sha512-pEHZqRkAlHfnfAU9tK+WpKv/gBNjGJrHMgA3A0iYRGyswBS2t0pfez+lWlwktb3Bqa0ovh7w/QJTFwp3fDxLNg==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-endpoints@3.965.0':
|
||||
resolution: {integrity: sha512-WqSCB0XIsGUwZWvrYkuoofi2vzoVHqyeJ2kN+WyoOsxPLTiQSBIoqm/01R/qJvoxwK/gOOF7su9i84Vw2NQQpQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.8':
|
||||
resolution: {integrity: sha512-uUbMs1cBZPafD0ohUj6EwNf0fPZ534NvBxHox4hjX+0Rxq5paSYUem7+hi833pYrzrcnBATKIYpR02MDXT5M9g==}
|
||||
engines: {node: '>=20.0.0'}
|
||||
|
||||
'@aws-sdk/util-user-agent-browser@3.965.0':
|
||||
resolution: {integrity: sha512-Xiza/zMntQGpkd2dETQeAK8So1pg5+STTzpcdGWxj5q0jGO5ayjqT/q1Q7BrsX5KIr6PvRkl9/V7lLCv04wGjQ==}
|
||||
|
||||
'@aws-sdk/util-user-agent-node@3.967.0':
|
||||
resolution: {integrity: sha512-yUz6pCGxyG4+QaDg0dkdIBphjQp8A9rrbZa/+U3RJgRrW47hy64clFQUROzj5Poy1Ur8ICVXEUpBsSqRuYEU2g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
aws-crt: '>=1.0.0'
|
||||
peerDependenciesMeta:
|
||||
aws-crt:
|
||||
optional: true
|
||||
|
||||
'@aws-sdk/xml-builder@3.965.0':
|
||||
resolution: {integrity: sha512-Tcod25/BTupraQwtb+Q+GX8bmEZfxIFjjJ/AvkhUZsZlkPeVluzq1uu3Oeqf145DCdMjzLIN6vab5MrykbDP+g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4':
|
||||
resolution: {integrity: sha512-iY8yvjE0y651BixKNPgmv1WrQc+GZ142sb0z4gYnChDDY2YqI4P/jsSopBWrKfAt7LOJAkOXt7rC/hms+WclQQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@azure/abort-controller@2.1.2':
|
||||
resolution: {integrity: sha512-nBrLsEWm4J2u5LpAPjxADTlq3trDgVZZXHNKabeXZtpq3d3AbN/KGO82R87rdDz5/lYB024rtEf10/q0urNgsA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -905,6 +1033,138 @@ packages:
|
||||
'@sinonjs/fake-timers@10.3.0':
|
||||
resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==}
|
||||
|
||||
'@smithy/config-resolver@4.6.2':
|
||||
resolution: {integrity: sha512-kUY1m60FASy0ijf+0vEuZ7v+WqFlCAyyhvLkO/A15wY0hK2BLEN9Wszf/Uc3PDJWlqnrwRf+lNXOmzyds3Eflw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/core@3.26.0':
|
||||
resolution: {integrity: sha512-mLUktFAn+Pa2agl1J7VgtYNFWCX8/b4GMJSK1hCu4YCvtBfM6F8Os3EP4ry+DFFlXOf3wyvlgXhuUdFoy52D3g==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/credential-provider-imds@4.4.2':
|
||||
resolution: {integrity: sha512-18UMDMyrAbDcpmL1gLUA7ww0fRTcdCrSjSJOi2Sbld+tVjwD/pW+OAwjlScFLR7vvBnhZrIPQ7kVuTf1mnJLug==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/fetch-http-handler@5.5.2':
|
||||
resolution: {integrity: sha512-Ei/UK/QMhq0rKaMqGPlOAkE2yS9DZeYmZdk1RAKc3vp3zxgleZHZyBLlZv8yLsxljX4svCRuMTD6u3LLIcU4Bg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/hash-node@4.4.2':
|
||||
resolution: {integrity: sha512-M/8PUgwzekn0GcMBjcrTXV4PGFIe1AKuMuHjkHNBfofCPQ9+9Nr35lCvRfQK9BwYHQM+LKiKj8GY0pXSwYm6qQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/invalid-dependency@4.4.2':
|
||||
resolution: {integrity: sha512-GjmewKjtK4GQlWmKTIXABHTCW6z14kebGnW1P3CTvP9WWp0nmydkb4yhjkuIWlpXkAJfcAO3cEfRLeSATaEKSQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/middleware-content-length@4.4.2':
|
||||
resolution: {integrity: sha512-yaZdk8sbKxDell/mN1OPwEwnZ0m7sTGkUK6OKlvWyCvPykAqojHEMjfLZlHdaMhIaJ2E4Jr2qMzLQHn9gp02DA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/middleware-endpoint@4.6.2':
|
||||
resolution: {integrity: sha512-uCYuVrw65beEpzQszGK7uiDK3Gk0HlzsZuN3JNjDM05/dwwEheg7gF4DKYWH8dpXK7c634GUfkH/vG5XuR9z2w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/middleware-retry@4.7.2':
|
||||
resolution: {integrity: sha512-QldB+7GcqKGVyjxTq5tpuHRFQKQJRnIETx8tdpEwYu5fHZR7z5+SM5eGd/TZqkUkV/6vUHgiiDVmmqjsobzPLg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/middleware-serde@4.4.2':
|
||||
resolution: {integrity: sha512-Nx6wuSMasSazyIC02cmmSF+jEGnBpVL6u/Ra9J+AoKQyX1Uwxqf96fSoD6G+D1SKqw51GVNC1V08GtJPe8Yhaw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/middleware-stack@4.4.2':
|
||||
resolution: {integrity: sha512-yWvlZxqgmk0ZiRd0QVzSAqvdkREeiw2boqT+DTbyi03ylx3LyYGs5hNvRjks5ay0cDotZ7H0AdG2tJDMy+IxgA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/node-config-provider@4.5.2':
|
||||
resolution: {integrity: sha512-kGnGhU7v/VSlKjcUHUNhMQ1eDiGKjDF6OlGlOWcilgsyR6Yyq4sDbEV6vs1RD8+xqj1OPpCYt817KAM44ymweQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/node-http-handler@4.8.2':
|
||||
resolution: {integrity: sha512-wfl1uwrAqMH9/pi4kqBo5LBcFwrJLxuDLqL7p7qNcJIFcyZDUc6pzhYk4CYv+DP7fIUpQCZumwNnkhPKS52osQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/property-provider@4.4.2':
|
||||
resolution: {integrity: sha512-V74/UQ+fPPIqZHzeXIUd0H+k/MF+WpeO3I+5wrMCMgsllLt6uqSNbWnLhJs6pMeXZGu6ry+g1bpIdcHNeL+Oww==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/protocol-http@5.5.2':
|
||||
resolution: {integrity: sha512-ZCQGk/UNS+YF5D+Jmhr8+PQX2PJKe4QzR6zuOR5sUinxO/ZiKNA7nyrbeO/Ds2hXLts3fZAtgLPKLJgvjCX9uQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/shared-ini-file-loader@4.6.2':
|
||||
resolution: {integrity: sha512-9Xti1yhj7Em9t4ZQ1E8YRA6wbhAqx1nfLu0XSAqv82HED43TMUvn3nOO2xBISxZc0hcOg7wvuHEypUy4L4XF2w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/signature-v4@5.5.2':
|
||||
resolution: {integrity: sha512-7xHpmPY4rt0IOmeAA8EfjgEH8isT+587TCdy9H6a7d4OMi5CQ0oEHhWllunvPu4j4Cq0vTFwdxXN/kABWPjdyA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/smithy-client@4.14.2':
|
||||
resolution: {integrity: sha512-H3fp0F/IhG8a6qnoWHeGTH1qDPRR9VVZjcxM9NMXPB6nqzaqcoYIX8D4FFI1Ap2siGr+yvYgKqHC76+afBgF1A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/types@4.15.0':
|
||||
resolution: {integrity: sha512-Z5TAOxygoFvybJV3igo5SloFflSokHx2hu1eFA+DxDTcn+FtKxUSui+rbTRG1pAafMA888Z3MVvCWUuvCrTXjg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/url-parser@4.4.2':
|
||||
resolution: {integrity: sha512-vx7FUYJMRfQqdqPRLYwpisOuYU6e1PaabcPUTEc7xG6X0VEr4B6PnjZloHz67qva0pQ9/eNs3xPxvQ6w5gRGAw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-base64@4.5.2':
|
||||
resolution: {integrity: sha512-OQKnJft8J4KPJyL4K3w3XpOvstpZUbPdcs840dH+JTp2w+v8Lce8nxzUoeOiAIRIhnWFQd7FxAjkZ0OkpX6jzw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-body-length-browser@4.4.2':
|
||||
resolution: {integrity: sha512-PTdWf5ApVEJQvnCaAJJPFHkJcUUY5yImYEcfzPqFP8ytet2oI0P/2nCF2t0600MkAH/MC9ne8SvgcRKh8Ooqhw==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-body-length-node@4.4.2':
|
||||
resolution: {integrity: sha512-KaT3bRb0khPrjXsXzyAi47F5aKOqBLypDhhvVC7p65AoZCPPAQ4j8KB1oaK+TKf6k5XFgkAY4gMK1EFr7xh5Wg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/util-defaults-mode-browser@4.5.2':
|
||||
resolution: {integrity: sha512-VexZx4D0vhOeXNLpmxgXK0s9CNV/jCSfjBDWS7YNjZ47K87rTzmgovr8s0CgOLASIkCAS4aAbTqIG4YzU6zduA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-defaults-mode-node@4.4.2':
|
||||
resolution: {integrity: sha512-nhmz4D2s2FGFTgWf8M2bQHCLKjJbCQBS91waDofexbptBESTExtpEgiaI0rWIP4MhAarCCdFapHdvlCoknBE6w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-endpoints@3.6.2':
|
||||
resolution: {integrity: sha512-K50/sbl8HRnBJt5+UYvIG8HCdEDNA9rhCmkWTDftzGMlJ4XlPgtqI1V6jTSi9tmK7MLfgw7CfGvzSKFSWOteOQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-middleware@4.4.2':
|
||||
resolution: {integrity: sha512-Xk039bCOxEZfasaHSHPj0QS8EY2npd0z3nxLkDvczg9ueF7eboiKw0HEa5pILLwjb+sjSlgzSjoCOVAM715TzA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-retry@4.5.2':
|
||||
resolution: {integrity: sha512-iinqHq7WnWWKQUCObyu4YBzXROJO8dHMQrM+pvut92rKgjhcLNHel8QVJNsIswjPLOEJffq6sYjOdq/ndOrIAA==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-stream@4.7.2':
|
||||
resolution: {integrity: sha512-/JZLMlF/pzY7QIA5M70bKSq5Bz/JpGLHiLlttvrcZpfLyGSCR3BvIebbipmCvSsnLXnU2pGDsDgD513N1/GGVg==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@smithy/util-utf8@4.4.2':
|
||||
resolution: {integrity: sha512-N5CpfaL+/LPQU9PFdOT55ayUo5T0QypG4Almzd1/efJvoDypuT1shkgJk1+hhg/02scYluW6Q2JGnSHIPwCEGQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@standard-schema/spec@1.1.0':
|
||||
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
|
||||
|
||||
@@ -1082,6 +1342,9 @@ packages:
|
||||
resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
anynum@1.0.1:
|
||||
resolution: {integrity: sha512-N6//FLET/tXYNM/F6ABca1oH6fWB+KlTt909Le28WMDBk8oaT4vY17DCrwg2MvmuqUKt3Ni4N5dGJ/EoBgcO6A==}
|
||||
|
||||
apparatus@0.0.10:
|
||||
resolution: {integrity: sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==}
|
||||
engines: {node: '>=0.2.6'}
|
||||
@@ -1158,6 +1421,9 @@ packages:
|
||||
bl@4.1.0:
|
||||
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
|
||||
|
||||
bowser@2.14.1:
|
||||
resolution: {integrity: sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==}
|
||||
|
||||
brace-expansion@1.1.15:
|
||||
resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==}
|
||||
|
||||
@@ -1503,6 +1769,10 @@ packages:
|
||||
fast-json-stable-stringify@2.1.0:
|
||||
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
|
||||
|
||||
fast-xml-parser@5.2.5:
|
||||
resolution: {integrity: sha512-pfX9uG9Ki0yekDHx2SiuRIyFdyAr1kMIMitPvb0YBo8SUfKvia7w7FIyd/l6av85pFYRhZscS75MwMnbvY+hcQ==}
|
||||
hasBin: true
|
||||
|
||||
fastq@1.20.1:
|
||||
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
|
||||
|
||||
@@ -2741,6 +3011,9 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
strnum@2.4.1:
|
||||
resolution: {integrity: sha512-M9eUSMT2dCB2cTNPG7UYj6KuK7RJR2SN2+yCV/fTW3xzTCS6EaGZ5pSMgDIjB7r8zSfTGk+dvvn9rTjpVS9Mwg==}
|
||||
|
||||
sucrase@3.35.1:
|
||||
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
@@ -3080,6 +3353,384 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
|
||||
'@aws-crypto/crc32@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.13
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-browser@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-crypto/supports-web-crypto': 5.2.0
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.13
|
||||
'@aws-sdk/util-locate-window': 3.965.8
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/sha256-js@5.2.0':
|
||||
dependencies:
|
||||
'@aws-crypto/util': 5.2.0
|
||||
'@aws-sdk/types': 3.973.13
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/supports-web-crypto@5.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-crypto/util@5.2.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.973.13
|
||||
'@smithy/util-utf8': 2.3.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/client-s3vectors@3.967.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/credential-provider-node': 3.967.0
|
||||
'@aws-sdk/middleware-host-header': 3.965.0
|
||||
'@aws-sdk/middleware-logger': 3.965.0
|
||||
'@aws-sdk/middleware-recursion-detection': 3.965.0
|
||||
'@aws-sdk/middleware-user-agent': 3.967.0
|
||||
'@aws-sdk/region-config-resolver': 3.965.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws-sdk/util-endpoints': 3.965.0
|
||||
'@aws-sdk/util-user-agent-browser': 3.965.0
|
||||
'@aws-sdk/util-user-agent-node': 3.967.0
|
||||
'@smithy/config-resolver': 4.6.2
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/fetch-http-handler': 5.5.2
|
||||
'@smithy/hash-node': 4.4.2
|
||||
'@smithy/invalid-dependency': 4.4.2
|
||||
'@smithy/middleware-content-length': 4.4.2
|
||||
'@smithy/middleware-endpoint': 4.6.2
|
||||
'@smithy/middleware-retry': 4.7.2
|
||||
'@smithy/middleware-serde': 4.4.2
|
||||
'@smithy/middleware-stack': 4.4.2
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/node-http-handler': 4.8.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/smithy-client': 4.14.2
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/url-parser': 4.4.2
|
||||
'@smithy/util-base64': 4.5.2
|
||||
'@smithy/util-body-length-browser': 4.4.2
|
||||
'@smithy/util-body-length-node': 4.4.2
|
||||
'@smithy/util-defaults-mode-browser': 4.5.2
|
||||
'@smithy/util-defaults-mode-node': 4.4.2
|
||||
'@smithy/util-endpoints': 3.6.2
|
||||
'@smithy/util-middleware': 4.4.2
|
||||
'@smithy/util-retry': 4.5.2
|
||||
'@smithy/util-utf8': 4.4.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/client-sso@3.967.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/middleware-host-header': 3.965.0
|
||||
'@aws-sdk/middleware-logger': 3.965.0
|
||||
'@aws-sdk/middleware-recursion-detection': 3.965.0
|
||||
'@aws-sdk/middleware-user-agent': 3.967.0
|
||||
'@aws-sdk/region-config-resolver': 3.965.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws-sdk/util-endpoints': 3.965.0
|
||||
'@aws-sdk/util-user-agent-browser': 3.965.0
|
||||
'@aws-sdk/util-user-agent-node': 3.967.0
|
||||
'@smithy/config-resolver': 4.6.2
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/fetch-http-handler': 5.5.2
|
||||
'@smithy/hash-node': 4.4.2
|
||||
'@smithy/invalid-dependency': 4.4.2
|
||||
'@smithy/middleware-content-length': 4.4.2
|
||||
'@smithy/middleware-endpoint': 4.6.2
|
||||
'@smithy/middleware-retry': 4.7.2
|
||||
'@smithy/middleware-serde': 4.4.2
|
||||
'@smithy/middleware-stack': 4.4.2
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/node-http-handler': 4.8.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/smithy-client': 4.14.2
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/url-parser': 4.4.2
|
||||
'@smithy/util-base64': 4.5.2
|
||||
'@smithy/util-body-length-browser': 4.4.2
|
||||
'@smithy/util-body-length-node': 4.4.2
|
||||
'@smithy/util-defaults-mode-browser': 4.5.2
|
||||
'@smithy/util-defaults-mode-node': 4.4.2
|
||||
'@smithy/util-endpoints': 3.6.2
|
||||
'@smithy/util-middleware': 4.4.2
|
||||
'@smithy/util-retry': 4.5.2
|
||||
'@smithy/util-utf8': 4.4.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/core@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws-sdk/xml-builder': 3.965.0
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/signature-v4': 5.5.2
|
||||
'@smithy/smithy-client': 4.14.2
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/util-base64': 4.5.2
|
||||
'@smithy/util-middleware': 4.4.2
|
||||
'@smithy/util-utf8': 4.4.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-env@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-http@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/fetch-http-handler': 5.5.2
|
||||
'@smithy/node-http-handler': 4.8.2
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/smithy-client': 4.14.2
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/util-stream': 4.7.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-ini@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/credential-provider-env': 3.967.0
|
||||
'@aws-sdk/credential-provider-http': 3.967.0
|
||||
'@aws-sdk/credential-provider-login': 3.967.0
|
||||
'@aws-sdk/credential-provider-process': 3.967.0
|
||||
'@aws-sdk/credential-provider-sso': 3.967.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.967.0
|
||||
'@aws-sdk/nested-clients': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/credential-provider-imds': 4.4.2
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-login@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/nested-clients': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-node@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/credential-provider-env': 3.967.0
|
||||
'@aws-sdk/credential-provider-http': 3.967.0
|
||||
'@aws-sdk/credential-provider-ini': 3.967.0
|
||||
'@aws-sdk/credential-provider-process': 3.967.0
|
||||
'@aws-sdk/credential-provider-sso': 3.967.0
|
||||
'@aws-sdk/credential-provider-web-identity': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/credential-provider-imds': 4.4.2
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-process@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/credential-provider-sso@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/client-sso': 3.967.0
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/token-providers': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/credential-provider-web-identity@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/nested-clients': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/middleware-host-header@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-logger@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-recursion-detection@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws/lambda-invoke-store': 0.2.4
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/middleware-user-agent@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws-sdk/util-endpoints': 3.965.0
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/nested-clients@3.967.0':
|
||||
dependencies:
|
||||
'@aws-crypto/sha256-browser': 5.2.0
|
||||
'@aws-crypto/sha256-js': 5.2.0
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/middleware-host-header': 3.965.0
|
||||
'@aws-sdk/middleware-logger': 3.965.0
|
||||
'@aws-sdk/middleware-recursion-detection': 3.965.0
|
||||
'@aws-sdk/middleware-user-agent': 3.967.0
|
||||
'@aws-sdk/region-config-resolver': 3.965.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@aws-sdk/util-endpoints': 3.965.0
|
||||
'@aws-sdk/util-user-agent-browser': 3.965.0
|
||||
'@aws-sdk/util-user-agent-node': 3.967.0
|
||||
'@smithy/config-resolver': 4.6.2
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/fetch-http-handler': 5.5.2
|
||||
'@smithy/hash-node': 4.4.2
|
||||
'@smithy/invalid-dependency': 4.4.2
|
||||
'@smithy/middleware-content-length': 4.4.2
|
||||
'@smithy/middleware-endpoint': 4.6.2
|
||||
'@smithy/middleware-retry': 4.7.2
|
||||
'@smithy/middleware-serde': 4.4.2
|
||||
'@smithy/middleware-stack': 4.4.2
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/node-http-handler': 4.8.2
|
||||
'@smithy/protocol-http': 5.5.2
|
||||
'@smithy/smithy-client': 4.14.2
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/url-parser': 4.4.2
|
||||
'@smithy/util-base64': 4.5.2
|
||||
'@smithy/util-body-length-browser': 4.4.2
|
||||
'@smithy/util-body-length-node': 4.4.2
|
||||
'@smithy/util-defaults-mode-browser': 4.5.2
|
||||
'@smithy/util-defaults-mode-node': 4.4.2
|
||||
'@smithy/util-endpoints': 3.6.2
|
||||
'@smithy/util-middleware': 4.4.2
|
||||
'@smithy/util-retry': 4.5.2
|
||||
'@smithy/util-utf8': 4.4.2
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/region-config-resolver@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/config-resolver': 4.6.2
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/token-providers@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/core': 3.967.0
|
||||
'@aws-sdk/nested-clients': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/property-provider': 4.4.2
|
||||
'@smithy/shared-ini-file-loader': 4.6.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
transitivePeerDependencies:
|
||||
- aws-crt
|
||||
|
||||
'@aws-sdk/types@3.965.0':
|
||||
dependencies:
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/types@3.973.13':
|
||||
dependencies:
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-endpoints@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/types': 4.15.0
|
||||
'@smithy/url-parser': 4.4.2
|
||||
'@smithy/util-endpoints': 3.6.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-locate-window@3.965.8':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-user-agent-browser@3.965.0':
|
||||
dependencies:
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/types': 4.15.0
|
||||
bowser: 2.14.1
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/util-user-agent-node@3.967.0':
|
||||
dependencies:
|
||||
'@aws-sdk/middleware-user-agent': 3.967.0
|
||||
'@aws-sdk/types': 3.965.0
|
||||
'@smithy/node-config-provider': 4.5.2
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws-sdk/xml-builder@3.965.0':
|
||||
dependencies:
|
||||
'@smithy/types': 4.15.0
|
||||
fast-xml-parser: 5.2.5
|
||||
tslib: 2.8.1
|
||||
|
||||
'@aws/lambda-invoke-store@0.2.4': {}
|
||||
|
||||
'@azure/abort-controller@2.1.2':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
@@ -3883,6 +4534,175 @@ snapshots:
|
||||
dependencies:
|
||||
'@sinonjs/commons': 3.0.1
|
||||
|
||||
'@smithy/config-resolver@4.6.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/core@3.26.0':
|
||||
dependencies:
|
||||
'@aws-crypto/crc32': 5.2.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/credential-provider-imds@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/fetch-http-handler@5.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/hash-node@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/invalid-dependency@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/is-array-buffer@2.2.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/middleware-content-length@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/middleware-endpoint@4.6.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/middleware-retry@4.7.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/middleware-serde@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/middleware-stack@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/node-config-provider@4.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/node-http-handler@4.8.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/property-provider@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/protocol-http@5.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/shared-ini-file-loader@4.6.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/signature-v4@5.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/smithy-client@4.14.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
'@smithy/types': 4.15.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/types@4.15.0':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/url-parser@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-base64@4.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-body-length-browser@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-body-length-node@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-buffer-from@2.2.0':
|
||||
dependencies:
|
||||
'@smithy/is-array-buffer': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-defaults-mode-browser@4.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-defaults-mode-node@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-endpoints@3.6.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-middleware@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-retry@4.5.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-stream@4.7.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-utf8@2.3.0':
|
||||
dependencies:
|
||||
'@smithy/util-buffer-from': 2.2.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@smithy/util-utf8@4.4.2':
|
||||
dependencies:
|
||||
'@smithy/core': 3.26.0
|
||||
tslib: 2.8.1
|
||||
|
||||
'@standard-schema/spec@1.1.0': {}
|
||||
|
||||
'@supabase/auth-js@2.108.1':
|
||||
@@ -4067,6 +4887,8 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.2
|
||||
|
||||
anynum@1.0.1: {}
|
||||
|
||||
apparatus@0.0.10:
|
||||
dependencies:
|
||||
sylvester: 0.0.21
|
||||
@@ -4171,6 +4993,8 @@ snapshots:
|
||||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
|
||||
bowser@2.14.1: {}
|
||||
|
||||
brace-expansion@1.1.15:
|
||||
dependencies:
|
||||
balanced-match: 1.0.2
|
||||
@@ -4513,6 +5337,10 @@ snapshots:
|
||||
|
||||
fast-json-stable-stringify@2.1.0: {}
|
||||
|
||||
fast-xml-parser@5.2.5:
|
||||
dependencies:
|
||||
strnum: 2.4.1
|
||||
|
||||
fastq@1.20.1:
|
||||
dependencies:
|
||||
reusify: 1.1.0
|
||||
@@ -5937,6 +6765,10 @@ snapshots:
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
strnum@2.4.1:
|
||||
dependencies:
|
||||
anynum: 1.0.1
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
|
||||
@@ -28,4 +28,5 @@ export * from "./vector_stores/langchain";
|
||||
export * from "./vector_stores/vectorize";
|
||||
export * from "./vector_stores/azure_ai_search";
|
||||
export * from "./vector_stores/pgvector";
|
||||
export * from "./vector_stores/s3_vectors";
|
||||
export * from "./utils/factory";
|
||||
|
||||
@@ -38,6 +38,7 @@ import { LangchainEmbedder } from "../embeddings/langchain";
|
||||
import { LangchainVectorStore } from "../vector_stores/langchain";
|
||||
import { AzureAISearch } from "../vector_stores/azure_ai_search";
|
||||
import { PGVector } from "../vector_stores/pgvector";
|
||||
import { S3Vectors } from "../vector_stores/s3_vectors";
|
||||
|
||||
export class EmbedderFactory {
|
||||
static create(provider: string, config: EmbeddingConfig): Embedder {
|
||||
@@ -116,6 +117,9 @@ export class VectorStoreFactory {
|
||||
return new AzureAISearch(config as any);
|
||||
case "pgvector":
|
||||
return new PGVector(config as any);
|
||||
case "s3-vectors":
|
||||
case "s3_vectors":
|
||||
return new S3Vectors(config as any);
|
||||
default:
|
||||
throw new Error(`Unsupported vector store provider: ${provider}`);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,841 @@
|
||||
import {
|
||||
CreateIndexCommand,
|
||||
CreateVectorBucketCommand,
|
||||
DeleteIndexCommand,
|
||||
DeleteVectorsCommand,
|
||||
GetIndexCommand,
|
||||
GetVectorBucketCommand,
|
||||
GetVectorsCommand,
|
||||
ListVectorsCommand,
|
||||
PutVectorsCommand,
|
||||
QueryVectorsCommand,
|
||||
S3VectorsClient,
|
||||
type DistanceMetric,
|
||||
type GetOutputVector,
|
||||
type ListOutputVector,
|
||||
type QueryOutputVector,
|
||||
type S3VectorsClientConfig,
|
||||
type VectorData,
|
||||
} from "@aws-sdk/client-s3vectors";
|
||||
import { VectorStore } from "./base";
|
||||
import { SearchFilters, VectorStoreConfig, VectorStoreResult } from "../types";
|
||||
|
||||
const MIGRATION_INDEX_NAME = "memory_migrations";
|
||||
const MIGRATION_VECTOR_KEY = "mem0-user";
|
||||
const DEFAULT_PAGE_SIZE = 500;
|
||||
const ALWAYS_FALSE_FILTER_KEY = "$alwaysFalse";
|
||||
|
||||
type S3Filter = Record<string, any>;
|
||||
|
||||
interface S3VectorsConfig extends VectorStoreConfig {
|
||||
vectorBucketName: string;
|
||||
collectionName: string;
|
||||
embeddingModelDims?: number;
|
||||
dimension?: number;
|
||||
distanceMetric?: DistanceMetric | "cosine" | "euclidean";
|
||||
region?: string;
|
||||
regionName?: string;
|
||||
client?: S3VectorsClientLike;
|
||||
clientConfig?: S3VectorsClientConfig;
|
||||
}
|
||||
|
||||
interface S3VectorsClientLike {
|
||||
send(command: any): Promise<any>;
|
||||
}
|
||||
|
||||
export class S3Vectors implements VectorStore {
|
||||
private readonly client: S3VectorsClientLike;
|
||||
private readonly vectorBucketName: string;
|
||||
private readonly collectionName: string;
|
||||
private readonly dimension: number;
|
||||
private readonly distanceMetric: DistanceMetric | "cosine" | "euclidean";
|
||||
private _initPromise?: Promise<void>;
|
||||
private cachedUserId?: string;
|
||||
|
||||
constructor(config: S3VectorsConfig) {
|
||||
if (!config.vectorBucketName) {
|
||||
throw new Error("vectorBucketName is required");
|
||||
}
|
||||
if (!config.collectionName) {
|
||||
throw new Error("collectionName is required");
|
||||
}
|
||||
|
||||
const dimension = config.embeddingModelDims ?? config.dimension;
|
||||
if (!dimension || dimension < 1) {
|
||||
throw new Error("embeddingModelDims or dimension is required");
|
||||
}
|
||||
|
||||
this.vectorBucketName = config.vectorBucketName;
|
||||
this.collectionName = config.collectionName;
|
||||
this.dimension = dimension;
|
||||
this.distanceMetric = config.distanceMetric || "cosine";
|
||||
this.client =
|
||||
config.client ||
|
||||
new S3VectorsClient({
|
||||
...(config.clientConfig || {}),
|
||||
...(config.region || config.regionName
|
||||
? { region: config.region || config.regionName }
|
||||
: {}),
|
||||
});
|
||||
|
||||
void this.initialize().catch(console.error);
|
||||
}
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (!this._initPromise) {
|
||||
this._initPromise = this._doInitialize();
|
||||
}
|
||||
return this._initPromise;
|
||||
}
|
||||
|
||||
private async _doInitialize(): Promise<void> {
|
||||
await this.ensureBucketExists();
|
||||
await this.ensureIndexExists(
|
||||
this.collectionName,
|
||||
this.dimension,
|
||||
this.distanceMetric,
|
||||
);
|
||||
}
|
||||
|
||||
async insert(
|
||||
vectors: number[][],
|
||||
ids: string[],
|
||||
payloads: Record<string, any>[],
|
||||
): Promise<void> {
|
||||
await this.initialize();
|
||||
this.assertBatchDimensions(vectors, "Insert");
|
||||
|
||||
await this.client.send(
|
||||
new PutVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
vectors: vectors.map((vector, index) => ({
|
||||
key: ids[index],
|
||||
data: this.toVectorData(vector),
|
||||
metadata: payloads[index] || {},
|
||||
})),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async keywordSearch(): Promise<null> {
|
||||
return null;
|
||||
}
|
||||
|
||||
async search(
|
||||
query: number[],
|
||||
topK: number = 5,
|
||||
filters?: SearchFilters,
|
||||
): Promise<VectorStoreResult[]> {
|
||||
await this.initialize();
|
||||
this.assertVectorDimension(query, "Query");
|
||||
|
||||
const filter = this.convertFilters(filters);
|
||||
if (filter && this.isAlwaysFalseFilter(filter)) {
|
||||
return [];
|
||||
}
|
||||
const response = await this.client.send(
|
||||
new QueryVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
queryVector: this.toVectorData(query),
|
||||
topK,
|
||||
filter,
|
||||
returnMetadata: true,
|
||||
returnDistance: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const responseDistanceMetric =
|
||||
response.distanceMetric || this.distanceMetric;
|
||||
return (response.vectors || [])
|
||||
.map((vector: QueryOutputVector) =>
|
||||
this.normalizeQueryVector(vector, responseDistanceMetric),
|
||||
)
|
||||
.slice(0, topK);
|
||||
}
|
||||
|
||||
async get(vectorId: string): Promise<VectorStoreResult | null> {
|
||||
await this.initialize();
|
||||
|
||||
try {
|
||||
const response = await this.client.send(
|
||||
new GetVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
keys: [vectorId],
|
||||
returnMetadata: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const vector = response.vectors?.[0];
|
||||
if (!vector) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.normalizeStoredVector(vector);
|
||||
} catch (error) {
|
||||
if (this.isNotFound(error)) {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async update(
|
||||
vectorId: string,
|
||||
vector: number[],
|
||||
payload: Record<string, any>,
|
||||
): Promise<void> {
|
||||
await this.initialize();
|
||||
|
||||
let nextVector = vector;
|
||||
let nextPayload = payload || {};
|
||||
|
||||
if (vector.length === 0 || !payload) {
|
||||
const existing = await this.fetchStoredVector(vectorId);
|
||||
if (!existing) {
|
||||
throw new Error(`Vector with ID ${vectorId} not found`);
|
||||
}
|
||||
nextVector = vector.length > 0 ? vector : existing.vector;
|
||||
nextPayload = payload || existing.payload;
|
||||
}
|
||||
|
||||
this.assertVectorDimension(nextVector, "Vector");
|
||||
|
||||
await this.client.send(
|
||||
new PutVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
vectors: [
|
||||
{
|
||||
key: vectorId,
|
||||
data: this.toVectorData(nextVector),
|
||||
metadata: nextPayload,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async delete(vectorId: string): Promise<void> {
|
||||
await this.initialize();
|
||||
|
||||
await this.client.send(
|
||||
new DeleteVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
keys: [vectorId],
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
async deleteCol(): Promise<void> {
|
||||
await this.initialize();
|
||||
|
||||
try {
|
||||
await this.client.send(
|
||||
new DeleteIndexCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (this.isNotFound(error)) {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async list(
|
||||
filters?: SearchFilters,
|
||||
topK: number = 100,
|
||||
): Promise<[VectorStoreResult[], number]> {
|
||||
await this.initialize();
|
||||
|
||||
const filter = this.convertFilters(filters);
|
||||
const results: VectorStoreResult[] = [];
|
||||
let nextToken: string | undefined;
|
||||
|
||||
// Stop paginating once we have topK matches. Both callers of list()
|
||||
// discard the count, so scanning the whole index just to total every
|
||||
// match is wasted round-trips (O(index size) on getAll/deleteAll).
|
||||
// Return the page length like qdrant does.
|
||||
do {
|
||||
const response = await this.client.send(
|
||||
new ListVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
maxResults: DEFAULT_PAGE_SIZE,
|
||||
nextToken,
|
||||
returnMetadata: true,
|
||||
}),
|
||||
);
|
||||
|
||||
for (const vector of response.vectors || []) {
|
||||
if (results.length >= topK) {
|
||||
break;
|
||||
}
|
||||
const normalized = this.normalizeListedVector(vector);
|
||||
if (filter && !this.matchesFilter(normalized.payload, filter)) {
|
||||
continue;
|
||||
}
|
||||
results.push(normalized);
|
||||
}
|
||||
|
||||
nextToken = response.nextToken;
|
||||
} while (nextToken && results.length < topK);
|
||||
|
||||
return [results, results.length];
|
||||
}
|
||||
|
||||
async getUserId(): Promise<string> {
|
||||
await this.initialize();
|
||||
if (this.cachedUserId) {
|
||||
return this.cachedUserId;
|
||||
}
|
||||
|
||||
await this.ensureMigrationIndex();
|
||||
|
||||
const response = await this.client.send(
|
||||
new GetVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: MIGRATION_INDEX_NAME,
|
||||
keys: [MIGRATION_VECTOR_KEY],
|
||||
returnMetadata: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const metadata = this.normalizeMetadata(response.vectors?.[0]?.metadata);
|
||||
const userId = metadata.user_id;
|
||||
if (typeof userId === "string" && userId.length > 0) {
|
||||
this.cachedUserId = userId;
|
||||
return userId;
|
||||
}
|
||||
|
||||
const randomUserId =
|
||||
Math.random().toString(36).substring(2, 15) +
|
||||
Math.random().toString(36).substring(2, 15);
|
||||
await this.setUserId(randomUserId);
|
||||
return randomUserId;
|
||||
}
|
||||
|
||||
async setUserId(userId: string): Promise<void> {
|
||||
await this.initialize();
|
||||
await this.ensureMigrationIndex();
|
||||
|
||||
await this.client.send(
|
||||
new PutVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: MIGRATION_INDEX_NAME,
|
||||
vectors: [
|
||||
{
|
||||
key: MIGRATION_VECTOR_KEY,
|
||||
data: this.toVectorData([0]),
|
||||
metadata: { user_id: userId },
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
|
||||
this.cachedUserId = userId;
|
||||
}
|
||||
|
||||
private async ensureBucketExists(): Promise<void> {
|
||||
try {
|
||||
await this.client.send(
|
||||
new GetVectorBucketCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isNotFound(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.send(
|
||||
new CreateVectorBucketCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
}),
|
||||
);
|
||||
} catch (createError) {
|
||||
if (!this.isConflict(createError)) {
|
||||
throw createError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureIndexExists(
|
||||
indexName: string,
|
||||
dimension: number,
|
||||
distanceMetric: DistanceMetric | "cosine" | "euclidean",
|
||||
): Promise<void> {
|
||||
try {
|
||||
await this.client.send(
|
||||
new GetIndexCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName,
|
||||
}),
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this.isNotFound(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.client.send(
|
||||
new CreateIndexCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName,
|
||||
dataType: "float32",
|
||||
dimension,
|
||||
distanceMetric,
|
||||
}),
|
||||
);
|
||||
} catch (createError) {
|
||||
if (!this.isConflict(createError)) {
|
||||
throw createError;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureMigrationIndex(): Promise<void> {
|
||||
await this.ensureIndexExists(MIGRATION_INDEX_NAME, 1, "euclidean");
|
||||
}
|
||||
|
||||
private async fetchStoredVector(
|
||||
vectorId: string,
|
||||
): Promise<{ vector: number[]; payload: Record<string, any> } | null> {
|
||||
const response = await this.client.send(
|
||||
new GetVectorsCommand({
|
||||
vectorBucketName: this.vectorBucketName,
|
||||
indexName: this.collectionName,
|
||||
keys: [vectorId],
|
||||
returnData: true,
|
||||
returnMetadata: true,
|
||||
}),
|
||||
);
|
||||
|
||||
const vector = response.vectors?.[0];
|
||||
const data = this.fromVectorData(vector?.data);
|
||||
if (!vector || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
vector: data,
|
||||
payload: this.normalizeMetadata(vector.metadata),
|
||||
};
|
||||
}
|
||||
|
||||
private convertFilters(filters?: SearchFilters): S3Filter | undefined {
|
||||
if (!filters || Object.keys(filters).length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
const normalized = this.convertFilterNode(filters);
|
||||
return this.isNoOpFilter(normalized) ? undefined : normalized;
|
||||
}
|
||||
|
||||
private convertFilterNode(node: Record<string, any>): S3Filter {
|
||||
const clauses: S3Filter[] = [];
|
||||
|
||||
for (const [key, value] of Object.entries(node)) {
|
||||
if (key === "$and" || key === "$or") {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
throw new Error(`${key} filter requires a non-empty list`);
|
||||
}
|
||||
const normalizedEntries = value.map((entry) =>
|
||||
this.convertFilterNode(entry),
|
||||
);
|
||||
const entries = normalizedEntries.filter(
|
||||
(entry) => !this.isNoOpFilter(entry),
|
||||
);
|
||||
if (key === "$and") {
|
||||
if (entries.some((entry) => this.isAlwaysFalseFilter(entry))) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
if (entries.length === 0) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if (normalizedEntries.some((entry) => this.isNoOpFilter(entry))) {
|
||||
continue;
|
||||
}
|
||||
const viableEntries = entries.filter(
|
||||
(entry) => !this.isAlwaysFalseFilter(entry),
|
||||
);
|
||||
if (viableEntries.length === 0) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
clauses.push(
|
||||
viableEntries.length === 1
|
||||
? viableEntries[0]
|
||||
: { [key]: viableEntries },
|
||||
);
|
||||
continue;
|
||||
}
|
||||
clauses.push(entries.length === 1 ? entries[0] : { [key]: entries });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key === "$not") {
|
||||
if (!Array.isArray(value) || value.length === 0) {
|
||||
throw new Error("$not filter requires a non-empty list");
|
||||
}
|
||||
|
||||
const normalizedEntries = value.map((entry) =>
|
||||
this.convertFilterNode(entry),
|
||||
);
|
||||
if (normalizedEntries.some((entry) => this.isNoOpFilter(entry))) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
const negated = normalizedEntries.filter(
|
||||
(entry) => !this.isAlwaysFalseFilter(entry),
|
||||
);
|
||||
if (negated.length === 0) {
|
||||
continue;
|
||||
}
|
||||
const negatedFilters = negated.map((entry) => this.negateFilter(entry));
|
||||
clauses.push(
|
||||
negatedFilters.length === 1
|
||||
? negatedFilters[0]
|
||||
: { $and: negatedFilters },
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const fieldFilter = this.convertFieldFilter(key, value);
|
||||
if (this.isAlwaysFalseFilter(fieldFilter)) {
|
||||
return fieldFilter;
|
||||
}
|
||||
if (this.isNoOpFilter(fieldFilter)) {
|
||||
continue;
|
||||
}
|
||||
clauses.push(fieldFilter);
|
||||
}
|
||||
|
||||
if (clauses.some((entry) => this.isAlwaysFalseFilter(entry))) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
if (clauses.length === 1) {
|
||||
return clauses[0];
|
||||
}
|
||||
if (clauses.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { $and: clauses };
|
||||
}
|
||||
|
||||
private convertFieldFilter(key: string, value: any): S3Filter {
|
||||
if (value === "*") {
|
||||
return { [key]: { $exists: true } };
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
if (value.length === 0) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
return { [key]: { $in: value } };
|
||||
}
|
||||
|
||||
if (typeof value !== "object" || value === null) {
|
||||
return { [key]: { $eq: value } };
|
||||
}
|
||||
|
||||
const operators: Record<string, any> = {};
|
||||
|
||||
for (const [operator, operand] of Object.entries(value)) {
|
||||
switch (operator) {
|
||||
case "eq":
|
||||
operators.$eq = operand;
|
||||
break;
|
||||
case "ne":
|
||||
operators.$ne = operand;
|
||||
break;
|
||||
case "gt":
|
||||
operators.$gt = operand;
|
||||
break;
|
||||
case "gte":
|
||||
operators.$gte = operand;
|
||||
break;
|
||||
case "lt":
|
||||
operators.$lt = operand;
|
||||
break;
|
||||
case "lte":
|
||||
operators.$lte = operand;
|
||||
break;
|
||||
case "in":
|
||||
if (!Array.isArray(operand) || operand.length === 0) {
|
||||
return { [ALWAYS_FALSE_FILTER_KEY]: true };
|
||||
}
|
||||
operators.$in = operand;
|
||||
break;
|
||||
case "nin":
|
||||
if (!Array.isArray(operand) || operand.length === 0) {
|
||||
break;
|
||||
}
|
||||
operators.$nin = operand;
|
||||
break;
|
||||
case "contains":
|
||||
case "icontains":
|
||||
case "startsWith":
|
||||
throw new Error(
|
||||
`S3 Vectors does not support '${operator}' metadata filters.`,
|
||||
);
|
||||
default:
|
||||
throw new Error(
|
||||
`Unsupported S3 Vectors filter operator: ${operator}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(operators).length === 0) {
|
||||
return {};
|
||||
}
|
||||
return { [key]: operators };
|
||||
}
|
||||
|
||||
private negateFilter(filter: S3Filter): S3Filter {
|
||||
if (Array.isArray(filter.$and)) {
|
||||
return {
|
||||
$or: filter.$and.map((entry: S3Filter) => this.negateFilter(entry)),
|
||||
};
|
||||
}
|
||||
|
||||
if (Array.isArray(filter.$or)) {
|
||||
return {
|
||||
$and: filter.$or.map((entry: S3Filter) => this.negateFilter(entry)),
|
||||
};
|
||||
}
|
||||
|
||||
const entries = Object.entries(filter);
|
||||
if (entries.length !== 1) {
|
||||
throw new Error("S3 Vectors cannot negate this filter shape.");
|
||||
}
|
||||
|
||||
const [key, rawValue] = entries[0];
|
||||
if (key.startsWith("$")) {
|
||||
throw new Error("S3 Vectors cannot negate this filter shape.");
|
||||
}
|
||||
|
||||
const value =
|
||||
typeof rawValue === "object" && rawValue !== null
|
||||
? rawValue
|
||||
: { $eq: rawValue };
|
||||
const negated: Record<string, any> = {};
|
||||
|
||||
for (const [operator, operand] of Object.entries(value)) {
|
||||
switch (operator) {
|
||||
case "$eq":
|
||||
negated.$ne = operand;
|
||||
break;
|
||||
case "$ne":
|
||||
negated.$eq = operand;
|
||||
break;
|
||||
case "$gt":
|
||||
negated.$lte = operand;
|
||||
break;
|
||||
case "$gte":
|
||||
negated.$lt = operand;
|
||||
break;
|
||||
case "$lt":
|
||||
negated.$gte = operand;
|
||||
break;
|
||||
case "$lte":
|
||||
negated.$gt = operand;
|
||||
break;
|
||||
case "$in":
|
||||
negated.$nin = operand;
|
||||
break;
|
||||
case "$nin":
|
||||
negated.$in = operand;
|
||||
break;
|
||||
case "$exists":
|
||||
negated.$exists = !operand;
|
||||
break;
|
||||
default:
|
||||
throw new Error("S3 Vectors cannot negate this filter shape.");
|
||||
}
|
||||
}
|
||||
|
||||
return { [key]: negated };
|
||||
}
|
||||
|
||||
private matchesFilter(
|
||||
metadata: Record<string, any>,
|
||||
filter: S3Filter,
|
||||
): boolean {
|
||||
if (this.isAlwaysFalseFilter(filter)) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(filter.$and)) {
|
||||
return filter.$and.every((entry: S3Filter) =>
|
||||
this.matchesFilter(metadata, entry),
|
||||
);
|
||||
}
|
||||
|
||||
if (Array.isArray(filter.$or)) {
|
||||
return filter.$or.some((entry: S3Filter) =>
|
||||
this.matchesFilter(metadata, entry),
|
||||
);
|
||||
}
|
||||
|
||||
return Object.entries(filter).every(([key, rawValue]) =>
|
||||
this.matchesFieldFilter(metadata[key], rawValue),
|
||||
);
|
||||
}
|
||||
|
||||
private matchesFieldFilter(metadataValue: any, rawValue: any): boolean {
|
||||
const value =
|
||||
typeof rawValue === "object" && rawValue !== null
|
||||
? rawValue
|
||||
: { $eq: rawValue };
|
||||
|
||||
return Object.entries(value).every(([operator, operand]) =>
|
||||
this.evaluateOperator(metadataValue, operator, operand),
|
||||
);
|
||||
}
|
||||
|
||||
private evaluateOperator(
|
||||
metadataValue: any,
|
||||
operator: string,
|
||||
operand: any,
|
||||
): boolean {
|
||||
switch (operator) {
|
||||
case "$eq":
|
||||
if (Array.isArray(metadataValue)) {
|
||||
return metadataValue.some((entry) => entry === operand);
|
||||
}
|
||||
return metadataValue === operand;
|
||||
case "$ne":
|
||||
if (Array.isArray(metadataValue)) {
|
||||
return !metadataValue.some((entry) => entry === operand);
|
||||
}
|
||||
return metadataValue !== operand;
|
||||
case "$gt":
|
||||
return typeof metadataValue === "number" && metadataValue > operand;
|
||||
case "$gte":
|
||||
return typeof metadataValue === "number" && metadataValue >= operand;
|
||||
case "$lt":
|
||||
return typeof metadataValue === "number" && metadataValue < operand;
|
||||
case "$lte":
|
||||
return typeof metadataValue === "number" && metadataValue <= operand;
|
||||
case "$in":
|
||||
if (!Array.isArray(operand) || operand.length === 0) {
|
||||
return false;
|
||||
}
|
||||
if (Array.isArray(metadataValue)) {
|
||||
return metadataValue.some((entry) => operand.includes(entry));
|
||||
}
|
||||
return operand.includes(metadataValue);
|
||||
case "$nin":
|
||||
if (!Array.isArray(operand) || operand.length === 0) {
|
||||
return true;
|
||||
}
|
||||
if (Array.isArray(metadataValue)) {
|
||||
return metadataValue.every((entry) => !operand.includes(entry));
|
||||
}
|
||||
return !operand.includes(metadataValue);
|
||||
case "$exists":
|
||||
return operand
|
||||
? metadataValue !== undefined
|
||||
: metadataValue === undefined;
|
||||
default:
|
||||
throw new Error(`Unsupported S3 Vectors filter operator: ${operator}`);
|
||||
}
|
||||
}
|
||||
|
||||
private toVectorData(vector: number[]): VectorData {
|
||||
return { float32: vector };
|
||||
}
|
||||
|
||||
private fromVectorData(data?: VectorData): number[] | undefined {
|
||||
if (!data || !("float32" in data) || !Array.isArray(data.float32)) {
|
||||
return undefined;
|
||||
}
|
||||
return data.float32;
|
||||
}
|
||||
|
||||
private normalizeMetadata(metadata: any): Record<string, any> {
|
||||
if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
|
||||
return { ...(metadata as Record<string, any>) };
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
private normalizeQueryVector(
|
||||
vector: QueryOutputVector,
|
||||
distanceMetric: DistanceMetric | "cosine" | "euclidean",
|
||||
): VectorStoreResult {
|
||||
return {
|
||||
id: String(vector.key),
|
||||
payload: this.normalizeMetadata(vector.metadata),
|
||||
score: this.normalizeScore(vector.distance, distanceMetric),
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeStoredVector(
|
||||
vector: GetOutputVector | ListOutputVector,
|
||||
): VectorStoreResult {
|
||||
return {
|
||||
id: String(vector.key),
|
||||
payload: this.normalizeMetadata(vector.metadata),
|
||||
};
|
||||
}
|
||||
|
||||
private normalizeListedVector(vector: ListOutputVector): VectorStoreResult {
|
||||
return this.normalizeStoredVector(vector);
|
||||
}
|
||||
|
||||
private normalizeScore(
|
||||
distance?: number,
|
||||
distanceMetric: DistanceMetric | "cosine" | "euclidean" = this
|
||||
.distanceMetric,
|
||||
): number | undefined {
|
||||
if (distance === undefined || distance === null) {
|
||||
return undefined;
|
||||
}
|
||||
if (!Number.isFinite(distance)) {
|
||||
return undefined;
|
||||
}
|
||||
if (distanceMetric === "euclidean") {
|
||||
return 1 / (1 + distance);
|
||||
}
|
||||
return Math.max(0, Math.min(1, 1 - distance));
|
||||
}
|
||||
|
||||
private assertVectorDimension(vector: number[], context: string): void {
|
||||
if (vector.length !== this.dimension) {
|
||||
throw new Error(
|
||||
`${context} dimension mismatch. Expected ${this.dimension}, got ${vector.length}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private assertBatchDimensions(vectors: number[][], context: string): void {
|
||||
for (const vector of vectors) {
|
||||
this.assertVectorDimension(vector, context);
|
||||
}
|
||||
}
|
||||
|
||||
private isNotFound(error: any): boolean {
|
||||
return error?.name === "NotFoundException";
|
||||
}
|
||||
|
||||
private isConflict(error: any): boolean {
|
||||
return error?.name === "ConflictException";
|
||||
}
|
||||
|
||||
private isAlwaysFalseFilter(filter: S3Filter): boolean {
|
||||
return filter[ALWAYS_FALSE_FILTER_KEY] === true;
|
||||
}
|
||||
|
||||
private isNoOpFilter(filter: S3Filter): boolean {
|
||||
return Object.keys(filter).length === 0;
|
||||
}
|
||||
}
|
||||
@@ -138,6 +138,11 @@ jest.mock("../src/vector_stores/pgvector", () => ({
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "pgvector", config })),
|
||||
}));
|
||||
jest.mock("../src/vector_stores/s3_vectors", () => ({
|
||||
S3Vectors: jest
|
||||
.fn()
|
||||
.mockImplementation((config) => ({ type: "s3-vectors", config })),
|
||||
}));
|
||||
jest.mock("../src/storage/SupabaseHistoryManager", () => ({
|
||||
SupabaseHistoryManager: jest
|
||||
.fn()
|
||||
@@ -260,6 +265,8 @@ describe("VectorStoreFactory", () => {
|
||||
["vectorize"],
|
||||
["azure-ai-search"],
|
||||
["pgvector"],
|
||||
["s3-vectors"],
|
||||
["s3_vectors"],
|
||||
])("creates vector store for provider '%s'", (provider) => {
|
||||
expect(() =>
|
||||
VectorStoreFactory.create(provider, dummyVSConfig),
|
||||
|
||||
@@ -14,7 +14,6 @@ describe("tsup.config.ts externals", () => {
|
||||
beforeAll(() => {
|
||||
const pkgPath = path.resolve(__dirname, "../../../package.json");
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
||||
// Filter out @types/* packages — they are type-only and not bundled at runtime
|
||||
peerDeps = Object.keys(pkg.peerDependencies || {}).filter(
|
||||
(dep) => !dep.startsWith("@types/"),
|
||||
);
|
||||
@@ -22,16 +21,16 @@ describe("tsup.config.ts externals", () => {
|
||||
|
||||
const tsupConfigPath = path.resolve(__dirname, "../../../tsup.config.ts");
|
||||
const tsupContent = fs.readFileSync(tsupConfigPath, "utf-8");
|
||||
|
||||
// Extract strings from the external array (supports double, single, and backtick quotes)
|
||||
const externalMatch = tsupContent.match(
|
||||
/const external\s*=\s*\[([\s\S]*?)\];/,
|
||||
);
|
||||
|
||||
if (!externalMatch) {
|
||||
throw new Error("Could not find external array in tsup.config.ts");
|
||||
}
|
||||
|
||||
const matches = externalMatch[1].match(/["'`]([^"'`]+)["'`]/g);
|
||||
externalDeps = (matches || []).map((m) => m.replace(/["'`]/g, ""));
|
||||
externalDeps = (matches || []).map((value) => value.replace(/["'`]/g, ""));
|
||||
});
|
||||
|
||||
it("should have every peerDependency in the external array", () => {
|
||||
|
||||
@@ -720,7 +720,358 @@ describe("AzureAISearch – backward compat with mocked client", () => {
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// 6. Vectorize — mock Cloudflare client, test idempotent init
|
||||
// 6. S3 Vectors — mock AWS client, test interface + init
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
describe("S3 Vectors – backward compat with mocked client", () => {
|
||||
function createMockS3VectorsClient(options?: {
|
||||
queryDistance?: number;
|
||||
queryDistanceMetric?: "cosine" | "euclidean";
|
||||
}) {
|
||||
const buckets = new Set<string>();
|
||||
const indexes = new Map<
|
||||
string,
|
||||
{
|
||||
dimension: number;
|
||||
distanceMetric: string;
|
||||
}
|
||||
>();
|
||||
const vectors = new Map<
|
||||
string,
|
||||
{
|
||||
key: string;
|
||||
data: { float32: number[] };
|
||||
metadata: Record<string, any>;
|
||||
}
|
||||
>();
|
||||
|
||||
const vectorMapKey = (indexName: string, key: string) =>
|
||||
`${indexName}:${key}`;
|
||||
|
||||
return {
|
||||
send: jest.fn().mockImplementation(async (command: any) => {
|
||||
const name = command.constructor.name;
|
||||
const input = command.input;
|
||||
|
||||
switch (name) {
|
||||
case "GetVectorBucketCommand":
|
||||
if (!buckets.has(input.vectorBucketName)) {
|
||||
const error: any = new Error("Bucket not found");
|
||||
error.name = "NotFoundException";
|
||||
throw error;
|
||||
}
|
||||
return {};
|
||||
case "CreateVectorBucketCommand":
|
||||
if (buckets.has(input.vectorBucketName)) {
|
||||
const error: any = new Error("Bucket exists");
|
||||
error.name = "ConflictException";
|
||||
throw error;
|
||||
}
|
||||
buckets.add(input.vectorBucketName);
|
||||
return {};
|
||||
case "GetIndexCommand":
|
||||
if (!indexes.has(input.indexName)) {
|
||||
const error: any = new Error("Index not found");
|
||||
error.name = "NotFoundException";
|
||||
throw error;
|
||||
}
|
||||
return {};
|
||||
case "CreateIndexCommand":
|
||||
if (indexes.has(input.indexName)) {
|
||||
const error: any = new Error("Index exists");
|
||||
error.name = "ConflictException";
|
||||
throw error;
|
||||
}
|
||||
indexes.set(input.indexName, {
|
||||
dimension: input.dimension,
|
||||
distanceMetric: input.distanceMetric,
|
||||
});
|
||||
return {};
|
||||
case "PutVectorsCommand":
|
||||
for (const vector of input.vectors || []) {
|
||||
vectors.set(vectorMapKey(input.indexName, vector.key), {
|
||||
key: vector.key,
|
||||
data: vector.data,
|
||||
metadata: vector.metadata || {},
|
||||
});
|
||||
}
|
||||
return {};
|
||||
case "QueryVectorsCommand":
|
||||
return {
|
||||
distanceMetric: options?.queryDistanceMetric ?? "cosine",
|
||||
vectors: [
|
||||
{
|
||||
key: "doc-1",
|
||||
metadata: { user_id: "u1", topic: "alpha" },
|
||||
distance: options?.queryDistance ?? 0.25,
|
||||
},
|
||||
],
|
||||
};
|
||||
case "GetVectorsCommand":
|
||||
return {
|
||||
vectors: (input.keys || [])
|
||||
.map((key: string) =>
|
||||
vectors.get(vectorMapKey(input.indexName, key)),
|
||||
)
|
||||
.filter(Boolean),
|
||||
};
|
||||
case "DeleteVectorsCommand":
|
||||
for (const key of input.keys || []) {
|
||||
vectors.delete(vectorMapKey(input.indexName, key));
|
||||
}
|
||||
return {};
|
||||
case "DeleteIndexCommand":
|
||||
indexes.delete(input.indexName);
|
||||
for (const key of Array.from(vectors.keys())) {
|
||||
if (key.startsWith(`${input.indexName}:`)) {
|
||||
vectors.delete(key);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
case "ListVectorsCommand":
|
||||
return {
|
||||
vectors: Array.from(vectors.values())
|
||||
.filter((entry) =>
|
||||
vectorMapKey(input.indexName, entry.key).startsWith(
|
||||
`${input.indexName}:`,
|
||||
),
|
||||
)
|
||||
.map((entry) => ({
|
||||
key: entry.key,
|
||||
metadata: entry.metadata,
|
||||
})),
|
||||
};
|
||||
default:
|
||||
throw new Error(`Unexpected S3Vectors command: ${name}`);
|
||||
}
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
function findCommandInput(
|
||||
client: { send: jest.Mock },
|
||||
commandName: string,
|
||||
): Record<string, any> | undefined {
|
||||
const match = client.send.mock.calls.find(
|
||||
([command]) => command.constructor.name === commandName,
|
||||
);
|
||||
return match?.[0]?.input;
|
||||
}
|
||||
|
||||
it("implements full VectorStore interface", () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const store = new S3Vectors({
|
||||
client: createMockS3VectorsClient(),
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
});
|
||||
expect(typeof store.insert).toBe("function");
|
||||
expect(typeof store.search).toBe("function");
|
||||
expect(typeof store.get).toBe("function");
|
||||
expect(typeof store.update).toBe("function");
|
||||
expect(typeof store.delete).toBe("function");
|
||||
expect(typeof store.deleteCol).toBe("function");
|
||||
expect(typeof store.list).toBe("function");
|
||||
expect(typeof store.getUserId).toBe("function");
|
||||
expect(typeof store.setUserId).toBe("function");
|
||||
expect(typeof store.initialize).toBe("function");
|
||||
});
|
||||
|
||||
it("initialize() is idempotent (same promise returned)", async () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const mockClient = createMockS3VectorsClient();
|
||||
const store = new S3Vectors({
|
||||
client: mockClient,
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
});
|
||||
|
||||
const p1 = store.initialize();
|
||||
const p2 = store.initialize();
|
||||
const p3 = store.initialize();
|
||||
|
||||
await Promise.all([p1, p2, p3]);
|
||||
|
||||
expect(
|
||||
mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "CreateVectorBucketCommand",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "CreateIndexCommand",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "GetVectorBucketCommand",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
expect(
|
||||
mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "GetIndexCommand",
|
||||
),
|
||||
).toHaveLength(1);
|
||||
});
|
||||
|
||||
it("shapes S3 write requests and normalizes search results", async () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const mockClient = createMockS3VectorsClient();
|
||||
const store = new S3Vectors({
|
||||
client: mockClient,
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
});
|
||||
|
||||
await store.initialize();
|
||||
await store.insert(
|
||||
[[0.1, 0.2, 0.3]],
|
||||
["doc-1"],
|
||||
[{ user_id: "u1", topic: "alpha" }],
|
||||
);
|
||||
|
||||
const putInput = findCommandInput(mockClient, "PutVectorsCommand");
|
||||
expect(putInput).toMatchObject({
|
||||
vectorBucketName: "test-bucket",
|
||||
indexName: "test-index",
|
||||
vectors: [
|
||||
{
|
||||
key: "doc-1",
|
||||
data: { float32: [0.1, 0.2, 0.3] },
|
||||
metadata: { user_id: "u1", topic: "alpha" },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const results = await store.search([0.1, 0.2, 0.3], 5, { user_id: "u1" });
|
||||
|
||||
const queryInput = findCommandInput(mockClient, "QueryVectorsCommand");
|
||||
expect(queryInput).toMatchObject({
|
||||
vectorBucketName: "test-bucket",
|
||||
indexName: "test-index",
|
||||
queryVector: { float32: [0.1, 0.2, 0.3] },
|
||||
topK: 5,
|
||||
filter: { user_id: { $eq: "u1" } },
|
||||
returnMetadata: true,
|
||||
returnDistance: true,
|
||||
});
|
||||
expect(results).toEqual([
|
||||
{
|
||||
id: "doc-1",
|
||||
payload: { user_id: "u1", topic: "alpha" },
|
||||
score: 0.75,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("normalizes euclidean distances without collapsing scores to zero", async () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const store = new S3Vectors({
|
||||
client: createMockS3VectorsClient({
|
||||
queryDistance: 1.5,
|
||||
queryDistanceMetric: "euclidean",
|
||||
}),
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
distanceMetric: "cosine",
|
||||
});
|
||||
|
||||
const [result] = await store.search([0.1, 0.2, 0.3], 1);
|
||||
|
||||
expect(result).toEqual({
|
||||
id: "doc-1",
|
||||
payload: { user_id: "u1", topic: "alpha" },
|
||||
score: 0.4,
|
||||
});
|
||||
});
|
||||
|
||||
it("normalizes empty in and nin operands before search hits AWS", async () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const mockClient = createMockS3VectorsClient();
|
||||
const store = new S3Vectors({
|
||||
client: mockClient,
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
});
|
||||
|
||||
const baselineQueryCalls = mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "QueryVectorsCommand",
|
||||
).length;
|
||||
const emptyInResults = await store.search([0.1, 0.2, 0.3], 5, {
|
||||
topic: { in: [] },
|
||||
});
|
||||
expect(emptyInResults).toEqual([]);
|
||||
expect(
|
||||
mockClient.send.mock.calls.filter(
|
||||
([command]) => command.constructor.name === "QueryVectorsCommand",
|
||||
),
|
||||
).toHaveLength(baselineQueryCalls);
|
||||
|
||||
await store.search([0.1, 0.2, 0.3], 5, {
|
||||
topic: { nin: [] },
|
||||
});
|
||||
|
||||
const queryInput = findCommandInput(mockClient, "QueryVectorsCommand");
|
||||
expect(queryInput).toMatchObject({
|
||||
vectorBucketName: "test-bucket",
|
||||
indexName: "test-index",
|
||||
queryVector: { float32: [0.1, 0.2, 0.3] },
|
||||
topK: 5,
|
||||
returnMetadata: true,
|
||||
returnDistance: true,
|
||||
});
|
||||
expect(queryInput.filter).toBeUndefined();
|
||||
});
|
||||
it("applies client-side list filters, including empty $nin operands", async () => {
|
||||
const { S3Vectors } = require("../src/vector_stores/s3_vectors");
|
||||
const mockClient = createMockS3VectorsClient();
|
||||
const store = new S3Vectors({
|
||||
client: mockClient,
|
||||
vectorBucketName: "test-bucket",
|
||||
collectionName: "test-index",
|
||||
embeddingModelDims: 3,
|
||||
});
|
||||
|
||||
await store.initialize();
|
||||
await store.insert(
|
||||
[
|
||||
[0.1, 0.2, 0.3],
|
||||
[0.3, 0.2, 0.1],
|
||||
],
|
||||
["doc-1", "doc-2"],
|
||||
[
|
||||
{ user_id: "u1", topic: "alpha", tags: ["keep"] },
|
||||
{ user_id: "u2", topic: "beta", tags: ["skip"] },
|
||||
],
|
||||
);
|
||||
|
||||
const [allRows, allCount] = await store.list({ topic: { nin: [] } }, 10);
|
||||
expect(allCount).toBe(2);
|
||||
expect(allRows.map((row) => row.id)).toEqual(["doc-1", "doc-2"]);
|
||||
|
||||
const [filteredRows, filteredCount] = await store.list(
|
||||
{
|
||||
$and: [{ topic: { nin: ["beta"] } }, { tags: { in: ["keep"] } }],
|
||||
},
|
||||
10,
|
||||
);
|
||||
|
||||
expect(filteredCount).toBe(1);
|
||||
expect(filteredRows).toEqual([
|
||||
{
|
||||
id: "doc-1",
|
||||
payload: { user_id: "u1", topic: "alpha", tags: ["keep"] },
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
// 7. Vectorize — mock Cloudflare client, test idempotent init
|
||||
// ───────────────────────────────────────────────────────────────────────────
|
||||
describe("Vectorize – backward compat with mocked client", () => {
|
||||
let VectorizeDB: any;
|
||||
|
||||
@@ -4,6 +4,7 @@ import pkg from "./package.json";
|
||||
const external = [
|
||||
"openai",
|
||||
"@anthropic-ai/sdk",
|
||||
"@aws-sdk/client-s3vectors",
|
||||
"groq-sdk",
|
||||
"uuid",
|
||||
"pg",
|
||||
|
||||
Reference in New Issue
Block a user