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:
Rod Boev
2026-07-06 10:38:13 -04:00
committed by GitHub
parent 580d390e4d
commit 6dc4606dcf
11 changed files with 2082 additions and 8 deletions
+39 -2
View File
@@ -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:
+1 -1
View File
@@ -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}>
+1
View File
@@ -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",
+832
View File
@@ -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
+1
View File
@@ -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";
+4
View File
@@ -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),
+3 -4
View File
@@ -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;
+1
View File
@@ -4,6 +4,7 @@ import pkg from "./package.json";
const external = [
"openai",
"@anthropic-ai/sdk",
"@aws-sdk/client-s3vectors",
"groq-sdk",
"uuid",
"pg",