diff --git a/MONITORING.md b/MONITORING.md new file mode 100644 index 0000000..fd1bc2b --- /dev/null +++ b/MONITORING.md @@ -0,0 +1,291 @@ +# Migration Monitoring + +Monitor the progress of the legacy spaces migration using the `migration-monitor.js` script. + +## DynamoDB Table Structure + +The migration uses a `migration-progress` table to track space-level progress: + +### Table Schema + +``` +Primary Key: + - PK: customer (string) - Customer DID + - SK: space (string) - Space DID + +Attributes: + - status (string) - 'pending' | 'in-progress' | 'completed' | 'failed' + - totalUploads (number) - Total uploads in this space + - completedUploads (number) - Number of uploads migrated + - lastProcessedUpload (string) - Last upload CID processed + - instanceId (string) - EC2 instance processing this space + - workerId (string) - Worker ID processing this space + - error (string) - Error message if failed + - createdAt (string) - ISO timestamp + - updatedAt (string) - ISO timestamp + +Global Secondary Index (GSI): status-index + - PK: status + - SK: updatedAt +``` + +### Table Operations + +All table operations are available in `src/lib/tables/migration-progress-table.js`: + +```javascript +import { + createDynamoClient, + getSpaceProgress, + createSpaceProgress, + updateSpaceProgress, + markSpaceCompleted, + markSpaceFailed, + getCustomerSpaces, + getFailedMigrations, + getStuckMigrations, + getInstanceSpaces, + scanAllProgress, +} from './lib/tables/migration-progress-table.js' +``` + +## Usage + +### Overall Statistics (Default) + +Shows total spaces, uploads, completion percentage, and breakdown by instance: + +```bash +node src/migration-monitor.js +``` + +**Output:** +``` +Migration Progress Overview +====================================================================== + +Total Spaces: 80,304 + ✓ Completed: 45,123 (56.2%) + ⏳ In Progress: 234 + ⏸️ Pending: 34,947 + ✗ Failed: 0 + +Total Uploads: 37,088,337 + ✓ Completed: 20,845,123 (56.2%) + +Progress by Instance: +---------------------------------------------------------------------- + +Instance 1: + Spaces: 8,234/16,060 (51.3%) + Uploads: 4,653,123/8,295,089 (56.1%) + +Instance 2: + Spaces: 9,123/16,061 (56.8%) + Uploads: 4,123,456/7,198,312 (57.3%) +... +``` + +### Customer Progress + +Shows all spaces for a specific customer with their migration status: + +```bash +node src/migration-monitor.js --customer did:mailto:example.com:user +``` + +**Output:** +``` +Customer: did:mailto:example.com:user +====================================================================== + +Total Spaces: 4 + +Status: + ✓ Completed: 2 + ⏳ In Progress: 1 + ⏸️ Pending: 1 + ✗ Failed: 0 + +Spaces: +---------------------------------------------------------------------- + ✓ did:key:z6Mkk... (1,234/1,234 uploads) + ✓ did:key:z6Mkj... (5,678/5,678 uploads) + ⏳ did:key:z6Mki... (234/1,000 uploads) + Instance: 2, Worker: 5 + Updated: 10/31/2025, 3:15:23 PM + ⏸️ did:key:z6Mkh... +``` + +### Space Status + +Shows detailed status for a specific space (requires customer DID): + +```bash +node src/migration-monitor.js --customer did:mailto:example.com:user --space did:key:z6Mkk... +``` + +**Output:** +``` +Space Migration Status +====================================================================== + +Space: did:key:z6Mkk... +Customer: did:mailto:example.com:user +Status: ⏳ in-progress + +Uploads: 234/1,000 + +Instance: 2 +Worker: 5 + +Created: 10/31/2025, 2:00:00 PM +Updated: 10/31/2025, 3:15:23 PM + +Last Upload: bafkreiabc123... +``` + +### Instance Progress + +Shows progress for a specific EC2 instance, grouped by worker: + +```bash +node src/migration-monitor.js --instance 1 +``` + +**Output:** +``` +Instance 1 Progress +====================================================================== + +Total Spaces: 16,060 + ✓ Completed: 8,234 + ⏳ In Progress: 45 + ⏸️ Pending: 7,781 + ✗ Failed: 0 + +Total Uploads: 8,295,089 + ✓ Completed: 4,653,123 + +Progress by Worker: +---------------------------------------------------------------------- + Worker 1: 823/1,606 completed + Worker 2: 834/1,606 completed + In Progress: 2 + Worker 3: 812/1,606 completed + In Progress: 1 +... +``` + +### Failed Migrations + +Lists all failed migrations with error details: + +```bash +node src/migration-monitor.js --failed +``` + +**Output:** +``` +Failed Migrations +====================================================================== + +Total Failed: 3 + +✗ did:key:z6Mkk... + Customer: did:mailto:example.com:user + Instance: 2, Worker: 5 + Error: Connection timeout after 3 retries + Updated: 10/31/2025, 2:45:12 PM + +✗ did:key:z6Mkj... + Customer: did:mailto:example.com:other + Instance: 3, Worker: 2 + Error: Invalid delegation proof + Updated: 10/31/2025, 1:23:45 PM +... +``` + +### Stuck Migrations + +Shows in-progress migrations that haven't updated in over 1 hour: + +```bash +node src/migration-monitor.js --stuck +``` + +**Output:** +``` +Stuck Migrations (in-progress >1 hour) +====================================================================== + +Total Stuck: 2 + +⏳ did:key:z6Mkk... + Customer: did:mailto:example.com:user + Instance: 2, Worker: 5 + Stuck for: 127 minutes + Progress: 234/1,000 uploads + Last Update: 10/31/2025, 1:08:23 PM + +⏳ did:key:z6Mkj... + Customer: did:mailto:example.com:other + Instance: 4, Worker: 8 + Stuck for: 93 minutes + Progress: 567/2,000 uploads + Last Update: 10/31/2025, 1:42:15 PM +``` + +### Live Monitoring + +Auto-refreshes every 30 seconds for real-time monitoring: + +```bash +node src/migration-monitor.js --watch +``` + +Press `Ctrl+C` to stop. + +## Integration with Migration Scripts + +The monitoring script reads from the same DynamoDB table that the migration workers write to. Workers update progress: + +- **On space start:** Create initial progress record +- **Every 1000 uploads:** Update `completedUploads` and `lastProcessedUpload` +- **On space completion:** Mark status as `completed` +- **On space failure:** Mark status as `failed` with error message + +This provides real-time visibility into the migration progress without impacting worker performance. + +## Troubleshooting + +### No data showing + +- Verify the `migration-progress` table exists in DynamoDB +- Check AWS credentials are configured correctly +- Ensure the migration workers have started writing progress + +### Stuck migrations + +If you see stuck migrations (>1 hour with no updates): + +1. Check if the EC2 instance is still running +2. Check instance logs for errors +3. Consider restarting the stuck worker +4. The migration is designed to be idempotent - restarting will resume from the last checkpoint + +### Failed migrations + +For failed migrations: + +1. Review the error message +2. Check if it's a transient error (network timeout) or permanent (invalid data) +3. For transient errors, restart the worker - it will retry +4. For permanent errors, investigate the specific space/customer data + +## Performance Considerations + +- **Scan operations** (overall stats, failed, stuck) scan the entire table - may be slow with millions of records +- **Query operations** (customer, space) use the primary key - fast and efficient +- **Watch mode** runs queries every 30 seconds - use sparingly to avoid DynamoDB throttling +- Consider using the **instance** view for focused monitoring during active migration diff --git a/package.json b/package.json index a650f30..b27f5c9 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,10 @@ "encryption:keygen": "node -e \"console.log(require('crypto').randomBytes(32).toString('base64'))\"" }, "dependencies": { - "@aws-sdk/client-dynamodb": "^3.911.0", - "@aws-sdk/client-s3": "^3.911.0", - "@aws-sdk/util-dynamodb": "^3.911.0", + "@aws-sdk/client-dynamodb": "^3.922.0", + "@aws-sdk/client-s3": "^3.922.0", + "@aws-sdk/lib-dynamodb": "^3.922.0", + "@aws-sdk/util-dynamodb": "^3.922.0", "@ipld/dag-json": "^10.2.5", "@storacha/access": "^1.4.0", "@storacha/blob-index": "^1.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 636373b..44e1107 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,14 +9,17 @@ importers: .: dependencies: '@aws-sdk/client-dynamodb': - specifier: ^3.911.0 - version: 3.911.0 + specifier: ^3.922.0 + version: 3.922.0 '@aws-sdk/client-s3': - specifier: ^3.911.0 - version: 3.916.0 + specifier: ^3.922.0 + version: 3.922.0 + '@aws-sdk/lib-dynamodb': + specifier: ^3.922.0 + version: 3.922.0(@aws-sdk/client-dynamodb@3.922.0) '@aws-sdk/util-dynamodb': - specifier: ^3.911.0 - version: 3.911.0(@aws-sdk/client-dynamodb@3.911.0) + specifier: ^3.922.0 + version: 3.922.0(@aws-sdk/client-dynamodb@3.922.0) '@ipld/dag-json': specifier: ^10.2.5 version: 10.2.5 @@ -92,225 +95,147 @@ packages: '@aws-crypto/util@5.2.0': resolution: {integrity: sha512-4RkU9EsI6ZpBve5fseQlGNUWKMa1RLPQ1dnjnQoe07ldfIzcsGb5hC5W0Dm7u423KWzawlrpbjXBrXCEv9zazQ==} - '@aws-sdk/client-dynamodb@3.911.0': - resolution: {integrity: sha512-5+VdV+e19auVtJMBT8AYab6my27ze+DM4hVfIZ1xD5wTM8B6ig9JiIQzpHNk4hkSG3w317y6GBb0mEObVRbMqw==} + '@aws-sdk/client-dynamodb@3.922.0': + resolution: {integrity: sha512-qq9PxhEY3U5Ged2oa75pnaEKSvr2NvLrgQlfBgxpRmrJerEPJQtFI0kRLW2ahadUNREkPkx2M3IxmloDjSHL9g==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-s3@3.916.0': - resolution: {integrity: sha512-myfO8UkJzF3wxLUV1cKzzxI1oVOe+tsEyUypFt8yrs0WT0usNfjpUOmA4XNjp/wRClpImkEHT0XC1p6xQCuktQ==} + '@aws-sdk/client-s3@3.922.0': + resolution: {integrity: sha512-SZRaZUUAHCWfEyBf4SRSPd29ko4uFoJpfd0E/w1meE68XhFB52FTtz/71UqYcwqZmN+s7oUNFFZT+DE/dnQSEA==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.911.0': - resolution: {integrity: sha512-N9QAeMvN3D1ZyKXkQp4aUgC4wUMuA5E1HuVCkajc0bq1pnH4PIke36YlrDGGREqPlyLFrXCkws2gbL5p23vtlg==} + '@aws-sdk/client-sso@3.922.0': + resolution: {integrity: sha512-jdHs7uy7cSpiMvrxhYmqHyJxgK7hyqw4plG8OQ4YTBpq0SbfAxdoOuOkwJ1IVUUQho4otR1xYYjiX/8e8J8qwQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/client-sso@3.916.0': - resolution: {integrity: sha512-Eu4PtEUL1MyRvboQnoq5YKg0Z9vAni3ccebykJy615xokVZUdA3di2YxHM/hykDQX7lcUC62q9fVIvh0+UNk/w==} + '@aws-sdk/core@3.922.0': + resolution: {integrity: sha512-EvfP4cqJfpO3L2v5vkIlTkMesPtRwWlMfsaW6Tpfm7iYfBOuTi6jx60pMDMTyJNVfh6cGmXwh/kj1jQdR+w99Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.911.0': - resolution: {integrity: sha512-k4QG9A+UCq/qlDJFmjozo6R0eXXfe++/KnCDMmajehIE9kh+b/5DqlGvAmbl9w4e92LOtrY6/DN3mIX1xs4sXw==} + '@aws-sdk/credential-provider-env@3.922.0': + resolution: {integrity: sha512-WikGQpKkROJSK3D3E7odPjZ8tU7WJp5/TgGdRuZw3izsHUeH48xMv6IznafpRTmvHcjAbDQj4U3CJZNAzOK/OQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/core@3.916.0': - resolution: {integrity: sha512-1JHE5s6MD5PKGovmx/F1e01hUbds/1y3X8rD+Gvi/gWVfdg5noO7ZCerpRsWgfzgvCMZC9VicopBqNHCKLykZA==} + '@aws-sdk/credential-provider-http@3.922.0': + resolution: {integrity: sha512-i72DgHMK7ydAEqdzU0Duqh60Q8W59EZmRJ73y0Y5oFmNOqnYsAI+UXyOoCsubp+Dkr6+yOwAn1gPt1XGE9Aowg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.911.0': - resolution: {integrity: sha512-6FWRwWn3LUZzLhqBXB+TPMW2ijCWUqGICSw8bVakEdODrvbiv1RT/MVUayzFwz/ek6e6NKZn6DbSWzx07N9Hjw==} + '@aws-sdk/credential-provider-ini@3.922.0': + resolution: {integrity: sha512-bVF+pI5UCLNkvbiZr/t2fgTtv84s8FCdOGAPxQiQcw5qOZywNuuCCY3wIIchmQr6GJr8YFkEp5LgDCac5EC5aQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-env@3.916.0': - resolution: {integrity: sha512-3gDeqOXcBRXGHScc6xb7358Lyf64NRG2P08g6Bu5mv1Vbg9PKDyCAZvhKLkG7hkdfAM8Yc6UJNhbFxr1ud/tCQ==} + '@aws-sdk/credential-provider-node@3.922.0': + resolution: {integrity: sha512-agCwaD6mBihToHkjycL8ObIS2XOnWypWZZWhJSoWyHwFrhEKz1zGvgylK9Dc711oUfU+zU6J8e0JPKNJMNb3BQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.911.0': - resolution: {integrity: sha512-xUlwKmIUW2fWP/eM3nF5u4CyLtOtyohlhGJ5jdsJokr3MrQ7w0tDITO43C9IhCn+28D5UbaiWnKw5ntkw7aVfA==} + '@aws-sdk/credential-provider-process@3.922.0': + resolution: {integrity: sha512-1DZOYezT6okslpvMW7oA2q+y17CJd4fxjNFH0jtThfswdh9CtG62+wxenqO+NExttq0UMaKisrkZiVrYQBTShw==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-http@3.916.0': - resolution: {integrity: sha512-NmooA5Z4/kPFJdsyoJgDxuqXC1C6oPMmreJjbOPqcwo6E/h2jxaG8utlQFgXe5F9FeJsMx668dtxVxSYnAAqHQ==} + '@aws-sdk/credential-provider-sso@3.922.0': + resolution: {integrity: sha512-nbD3G3hShTYxLCkKMqLkLPtKwAAfxdY/k9jHtZmVBFXek2T6tQrqZHKxlAu+fd23Ga4/Aik7DLQQx1RA1a5ipg==} engines: {node: '>=18.0.0'} - '@aws-sdk/credential-provider-ini@3.911.0': - resolution: {integrity: sha512-bQ86kWAZ0Imn7uWl7uqOYZ2aqlkftPmEc8cQh+QyhmUXbia8II4oYKq/tMek6j3M5UOMCiJVxzJoxemJZA6/sw==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-ini@3.916.0': - resolution: {integrity: sha512-iR0FofvdPs87o6MhfNPv0F6WzB4VZ9kx1hbvmR7bSFCk7l0gc7G4fHJOg4xg2lsCptuETboX3O/78OQ2Djeakw==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-node@3.911.0': - resolution: {integrity: sha512-4oGpLwgQCKNtVoJROztJ4v7lZLhCqcUMX6pe/DQ2aU0TktZX7EczMCIEGjVo5b7yHwSNWt2zW0tDdgVUTsMHPw==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-node@3.916.0': - resolution: {integrity: sha512-8TrMpHqct0zTalf2CP2uODiN/PH9LPdBC6JDgPVK0POELTT4ITHerMxIhYGEiKN+6E4oRwSjM/xVTHCD4nMcrQ==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-process@3.911.0': - resolution: {integrity: sha512-mKshhV5jRQffZjbK9x7bs+uC2IsYKfpzYaBamFsEov3xtARCpOiKaIlM8gYKFEbHT2M+1R3rYYlhhl9ndVWS2g==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-process@3.916.0': - resolution: {integrity: sha512-SXDyDvpJ1+WbotZDLJW1lqP6gYGaXfZJrgFSXIuZjHb75fKeNRgPkQX/wZDdUvCwdrscvxmtyJorp2sVYkMcvA==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-sso@3.911.0': - resolution: {integrity: sha512-JAxd4uWe0Zc9tk6+N0cVxe9XtJVcOx6Ms0k933ZU9QbuRMH6xti/wnZxp/IvGIWIDzf5fhqiGyw5MSyDeI5b1w==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-sso@3.916.0': - resolution: {integrity: sha512-gu9D+c+U/Dp1AKBcVxYHNNoZF9uD4wjAKYCjgSN37j4tDsazwMEylbbZLuRNuxfbXtizbo4/TiaxBXDbWM7AkQ==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.911.0': - resolution: {integrity: sha512-urIbXWWG+cm54RwwTFQuRwPH0WPsMFSDF2/H9qO2J2fKoHRURuyblFCyYG3aVKZGvFBhOizJYexf5+5w3CJKBw==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/credential-provider-web-identity@3.916.0': - resolution: {integrity: sha512-VFnL1EjHiwqi2kR19MLXjEgYBuWViCuAKLGSFGSzfFF/+kSpamVrOSFbqsTk8xwHan8PyNnQg4BNuusXwwLoIw==} + '@aws-sdk/credential-provider-web-identity@3.922.0': + resolution: {integrity: sha512-wjGIhgMHGGQfQTdFaJphNOKyAL8wZs6znJdHADPVURmgR+EWLyN/0fDO1u7wx8xaLMZpbHIFWBEvf9TritR/cQ==} engines: {node: '>=18.0.0'} '@aws-sdk/endpoint-cache@3.893.0': resolution: {integrity: sha512-KSwTfyLZyNLszz5f/yoLC+LC+CRKpeJii/+zVAy7JUOQsKhSykiRUPYUx7o2Sdc4oJfqqUl26A/jSttKYnYtAA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-bucket-endpoint@3.914.0': - resolution: {integrity: sha512-mHLsVnPPp4iq3gL2oEBamfpeETFV0qzxRHmcnCfEP3hualV8YF8jbXGmwPCPopUPQDpbYDBHYtXaoClZikCWPQ==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-endpoint-discovery@3.910.0': - resolution: {integrity: sha512-KZvTt8lUUhkQptu00iSSdf5+6h6NP3L5tP251/4FRh9XDXMdpIoAAGsmamhVySkUSODDaALMHjXPSK5SJq/RYw==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-expect-continue@3.916.0': - resolution: {integrity: sha512-p7TMLZZ/j5NbC7/cz7xNgxLz/OHYuh91MeCZdCedJiyh3rx6gunFtl9eiDtrh+Y8hjs0EwR0zYIuhd6pL1O8zg==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-flexible-checksums@3.916.0': - resolution: {integrity: sha512-CBRRg6slHHBYAm26AWY/pECHK0vVO/peDoNhZiAzUNt4jV6VftotjszEJ904pKGOr7/86CfZxtCnP3CCs3lQjA==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-host-header@3.910.0': - resolution: {integrity: sha512-F9Lqeu80/aTM6S/izZ8RtwSmjfhWjIuxX61LX+/9mxJyEkgaECRxv0chsLQsLHJumkGnXRy/eIyMLBhcTPF5vg==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-host-header@3.914.0': - resolution: {integrity: sha512-7r9ToySQ15+iIgXMF/h616PcQStByylVkCshmQqcdeynD/lCn2l667ynckxW4+ql0Q+Bo/URljuhJRxVJzydNA==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-location-constraint@3.914.0': - resolution: {integrity: sha512-Mpd0Sm9+GN7TBqGnZg1+dO5QZ/EOYEcDTo7KfvoyrXScMlxvYm9fdrUVMmLdPn/lntweZGV3uNrs+huasGOOTA==} - engines: {node: '>=18.0.0'} - - '@aws-sdk/middleware-logger@3.910.0': - resolution: {integrity: sha512-3LJyyfs1USvRuRDla1pGlzGRtXJBXD1zC9F+eE9Iz/V5nkmhyv52A017CvKWmYoR0DM9dzjLyPOI0BSSppEaTw==} + '@aws-sdk/lib-dynamodb@3.922.0': + resolution: {integrity: sha512-9NprxIxxJFqit6l5ncGKn7U8d8ZBH4gaeGR/GBMdNkbvC7hMbBTIsjcVhLLglDHnIywY45t9nSAH0bqLKLo7fw==} engines: {node: '>=18.0.0'} + peerDependencies: + '@aws-sdk/client-dynamodb': ^3.922.0 - '@aws-sdk/middleware-logger@3.914.0': - resolution: {integrity: sha512-/gaW2VENS5vKvJbcE1umV4Ag3NuiVzpsANxtrqISxT3ovyro29o1RezW/Avz/6oJqjnmgz8soe9J1t65jJdiNg==} + '@aws-sdk/middleware-bucket-endpoint@3.922.0': + resolution: {integrity: sha512-Dpr2YeOaLFqt3q1hocwBesynE3x8/dXZqXZRuzSX/9/VQcwYBFChHAm4mTAl4zuvArtDbLrwzWSxmOWYZGtq5w==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.910.0': - resolution: {integrity: sha512-m/oLz0EoCy+WoIVBnXRXJ4AtGpdl0kPE7U+VH9TsuUzHgxY1Re/176Q1HWLBRVlz4gr++lNsgsMWEC+VnAwMpw==} + '@aws-sdk/middleware-endpoint-discovery@3.922.0': + resolution: {integrity: sha512-F7Qhwz/bs/Wkbu4SLwKbAeQKoZ7Bzo+JPpVzSqSJGxEely8KBAfsOItXRF8c0d06OEzyeSyml0S6/3TP8T5KUw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-recursion-detection@3.914.0': - resolution: {integrity: sha512-yiAjQKs5S2JKYc+GrkvGMwkUvhepXDigEXpSJqUseR/IrqHhvGNuOxDxq+8LbDhM4ajEW81wkiBbU+Jl9G82yQ==} + '@aws-sdk/middleware-expect-continue@3.922.0': + resolution: {integrity: sha512-xmnLWMtmHJHJBupSWMUEW1gyxuRIeQ1Ov2xa8Tqq77fPr4Ft2AluEwiDMaZIMHoAvpxWKEEt9Si59Li7GIA+bQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-sdk-s3@3.916.0': - resolution: {integrity: sha512-pjmzzjkEkpJObzmTthqJPq/P13KoNFuEi/x5PISlzJtHofCNcyXeVAQ90yvY2dQ6UXHf511Rh1/ytiKy2A8M0g==} + '@aws-sdk/middleware-flexible-checksums@3.922.0': + resolution: {integrity: sha512-G363np7YcJhf+gBucskdv8cOTbs2TRwocEzRupuqDIooGDlLBlfJrvwehdgtWR8l53yjJR3zcHvGrVPTe2h8Nw==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-ssec@3.914.0': - resolution: {integrity: sha512-V1Oae/oLVbpNb9uWs+v80GKylZCdsbqs2c2Xb1FsAUPtYeSnxFuAWsF3/2AEMSSpFe0dTC5KyWr/eKl2aim9VQ==} + '@aws-sdk/middleware-host-header@3.922.0': + resolution: {integrity: sha512-HPquFgBnq/KqKRVkiuCt97PmWbKtxQ5iUNLEc6FIviqOoZTmaYG3EDsIbuFBz9C4RHJU4FKLmHL2bL3FEId6AA==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.911.0': - resolution: {integrity: sha512-rY3LvGvgY/UI0nmt5f4DRzjEh8135A2TeHcva1bgOmVfOI4vkkGfA20sNRqerOkSO6hPbkxJapO50UJHFzmmyA==} + '@aws-sdk/middleware-location-constraint@3.922.0': + resolution: {integrity: sha512-T4iqd7WQ2DDjCH/0s50mnhdoX+IJns83ZE+3zj9IDlpU0N2aq8R91IG890qTfYkUEdP9yRm0xir/CNed+v6Dew==} engines: {node: '>=18.0.0'} - '@aws-sdk/middleware-user-agent@3.916.0': - resolution: {integrity: sha512-mzF5AdrpQXc2SOmAoaQeHpDFsK2GE6EGcEACeNuoESluPI2uYMpuuNMYrUufdnIAIyqgKlis0NVxiahA5jG42w==} + '@aws-sdk/middleware-logger@3.922.0': + resolution: {integrity: sha512-AkvYO6b80FBm5/kk2E636zNNcNgjztNNUxpqVx+huyGn9ZqGTzS4kLqW2hO6CBe5APzVtPCtiQsXL24nzuOlAg==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.911.0': - resolution: {integrity: sha512-lp/sXbdX/S0EYaMYPVKga0omjIUbNNdFi9IJITgKZkLC6CzspihIoHd5GIdl4esMJevtTQQfkVncXTFkf/a4YA==} + '@aws-sdk/middleware-recursion-detection@3.922.0': + resolution: {integrity: sha512-TtSCEDonV/9R0VhVlCpxZbp/9sxQvTTRKzIf8LxW3uXpby6Wl8IxEciBJlxmSkoqxh542WRcko7NYODlvL/gDA==} engines: {node: '>=18.0.0'} - '@aws-sdk/nested-clients@3.916.0': - resolution: {integrity: sha512-tgg8e8AnVAer0rcgeWucFJ/uNN67TbTiDHfD+zIOPKep0Z61mrHEoeT/X8WxGIOkEn4W6nMpmS4ii8P42rNtnA==} + '@aws-sdk/middleware-sdk-s3@3.922.0': + resolution: {integrity: sha512-ygg8lME1oFAbsH42ed2wtGqfHLoT5irgx6VC4X98j79fV1qXEwwwbqMsAiMQ/HJehpjqAFRVsHox3MHLN48Z5A==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.910.0': - resolution: {integrity: sha512-gzQAkuHI3xyG6toYnH/pju+kc190XmvnB7X84vtN57GjgdQJICt9So/BD0U6h+eSfk9VBnafkVrAzBzWMEFZVw==} + '@aws-sdk/middleware-ssec@3.922.0': + resolution: {integrity: sha512-eHvSJZTSRJO+/tjjGD6ocnPc8q9o3m26+qbwQTu/4V6yOJQ1q+xkDZNqwJQphL+CodYaQ7uljp8g1Ji/AN3D9w==} engines: {node: '>=18.0.0'} - '@aws-sdk/region-config-resolver@3.914.0': - resolution: {integrity: sha512-KlmHhRbn1qdwXUdsdrJ7S/MAkkC1jLpQ11n+XvxUUUCGAJd1gjC7AjxPZUM7ieQ2zcb8bfEzIU7al+Q3ZT0u7Q==} + '@aws-sdk/middleware-user-agent@3.922.0': + resolution: {integrity: sha512-N4Qx/9KP3oVQBJOrSghhz8iZFtUC2NNeSZt88hpPhbqAEAtuX8aD8OzVcpnAtrwWqy82Yd2YTxlkqMGkgqnBsQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/signature-v4-multi-region@3.916.0': - resolution: {integrity: sha512-fuzUMo6xU7e0NBzBA6TQ4FUf1gqNbg4woBSvYfxRRsIfKmSMn9/elXXn4sAE5UKvlwVQmYnb6p7dpVRPyFvnQA==} + '@aws-sdk/nested-clients@3.922.0': + resolution: {integrity: sha512-uYvKCF1TGh/MuJ4TMqmUM0Csuao02HawcseG4LUDyxdUsd/EFuxalWq1Cx4fKZQ2K8F504efZBjctMAMNY+l7A==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.911.0': - resolution: {integrity: sha512-O1c5F1pbEImgEe3Vr8j1gpWu69UXWj3nN3vvLGh77hcrG5dZ8I27tSP5RN4Labm8Dnji/6ia+vqSYpN8w6KN5A==} + '@aws-sdk/region-config-resolver@3.922.0': + resolution: {integrity: sha512-44Y/rNNwhngR2KHp6gkx//TOr56/hx6s4l+XLjOqH7EBCHL7XhnrT1y92L+DLiroVr1tCSmO8eHQwBv0Y2+mvw==} engines: {node: '>=18.0.0'} - '@aws-sdk/token-providers@3.916.0': - resolution: {integrity: sha512-13GGOEgq5etbXulFCmYqhWtpcEQ6WI6U53dvXbheW0guut8fDFJZmEv7tKMTJgiybxh7JHd0rWcL9JQND8DwoQ==} + '@aws-sdk/signature-v4-multi-region@3.922.0': + resolution: {integrity: sha512-mmsgEEL5pE+A7gFYiJMDBCLVciaXq4EFI5iAP7bPpnHvOplnNOYxVy2IreKMllGvrfjVyLnwxzZYlo5zZ65FWg==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.910.0': - resolution: {integrity: sha512-o67gL3vjf4nhfmuSUNNkit0d62QJEwwHLxucwVJkR/rw9mfUtAWsgBs8Tp16cdUbMgsyQtCQilL8RAJDoGtadQ==} + '@aws-sdk/token-providers@3.922.0': + resolution: {integrity: sha512-/inmPnjZE0ZBE16zaCowAvouSx05FJ7p6BQYuzlJ8vxEU0sS0Hf8fvhuiRnN9V9eDUPIBY+/5EjbMWygXL4wlQ==} engines: {node: '>=18.0.0'} - '@aws-sdk/types@3.914.0': - resolution: {integrity: sha512-kQWPsRDmom4yvAfyG6L1lMmlwnTzm1XwMHOU+G5IFlsP4YEaMtXidDzW/wiivY0QFrhfCz/4TVmu0a2aPU57ug==} + '@aws-sdk/types@3.922.0': + resolution: {integrity: sha512-eLA6XjVobAUAMivvM7DBL79mnHyrm+32TkXNWZua5mnxF+6kQCfblKKJvxMZLGosO53/Ex46ogim8IY5Nbqv2w==} engines: {node: '>=18.0.0'} '@aws-sdk/util-arn-parser@3.893.0': resolution: {integrity: sha512-u8H4f2Zsi19DGnwj5FSZzDMhytYF/bCh37vAtBsn3cNDL3YG578X5oc+wSX54pM3tOxS+NY7tvOAo52SW7koUA==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-dynamodb@3.911.0': - resolution: {integrity: sha512-wpBLz/Mz9OM46wciamsHtUuB7itX3RGgaTjNMKfE/xEdzeCJAPLef+zjGhqX0o0LfmEKWxjeYrReZhpJZPtiTg==} + '@aws-sdk/util-dynamodb@3.922.0': + resolution: {integrity: sha512-QEsXGkdy6JzYS1IgaiU40s6cZoQy2/Kr3wcpLL6/JtLn7xHnc86RaC3Si+eQXx71aBwLVAtIuyVynjTV1yIWaw==} engines: {node: '>=18.0.0'} peerDependencies: - '@aws-sdk/client-dynamodb': ^3.911.0 - - '@aws-sdk/util-endpoints@3.910.0': - resolution: {integrity: sha512-6XgdNe42ibP8zCQgNGDWoOF53RfEKzpU/S7Z29FTTJ7hcZv0SytC0ZNQQZSx4rfBl036YWYwJRoJMlT4AA7q9A==} - engines: {node: '>=18.0.0'} + '@aws-sdk/client-dynamodb': ^3.922.0 - '@aws-sdk/util-endpoints@3.916.0': - resolution: {integrity: sha512-bAgUQwvixdsiGNcuZSDAOWbyHlnPtg8G8TyHD6DTfTmKTHUW6tAn+af/ZYJPXEzXhhpwgJqi58vWnsiDhmr7NQ==} + '@aws-sdk/util-endpoints@3.922.0': + resolution: {integrity: sha512-4ZdQCSuNMY8HMlR1YN4MRDdXuKd+uQTeKIr5/pIM+g3TjInZoj8imvXudjcrFGA63UF3t92YVTkBq88mg58RXQ==} engines: {node: '>=18.0.0'} '@aws-sdk/util-locate-window@3.893.0': resolution: {integrity: sha512-T89pFfgat6c8nMmpI8eKjBcDcgJq36+m9oiXbcUzeU55MP9ZuGgBomGjGnHaEyF36jenW9gmg3NfZDm0AO2XPg==} engines: {node: '>=18.0.0'} - '@aws-sdk/util-user-agent-browser@3.910.0': - resolution: {integrity: sha512-iOdrRdLZHrlINk9pezNZ82P/VxO/UmtmpaOAObUN+xplCUJu31WNM2EE/HccC8PQw6XlAudpdA6HDTGiW6yVGg==} - - '@aws-sdk/util-user-agent-browser@3.914.0': - resolution: {integrity: sha512-rMQUrM1ECH4kmIwlGl9UB0BtbHy6ZuKdWFrIknu8yGTRI/saAucqNTh5EI1vWBxZ0ElhK5+g7zOnUuhSmVQYUA==} - - '@aws-sdk/util-user-agent-node@3.911.0': - resolution: {integrity: sha512-3l+f6ooLF6Z6Lz0zGi7vSKSUYn/EePPizv88eZQpEAFunBHv+CSVNPtxhxHfkm7X9tTsV4QGZRIqo3taMLolmA==} - engines: {node: '>=18.0.0'} - peerDependencies: - aws-crt: '>=1.0.0' - peerDependenciesMeta: - aws-crt: - optional: true + '@aws-sdk/util-user-agent-browser@3.922.0': + resolution: {integrity: sha512-qOJAERZ3Plj1st7M4Q5henl5FRpE30uLm6L9edZqZXGR6c7ry9jzexWamWVpQ4H4xVAVmiO9dIEBAfbq4mduOA==} - '@aws-sdk/util-user-agent-node@3.916.0': - resolution: {integrity: sha512-CwfWV2ch6UdjuSV75ZU99N03seEUb31FIUrXBnwa6oONqj/xqXwrxtlUMLx6WH3OJEE4zI3zt5PjlTdGcVwf4g==} + '@aws-sdk/util-user-agent-node@3.922.0': + resolution: {integrity: sha512-NrPe/Rsr5kcGunkog0eBV+bY0inkRELsD2SacC4lQZvZiXf8VJ2Y7j+Yq1tB+h+FPLsdt3v9wItIvDf/laAm0Q==} engines: {node: '>=18.0.0'} peerDependencies: aws-crt: '>=1.0.0' @@ -318,16 +243,12 @@ packages: aws-crt: optional: true - '@aws-sdk/xml-builder@3.911.0': - resolution: {integrity: sha512-/yh3oe26bZfCVGrIMRM9Z4hvvGJD+qx5tOLlydOkuBkm72aXON7D9+MucjJXTAcI8tF2Yq+JHa0478eHQOhnLg==} + '@aws-sdk/xml-builder@3.921.0': + resolution: {integrity: sha512-LVHg0jgjyicKKvpNIEMXIMr1EBViESxcPkqfOlT+X1FkmUMTNZEEVF18tOJg4m4hV5vxtkWcqtr4IEeWa1C41Q==} engines: {node: '>=18.0.0'} - '@aws-sdk/xml-builder@3.914.0': - resolution: {integrity: sha512-k75evsBD5TcIjedycYS7QXQ98AmOtbnxRJOPtCo0IwYRmy7UvqgS/gBL5SmrIqeV6FDSYRQMgdBxSMp6MLmdew==} - engines: {node: '>=18.0.0'} - - '@aws/lambda-invoke-store@0.0.1': - resolution: {integrity: sha512-ORHRQ2tmvnBXc8t/X9Z8IcSbBA4xTLKuN873FopzklHMeqBst7YG0d+AX97inkvDX+NChYtSr+qGfcqGFaI8Zw==} + '@aws/lambda-invoke-store@0.1.1': + resolution: {integrity: sha512-RcLam17LdlbSOSp9VxmUu1eI6Mwxp+OwhD2QhiSNmNCzoDb0EeUXTD2n/WbcnrAYMGlmf05th6QYq23VqvJqpA==} engines: {node: '>=18.0.0'} '@ipld/car@5.4.2': @@ -406,8 +327,8 @@ packages: '@scure/bip39@1.6.0': resolution: {integrity: sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==} - '@smithy/abort-controller@4.2.3': - resolution: {integrity: sha512-xWL9Mf8b7tIFuAlpjKtRPnHrR8XVrwTj5NPYO/QwZPtc0SDLsPxb56V5tzi5yspSMytISHybifez+4jlrx0vkQ==} + '@smithy/abort-controller@4.2.4': + resolution: {integrity: sha512-Z4DUr/AkgyFf1bOThW2HwzREagee0sB5ycl+hDiSZOfRLW8ZgrOjDi6g8mHH19yyU5E2A/64W3z6SMIf5XiUSQ==} engines: {node: '>=18.0.0'} '@smithy/chunked-blob-reader-native@4.2.1': @@ -418,64 +339,56 @@ packages: resolution: {integrity: sha512-WmU0TnhEAJLWvfSeMxBNe5xtbselEO8+4wG0NtZeL8oR21WgH1xiO37El+/Y+H/Ie4SCwBy3MxYWmOYaGgZueA==} engines: {node: '>=18.0.0'} - '@smithy/config-resolver@4.3.3': - resolution: {integrity: sha512-xSql8A1Bl41O9JvGU/CtgiLBlwkvpHTSKRlvz9zOBvBCPjXghZ6ZkcVzmV2f7FLAA+80+aqKmIOmy8pEDrtCaw==} - engines: {node: '>=18.0.0'} - - '@smithy/config-resolver@4.4.0': - resolution: {integrity: sha512-Kkmz3Mup2PGp/HNJxhCWkLNdlajJORLSjwkcfrj0E7nu6STAEdcMR1ir5P9/xOmncx8xXfru0fbUYLlZog/cFg==} - engines: {node: '>=18.0.0'} - - '@smithy/core@3.17.0': - resolution: {integrity: sha512-Tir3DbfoTO97fEGUZjzGeoXgcQAUBRDTmuH9A8lxuP8ATrgezrAJ6cLuRvwdKN4ZbYNlHgKlBX69Hyu3THYhtg==} + '@smithy/config-resolver@4.4.1': + resolution: {integrity: sha512-BciDJ5hkyYEGBBKMbjGB1A/Zq8bYZ41Zo9BMnGdKF6QD1fY4zIkYx6zui/0CHaVGnv6h0iy8y4rnPX9CPCAPyQ==} engines: {node: '>=18.0.0'} - '@smithy/core@3.17.1': - resolution: {integrity: sha512-V4Qc2CIb5McABYfaGiIYLTmo/vwNIK7WXI5aGveBd9UcdhbOMwcvIMxIw/DJj1S9QgOMa/7FBkarMdIC0EOTEQ==} + '@smithy/core@3.17.2': + resolution: {integrity: sha512-n3g4Nl1Te+qGPDbNFAYf+smkRVB+JhFsGy9uJXXZQEufoP4u0r+WLh6KvTDolCswaagysDc/afS1yvb2jnj1gQ==} engines: {node: '>=18.0.0'} - '@smithy/credential-provider-imds@4.2.3': - resolution: {integrity: sha512-hA1MQ/WAHly4SYltJKitEsIDVsNmXcQfYBRv2e+q04fnqtAX5qXaybxy/fhUeAMCnQIdAjaGDb04fMHQefWRhw==} + '@smithy/credential-provider-imds@4.2.4': + resolution: {integrity: sha512-YVNMjhdz2pVto5bRdux7GMs0x1m0Afz3OcQy/4Yf9DH4fWOtroGH7uLvs7ZmDyoBJzLdegtIPpXrpJOZWvUXdw==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-codec@4.2.3': - resolution: {integrity: sha512-rcr0VH0uNoMrtgKuY7sMfyKqbHc4GQaQ6Yp4vwgm+Z6psPuOgL+i/Eo/QWdXRmMinL3EgFM0Z1vkfyPyfzLmjw==} + '@smithy/eventstream-codec@4.2.4': + resolution: {integrity: sha512-aV8blR9RBDKrOlZVgjOdmOibTC2sBXNiT7WA558b4MPdsLTV6sbyc1WIE9QiIuYMJjYtnPLciefoqSW8Gi+MZQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-browser@4.2.3': - resolution: {integrity: sha512-EcS0kydOr2qJ3vV45y7nWnTlrPmVIMbUFOZbMG80+e2+xePQISX9DrcbRpVRFTS5Nqz3FiEbDcTCAV0or7bqdw==} + '@smithy/eventstream-serde-browser@4.2.4': + resolution: {integrity: sha512-d5T7ZS3J/r8P/PDjgmCcutmNxnSRvPH1U6iHeXjzI50sMr78GLmFcrczLw33Ap92oEKqa4CLrkAPeSSOqvGdUA==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-config-resolver@4.3.3': - resolution: {integrity: sha512-GewKGZ6lIJ9APjHFqR2cUW+Efp98xLu1KmN0jOWxQ1TN/gx3HTUPVbLciFD8CfScBj2IiKifqh9vYFRRXrYqXA==} + '@smithy/eventstream-serde-config-resolver@4.3.4': + resolution: {integrity: sha512-lxfDT0UuSc1HqltOGsTEAlZ6H29gpfDSdEPTapD5G63RbnYToZ+ezjzdonCCH90j5tRRCw3aLXVbiZaBW3VRVg==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-node@4.2.3': - resolution: {integrity: sha512-uQobOTQq2FapuSOlmGLUeGTpvcBLE5Fc7XjERUSk4dxEi4AhTwuyHYZNAvL4EMUp7lzxxkKDFaJ1GY0ovrj0Kg==} + '@smithy/eventstream-serde-node@4.2.4': + resolution: {integrity: sha512-TPhiGByWnYyzcpU/K3pO5V7QgtXYpE0NaJPEZBCa1Y5jlw5SjqzMSbFiLb+ZkJhqoQc0ImGyVINqnq1ze0ZRcQ==} engines: {node: '>=18.0.0'} - '@smithy/eventstream-serde-universal@4.2.3': - resolution: {integrity: sha512-QIvH/CKOk1BZPz/iwfgbh1SQD5Y0lpaw2kLA8zpLRRtYMPXeYUEWh+moTaJyqDaKlbrB174kB7FSRFiZ735tWw==} + '@smithy/eventstream-serde-universal@4.2.4': + resolution: {integrity: sha512-GNI/IXaY/XBB1SkGBFmbW033uWA0tj085eCxYih0eccUe/PFR7+UBQv9HNDk2fD9TJu7UVsCWsH99TkpEPSOzQ==} engines: {node: '>=18.0.0'} - '@smithy/fetch-http-handler@5.3.4': - resolution: {integrity: sha512-bwigPylvivpRLCm+YK9I5wRIYjFESSVwl8JQ1vVx/XhCw0PtCi558NwTnT2DaVCl5pYlImGuQTSwMsZ+pIavRw==} + '@smithy/fetch-http-handler@5.3.5': + resolution: {integrity: sha512-mg83SM3FLI8Sa2ooTJbsh5MFfyMTyNRwxqpKHmE0ICRIa66Aodv80DMsTQI02xBLVJ0hckwqTRr5IGAbbWuFLQ==} engines: {node: '>=18.0.0'} - '@smithy/hash-blob-browser@4.2.4': - resolution: {integrity: sha512-W7eIxD+rTNsLB/2ynjmbdeP7TgxRXprfvqQxKFEfy9HW2HeD7t+g+KCIrY0pIn/GFjA6/fIpH+JQnfg5TTk76Q==} + '@smithy/hash-blob-browser@4.2.5': + resolution: {integrity: sha512-kCdgjD2J50qAqycYx0imbkA9tPtyQr1i5GwbK/EOUkpBmJGSkJe4mRJm+0F65TUSvvui1HZ5FFGFCND7l8/3WQ==} engines: {node: '>=18.0.0'} - '@smithy/hash-node@4.2.3': - resolution: {integrity: sha512-6+NOdZDbfuU6s1ISp3UOk5Rg953RJ2aBLNLLBEcamLjHAg1Po9Ha7QIB5ZWhdRUVuOUrT8BVFR+O2KIPmw027g==} + '@smithy/hash-node@4.2.4': + resolution: {integrity: sha512-kKU0gVhx/ppVMntvUOZE7WRMFW86HuaxLwvqileBEjL7PoILI8/djoILw3gPQloGVE6O0oOzqafxeNi2KbnUJw==} engines: {node: '>=18.0.0'} - '@smithy/hash-stream-node@4.2.3': - resolution: {integrity: sha512-EXMSa2yiStVII3x/+BIynyOAZlS7dGvI7RFrzXa/XssBgck/7TXJIvnjnCu328GY/VwHDC4VeDyP1S4rqwpYag==} + '@smithy/hash-stream-node@4.2.4': + resolution: {integrity: sha512-amuh2IJiyRfO5MV0X/YFlZMD6banjvjAwKdeJiYGUbId608x+oSNwv3vlyW2Gt6AGAgl3EYAuyYLGRX/xU8npQ==} engines: {node: '>=18.0.0'} - '@smithy/invalid-dependency@4.2.3': - resolution: {integrity: sha512-Cc9W5DwDuebXEDMpOpl4iERo8I0KFjTnomK2RMdhhR87GwrSmUmwMxS4P5JdRf+LsjOdIqumcerwRgYMr/tZ9Q==} + '@smithy/invalid-dependency@4.2.4': + resolution: {integrity: sha512-z6aDLGiHzsMhbS2MjetlIWopWz//K+mCoPXjW6aLr0mypF+Y7qdEh5TyJ20Onf9FbWHiWl4eC+rITdizpnXqOw==} engines: {node: '>=18.0.0'} '@smithy/is-array-buffer@2.2.0': @@ -486,92 +399,76 @@ packages: resolution: {integrity: sha512-DZZZBvC7sjcYh4MazJSGiWMI2L7E0oCiRHREDzIxi/M2LY79/21iXt6aPLHge82wi5LsuRF5A06Ds3+0mlh6CQ==} engines: {node: '>=18.0.0'} - '@smithy/md5-js@4.2.3': - resolution: {integrity: sha512-5+4bUEJQi/NRgzdA5SVXvAwyvEnD0ZAiKzV3yLO6dN5BG8ScKBweZ8mxXXUtdxq+Dx5k6EshKk0XJ7vgvIPSnA==} + '@smithy/md5-js@4.2.4': + resolution: {integrity: sha512-h7kzNWZuMe5bPnZwKxhVbY1gan5+TZ2c9JcVTHCygB14buVGOZxLl+oGfpY2p2Xm48SFqEWdghpvbBdmaz3ncQ==} engines: {node: '>=18.0.0'} - '@smithy/middleware-content-length@4.2.3': - resolution: {integrity: sha512-/atXLsT88GwKtfp5Jr0Ks1CSa4+lB+IgRnkNrrYP0h1wL4swHNb0YONEvTceNKNdZGJsye+W2HH8W7olbcPUeA==} + '@smithy/middleware-content-length@4.2.4': + resolution: {integrity: sha512-hJRZuFS9UsElX4DJSJfoX4M1qXRH+VFiLMUnhsWvtOOUWRNvvOfDaUSdlNbjwv1IkpVjj/Rd/O59Jl3nhAcxow==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.3.4': - resolution: {integrity: sha512-/RJhpYkMOaUZoJEkddamGPPIYeKICKXOu/ojhn85dKDM0n5iDIhjvYAQLP3K5FPhgB203O3GpWzoK2OehEoIUw==} + '@smithy/middleware-endpoint@4.3.6': + resolution: {integrity: sha512-PXehXofGMFpDqr933rxD8RGOcZ0QBAWtuzTgYRAHAL2BnKawHDEdf/TnGpcmfPJGwonhginaaeJIKluEojiF/w==} engines: {node: '>=18.0.0'} - '@smithy/middleware-endpoint@4.3.5': - resolution: {integrity: sha512-SIzKVTvEudFWJbxAaq7f2GvP3jh2FHDpIFI6/VAf4FOWGFZy0vnYMPSRj8PGYI8Hjt29mvmwSRgKuO3bK4ixDw==} + '@smithy/middleware-retry@4.4.6': + resolution: {integrity: sha512-OhLx131znrEDxZPAvH/OYufR9d1nB2CQADyYFN4C3V/NQS7Mg4V6uvxHC/Dr96ZQW8IlHJTJ+vAhKt6oxWRndA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.4': - resolution: {integrity: sha512-vSgABQAkuUHRO03AhR2rWxVQ1un284lkBn+NFawzdahmzksAoOeVMnXXsuPViL4GlhRHXqFaMlc8Mj04OfQk1w==} + '@smithy/middleware-serde@4.2.4': + resolution: {integrity: sha512-jUr3x2CDhV15TOX2/Uoz4gfgeqLrRoTQbYAuhLS7lcVKNev7FeYSJ1ebEfjk+l9kbb7k7LfzIR/irgxys5ZTOg==} engines: {node: '>=18.0.0'} - '@smithy/middleware-retry@4.4.5': - resolution: {integrity: sha512-DCaXbQqcZ4tONMvvdz+zccDE21sLcbwWoNqzPLFlZaxt1lDtOE2tlVpRSwcTOJrjJSUThdgEYn7HrX5oLGlK9A==} + '@smithy/middleware-stack@4.2.4': + resolution: {integrity: sha512-Gy3TKCOnm9JwpFooldwAboazw+EFYlC+Bb+1QBsSi5xI0W5lX81j/P5+CXvD/9ZjtYKRgxq+kkqd/KOHflzvgA==} engines: {node: '>=18.0.0'} - '@smithy/middleware-serde@4.2.3': - resolution: {integrity: sha512-8g4NuUINpYccxiCXM5s1/V+uLtts8NcX4+sPEbvYQDZk4XoJfDpq5y2FQxfmUL89syoldpzNzA0R9nhzdtdKnQ==} + '@smithy/node-config-provider@4.3.4': + resolution: {integrity: sha512-3X3w7qzmo4XNNdPKNS4nbJcGSwiEMsNsRSunMA92S4DJLLIrH5g1AyuOA2XKM9PAPi8mIWfqC+fnfKNsI4KvHw==} engines: {node: '>=18.0.0'} - '@smithy/middleware-stack@4.2.3': - resolution: {integrity: sha512-iGuOJkH71faPNgOj/gWuEGS6xvQashpLwWB1HjHq1lNNiVfbiJLpZVbhddPuDbx9l4Cgl0vPLq5ltRfSaHfspA==} + '@smithy/node-http-handler@4.4.4': + resolution: {integrity: sha512-VXHGfzCXLZeKnFp6QXjAdy+U8JF9etfpUXD1FAbzY1GzsFJiDQRQIt2CnMUvUdz3/YaHNqT3RphVWMUpXTIODA==} engines: {node: '>=18.0.0'} - '@smithy/node-config-provider@4.3.3': - resolution: {integrity: sha512-NzI1eBpBSViOav8NVy1fqOlSfkLgkUjUTlohUSgAEhHaFWA3XJiLditvavIP7OpvTjDp5u2LhtlBhkBlEisMwA==} + '@smithy/property-provider@4.2.4': + resolution: {integrity: sha512-g2DHo08IhxV5GdY3Cpt/jr0mkTlAD39EJKN27Jb5N8Fb5qt8KG39wVKTXiTRCmHHou7lbXR8nKVU14/aRUf86w==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.4.2': - resolution: {integrity: sha512-MHFvTjts24cjGo1byXqhXrbqm7uznFD/ESFx8npHMWTFQVdBZjrT1hKottmp69LBTRm/JQzP/sn1vPt0/r6AYQ==} + '@smithy/protocol-http@5.3.4': + resolution: {integrity: sha512-3sfFd2MAzVt0Q/klOmjFi3oIkxczHs0avbwrfn1aBqtc23WqQSmjvk77MBw9WkEQcwbOYIX5/2z4ULj8DuxSsw==} engines: {node: '>=18.0.0'} - '@smithy/node-http-handler@4.4.3': - resolution: {integrity: sha512-MAwltrDB0lZB/H6/2M5PIsISSwdI5yIh6DaBB9r0Flo9nx3y0dzl/qTMJPd7tJvPdsx6Ks/cwVzheGNYzXyNbQ==} + '@smithy/querystring-builder@4.2.4': + resolution: {integrity: sha512-KQ1gFXXC+WsbPFnk7pzskzOpn4s+KheWgO3dzkIEmnb6NskAIGp/dGdbKisTPJdtov28qNDohQrgDUKzXZBLig==} engines: {node: '>=18.0.0'} - '@smithy/property-provider@4.2.3': - resolution: {integrity: sha512-+1EZ+Y+njiefCohjlhyOcy1UNYjT+1PwGFHCxA/gYctjg3DQWAU19WigOXAco/Ql8hZokNehpzLd0/+3uCreqQ==} + '@smithy/querystring-parser@4.2.4': + resolution: {integrity: sha512-aHb5cqXZocdzEkZ/CvhVjdw5l4r1aU/9iMEyoKzH4eXMowT6M0YjBpp7W/+XjkBnY8Xh0kVd55GKjnPKlCwinQ==} engines: {node: '>=18.0.0'} - '@smithy/protocol-http@5.3.3': - resolution: {integrity: sha512-Mn7f/1aN2/jecywDcRDvWWWJF4uwg/A0XjFMJtj72DsgHTByfjRltSqcT9NyE9RTdBSN6X1RSXrhn/YWQl8xlw==} + '@smithy/service-error-classification@4.2.4': + resolution: {integrity: sha512-fdWuhEx4+jHLGeew9/IvqVU/fxT/ot70tpRGuOLxE3HzZOyKeTQfYeV1oaBXpzi93WOk668hjMuuagJ2/Qs7ng==} engines: {node: '>=18.0.0'} - '@smithy/querystring-builder@4.2.3': - resolution: {integrity: sha512-LOVCGCmwMahYUM/P0YnU/AlDQFjcu+gWbFJooC417QRB/lDJlWSn8qmPSDp+s4YVAHOgtgbNG4sR+SxF/VOcJQ==} + '@smithy/shared-ini-file-loader@4.3.4': + resolution: {integrity: sha512-y5ozxeQ9omVjbnJo9dtTsdXj9BEvGx2X8xvRgKnV+/7wLBuYJQL6dOa/qMY6omyHi7yjt1OA97jZLoVRYi8lxA==} engines: {node: '>=18.0.0'} - '@smithy/querystring-parser@4.2.3': - resolution: {integrity: sha512-cYlSNHcTAX/wc1rpblli3aUlLMGgKZ/Oqn8hhjFASXMCXjIqeuQBei0cnq2JR8t4RtU9FpG6uyl6PxyArTiwKA==} + '@smithy/signature-v4@5.3.4': + resolution: {integrity: sha512-ScDCpasxH7w1HXHYbtk3jcivjvdA1VICyAdgvVqKhKKwxi+MTwZEqFw0minE+oZ7F07oF25xh4FGJxgqgShz0A==} engines: {node: '>=18.0.0'} - '@smithy/service-error-classification@4.2.3': - resolution: {integrity: sha512-NkxsAxFWwsPsQiwFG2MzJ/T7uIR6AQNh1SzcxSUnmmIqIQMlLRQDKhc17M7IYjiuBXhrQRjQTo3CxX+DobS93g==} + '@smithy/smithy-client@4.9.2': + resolution: {integrity: sha512-gZU4uAFcdrSi3io8U99Qs/FvVdRxPvIMToi+MFfsy/DN9UqtknJ1ais+2M9yR8e0ASQpNmFYEKeIKVcMjQg3rg==} engines: {node: '>=18.0.0'} - '@smithy/shared-ini-file-loader@4.3.3': - resolution: {integrity: sha512-9f9Ixej0hFhroOK2TxZfUUDR13WVa8tQzhSzPDgXe5jGL3KmaM9s8XN7RQwqtEypI82q9KHnKS71CJ+q/1xLtQ==} + '@smithy/types@4.8.1': + resolution: {integrity: sha512-N0Zn0OT1zc+NA+UVfkYqQzviRh5ucWwO7mBV3TmHHprMnfcJNfhlPicDkBHi0ewbh+y3evR6cNAW0Raxvb01NA==} engines: {node: '>=18.0.0'} - '@smithy/signature-v4@5.3.3': - resolution: {integrity: sha512-CmSlUy+eEYbIEYN5N3vvQTRfqt0lJlQkaQUIf+oizu7BbDut0pozfDjBGecfcfWf7c62Yis4JIEgqQ/TCfodaA==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.9.0': - resolution: {integrity: sha512-qz7RTd15GGdwJ3ZCeBKLDQuUQ88m+skh2hJwcpPm1VqLeKzgZvXf6SrNbxvx7uOqvvkjCMXqx3YB5PDJyk00ww==} - engines: {node: '>=18.0.0'} - - '@smithy/smithy-client@4.9.1': - resolution: {integrity: sha512-Ngb95ryR5A9xqvQFT5mAmYkCwbXvoLavLFwmi7zVg/IowFPCfiqRfkOKnbc/ZRL8ZKJ4f+Tp6kSu6wjDQb8L/g==} - engines: {node: '>=18.0.0'} - - '@smithy/types@4.8.0': - resolution: {integrity: sha512-QpELEHLO8SsQVtqP+MkEgCYTFW0pleGozfs3cZ183ZBj9z3VC1CX1/wtFMK64p+5bhtZo41SeLK1rBRtd25nHQ==} - engines: {node: '>=18.0.0'} - - '@smithy/url-parser@4.2.3': - resolution: {integrity: sha512-I066AigYvY3d9VlU3zG9XzZg1yT10aNqvCaBTw9EPgu5GrsEl1aUkcMvhkIXascYH1A8W0LQo3B1Kr1cJNcQEw==} + '@smithy/url-parser@4.2.4': + resolution: {integrity: sha512-w/N/Iw0/PTwJ36PDqU9PzAwVElo4qXxCC0eCTlUtIz/Z5V/2j/cViMHi0hPukSBHp4DVwvUlUhLgCzqSJ6plrg==} engines: {node: '>=18.0.0'} '@smithy/util-base64@4.3.0': @@ -598,44 +495,32 @@ packages: resolution: {integrity: sha512-YEjpl6XJ36FTKmD+kRJJWYvrHeUvm5ykaUS5xK+6oXffQPHeEM4/nXlZPe+Wu0lsgRUcNZiliYNh/y7q9c2y6Q==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-browser@4.3.3': - resolution: {integrity: sha512-vqHoybAuZXbFXZqgzquiUXtdY+UT/aU33sxa4GBPkiYklmR20LlCn+d3Wc3yA5ZM13gQ92SZe/D8xh6hkjx+IQ==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-browser@4.3.4': - resolution: {integrity: sha512-qI5PJSW52rnutos8Bln8nwQZRpyoSRN6k2ajyoUHNMUzmWqHnOJCnDELJuV6m5PML0VkHI+XcXzdB+6awiqYUw==} - engines: {node: '>=18.0.0'} - - '@smithy/util-defaults-mode-node@4.2.4': - resolution: {integrity: sha512-X5/xrPHedifo7hJUUWKlpxVb2oDOiqPUXlvsZv1EZSjILoutLiJyWva3coBpn00e/gPSpH8Rn2eIbgdwHQdW7Q==} + '@smithy/util-defaults-mode-browser@4.3.5': + resolution: {integrity: sha512-GwaGjv/QLuL/QHQaqhf/maM7+MnRFQQs7Bsl6FlaeK6lm6U7mV5AAnVabw68cIoMl5FQFyKK62u7RWRzWL25OQ==} engines: {node: '>=18.0.0'} - '@smithy/util-defaults-mode-node@4.2.6': - resolution: {integrity: sha512-c6M/ceBTm31YdcFpgfgQAJaw3KbaLuRKnAz91iMWFLSrgxRpYm03c3bu5cpYojNMfkV9arCUelelKA7XQT36SQ==} + '@smithy/util-defaults-mode-node@4.2.7': + resolution: {integrity: sha512-6hinjVqec0WYGsqN7h9hL/ywfULmJJNXGXnNZW7jrIn/cFuC/aVlVaiDfBIJEvKcOrmN8/EgsW69eY0gXABeHw==} engines: {node: '>=18.0.0'} - '@smithy/util-endpoints@3.2.3': - resolution: {integrity: sha512-aCfxUOVv0CzBIkU10TubdgKSx5uRvzH064kaiPEWfNIvKOtNpu642P4FP1hgOFkjQIkDObrfIDnKMKkeyrejvQ==} + '@smithy/util-endpoints@3.2.4': + resolution: {integrity: sha512-f+nBDhgYRCmUEDKEQb6q0aCcOTXRDqH5wWaFHJxt4anB4pKHlgGoYP3xtioKXH64e37ANUkzWf6p4Mnv1M5/Vg==} engines: {node: '>=18.0.0'} '@smithy/util-hex-encoding@4.2.0': resolution: {integrity: sha512-CCQBwJIvXMLKxVbO88IukazJD9a4kQ9ZN7/UMGBjBcJYvatpWk+9g870El4cB8/EJxfe+k+y0GmR9CAzkF+Nbw==} engines: {node: '>=18.0.0'} - '@smithy/util-middleware@4.2.3': - resolution: {integrity: sha512-v5ObKlSe8PWUHCqEiX2fy1gNv6goiw6E5I/PN2aXg3Fb/hse0xeaAnSpXDiWl7x6LamVKq7senB+m5LOYHUAHw==} + '@smithy/util-middleware@4.2.4': + resolution: {integrity: sha512-fKGQAPAn8sgV0plRikRVo6g6aR0KyKvgzNrPuM74RZKy/wWVzx3BMk+ZWEueyN3L5v5EDg+P582mKU+sH5OAsg==} engines: {node: '>=18.0.0'} - '@smithy/util-retry@4.2.3': - resolution: {integrity: sha512-lLPWnakjC0q9z+OtiXk+9RPQiYPNAovt2IXD3CP4LkOnd9NpUsxOjMx1SnoUVB7Orb7fZp67cQMtTBKMFDvOGg==} + '@smithy/util-retry@4.2.4': + resolution: {integrity: sha512-yQncJmj4dtv/isTXxRb4AamZHy4QFr4ew8GxS6XLWt7sCIxkPxPzINWd7WLISEFPsIan14zrKgvyAF+/yzfwoA==} engines: {node: '>=18.0.0'} - '@smithy/util-stream@4.5.3': - resolution: {integrity: sha512-oZvn8a5bwwQBNYHT2eNo0EU8Kkby3jeIg1P2Lu9EQtqDxki1LIjGRJM6dJ5CZUig8QmLxWxqOKWvg3mVoOBs5A==} - engines: {node: '>=18.0.0'} - - '@smithy/util-stream@4.5.4': - resolution: {integrity: sha512-+qDxSkiErejw1BAIXUFBSfM5xh3arbz1MmxlbMCKanDDZtVEQ7PSKW9FQS0Vud1eI/kYn0oCTVKyNzRlq+9MUw==} + '@smithy/util-stream@4.5.5': + resolution: {integrity: sha512-7M5aVFjT+HPilPOKbOmQfCIPchZe4DSBc1wf1+NvHvSoFTiFtauZzT+onZvCj70xhXd0AEmYnZYmdJIuwxOo4w==} engines: {node: '>=18.0.0'} '@smithy/util-uri-escape@4.2.0': @@ -650,8 +535,8 @@ packages: resolution: {integrity: sha512-zBPfuzoI8xyBtR2P6WQj63Rz8i3AmfAaJLuNG8dWsfvPe8lO4aCPYLn879mEgHndZH1zQ2oXmG8O1GGzzaoZiw==} engines: {node: '>=18.0.0'} - '@smithy/util-waiter@4.2.3': - resolution: {integrity: sha512-5+nU///E5sAdD7t3hs4uwvCTWQtTR8JwKwOCSJtBRx0bY1isDo1QwH87vRK86vlFLBTISqoDA2V6xvP6nF1isQ==} + '@smithy/util-waiter@4.2.4': + resolution: {integrity: sha512-roKXtXIC6fopFvVOju8VYHtguc/jAcMlK8IlDOHsrQn0ayMkHynjm/D2DCMRf7MJFXzjHhlzg2edr3QPEakchQ==} engines: {node: '>=18.0.0'} '@smithy/uuid@1.1.0': @@ -1058,20 +943,20 @@ snapshots: '@aws-crypto/crc32@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.914.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.914.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/sha1-browser@5.2.0': dependencies: '@aws-crypto/supports-web-crypto': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.914.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-locate-window': 3.893.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -1081,7 +966,7 @@ snapshots: '@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.910.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-locate-window': 3.893.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 @@ -1089,7 +974,7 @@ snapshots: '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 - '@aws-sdk/types': 3.910.0 + '@aws-sdk/types': 3.922.0 tslib: 2.8.1 '@aws-crypto/supports-web-crypto@5.2.0': @@ -1098,772 +983,505 @@ snapshots: '@aws-crypto/util@5.2.0': dependencies: - '@aws-sdk/types': 3.910.0 + '@aws-sdk/types': 3.922.0 '@smithy/util-utf8': 2.3.0 tslib: 2.8.1 - '@aws-sdk/client-dynamodb@3.911.0': + '@aws-sdk/client-dynamodb@3.922.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.911.0 - '@aws-sdk/credential-provider-node': 3.911.0 - '@aws-sdk/middleware-endpoint-discovery': 3.910.0 - '@aws-sdk/middleware-host-header': 3.910.0 - '@aws-sdk/middleware-logger': 3.910.0 - '@aws-sdk/middleware-recursion-detection': 3.910.0 - '@aws-sdk/middleware-user-agent': 3.911.0 - '@aws-sdk/region-config-resolver': 3.910.0 - '@aws-sdk/types': 3.910.0 - '@aws-sdk/util-endpoints': 3.910.0 - '@aws-sdk/util-user-agent-browser': 3.910.0 - '@aws-sdk/util-user-agent-node': 3.911.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/credential-provider-node': 3.922.0 + '@aws-sdk/middleware-endpoint-discovery': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/core': 3.17.2 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.7 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.2.3 + '@smithy/util-waiter': 4.2.4 '@smithy/uuid': 1.1.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-s3@3.916.0': + '@aws-sdk/client-s3@3.922.0': dependencies: '@aws-crypto/sha1-browser': 5.2.0 '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.916.0 - '@aws-sdk/credential-provider-node': 3.916.0 - '@aws-sdk/middleware-bucket-endpoint': 3.914.0 - '@aws-sdk/middleware-expect-continue': 3.916.0 - '@aws-sdk/middleware-flexible-checksums': 3.916.0 - '@aws-sdk/middleware-host-header': 3.914.0 - '@aws-sdk/middleware-location-constraint': 3.914.0 - '@aws-sdk/middleware-logger': 3.914.0 - '@aws-sdk/middleware-recursion-detection': 3.914.0 - '@aws-sdk/middleware-sdk-s3': 3.916.0 - '@aws-sdk/middleware-ssec': 3.914.0 - '@aws-sdk/middleware-user-agent': 3.916.0 - '@aws-sdk/region-config-resolver': 3.914.0 - '@aws-sdk/signature-v4-multi-region': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@aws-sdk/util-endpoints': 3.916.0 - '@aws-sdk/util-user-agent-browser': 3.914.0 - '@aws-sdk/util-user-agent-node': 3.916.0 - '@aws-sdk/xml-builder': 3.914.0 - '@smithy/config-resolver': 4.4.0 - '@smithy/core': 3.17.1 - '@smithy/eventstream-serde-browser': 4.2.3 - '@smithy/eventstream-serde-config-resolver': 4.3.3 - '@smithy/eventstream-serde-node': 4.2.3 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-blob-browser': 4.2.4 - '@smithy/hash-node': 4.2.3 - '@smithy/hash-stream-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/md5-js': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.5 - '@smithy/middleware-retry': 4.4.5 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/credential-provider-node': 3.922.0 + '@aws-sdk/middleware-bucket-endpoint': 3.922.0 + '@aws-sdk/middleware-expect-continue': 3.922.0 + '@aws-sdk/middleware-flexible-checksums': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-location-constraint': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-sdk-s3': 3.922.0 + '@aws-sdk/middleware-ssec': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/signature-v4-multi-region': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@aws-sdk/xml-builder': 3.921.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/core': 3.17.2 + '@smithy/eventstream-serde-browser': 4.2.4 + '@smithy/eventstream-serde-config-resolver': 4.3.4 + '@smithy/eventstream-serde-node': 4.2.4 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-blob-browser': 4.2.5 + '@smithy/hash-node': 4.2.4 + '@smithy/hash-stream-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/md5-js': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.4 - '@smithy/util-defaults-mode-node': 4.2.6 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/util-stream': 4.5.4 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.7 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 + '@smithy/util-stream': 4.5.5 '@smithy/util-utf8': 4.2.0 - '@smithy/util-waiter': 4.2.3 + '@smithy/util-waiter': 4.2.4 '@smithy/uuid': 1.1.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/client-sso@3.911.0': - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.911.0 - '@aws-sdk/middleware-host-header': 3.910.0 - '@aws-sdk/middleware-logger': 3.910.0 - '@aws-sdk/middleware-recursion-detection': 3.910.0 - '@aws-sdk/middleware-user-agent': 3.911.0 - '@aws-sdk/region-config-resolver': 3.910.0 - '@aws-sdk/types': 3.910.0 - '@aws-sdk/util-endpoints': 3.910.0 - '@aws-sdk/util-user-agent-browser': 3.910.0 - '@aws-sdk/util-user-agent-node': 3.911.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/client-sso@3.916.0': + '@aws-sdk/client-sso@3.922.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.916.0 - '@aws-sdk/middleware-host-header': 3.914.0 - '@aws-sdk/middleware-logger': 3.914.0 - '@aws-sdk/middleware-recursion-detection': 3.914.0 - '@aws-sdk/middleware-user-agent': 3.916.0 - '@aws-sdk/region-config-resolver': 3.914.0 - '@aws-sdk/types': 3.914.0 - '@aws-sdk/util-endpoints': 3.916.0 - '@aws-sdk/util-user-agent-browser': 3.914.0 - '@aws-sdk/util-user-agent-node': 3.916.0 - '@smithy/config-resolver': 4.4.0 - '@smithy/core': 3.17.1 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.5 - '@smithy/middleware-retry': 4.4.5 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/core': 3.17.2 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.4 - '@smithy/util-defaults-mode-node': 4.2.6 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.7 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/core@3.911.0': - dependencies: - '@aws-sdk/types': 3.910.0 - '@aws-sdk/xml-builder': 3.911.0 - '@smithy/core': 3.17.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - - '@aws-sdk/core@3.916.0': - dependencies: - '@aws-sdk/types': 3.914.0 - '@aws-sdk/xml-builder': 3.914.0 - '@smithy/core': 3.17.1 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 + '@aws-sdk/core@3.922.0': + dependencies: + '@aws-sdk/types': 3.922.0 + '@aws-sdk/xml-builder': 3.921.0 + '@smithy/core': 3.17.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 '@smithy/util-base64': 4.3.0 - '@smithy/util-middleware': 4.2.3 + '@smithy/util-middleware': 4.2.4 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/credential-provider-env@3.911.0': - dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-env@3.916.0': - dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-http@3.911.0': + '@aws-sdk/credential-provider-env@3.922.0': dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.2 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-http@3.916.0': + '@aws-sdk/credential-provider-http@3.922.0': dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.3 - '@smithy/property-provider': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.4 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/node-http-handler': 4.4.4 + '@smithy/property-provider': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-stream': 4.5.5 tslib: 2.8.1 - '@aws-sdk/credential-provider-ini@3.911.0': + '@aws-sdk/credential-provider-ini@3.922.0': dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/credential-provider-env': 3.911.0 - '@aws-sdk/credential-provider-http': 3.911.0 - '@aws-sdk/credential-provider-process': 3.911.0 - '@aws-sdk/credential-provider-sso': 3.911.0 - '@aws-sdk/credential-provider-web-identity': 3.911.0 - '@aws-sdk/nested-clients': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/credential-provider-env': 3.922.0 + '@aws-sdk/credential-provider-http': 3.922.0 + '@aws-sdk/credential-provider-process': 3.922.0 + '@aws-sdk/credential-provider-sso': 3.922.0 + '@aws-sdk/credential-provider-web-identity': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-ini@3.916.0': - dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/credential-provider-env': 3.916.0 - '@aws-sdk/credential-provider-http': 3.916.0 - '@aws-sdk/credential-provider-process': 3.916.0 - '@aws-sdk/credential-provider-sso': 3.916.0 - '@aws-sdk/credential-provider-web-identity': 3.916.0 - '@aws-sdk/nested-clients': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-node@3.911.0': - dependencies: - '@aws-sdk/credential-provider-env': 3.911.0 - '@aws-sdk/credential-provider-http': 3.911.0 - '@aws-sdk/credential-provider-ini': 3.911.0 - '@aws-sdk/credential-provider-process': 3.911.0 - '@aws-sdk/credential-provider-sso': 3.911.0 - '@aws-sdk/credential-provider-web-identity': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/credential-provider-node@3.916.0': + '@aws-sdk/credential-provider-node@3.922.0': dependencies: - '@aws-sdk/credential-provider-env': 3.916.0 - '@aws-sdk/credential-provider-http': 3.916.0 - '@aws-sdk/credential-provider-ini': 3.916.0 - '@aws-sdk/credential-provider-process': 3.916.0 - '@aws-sdk/credential-provider-sso': 3.916.0 - '@aws-sdk/credential-provider-web-identity': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/credential-provider-env': 3.922.0 + '@aws-sdk/credential-provider-http': 3.922.0 + '@aws-sdk/credential-provider-ini': 3.922.0 + '@aws-sdk/credential-provider-process': 3.922.0 + '@aws-sdk/credential-provider-sso': 3.922.0 + '@aws-sdk/credential-provider-web-identity': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-process@3.911.0': - dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/credential-provider-process@3.916.0': + '@aws-sdk/credential-provider-process@3.922.0': dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/credential-provider-sso@3.911.0': + '@aws-sdk/credential-provider-sso@3.922.0': dependencies: - '@aws-sdk/client-sso': 3.911.0 - '@aws-sdk/core': 3.911.0 - '@aws-sdk/token-providers': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/client-sso': 3.922.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/token-providers': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-sso@3.916.0': + '@aws-sdk/credential-provider-web-identity@3.922.0': dependencies: - '@aws-sdk/client-sso': 3.916.0 - '@aws-sdk/core': 3.916.0 - '@aws-sdk/token-providers': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/credential-provider-web-identity@3.911.0': + '@aws-sdk/endpoint-cache@3.893.0': dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/nested-clients': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + mnemonist: 0.38.3 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/credential-provider-web-identity@3.916.0': + '@aws-sdk/lib-dynamodb@3.922.0(@aws-sdk/client-dynamodb@3.922.0)': dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/nested-clients': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/client-dynamodb': 3.922.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/util-dynamodb': 3.922.0(@aws-sdk/client-dynamodb@3.922.0) + '@smithy/core': 3.17.2 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/endpoint-cache@3.893.0': + '@aws-sdk/middleware-bucket-endpoint@3.922.0': dependencies: - mnemonist: 0.38.3 - tslib: 2.8.1 - - '@aws-sdk/middleware-bucket-endpoint@3.914.0': - dependencies: - '@aws-sdk/types': 3.914.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-arn-parser': 3.893.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 '@smithy/util-config-provider': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-endpoint-discovery@3.910.0': + '@aws-sdk/middleware-endpoint-discovery@3.922.0': dependencies: '@aws-sdk/endpoint-cache': 3.893.0 - '@aws-sdk/types': 3.910.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-expect-continue@3.916.0': + '@aws-sdk/middleware-expect-continue@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-flexible-checksums@3.916.0': + '@aws-sdk/middleware-flexible-checksums@3.922.0': dependencies: '@aws-crypto/crc32': 5.2.0 '@aws-crypto/crc32c': 5.2.0 '@aws-crypto/util': 5.2.0 - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 '@smithy/is-array-buffer': 4.2.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.910.0': + '@aws-sdk/middleware-host-header@3.922.0': dependencies: - '@aws-sdk/types': 3.910.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-host-header@3.914.0': + '@aws-sdk/middleware-location-constraint@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-location-constraint@3.914.0': + '@aws-sdk/middleware-logger@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.910.0': + '@aws-sdk/middleware-recursion-detection@3.922.0': dependencies: - '@aws-sdk/types': 3.910.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@aws/lambda-invoke-store': 0.1.1 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-logger@3.914.0': + '@aws-sdk/middleware-sdk-s3@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-recursion-detection@3.910.0': - dependencies: - '@aws-sdk/types': 3.910.0 - '@aws/lambda-invoke-store': 0.0.1 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-recursion-detection@3.914.0': - dependencies: - '@aws-sdk/types': 3.914.0 - '@aws/lambda-invoke-store': 0.0.1 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-sdk-s3@3.916.0': - dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 '@aws-sdk/util-arn-parser': 3.893.0 - '@smithy/core': 3.17.1 - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 + '@smithy/core': 3.17.2 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@aws-sdk/middleware-ssec@3.914.0': - dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/middleware-user-agent@3.911.0': + '@aws-sdk/middleware-ssec@3.922.0': dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@aws-sdk/util-endpoints': 3.910.0 - '@smithy/core': 3.17.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/middleware-user-agent@3.916.0': + '@aws-sdk/middleware-user-agent@3.922.0': dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@aws-sdk/util-endpoints': 3.916.0 - '@smithy/core': 3.17.1 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@smithy/core': 3.17.2 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/nested-clients@3.911.0': + '@aws-sdk/nested-clients@3.922.0': dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.911.0 - '@aws-sdk/middleware-host-header': 3.910.0 - '@aws-sdk/middleware-logger': 3.910.0 - '@aws-sdk/middleware-recursion-detection': 3.910.0 - '@aws-sdk/middleware-user-agent': 3.911.0 - '@aws-sdk/region-config-resolver': 3.910.0 - '@aws-sdk/types': 3.910.0 - '@aws-sdk/util-endpoints': 3.910.0 - '@aws-sdk/util-user-agent-browser': 3.910.0 - '@aws-sdk/util-user-agent-node': 3.911.0 - '@smithy/config-resolver': 4.3.3 - '@smithy/core': 3.17.0 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-retry': 4.4.4 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.2 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/middleware-host-header': 3.922.0 + '@aws-sdk/middleware-logger': 3.922.0 + '@aws-sdk/middleware-recursion-detection': 3.922.0 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/region-config-resolver': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@aws-sdk/util-endpoints': 3.922.0 + '@aws-sdk/util-user-agent-browser': 3.922.0 + '@aws-sdk/util-user-agent-node': 3.922.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/core': 3.17.2 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/hash-node': 4.2.4 + '@smithy/invalid-dependency': 4.2.4 + '@smithy/middleware-content-length': 4.2.4 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-retry': 4.4.6 + '@smithy/middleware-serde': 4.2.4 + '@smithy/middleware-stack': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/node-http-handler': 4.4.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.3 - '@smithy/util-defaults-mode-node': 4.2.4 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/util-defaults-mode-browser': 4.3.5 + '@smithy/util-defaults-mode-node': 4.2.7 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/nested-clients@3.916.0': + '@aws-sdk/region-config-resolver@3.922.0': dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/core': 3.916.0 - '@aws-sdk/middleware-host-header': 3.914.0 - '@aws-sdk/middleware-logger': 3.914.0 - '@aws-sdk/middleware-recursion-detection': 3.914.0 - '@aws-sdk/middleware-user-agent': 3.916.0 - '@aws-sdk/region-config-resolver': 3.914.0 - '@aws-sdk/types': 3.914.0 - '@aws-sdk/util-endpoints': 3.916.0 - '@aws-sdk/util-user-agent-browser': 3.914.0 - '@aws-sdk/util-user-agent-node': 3.916.0 - '@smithy/config-resolver': 4.4.0 - '@smithy/core': 3.17.1 - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/hash-node': 4.2.3 - '@smithy/invalid-dependency': 4.2.3 - '@smithy/middleware-content-length': 4.2.3 - '@smithy/middleware-endpoint': 4.3.5 - '@smithy/middleware-retry': 4.4.5 - '@smithy/middleware-serde': 4.2.3 - '@smithy/middleware-stack': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/node-http-handler': 4.4.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-body-length-node': 4.2.1 - '@smithy/util-defaults-mode-browser': 4.3.4 - '@smithy/util-defaults-mode-node': 4.2.6 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/util-utf8': 4.2.0 - tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - - '@aws-sdk/region-config-resolver@3.910.0': - dependencies: - '@aws-sdk/types': 3.910.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 - tslib: 2.8.1 - - '@aws-sdk/region-config-resolver@3.914.0': - dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/config-resolver': 4.4.0 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/signature-v4-multi-region@3.916.0': - dependencies: - '@aws-sdk/middleware-sdk-s3': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/signature-v4': 5.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/token-providers@3.911.0': + '@aws-sdk/signature-v4-multi-region@3.922.0': dependencies: - '@aws-sdk/core': 3.911.0 - '@aws-sdk/nested-clients': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/middleware-sdk-s3': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/signature-v4': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - transitivePeerDependencies: - - aws-crt - '@aws-sdk/token-providers@3.916.0': + '@aws-sdk/token-providers@3.922.0': dependencies: - '@aws-sdk/core': 3.916.0 - '@aws-sdk/nested-clients': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@aws-sdk/core': 3.922.0 + '@aws-sdk/nested-clients': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 transitivePeerDependencies: - aws-crt - '@aws-sdk/types@3.910.0': + '@aws-sdk/types@3.922.0': dependencies: - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/types@3.914.0': - dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@aws-sdk/util-arn-parser@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-dynamodb@3.911.0(@aws-sdk/client-dynamodb@3.911.0)': - dependencies: - '@aws-sdk/client-dynamodb': 3.911.0 - tslib: 2.8.1 - - '@aws-sdk/util-endpoints@3.910.0': + '@aws-sdk/util-dynamodb@3.922.0(@aws-sdk/client-dynamodb@3.922.0)': dependencies: - '@aws-sdk/types': 3.910.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-endpoints': 3.2.3 + '@aws-sdk/client-dynamodb': 3.922.0 tslib: 2.8.1 - '@aws-sdk/util-endpoints@3.916.0': + '@aws-sdk/util-endpoints@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-endpoints': 3.2.3 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-endpoints': 3.2.4 tslib: 2.8.1 '@aws-sdk/util-locate-window@3.893.0': dependencies: tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.910.0': + '@aws-sdk/util-user-agent-browser@3.922.0': dependencies: - '@aws-sdk/types': 3.910.0 - '@smithy/types': 4.8.0 + '@aws-sdk/types': 3.922.0 + '@smithy/types': 4.8.1 bowser: 2.12.1 tslib: 2.8.1 - '@aws-sdk/util-user-agent-browser@3.914.0': + '@aws-sdk/util-user-agent-node@3.922.0': dependencies: - '@aws-sdk/types': 3.914.0 - '@smithy/types': 4.8.0 - bowser: 2.12.1 - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-node@3.911.0': - dependencies: - '@aws-sdk/middleware-user-agent': 3.911.0 - '@aws-sdk/types': 3.910.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/util-user-agent-node@3.916.0': - dependencies: - '@aws-sdk/middleware-user-agent': 3.916.0 - '@aws-sdk/types': 3.914.0 - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@aws-sdk/xml-builder@3.911.0': - dependencies: - '@smithy/types': 4.8.0 - fast-xml-parser: 5.2.5 + '@aws-sdk/middleware-user-agent': 3.922.0 + '@aws-sdk/types': 3.922.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@aws-sdk/xml-builder@3.914.0': + '@aws-sdk/xml-builder@3.921.0': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 fast-xml-parser: 5.2.5 tslib: 2.8.1 - '@aws/lambda-invoke-store@0.0.1': {} + '@aws/lambda-invoke-store@0.1.1': {} '@ipld/car@5.4.2': dependencies: @@ -1950,9 +1568,9 @@ snapshots: '@noble/hashes': 1.8.0 '@scure/base': 1.2.6 - '@smithy/abort-controller@4.2.3': + '@smithy/abort-controller@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/chunked-blob-reader-native@4.2.1': @@ -1964,118 +1582,97 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/config-resolver@4.3.3': - dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-config-provider': 4.2.0 - '@smithy/util-middleware': 4.2.3 - tslib: 2.8.1 - - '@smithy/config-resolver@4.4.0': + '@smithy/config-resolver@4.4.1': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 '@smithy/util-config-provider': 4.2.0 - '@smithy/util-endpoints': 3.2.3 - '@smithy/util-middleware': 4.2.3 - tslib: 2.8.1 - - '@smithy/core@3.17.0': - dependencies: - '@smithy/middleware-serde': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.3 - '@smithy/util-utf8': 4.2.0 - '@smithy/uuid': 1.1.0 + '@smithy/util-endpoints': 3.2.4 + '@smithy/util-middleware': 4.2.4 tslib: 2.8.1 - '@smithy/core@3.17.1': + '@smithy/core@3.17.2': dependencies: - '@smithy/middleware-serde': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/middleware-serde': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 '@smithy/util-base64': 4.3.0 '@smithy/util-body-length-browser': 4.2.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-stream': 4.5.4 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-stream': 4.5.5 '@smithy/util-utf8': 4.2.0 '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/credential-provider-imds@4.2.3': + '@smithy/credential-provider-imds@4.2.4': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 tslib: 2.8.1 - '@smithy/eventstream-codec@4.2.3': + '@smithy/eventstream-codec@4.2.4': dependencies: '@aws-crypto/crc32': 5.2.0 - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 '@smithy/util-hex-encoding': 4.2.0 tslib: 2.8.1 - '@smithy/eventstream-serde-browser@4.2.3': + '@smithy/eventstream-serde-browser@4.2.4': dependencies: - '@smithy/eventstream-serde-universal': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/eventstream-serde-universal': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-config-resolver@4.3.3': + '@smithy/eventstream-serde-config-resolver@4.3.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-node@4.2.3': + '@smithy/eventstream-serde-node@4.2.4': dependencies: - '@smithy/eventstream-serde-universal': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/eventstream-serde-universal': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/eventstream-serde-universal@4.2.3': + '@smithy/eventstream-serde-universal@4.2.4': dependencies: - '@smithy/eventstream-codec': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/eventstream-codec': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/fetch-http-handler@5.3.4': + '@smithy/fetch-http-handler@5.3.5': dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/querystring-builder': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/querystring-builder': 4.2.4 + '@smithy/types': 4.8.1 '@smithy/util-base64': 4.3.0 tslib: 2.8.1 - '@smithy/hash-blob-browser@4.2.4': + '@smithy/hash-blob-browser@4.2.5': dependencies: '@smithy/chunked-blob-reader': 5.2.0 '@smithy/chunked-blob-reader-native': 4.2.1 - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/hash-node@4.2.3': + '@smithy/hash-node@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 '@smithy/util-buffer-from': 4.2.0 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/hash-stream-node@4.2.3': + '@smithy/hash-stream-node@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/invalid-dependency@4.2.3': + '@smithy/invalid-dependency@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/is-array-buffer@2.2.0': @@ -2086,167 +1683,126 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/md5-js@4.2.3': + '@smithy/md5-js@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/middleware-content-length@4.2.3': - dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.3.4': - dependencies: - '@smithy/core': 3.17.0 - '@smithy/middleware-serde': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-middleware': 4.2.3 - tslib: 2.8.1 - - '@smithy/middleware-endpoint@4.3.5': + '@smithy/middleware-content-length@4.2.4': dependencies: - '@smithy/core': 3.17.1 - '@smithy/middleware-serde': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 - '@smithy/url-parser': 4.2.3 - '@smithy/util-middleware': 4.2.3 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.4': + '@smithy/middleware-endpoint@4.3.6': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/service-error-classification': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 - '@smithy/uuid': 1.1.0 + '@smithy/core': 3.17.2 + '@smithy/middleware-serde': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 + '@smithy/url-parser': 4.2.4 + '@smithy/util-middleware': 4.2.4 tslib: 2.8.1 - '@smithy/middleware-retry@4.4.5': + '@smithy/middleware-retry@4.4.6': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/service-error-classification': 4.2.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - '@smithy/util-middleware': 4.2.3 - '@smithy/util-retry': 4.2.3 + '@smithy/node-config-provider': 4.3.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/service-error-classification': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 + '@smithy/util-middleware': 4.2.4 + '@smithy/util-retry': 4.2.4 '@smithy/uuid': 1.1.0 tslib: 2.8.1 - '@smithy/middleware-serde@4.2.3': - dependencies: - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@smithy/middleware-stack@4.2.3': + '@smithy/middleware-serde@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/node-config-provider@4.3.3': + '@smithy/middleware-stack@4.2.4': dependencies: - '@smithy/property-provider': 4.2.3 - '@smithy/shared-ini-file-loader': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.4.2': + '@smithy/node-config-provider@4.3.4': dependencies: - '@smithy/abort-controller': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/querystring-builder': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.4 + '@smithy/shared-ini-file-loader': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/node-http-handler@4.4.3': + '@smithy/node-http-handler@4.4.4': dependencies: - '@smithy/abort-controller': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/querystring-builder': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/abort-controller': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/querystring-builder': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/property-provider@4.2.3': + '@smithy/property-provider@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/protocol-http@5.3.3': + '@smithy/protocol-http@5.3.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/querystring-builder@4.2.3': + '@smithy/querystring-builder@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 '@smithy/util-uri-escape': 4.2.0 tslib: 2.8.1 - '@smithy/querystring-parser@4.2.3': + '@smithy/querystring-parser@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/service-error-classification@4.2.3': + '@smithy/service-error-classification@4.2.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 - '@smithy/shared-ini-file-loader@4.3.3': + '@smithy/shared-ini-file-loader@4.3.4': dependencies: - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/signature-v4@5.3.3': + '@smithy/signature-v4@5.3.4': dependencies: '@smithy/is-array-buffer': 4.2.0 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-middleware': 4.2.3 + '@smithy/util-middleware': 4.2.4 '@smithy/util-uri-escape': 4.2.0 '@smithy/util-utf8': 4.2.0 tslib: 2.8.1 - '@smithy/smithy-client@4.9.0': - dependencies: - '@smithy/core': 3.17.0 - '@smithy/middleware-endpoint': 4.3.4 - '@smithy/middleware-stack': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.3 - tslib: 2.8.1 - - '@smithy/smithy-client@4.9.1': + '@smithy/smithy-client@4.9.2': dependencies: - '@smithy/core': 3.17.1 - '@smithy/middleware-endpoint': 4.3.5 - '@smithy/middleware-stack': 4.2.3 - '@smithy/protocol-http': 5.3.3 - '@smithy/types': 4.8.0 - '@smithy/util-stream': 4.5.4 + '@smithy/core': 3.17.2 + '@smithy/middleware-endpoint': 4.3.6 + '@smithy/middleware-stack': 4.2.4 + '@smithy/protocol-http': 5.3.4 + '@smithy/types': 4.8.1 + '@smithy/util-stream': 4.5.5 tslib: 2.8.1 - '@smithy/types@4.8.0': + '@smithy/types@4.8.1': dependencies: tslib: 2.8.1 - '@smithy/url-parser@4.2.3': + '@smithy/url-parser@4.2.4': dependencies: - '@smithy/querystring-parser': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/querystring-parser': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/util-base64@4.3.0': @@ -2277,77 +1833,49 @@ snapshots: dependencies: tslib: 2.8.1 - '@smithy/util-defaults-mode-browser@4.3.3': - dependencies: - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-browser@4.3.4': - dependencies: - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@smithy/util-defaults-mode-node@4.2.4': + '@smithy/util-defaults-mode-browser@4.3.5': dependencies: - '@smithy/config-resolver': 4.3.3 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.0 - '@smithy/types': 4.8.0 + '@smithy/property-provider': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-defaults-mode-node@4.2.6': + '@smithy/util-defaults-mode-node@4.2.7': dependencies: - '@smithy/config-resolver': 4.4.0 - '@smithy/credential-provider-imds': 4.2.3 - '@smithy/node-config-provider': 4.3.3 - '@smithy/property-provider': 4.2.3 - '@smithy/smithy-client': 4.9.1 - '@smithy/types': 4.8.0 + '@smithy/config-resolver': 4.4.1 + '@smithy/credential-provider-imds': 4.2.4 + '@smithy/node-config-provider': 4.3.4 + '@smithy/property-provider': 4.2.4 + '@smithy/smithy-client': 4.9.2 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-endpoints@3.2.3': + '@smithy/util-endpoints@3.2.4': dependencies: - '@smithy/node-config-provider': 4.3.3 - '@smithy/types': 4.8.0 + '@smithy/node-config-provider': 4.3.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/util-hex-encoding@4.2.0': dependencies: tslib: 2.8.1 - '@smithy/util-middleware@4.2.3': - dependencies: - '@smithy/types': 4.8.0 - tslib: 2.8.1 - - '@smithy/util-retry@4.2.3': + '@smithy/util-middleware@4.2.4': dependencies: - '@smithy/service-error-classification': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-stream@4.5.3': + '@smithy/util-retry@4.2.4': dependencies: - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.2 - '@smithy/types': 4.8.0 - '@smithy/util-base64': 4.3.0 - '@smithy/util-buffer-from': 4.2.0 - '@smithy/util-hex-encoding': 4.2.0 - '@smithy/util-utf8': 4.2.0 + '@smithy/service-error-classification': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 - '@smithy/util-stream@4.5.4': + '@smithy/util-stream@4.5.5': dependencies: - '@smithy/fetch-http-handler': 5.3.4 - '@smithy/node-http-handler': 4.4.3 - '@smithy/types': 4.8.0 + '@smithy/fetch-http-handler': 5.3.5 + '@smithy/node-http-handler': 4.4.4 + '@smithy/types': 4.8.1 '@smithy/util-base64': 4.3.0 '@smithy/util-buffer-from': 4.2.0 '@smithy/util-hex-encoding': 4.2.0 @@ -2368,10 +1896,10 @@ snapshots: '@smithy/util-buffer-from': 4.2.0 tslib: 2.8.1 - '@smithy/util-waiter@4.2.3': + '@smithy/util-waiter@4.2.4': dependencies: - '@smithy/abort-controller': 4.2.3 - '@smithy/types': 4.8.0 + '@smithy/abort-controller': 4.2.4 + '@smithy/types': 4.8.1 tslib: 2.8.1 '@smithy/uuid@1.1.0': diff --git a/src/config.js b/src/config.js index 7bf6e16..45250c0 100644 --- a/src/config.js +++ b/src/config.js @@ -61,6 +61,7 @@ export const config = { subscription: process.env.SUBSCRIPTION_TABLE_NAME || `${env.tablePrefix}-subscription`, // Billing subscriptions (customer -> provider relationship) delegation: process.env.DELEGATION_TABLE_NAME || `${env.tablePrefix}-delegation`, // Delegation storage migrationSpaces: process.env.MIGRATION_SPACES_TABLE_NAME || `${process.env.STORACHA_ENV || 'prod'}-migration-spaces`, // Migration space tracking + migrationProgress: process.env.MIGRATION_PROGRESS_TABLE_NAME || `${process.env.STORACHA_ENV === 'staging' ? 'staging' : 'prod'}-migration-progress`, // Migration progress tracking }, services: { diff --git a/src/lib/dynamo-client.js b/src/lib/dynamo-client.js new file mode 100644 index 0000000..1a15dac --- /dev/null +++ b/src/lib/dynamo-client.js @@ -0,0 +1,46 @@ +/** + * Centralized DynamoDB Document Client + * + * Provides a cached DynamoDB Document Client instance that can be reused + * across all table operations to avoid creating multiple connections. + */ +import { DynamoDBClient } from '@aws-sdk/client-dynamodb' +import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb' +import { config } from '../config.js' + +/** + * Cached DynamoDB Document Client instance + */ +let cachedClient = null + +/** + * Get or create DynamoDB Document Client + * + * Returns a cached instance to avoid creating multiple connections. + * The Document Client automatically handles marshalling/unmarshalling + * between JavaScript types and DynamoDB types. + * + * @returns {DynamoDBDocumentClient} + */ +export function getDynamoClient() { + if (!cachedClient) { + const baseClient = new DynamoDBClient({ + region: config.aws.region, + credentials: config.aws.accessKeyId ? { + accessKeyId: config.aws.accessKeyId, + secretAccessKey: config.aws.secretAccessKey, + } : undefined, + }) + + cachedClient = DynamoDBDocumentClient.from(baseClient) + } + + return cachedClient +} + +/** + * Reset the cached client (useful for testing) + */ +export function resetClient() { + cachedClient = null +} diff --git a/src/lib/indexing-service.js b/src/lib/indexing-service.js index ed4106c..dfb5597 100644 --- a/src/lib/indexing-service.js +++ b/src/lib/indexing-service.js @@ -5,7 +5,7 @@ import { Client } from '@storacha/indexing-service-client' import * as Digest from 'multiformats/hashes/digest' import { CID } from 'multiformats/cid' import { config } from '../config.js' -q + /** * Create indexing service client */ diff --git a/src/lib/migration-steps.js b/src/lib/migration-steps.js index 7e6cba3..2b73dd3 100644 --- a/src/lib/migration-steps.js +++ b/src/lib/migration-steps.js @@ -17,7 +17,6 @@ import { Absentee } from '@ucanto/principal' import * as UCAN from '@storacha/capabilities/ucan' import { Assert } from '@web3-storage/content-claims/capability' import { Access, Space as SpaceCapabilities } from '@storacha/capabilities' -import * as DID from '@ipld/dag-ucan/did' import { CID } from 'multiformats/cid' import { base58btc } from 'multiformats/bases/base58' import * as Digest from 'multiformats/hashes/digest' @@ -348,16 +347,21 @@ export async function createGatewayAuth({ space }) { channel: HTTP.open({ url: gatewayServiceURL }) }) - // Parse gateway DID - const gatewayPrincipal = DID.parse(config.gateway.did) - console.log(` Gateway: ${gatewayPrincipal}`) + // Gateway DID (audience for the delegation) + const gatewayDID = config.gateway.did + console.log(` Gateway: ${gatewayDID}`) + + // Create principal object for ucanto + const gatewayPrincipal = { + did: () => gatewayDID + } try { // Create delegation with Absentee issuer (space doesn't have private key) // This grants the gateway the ability to serve content from this space const delegation = await SpaceCapabilities.contentServe.delegate({ issuer: Absentee.from({ id: space }), - audience: gatewayPrincipal, + audience: gatewayPrincipal, // Principal with did() method with: space, nb: {}, // No additional caveats - allows serving all content expiration: Infinity, @@ -377,7 +381,7 @@ export async function createGatewayAuth({ space }) { // Publish to gateway via access/delegate const result = await Access.delegate.invoke({ issuer: serviceSigner, - audience: gatewayPrincipal, + audience: gatewayPrincipal, // Principal with did() method with: space, nb: { delegations: { @@ -401,3 +405,119 @@ export async function createGatewayAuth({ space }) { // Don't throw - allow migration to continue } } + +/** + * Verify that all migration steps completed successfully + * + * Re-queries the indexing service to confirm: + * 1. Index claim exists for the root CID + * 2. Location claims exist for all shards + * 3. Location claims include space information + * + * Note: Gateway authorization verification is not included as it would require + * querying the gateway's CloudFlare KV store, which is not easily accessible + * from this script. We rely on the access/delegate invocation response to + * confirm the delegation was stored. + * + * @param {object} params + * @param {object} params.upload - Upload record from Upload Table + * @param {string} params.upload.space - Space DID + * @param {string} params.upload.root - Root CID + * @param {string[]} params.upload.shards - Shard CIDs + * @returns {Promise<{ + * success: boolean, + * indexVerified: boolean, + * locationClaimsVerified: boolean, + * allShardsHaveSpace: boolean, + * shardsWithoutSpace: string[], + * details: string + * }>} + */ +export async function verifyMigration({ upload }) { + console.log(` Verifying migration...`) + console.log(` Root: ${upload.root}`) + console.log(` Space: ${upload.space}`) + console.log(` Shards: ${upload.shards.length}`) + + try { + // Query indexing service to check current state + const indexingData = await queryIndexingService(upload.root) + + // Verify index claim exists + const indexVerified = indexingData.hasIndexClaim + console.log(` Index claim: ${indexVerified ? '✓ EXISTS' : '✗ MISSING'}`) + + // Extract location claims + const locationClaims = indexingData.claims.filter(c => c.type === 'assert/location') + console.log(` Location claims found: ${locationClaims.length}`) + + // Build map of shard multihash -> location claim with space info + const shardLocationMap = new Map() + for (const claim of locationClaims) { + const contentMultihash = claim.content.multihash ?? Digest.decode(claim.content.digest) + const hasSpace = claim.space != null && claim.space === upload.space + shardLocationMap.set(base58btc.encode(contentMultihash.bytes), { claim, hasSpace }) + } + + // Check each shard + const shardsWithoutSpace = [] + let allShardsHaveLocationClaims = true + + for (const shardCID of upload.shards) { + const cid = CID.parse(shardCID) + const multihashStr = base58btc.encode(cid.multihash.bytes) + const locationInfo = shardLocationMap.get(multihashStr) + + if (!locationInfo) { + allShardsHaveLocationClaims = false + shardsWithoutSpace.push(shardCID) + console.log(` ✗ ${shardCID}: no location claim`) + } else if (!locationInfo.hasSpace) { + shardsWithoutSpace.push(shardCID) + console.log(` ✗ ${shardCID}: location claim missing space field`) + } else { + console.log(` ✓ ${shardCID}: location claim with space`) + } + } + + const locationClaimsVerified = allShardsHaveLocationClaims + const allShardsHaveSpace = shardsWithoutSpace.length === 0 + const success = indexVerified && locationClaimsVerified && allShardsHaveSpace + + // Summary + console.log(`\n Verification Summary:`) + console.log(` Index: ${indexVerified ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Location claims: ${locationClaimsVerified ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Space information: ${allShardsHaveSpace ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Shards without space: ${shardsWithoutSpace.length}/${upload.shards.length}`) + console.log(` Overall: ${success ? '✓ PASSED' : '✗ FAILED'}`) + + let details = '' + if (!success) { + const issues = [] + if (!indexVerified) issues.push('index claim missing') + if (!locationClaimsVerified) issues.push('location claims missing') + if (!allShardsHaveSpace) issues.push(`${shardsWithoutSpace.length} shards missing space info`) + details = issues.join(', ') + } + + return { + success, + indexVerified, + locationClaimsVerified, + allShardsHaveSpace, + shardsWithoutSpace, + details: details || 'All verification checks passed', + } + } catch (error) { + console.error(` ✗ Verification failed: ${error.message}`) + return { + success: false, + indexVerified: false, + locationClaimsVerified: false, + allShardsHaveSpace: false, + shardsWithoutSpace: upload.shards, + details: `Verification error: ${error.message}`, + } + } +} diff --git a/src/lib/migration-verify.js b/src/lib/migration-verify.js new file mode 100644 index 0000000..9662143 --- /dev/null +++ b/src/lib/migration-verify.js @@ -0,0 +1,120 @@ +import { queryIndexingService } from './indexing-service.js' +import { CID } from 'multiformats/cid' +import { base58btc } from 'multiformats/bases/base58' +import * as Digest from 'multiformats/hashes/digest' + +/** + * Verify that all migration steps completed successfully + * + * Re-queries the indexing service to confirm: + * 1. Index claim exists for the root CID + * 2. Location claims exist for all shards + * 3. Location claims include space information + * + * Note: Gateway authorization verification is not included as it would require + * querying the gateway's CloudFlare KV store, which is not easily accessible + * from this script. We rely on the access/delegate invocation response to + * confirm the delegation was stored. + * + * @param {object} params + * @param {object} params.upload - Upload record from Upload Table + * @param {string} params.upload.space - Space DID + * @param {string} params.upload.root - Root CID + * @param {string[]} params.upload.shards - Shard CIDs + * @returns {Promise<{ + * success: boolean, + * indexVerified: boolean, + * locationClaimsVerified: boolean, + * allShardsHaveSpace: boolean, + * shardsWithoutSpace: string[], + * details: string + * }>} + */ +export async function verifyMigration({ upload }) { + console.log(` Verifying migration...`) + console.log(` Root: ${upload.root}`) + console.log(` Space: ${upload.space}`) + console.log(` Shards: ${upload.shards.length}`) + + try { + // Query indexing service to check current state + const indexingData = await queryIndexingService(upload.root) + + // Verify index claim exists + const indexVerified = indexingData.hasIndexClaim + console.log(` Index claim: ${indexVerified ? '✓ EXISTS' : '✗ MISSING'}`) + + // Extract location claims + const locationClaims = indexingData.claims.filter(c => c.type === 'assert/location') + console.log(` Location claims found: ${locationClaims.length}`) + + // Build map of shard multihash -> location claim with space info + const shardLocationMap = new Map() + for (const claim of locationClaims) { + const contentMultihash = claim.content.multihash ?? Digest.decode(claim.content.digest) + const hasSpace = claim.space != null && claim.space === upload.space + shardLocationMap.set(base58btc.encode(contentMultihash.bytes), { claim, hasSpace }) + } + + // Check each shard + const shardsWithoutSpace = [] + let allShardsHaveLocationClaims = true + + for (const shardCID of upload.shards) { + const cid = CID.parse(shardCID) + const multihashStr = base58btc.encode(cid.multihash.bytes) + const locationInfo = shardLocationMap.get(multihashStr) + + if (!locationInfo) { + allShardsHaveLocationClaims = false + shardsWithoutSpace.push(shardCID) + console.log(` ✗ ${shardCID}: no location claim`) + } else if (!locationInfo.hasSpace) { + shardsWithoutSpace.push(shardCID) + console.log(` ✗ ${shardCID}: location claim missing space field`) + } else { + console.log(` ✓ ${shardCID}: location claim with space`) + } + } + + const locationClaimsVerified = allShardsHaveLocationClaims + const allShardsHaveSpace = shardsWithoutSpace.length === 0 + const success = indexVerified && locationClaimsVerified && allShardsHaveSpace + + // Summary + console.log(`\n Verification Summary:`) + console.log(` Index: ${indexVerified ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Location claims: ${locationClaimsVerified ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Space information: ${allShardsHaveSpace ? '✓ VERIFIED' : '✗ FAILED'}`) + console.log(` Shards without space: ${shardsWithoutSpace.length}/${upload.shards.length}`) + console.log(` Overall: ${success ? '✓ PASSED' : '✗ FAILED'}`) + + let details = '' + if (!success) { + const issues = [] + if (!indexVerified) issues.push('index claim missing') + if (!locationClaimsVerified) issues.push('location claims missing') + if (!allShardsHaveSpace) issues.push(`${shardsWithoutSpace.length} shards missing space info`) + details = issues.join(', ') + } + + return { + success, + indexVerified, + locationClaimsVerified, + allShardsHaveSpace, + shardsWithoutSpace, + details: details || 'All verification checks passed', + } + } catch (error) { + console.error(` ✗ Verification failed: ${error.message}`) + return { + success: false, + indexVerified: false, + locationClaimsVerified: false, + allShardsHaveSpace: false, + shardsWithoutSpace: upload.shards, + details: `Verification error: ${error.message}`, + } + } +} diff --git a/src/lib/tables/consumer-table.js b/src/lib/tables/consumer-table.js index 281c976..30b6d5a 100644 --- a/src/lib/tables/consumer-table.js +++ b/src/lib/tables/consumer-table.js @@ -1,23 +1,10 @@ /** * Query Consumer Table to get space ownership (space -> customer mapping) */ -import { DynamoDBClient, QueryCommand, PutItemCommand, GetItemCommand } from '@aws-sdk/client-dynamodb' -import { unmarshall, marshall } from '@aws-sdk/util-dynamodb' +import { GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb' import { CBOR } from '@ucanto/core' import { config } from '../../config.js' - -/** - * Create DynamoDB client - */ -export function createDynamoClient() { - return new DynamoDBClient({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) -} +import { getDynamoClient } from '../dynamo-client.js' // Cache for space -> customer lookups // Reset per migration run (not persistent across restarts) @@ -36,18 +23,16 @@ export async function getCustomerForSpace(space) { } // Query DynamoDB - const client = createDynamoClient() + const client = getDynamoClient() - const command = new GetItemCommand({ + const command = new GetCommand({ TableName: config.tables.consumer, - Key: marshall({ consumer: space }), + Key: { consumer: space }, }) const response = await client.send(command) - const customer = response.Items && response.Items.length > 0 - ? unmarshall(response.Items[0]).customer || null - : null + const customer = response.Item?.customer || null // Cache the result (even if null to avoid repeated failed lookups) customerCache.set(space, customer) @@ -102,7 +87,7 @@ async function createProvisionSubscriptionId(space) { * @returns {Promise} */ export async function provisionSpace(customer, space, provider) { - const client = createDynamoClient() + const client = getDynamoClient() const providerDID = provider || config.credentials.serviceDID const now = new Date().toISOString() @@ -111,14 +96,14 @@ export async function provisionSpace(customer, space, provider) { const subscription = await createProvisionSubscriptionId(space) // Step 1: Create subscription record (idempotent) - const subscriptionCommand = new PutItemCommand({ + const subscriptionCommand = new PutCommand({ TableName: config.tables.subscription, - Item: marshall({ + Item: { subscription, // Subscription ID (partition key) provider: providerDID, // Provider DID (sort key) customer, // Customer account DID insertedAt: now, - }), + }, // Don't overwrite if already exists (idempotent) ConditionExpression: 'attribute_not_exists(subscription) AND attribute_not_exists(provider)', }) @@ -136,15 +121,15 @@ export async function provisionSpace(customer, space, provider) { } // Step 2: Create consumer record (links space to subscription) - const consumerCommand = new PutItemCommand({ + const consumerCommand = new PutCommand({ TableName: config.tables.consumer, - Item: marshall({ + Item: { subscription, // Subscription ID (partition key) provider: providerDID, // Provider DID (sort key) consumer: space, // Space DID (indexed via GSI) customer, // Customer account DID insertedAt: now, - }), + }, // Don't overwrite if already exists (idempotent) ConditionExpression: 'attribute_not_exists(subscription) AND attribute_not_exists(provider)', }) diff --git a/src/lib/tables/delegations-table.js b/src/lib/tables/delegations-table.js index 52bbbec..e1c101a 100644 --- a/src/lib/tables/delegations-table.js +++ b/src/lib/tables/delegations-table.js @@ -4,37 +4,33 @@ * Stores delegations in DynamoDB for indexing and S3/R2 for content. * Simplified version for migration - only supports putMany for now. */ -import { DynamoDBClient, BatchWriteItemCommand } from '@aws-sdk/client-dynamodb' +import { BatchWriteCommand } from '@aws-sdk/lib-dynamodb' import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3' -import { marshall } from '@aws-sdk/util-dynamodb' import { base32 } from 'multiformats/bases/base32' import { delegationsToBytes } from '@storacha/access/encoding' import { config } from '../../config.js' +import { getDynamoClient } from '../dynamo-client.js' /** - * Create DynamoDB client + * Cached S3 client */ -function createDynamoClient() { - return new DynamoDBClient({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) -} +let cachedS3Client = null /** - * Create S3 client for delegation bucket + * Get or create S3 client for delegation bucket + * Caches the client to avoid creating multiple connections */ -function createS3Client() { - return new S3Client({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) +function getS3Client() { + if (!cachedS3Client) { + cachedS3Client = new S3Client({ + region: config.aws.region, + credentials: config.aws.accessKeyId ? { + accessKeyId: config.aws.accessKeyId, + secretAccessKey: config.aws.secretAccessKey, + } : undefined, + }) + } + return cachedS3Client } /** @@ -65,8 +61,8 @@ export async function storeDelegations(delegations, options = {}) { return } - const dynamoClient = createDynamoClient() - const s3Client = createS3Client() + const dynamoClient = getDynamoClient() + const s3Client = getS3Client() // Store delegation CAR bytes in S3/R2 (matching w3infra format) // Each delegation is encoded as a CAR file containing just that delegation @@ -86,17 +82,17 @@ export async function storeDelegations(delegations, options = {}) { })) // Index delegations in DynamoDB - const batchWrite = new BatchWriteItemCommand({ + const batchWrite = new BatchWriteCommand({ RequestItems: { [config.tables.delegation]: delegations.map(d => ({ PutRequest: { - Item: marshall({ + Item: { link: d.cid.toString(), audience: d.audience.did(), issuer: d.issuer.did(), expiration: d.expiration === Infinity ? null : d.expiration, cause: options.cause?.toString(), // CID of space/index/add invocation - }, { removeUndefinedValues: true }) + } } })) } diff --git a/src/lib/tables/migration-progress-table.js b/src/lib/tables/migration-progress-table.js new file mode 100644 index 0000000..094cd5e --- /dev/null +++ b/src/lib/tables/migration-progress-table.js @@ -0,0 +1,300 @@ +/** + * Migration Progress Table operations + * + * Tracks space-level migration progress for resume/retry capability + * + * Table Schema: + * - PK: customer (string) - Customer DID + * - SK: space (string) - Space DID + * - status (string) - 'pending' | 'in-progress' | 'completed' | 'failed' + * - totalUploads (number) - Total uploads in this space + * - completedUploads (number) - Number of uploads migrated + * - lastProcessedUpload (string) - Last upload CID processed + * - instanceId (string) - EC2 instance processing this space + * - workerId (string) - Worker ID processing this space + * - error (string) - Error message if failed + * - createdAt (string) - ISO timestamp + * - updatedAt (string) - ISO timestamp + * + * GSI: status-index + * - PK: status + * - SK: updatedAt + */ + +import { + GetCommand, + PutCommand, + UpdateCommand, + QueryCommand, + ScanCommand +} from '@aws-sdk/lib-dynamodb' +import { config } from '../../config.js' +import { getDynamoClient } from '../dynamo-client.js' + +const PROGRESS_TABLE = config.tables.migrationProgress + +/** + * Get progress for a specific space + * + * @param {string} customer - Customer DID + * @param {string} space - Space DID + * @returns {Promise} + */ +export async function getSpaceProgress(customer, space) { + const client = getDynamoClient() + + const command = new GetCommand({ + TableName: PROGRESS_TABLE, + Key: { customer, space }, + }) + + const response = await client.send(command) + return response.Item || null +} + +/** + * Create initial progress record for a space + * + * @param {object} params + * @param {string} params.customer - Customer DID + * @param {string} params.space - Space DID + * @param {number} params.totalUploads - Total uploads in space + * @param {string} params.instanceId - EC2 instance ID + * @param {string} params.workerId - Worker ID + * @returns {Promise} + */ +export async function createSpaceProgress({ customer, space, totalUploads, instanceId, workerId }) { + const client = getDynamoClient() + + const now = new Date().toISOString() + + const command = new PutCommand({ + TableName: PROGRESS_TABLE, + Item: { + customer, + space, + status: 'in-progress', + totalUploads, + completedUploads: 0, + instanceId, + workerId, + createdAt: now, + updatedAt: now, + }, + ConditionExpression: 'attribute_not_exists(customer)', + }) + + try { + await client.send(command) + } catch (error) { + // If already exists, that's OK (resume scenario) + if (error.name === 'ConditionalCheckFailedException') { + return + } + throw error + } +} + +/** + * Update progress for a space + * + * @param {object} params + * @param {string} params.customer - Customer DID + * @param {string} params.space - Space DID + * @param {number} params.completedUploads - Number of uploads completed + * @param {string} [params.lastProcessedUpload] - Last upload CID processed + * @returns {Promise} + */ +export async function updateSpaceProgress({ customer, space, completedUploads, lastProcessedUpload }) { + const client = getDynamoClient() + + const updateExpression = lastProcessedUpload + ? 'SET completedUploads = :completed, lastProcessedUpload = :lastUpload, updatedAt = :now' + : 'SET completedUploads = :completed, updatedAt = :now' + + const expressionValues = { + ':completed': completedUploads, + ':now': new Date().toISOString(), + } + + if (lastProcessedUpload) { + expressionValues[':lastUpload'] = lastProcessedUpload + } + + const command = new UpdateCommand({ + TableName: PROGRESS_TABLE, + Key: { customer, space }, + UpdateExpression: updateExpression, + ExpressionAttributeValues: expressionValues, + }) + + await client.send(command) +} + +/** + * Mark space as completed + * + * @param {string} customer - Customer DID + * @param {string} space - Space DID + * @returns {Promise} + */ +export async function markSpaceCompleted(customer, space) { + const client = getDynamoClient() + + const command = new UpdateCommand({ + TableName: PROGRESS_TABLE, + Key: { customer, space }, + UpdateExpression: 'SET #status = :status, updatedAt = :now', + ExpressionAttributeNames: { + '#status': 'status', + }, + ExpressionAttributeValues: { + ':status': 'completed', + ':now': new Date().toISOString(), + }, + }) + + await client.send(command) +} + +/** + * Mark space as failed + * + * @param {string} customer - Customer DID + * @param {string} space - Space DID + * @param {string} error - Error message + * @returns {Promise} + */ +export async function markSpaceFailed(customer, space, error) { + const client = getDynamoClient() + + const command = new UpdateCommand({ + TableName: PROGRESS_TABLE, + Key: { customer, space }, + UpdateExpression: 'SET #status = :status, #error = :error, updatedAt = :now', + ExpressionAttributeNames: { + '#status': 'status', + '#error': 'error', + }, + ExpressionAttributeValues: { + ':status': 'failed', + ':error': error, + ':now': new Date().toISOString(), + }, + }) + + await client.send(command) +} + +/** + * Get all spaces for a customer + * + * @param {string} customer - Customer DID + * @returns {Promise>} + */ +export async function getCustomerSpaces(customer) { + const client = getDynamoClient() + + const command = new QueryCommand({ + TableName: PROGRESS_TABLE, + KeyConditionExpression: 'customer = :customer', + ExpressionAttributeValues: { + ':customer': customer, + }, + }) + + const response = await client.send(command) + return response.Items || [] +} + +/** + * Get failed migrations + * + * @returns {Promise>} + */ +export async function getFailedMigrations() { + const client = getDynamoClient() + + const command = new ScanCommand({ + TableName: PROGRESS_TABLE, + FilterExpression: '#status = :failed', + ExpressionAttributeNames: { + '#status': 'status', + }, + ExpressionAttributeValues: { + ':failed': 'failed', + }, + }) + + const response = await client.send(command) + return response.Items || [] +} + +/** + * Get stuck migrations (in-progress for >1 hour) + * + * @returns {Promise>} + */ +export async function getStuckMigrations() { + const client = getDynamoClient() + const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000).toISOString() + + const command = new ScanCommand({ + TableName: PROGRESS_TABLE, + FilterExpression: '#status = :inProgress AND updatedAt < :threshold', + ExpressionAttributeNames: { + '#status': 'status', + }, + ExpressionAttributeValues: { + ':inProgress': 'in-progress', + ':threshold': oneHourAgo, + }, + }) + + const response = await client.send(command) + return response.Items || [] +} + +/** + * Get spaces by instance + * + * @param {string} instanceId - Instance ID + * @returns {Promise>} + */ +export async function getInstanceSpaces(instanceId) { + const client = getDynamoClient() + + const command = new ScanCommand({ + TableName: PROGRESS_TABLE, + FilterExpression: 'instanceId = :instanceId', + ExpressionAttributeValues: { + ':instanceId': instanceId, + }, + }) + + const response = await client.send(command) + return response.Items || [] +} + +/** + * Scan all progress records (for statistics) + * + * @param {object} [options] + * @param {object} [options.lastEvaluatedKey] - For pagination + * @returns {Promise<{items: Array, lastEvaluatedKey?: object}>} + */ +export async function scanAllProgress(options = {}) { + const client = getDynamoClient() + + const command = new ScanCommand({ + TableName: PROGRESS_TABLE, + ExclusiveStartKey: options.lastEvaluatedKey, + }) + + const response = await client.send(command) + + return { + items: response.Items || [], + lastEvaluatedKey: response.LastEvaluatedKey, + } +} diff --git a/src/lib/tables/migration-spaces-table.js b/src/lib/tables/migration-spaces-table.js index f698321..2c635d7 100644 --- a/src/lib/tables/migration-spaces-table.js +++ b/src/lib/tables/migration-spaces-table.js @@ -3,22 +3,9 @@ * * Tracks one migration space per customer for storing legacy indexes */ -import { DynamoDBClient, GetItemCommand, PutItemCommand, UpdateItemCommand } from '@aws-sdk/client-dynamodb' -import { marshall, unmarshall } from '@aws-sdk/util-dynamodb' +import { GetCommand, PutCommand, UpdateCommand } from '@aws-sdk/lib-dynamodb' import { config } from '../../config.js' - -/** - * Create DynamoDB client - */ -export function createDynamoClient() { - return new DynamoDBClient({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) -} +import { getDynamoClient } from '../dynamo-client.js' /** * Get migration space for a customer @@ -27,11 +14,11 @@ export function createDynamoClient() { * @returns {Promise<{migrationSpace: string, spaceName: string, indexCount: number, privateKey?: string} | null>} */ export async function getMigrationSpace(customer) { - const client = createDynamoClient() + const client = getDynamoClient() - const command = new GetItemCommand({ + const command = new GetCommand({ TableName: config.tables.migrationSpaces, - Key: marshall({ customer }), + Key: { customer }, ProjectionExpression: 'migrationSpace, spaceName, indexCount, #status, privateKey', ExpressionAttributeNames: { '#status': 'status', @@ -44,13 +31,12 @@ export async function getMigrationSpace(customer) { return null } - const item = unmarshall(response.Item) return { - migrationSpace: item.migrationSpace, - spaceName: item.spaceName, - indexCount: item.indexCount || 0, - status: item.status, - privateKey: item.privateKey, // Encrypted private key + migrationSpace: response.Item.migrationSpace, + spaceName: response.Item.spaceName, + indexCount: response.Item.indexCount || 0, + status: response.Item.status, + privateKey: response.Item.privateKey, // Encrypted private key } } @@ -65,7 +51,7 @@ export async function getMigrationSpace(customer) { * @returns {Promise} */ export async function createMigrationSpace({ customer, migrationSpace, spaceName, privateKey }) { - const client = createDynamoClient() + const client = getDynamoClient() const now = new Date().toISOString() @@ -84,9 +70,9 @@ export async function createMigrationSpace({ customer, migrationSpace, spaceName item.privateKey = privateKey } - const command = new PutItemCommand({ + const command = new PutCommand({ TableName: config.tables.migrationSpaces, - Item: marshall(item), + Item: item, // Prevent overwriting if race condition ConditionExpression: 'attribute_not_exists(customer)', }) @@ -135,17 +121,17 @@ export async function markSpaceAsProvisioned(customer) { * @returns {Promise} */ export async function incrementIndexCount(customer) { - const client = createDynamoClient() + const client = getDynamoClient() - const command = new UpdateItemCommand({ + const command = new UpdateCommand({ TableName: config.tables.migrationSpaces, - Key: marshall({ customer }), + Key: { customer }, UpdateExpression: 'SET indexCount = if_not_exists(indexCount, :zero) + :one, lastUsed = :now', - ExpressionAttributeValues: marshall({ + ExpressionAttributeValues: { ':zero': 0, ':one': 1, ':now': new Date().toISOString(), - }), + }, }) await client.send(command) diff --git a/src/lib/tables/shard-data-table.js b/src/lib/tables/shard-data-table.js index f46ea3a..a03a674 100644 --- a/src/lib/tables/shard-data-table.js +++ b/src/lib/tables/shard-data-table.js @@ -5,24 +5,11 @@ * We are still writing to the blob-registry and allocations tables. * So we can query either table to get the size, but the majority of the data is in allocations. */ -import { DynamoDBClient, QueryCommand } from '@aws-sdk/client-dynamodb' -import { unmarshall } from '@aws-sdk/util-dynamodb' +import { QueryCommand } from '@aws-sdk/lib-dynamodb' import { CID } from 'multiformats/cid' import { base58btc } from 'multiformats/bases/base58' import { config } from '../../config.js' - -/** - * Create DynamoDB client - */ -export function createDynamoClient() { - return new DynamoDBClient({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) -} +import { getDynamoClient } from '../dynamo-client.js' /** * Query allocations table to get shard size @@ -37,7 +24,7 @@ export function createDynamoClient() { * @returns {Promise} - Blob size in bytes */ export async function getShardSize(space, shardCID) { - const client = createDynamoClient() + const client = getDynamoClient() // Parse the CAR CID to get its multihash const cid = CID.parse(shardCID) @@ -53,15 +40,15 @@ export async function getShardSize(space, shardCID) { '#space': 'space', }, ExpressionAttributeValues: { - ':space': { S: space }, - ':multihash': { S: digest }, + ':space': space, + ':multihash': digest, }, }) const allocationsResponse = await client.send(allocationsCommand) if (allocationsResponse.Items && allocationsResponse.Items.length > 0) { - const blob = unmarshall(allocationsResponse.Items[0]) + const blob = allocationsResponse.Items[0] return parseInt(blob.size, 10) } @@ -74,14 +61,14 @@ export async function getShardSize(space, shardCID) { '#space': 'space', }, ExpressionAttributeValues: { - ':space': { S: space }, - ':link': { S: shardCID }, + ':space': space, + ':link': shardCID, }, }) const storeResponse = await client.send(storeCommand) if (storeResponse.Items && storeResponse.Items.length > 0) { - const blob = unmarshall(storeResponse.Items[0]) + const blob = storeResponse.Items[0] return parseInt(blob.size, 10) } diff --git a/src/lib/tables/upload-table.js b/src/lib/tables/upload-table.js index 34ae6f9..1dd2507 100644 --- a/src/lib/tables/upload-table.js +++ b/src/lib/tables/upload-table.js @@ -1,22 +1,9 @@ /** * Query Upload Table to get legacy uploads */ -import { DynamoDBClient, ScanCommand, QueryCommand } from '@aws-sdk/client-dynamodb' -import { unmarshall } from '@aws-sdk/util-dynamodb' +import { ScanCommand, QueryCommand } from '@aws-sdk/lib-dynamodb' import { config } from '../../config.js' - -/** - * Create DynamoDB client - */ -export function createDynamoClient() { - return new DynamoDBClient({ - region: config.aws.region, - credentials: config.aws.accessKeyId ? { - accessKeyId: config.aws.accessKeyId, - secretAccessKey: config.aws.secretAccessKey, - } : undefined, - }) -} +import { getDynamoClient } from '../dynamo-client.js' /** * Sample uploads from the Upload Table @@ -27,7 +14,7 @@ export function createDynamoClient() { * @returns {AsyncGenerator<{space: string, root: string, shards: string[], insertedAt: string}>} */ export async function* sampleUploads({ limit, space }) { - const client = createDynamoClient() + const client = getDynamoClient() let count = 0 let lastEvaluatedKey @@ -40,7 +27,7 @@ export async function* sampleUploads({ limit, space }) { '#space': 'space', }, ExpressionAttributeValues: { - ':space': { S: space }, + ':space': space, }, Limit: Math.min(100, limit - count), ExclusiveStartKey: lastEvaluatedKey, @@ -57,8 +44,7 @@ export async function* sampleUploads({ limit, space }) { break } - for (const item of response.Items) { - const upload = unmarshall(item) + for (const upload of response.Items) { yield { space: upload.space, root: upload.root, @@ -88,7 +74,7 @@ export async function* sampleUploads({ limit, space }) { * @returns {Promise<{space: string, root: string, shards: string[]} | null>} */ export async function getUpload(space, root) { - const client = createDynamoClient() + const client = getDynamoClient() const command = new QueryCommand({ TableName: config.tables.upload, @@ -98,8 +84,8 @@ export async function getUpload(space, root) { '#root': 'root', }, ExpressionAttributeValues: { - ':space': { S: space }, - ':root': { S: root }, + ':space': space, + ':root': root, }, }) @@ -109,7 +95,7 @@ export async function getUpload(space, root) { return null } - const upload = unmarshall(response.Items[0]) + const upload = response.Items[0] return { space: upload.space, root: upload.root, @@ -125,7 +111,7 @@ export async function getUpload(space, root) { * @returns {Promise<{space: string, root: string, shards: string[]} | null>} */ export async function getUploadByRoot(root) { - const client = createDynamoClient() + const client = getDynamoClient() const command = new QueryCommand({ TableName: config.tables.upload, @@ -135,7 +121,7 @@ export async function getUploadByRoot(root) { '#root': 'root', }, ExpressionAttributeValues: { - ':root': { S: root }, + ':root': root, }, Limit: 1, }) @@ -146,7 +132,7 @@ export async function getUploadByRoot(root) { return null } - const upload = unmarshall(response.Items[0]) + const upload = response.Items[0] return { space: upload.space, root: upload.root, diff --git a/src/migrate.js b/src/migrate.js index 1f49bc6..d5b897c 100644 --- a/src/migrate.js +++ b/src/migrate.js @@ -9,8 +9,7 @@ * 4. [x] Upload and register indices * 5. [x] Republish location claims with space * 6. [x] Create gateway authorizations - * 7. [ ] Check if the upload is fully migrated - * - TODO + * 7. [x] Verify migration completed successfully * * Usage Examples: * @@ -26,6 +25,11 @@ * # Test gateway auth only: * node src/migrate.js --test-gateway-auth --limit 10 * + * # Verify migration only (no changes): + * node src/migrate.js --verify-only --limit 10 + * node src/migrate.js --verify-only --space did:key:z6Mk... + * node src/migrate.js --verify-only --customer did:mailto:... + * * # Migrate specific space: * node src/migrate.js --space did:key:z6Mk... --limit 50 */ @@ -40,6 +44,7 @@ import { republishLocationClaims, createGatewayAuth, } from './lib/migration-steps.js' +import { verifyMigration } from './lib/migration-verify.js' /** * Migrate a single upload through all required steps @@ -47,6 +52,7 @@ import { * @param {object} upload - Upload from Upload Table * @param {object} options - Migration options * @param {string} options.testMode - Test mode: 'index' | 'location-claims' | 'gateway-auth' | null (null = full migration) + * @param {boolean} options.verifyOnly - If true, only verify migration status without making changes */ async function migrateUpload(upload, options = {}) { console.log(`\n${'='.repeat(70)}`) @@ -56,6 +62,20 @@ async function migrateUpload(upload, options = {}) { console.log('='.repeat(70)) try { + // If verify-only mode, skip to verification + if (options.verifyOnly) { + console.log(`\n>>> Verify-Only Mode: Checking migration status...`) + const verificationResult = await verifyMigration({ upload }) + + return { + success: verificationResult.success, + verifyOnly: true, + upload: upload.root, + space: upload.space, + verification: verificationResult, + } + } + // Step 1: Check what migration steps are needed console.log(`\n1) Checking migration status...`) const status = await checkMigrationNeeded(upload) @@ -177,18 +197,28 @@ async function migrateUpload(upload, options = {}) { console.log(`\n⏭ Gateway authorization already exists, skipping`) } + // Step 5: Verify migration completed successfully + console.log(`\n5) Verifying migration...`) + const verificationResult = await verifyMigration({ upload }) + + if (!verificationResult.success) { + console.error(`\n⚠️ Verification failed: ${verificationResult.details}`) + console.error(` Migration may need to be retried`) + } + console.log(`\n${'='.repeat(70)}`) - console.log(`✅ Migration complete for ${upload.root}`) + console.log(`${verificationResult.success ? '✅' : '⚠️'} Migration ${verificationResult.success ? 'complete' : 'completed with issues'} for ${upload.root}`) console.log('='.repeat(70)) return { - success: true, + success: verificationResult.success, upload: upload.root, space: upload.space, migrationSpace, indexCID: indexCID?.toString(), shardsRepublished: status.shardsNeedingLocationClaims.length, status, + verification: verificationResult, } } catch (error) { @@ -211,8 +241,11 @@ async function runMigrationMode(values) { // Determine test mode let testMode = null let modeLabel = 'Full Migration' + const verifyOnly = values['verify-only'] || false - if (values['test-index']) { + if (verifyOnly) { + modeLabel = 'Verification Only' + } else if (values['test-index']) { testMode = 'index' modeLabel = 'Index Generation Only' } else if (values['test-location-claims']) { @@ -252,6 +285,7 @@ async function runMigrationMode(values) { const result = await migrateUpload(upload, { testMode, + verifyOnly, }) results.push(result) @@ -272,11 +306,16 @@ async function runMigrationMode(values) { const failed = results.filter(r => !r.success).length const alreadyMigrated = results.filter(r => r.alreadyMigrated).length const testModeResults = results.filter(r => r.testMode).length + const verifyOnlyResults = results.filter(r => r.verifyOnly).length console.log(`Total processed: ${results.length}`) console.log(`Successful: ${successful}`) console.log(`Already migrated: ${alreadyMigrated}`) - if (testMode) { + if (verifyOnly) { + console.log(`Verified: ${verifyOnlyResults}`) + console.log(`Verification passed: ${results.filter(r => r.verifyOnly && r.success).length}`) + console.log(`Verification failed: ${results.filter(r => r.verifyOnly && !r.success).length}`) + } else if (testMode) { console.log(`Test mode (${testMode}): ${testModeResults}`) } console.log(`Failed: ${failed}`) @@ -321,6 +360,11 @@ async function main() { default: false, description: 'Test mode: Only test gateway authorization', }, + 'verify-only': { + type: 'boolean', + default: false, + description: 'Verify migration status without making changes', + }, cid: { type: 'string', description: 'Specific upload CID', diff --git a/src/migration-monitor.js b/src/migration-monitor.js new file mode 100644 index 0000000..0bd47cd --- /dev/null +++ b/src/migration-monitor.js @@ -0,0 +1,442 @@ +#!/usr/bin/env node + +/** + * Monitor migration progress from DynamoDB + * + * Usage: + * node src/migration-monitor.js # Overall stats + * node src/migration-monitor.js --customer # Customer progress + * node src/migration-monitor.js --space # Space status (requires --customer) + * node src/migration-monitor.js --instance # Instance progress + * node src/migration-monitor.js --failed # Show failed migrations + * node src/migration-monitor.js --stuck # Show stuck migrations (>1 hour) + * node src/migration-monitor.js --watch # Live monitoring (refresh every 30s) + */ + +import dotenv from 'dotenv' +dotenv.config() +import { parseArgs } from 'node:util' +import { validateConfig, config } from './config.js' +import { + getSpaceProgress, + getCustomerSpaces, + getFailedMigrations, + getStuckMigrations, + getInstanceSpaces, + scanAllProgress, +} from './lib/tables/migration-progress-table.js' + +/** + * Get overall migration statistics + */ +async function getOverallStats() { + const stats = { + total: 0, + pending: 0, + inProgress: 0, + completed: 0, + failed: 0, + totalUploads: 0, + completedUploads: 0, + byInstance: {}, + } + + let lastEvaluatedKey = undefined + + do { + const { items, lastEvaluatedKey: nextKey } = await scanAllProgress({ lastEvaluatedKey }) + + for (const item of items) { + stats.total++ + stats.totalUploads += item.totalUploads || 0 + stats.completedUploads += item.completedUploads || 0 + + // Count by status + if (item.status === 'pending') stats.pending++ + else if (item.status === 'in-progress') stats.inProgress++ + else if (item.status === 'completed') stats.completed++ + else if (item.status === 'failed') stats.failed++ + + // Count by instance + if (item.instanceId) { + if (!stats.byInstance[item.instanceId]) { + stats.byInstance[item.instanceId] = { + total: 0, + completed: 0, + failed: 0, + uploads: 0, + completedUploads: 0, + } + } + stats.byInstance[item.instanceId].total++ + if (item.status === 'completed') stats.byInstance[item.instanceId].completed++ + if (item.status === 'failed') stats.byInstance[item.instanceId].failed++ + stats.byInstance[item.instanceId].uploads += item.totalUploads || 0 + stats.byInstance[item.instanceId].completedUploads += item.completedUploads || 0 + } + } + + lastEvaluatedKey = nextKey + } while (lastEvaluatedKey) + + return stats +} + + +/** + * Print overall statistics + */ +function printOverallStats(stats) { + console.log() + console.log('Migration Progress Overview') + console.log('='.repeat(70)) + console.log() + console.log(`Environment: ${config.environment}`) + console.log(`Region: ${config.aws.region}`) + console.log(`Table: ${config.tables.migrationProgress}`) + console.log() + + const completionPct = stats.total > 0 ? ((stats.completed / stats.total) * 100).toFixed(1) : '0.0' + const uploadPct = stats.totalUploads > 0 ? ((stats.completedUploads / stats.totalUploads) * 100).toFixed(1) : '0.0' + // add a red X emoji here -> + console.log(`Total Spaces: ${stats.total.toLocaleString()}`) + console.log(` 🟢 Completed: ${stats.completed.toLocaleString()} (${completionPct}%)`) + console.log(` 🔵 In Progress: ${stats.inProgress.toLocaleString()}`) + console.log(` 🟡 Pending: ${stats.pending.toLocaleString()}`) + console.log(` 🔴 Failed: ${stats.failed.toLocaleString()}`) + console.log() + console.log(`Total Uploads: ${stats.totalUploads.toLocaleString()}`) + console.log(` 🟢 Completed: ${stats.completedUploads.toLocaleString()} (${uploadPct}%)`) + console.log() + + if (Object.keys(stats.byInstance).length > 0) { + console.log('Progress by Instance:') + console.log('-'.repeat(70)) + + for (const [instanceId, instanceStats] of Object.entries(stats.byInstance).sort()) { + const instPct = instanceStats.total > 0 ? ((instanceStats.completed / instanceStats.total) * 100).toFixed(1) : '0.0' + const instUploadPct = instanceStats.uploads > 0 ? ((instanceStats.completedUploads / instanceStats.uploads) * 100).toFixed(1) : '0.0' + + console.log() + console.log(`Instance ${instanceId}:`) + console.log(` Spaces: ${instanceStats.completed.toLocaleString()}/${instanceStats.total.toLocaleString()} (${instPct}%)`) + console.log(` Uploads: ${instanceStats.completedUploads.toLocaleString()}/${instanceStats.uploads.toLocaleString()} (${instUploadPct}%)`) + if (instanceStats.failed > 0) { + console.log(` Failed: ${instanceStats.failed.toLocaleString()}`) + } + } + } + + console.log() +} + +/** + * Print customer progress + */ +function printCustomerProgress(customer, spaces) { + console.log() + console.log(`Customer: ${customer}`) + console.log('='.repeat(70)) + console.log() + console.log(`Total Spaces: ${spaces.length}`) + console.log() + + if (spaces.length === 0) { + console.log('No migration data found for this customer.') + return + } + + const completed = spaces.filter(s => s.status === 'completed').length + const inProgress = spaces.filter(s => s.status === 'in-progress').length + const failed = spaces.filter(s => s.status === 'failed').length + const pending = spaces.filter(s => s.status === 'pending').length + + console.log(`Status:`) + console.log(` 🟢 Completed: ${completed}`) + console.log(` 🔵 In Progress: ${inProgress}`) + console.log(` 🟡 Pending: ${pending}`) + console.log(` 🔴 Failed: ${failed}`) + console.log() + + console.log('Spaces:') + console.log('-'.repeat(70)) + + for (const space of spaces.slice(0, 20)) { + const statusIcon = space.status === 'completed' ? '🟢' : + space.status === 'in-progress' ? '🔵' : + space.status === 'failed' ? '🔴' : '🟡' + const uploadProgress = space.totalUploads > 0 ? + ` (${space.completedUploads}/${space.totalUploads} uploads)` : '' + + console.log(` ${statusIcon} ${space.space}${uploadProgress}`) + if (space.status === 'in-progress') { + console.log(` Instance: ${space.instanceId}, Worker: ${space.workerId}`) + console.log(` Updated: ${new Date(space.updatedAt).toLocaleString()}`) + } + if (space.status === 'failed' && space.error) { + console.log(` Error: ${space.error}`) + } + } + + if (spaces.length > 20) { + console.log(` ... and ${spaces.length - 20} more spaces`) + } + + console.log() +} + +/** + * Print space status + */ +function printSpaceStatus(space) { + console.log() + console.log('Space Migration Status') + console.log('='.repeat(70)) + console.log() + + if (!space) { + console.log('No migration data found for this space.') + return + } + + const statusIcon = space.status === 'completed' ? '🟢' : + space.status === 'in-progress' ? '🔵' : + space.status === 'failed' ? '🔴' : '🟡' + + console.log(`Space: ${space.space}`) + console.log(`Customer: ${space.customer}`) + console.log(`Status: ${statusIcon} ${space.status}`) + console.log() + console.log(`Uploads: ${space.completedUploads || 0}/${space.totalUploads || 0}`) + + if (space.instanceId) { + console.log(`Instance: ${space.instanceId}`) + } + if (space.workerId) { + console.log(`Worker: ${space.workerId}`) + } + + console.log() + console.log(`Created: ${new Date(space.createdAt).toLocaleString()}`) + console.log(`Updated: ${new Date(space.updatedAt).toLocaleString()}`) + + if (space.lastProcessedUpload) { + console.log(`Last Upload: ${space.lastProcessedUpload}`) + } + + if (space.error) { + console.log() + console.log(`Error: ${space.error}`) + } + + console.log() +} + +/** + * Print failed migrations + */ +function printFailedMigrations(failed) { + console.log() + console.log('Failed Migrations') + console.log('='.repeat(70)) + console.log() + console.log(`Total Failed: ${failed.length}`) + console.log() + + if (failed.length === 0) { + console.log('No failed migrations found.') + return + } + + for (const item of failed.slice(0, 20)) { + console.log(`🔴 ${item.space}`) + console.log(` Customer: ${item.customer}`) + console.log(` Instance: ${item.instanceId}, Worker: ${item.workerId}`) + console.log(` Error: ${item.error || 'Unknown error'}`) + console.log(` Updated: ${new Date(item.updatedAt).toLocaleString()}`) + console.log() + } + + if (failed.length > 20) { + console.log(`... and ${failed.length - 20} more failed migrations`) + } + + console.log() +} + +/** + * Print stuck migrations + */ +function printStuckMigrations(stuck) { + console.log() + console.log('Stuck Migrations (in-progress >1 hour)') + console.log('='.repeat(70)) + console.log() + console.log(`Total Stuck: ${stuck.length}`) + console.log() + + if (stuck.length === 0) { + console.log('No stuck migrations found.') + return + } + + for (const item of stuck.slice(0, 20)) { + const stuckDuration = Math.floor((Date.now() - new Date(item.updatedAt).getTime()) / (60 * 1000)) + console.log(`🔵 ${item.space}`) + console.log(` Customer: ${item.customer}`) + console.log(` Instance: ${item.instanceId}, Worker: ${item.workerId}`) + console.log(` Stuck for: ${stuckDuration} minutes`) + console.log(` Progress: ${item.completedUploads}/${item.totalUploads} uploads`) + console.log(` Last Update: ${new Date(item.updatedAt).toLocaleString()}`) + console.log() + } + + if (stuck.length > 20) { + console.log(`... and ${stuck.length - 20} more stuck migrations`) + } + + console.log() +} + +/** + * Print instance progress + */ +function printInstanceProgress(instanceId, spaces) { + console.log() + console.log(`Instance ${instanceId} Progress`) + console.log('='.repeat(70)) + console.log() + + if (spaces.length === 0) { + console.log('No migration data found for this instance.') + return + } + + const completed = spaces.filter(s => s.status === 'completed').length + const inProgress = spaces.filter(s => s.status === 'in-progress').length + const failed = spaces.filter(s => s.status === 'failed').length + const pending = spaces.filter(s => s.status === 'pending').length + + const totalUploads = spaces.reduce((sum, s) => sum + (s.totalUploads || 0), 0) + const completedUploads = spaces.reduce((sum, s) => sum + (s.completedUploads || 0), 0) + + console.log(`Total Spaces: ${spaces.length}`) + console.log(` 🟢 Completed: ${completed}`) + console.log(` 🔵 In Progress: ${inProgress}`) + console.log(` 🟡 Pending: ${pending}`) + console.log(` 🔴 Failed: ${failed}`) + console.log() + console.log(`Total Uploads: ${totalUploads.toLocaleString()}`) + console.log(` 🟢 Completed: ${completedUploads.toLocaleString()}`) + console.log() + + // Group by worker + const byWorker = {} + for (const space of spaces) { + if (space.workerId) { + if (!byWorker[space.workerId]) { + byWorker[space.workerId] = { total: 0, completed: 0, inProgress: 0, failed: 0 } + } + byWorker[space.workerId].total++ + if (space.status === 'completed') byWorker[space.workerId].completed++ + if (space.status === 'in-progress') byWorker[space.workerId].inProgress++ + if (space.status === 'failed') byWorker[space.workerId].failed++ + } + } + + if (Object.keys(byWorker).length > 0) { + console.log('Progress by Worker:') + console.log('-'.repeat(70)) + + for (const [workerId, stats] of Object.entries(byWorker).sort()) { + console.log(` Worker ${workerId}: ${stats.completed}/${stats.total} completed`) + if (stats.inProgress > 0) console.log(` In Progress: ${stats.inProgress}`) + if (stats.failed > 0) console.log(` Failed: ${stats.failed}`) + } + } + + console.log() +} + +/** + * Main function + */ +async function main() { + const { values } = parseArgs({ + options: { + customer: { + type: 'string', + description: 'Query specific customer DID', + }, + space: { + type: 'string', + description: 'Query specific space DID (requires --customer)', + }, + instance: { + type: 'string', + description: 'Show progress for specific instance', + }, + failed: { + type: 'boolean', + default: false, + description: 'Show failed migrations', + }, + stuck: { + type: 'boolean', + default: false, + description: 'Show stuck migrations (>1 hour)', + }, + watch: { + type: 'boolean', + default: false, + description: 'Live monitoring (refresh every 30s)', + }, + }, + }) + + validateConfig() + + const runQuery = async () => { + try { + // Specific queries + if (values.customer && values.space) { + const space = await getSpaceProgress(values.customer, values.space) + printSpaceStatus(space) + } else if (values.customer) { + const spaces = await getCustomerSpaces(values.customer) + printCustomerProgress(values.customer, spaces) + } else if (values.instance) { + const spaces = await getInstanceSpaces(values.instance) + printInstanceProgress(values.instance, spaces) + } else if (values.failed) { + const failed = await getFailedMigrations() + printFailedMigrations(failed) + } else if (values.stuck) { + const stuck = await getStuckMigrations() + printStuckMigrations(stuck) + } else { + // Default: overall stats + const stats = await getOverallStats() + printOverallStats(stats) + } + } catch (error) { + console.error('Error querying migration progress:', error) + process.exit(1) + } + } + + // Run once or watch + if (values.watch) { + console.log('Starting live monitoring (Ctrl+C to stop)...') + while (true) { + console.clear() + await runQuery() + console.log('Refreshing in 30 seconds...') + await new Promise(resolve => setTimeout(resolve, 30000)) + } + } else { + await runQuery() + } +} + +main() diff --git a/src/setup-distribution.js b/src/setup-distribution.js new file mode 100755 index 0000000..b04d085 --- /dev/null +++ b/src/setup-distribution.js @@ -0,0 +1,699 @@ +#!/usr/bin/env node +/** + * Setup Distribution Script + * + * Discovers all customers from the Upload Table and distributes them + * across multiple EC2 instances for parallel migration. + * + * Uses parallel DynamoDB scan (4 segments by default) for faster discovery. + * + * Usage: + * # Analyze customer distribution (dry run) + * node src/setup-distribution.js --analyze + * + * # Generate distribution for 5 instances + * node src/setup-distribution.js --instances 5 + * + * # Use more parallel segments for faster scanning (1-10) + * node src/setup-distribution.js --analyze --parallel-segments 8 + * + * # Generate distribution with filters + * node src/setup-distribution.js --instances 5 --min-uploads 100 + * + * # Estimate with different worker counts + * node src/setup-distribution.js --instances 5 --workers-per-instance 15 + */ +import dotenv from 'dotenv' +dotenv.config() +import { parseArgs } from 'node:util' +import { DynamoDBClient } from '@aws-sdk/client-dynamodb' +import { DynamoDBDocumentClient, ScanCommand, QueryCommand } from '@aws-sdk/lib-dynamodb' +import { validateConfig, config } from './config.js' +import fs from 'fs/promises' +import path from 'path' + +const DISTRIBUTION_DIR = 'migration-state' + +/** + * Scan consumer table to get space -> customer mappings + * + * @param {number} segment - Segment number (0-based) + * @param {number} totalSegments - Total number of segments + * @returns {Promise} Map of customer -> Set of spaces + */ +async function scanConsumerSegment(segment, totalSegments) { + const baseClient = new DynamoDBClient({ + region: config.aws.region, + }) + const client = DynamoDBDocumentClient.from(baseClient) + const customerSpacesMap = new Map() // customer -> Set(spaces) + + let scanned = 0 + let lastEvaluatedKey + const startTime = Date.now() + + console.log(` [Segment ${segment}] Scanning consumer table...`) + + while (true) { + const command = new ScanCommand({ + TableName: config.tables.consumer, + ProjectionExpression: 'consumer, customer', + Limit: 1000, + ExclusiveStartKey: lastEvaluatedKey, + Segment: segment, + TotalSegments: totalSegments, + }) + + const response = await client.send(command) + + if (!response.Items || response.Items.length === 0) { + break + } + + for (const record of response.Items) { + if (record.consumer && record.customer) { + if (!customerSpacesMap.has(record.customer)) { + customerSpacesMap.set(record.customer, new Set()) + } + customerSpacesMap.get(record.customer).add(record.consumer) + } + } + + scanned += response.Items.length + + lastEvaluatedKey = response.LastEvaluatedKey + if (!lastEvaluatedKey) { + break + } + } + + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1) + console.log(` [Segment ${segment}] Complete: ${scanned.toLocaleString()} consumer records in ${elapsed}s`) + + return { segment, scanned, customerSpacesMap } +} + +/** + * Count uploads for a specific space with retry logic + * + * @param {DynamoDBClient} client - Reusable DynamoDB client + * @param {string} space - Space DID + * @returns {Promise} Upload count (0 if space is empty) + */ +async function countUploadsForSpace(client, space) { + const maxRetries = 5 + const baseDelay = 1000 // 1 second + + async function executeWithRetry(command, retryCount = 0) { + try { + return await client.send(command) + } catch (error) { + // Retry on timeout or throttling errors + if ((error.code === 'ETIMEDOUT' || error.name === 'TimeoutError' || + error.name === 'ProvisionedThroughputExceededException') && + retryCount < maxRetries) { + const delay = baseDelay * Math.pow(2, retryCount) // Exponential backoff + console.log(` [Retry ${retryCount + 1}/${maxRetries}] Connection error, retrying in ${delay}ms...`) + await new Promise(resolve => setTimeout(resolve, delay)) + return executeWithRetry(command, retryCount + 1) + } + throw error + } + } + + // First query to check if space has any uploads + const command = new QueryCommand({ + TableName: config.tables.upload, + KeyConditionExpression: '#space = :space', + ExpressionAttributeNames: { + '#space': 'space', + }, + ExpressionAttributeValues: { + ':space': space, + }, + Select: 'COUNT', + Limit: 1, // Just check if space has any uploads + }) + + const response = await executeWithRetry(command) + + // If space is empty, return 0 immediately + if (!response.Count || response.Count === 0) { + return 0 + } + + // If space has uploads and needs pagination, continue counting + let count = response.Count + let lastEvaluatedKey = response.LastEvaluatedKey + + while (lastEvaluatedKey) { + const paginatedCommand = new QueryCommand({ + TableName: config.tables.upload, + KeyConditionExpression: '#space = :space', + ExpressionAttributeNames: { + '#space': 'space', + }, + ExpressionAttributeValues: { + ':space': space, + }, + Select: 'COUNT', + ExclusiveStartKey: lastEvaluatedKey, + }) + + const paginatedResponse = await executeWithRetry(paginatedCommand) + count += paginatedResponse.Count || 0 + lastEvaluatedKey = paginatedResponse.LastEvaluatedKey + } + + return count +} + +/** + * Discover all unique customers and their upload counts using parallel scan + * + * @param {object} options + * @param {number} [options.minUploads] - Minimum uploads to include customer + * @param {number} [options.parallelSegments] - Number of parallel scan segments + * @returns {Promise>} + */ +async function discoverCustomers({ minUploads = 0, parallelSegments = 4 } = {}) { + console.log('Step 1: Scanning Consumer Table for customer->space mappings...') + console.log(`Using ${parallelSegments} parallel segments for faster scanning`) + console.log('='.repeat(70)) + + const startTime = Date.now() + + // Launch parallel scans of consumer table + const scanPromises = [] + for (let i = 0; i < parallelSegments; i++) { + scanPromises.push(scanConsumerSegment(i, parallelSegments)) + } + + // Progress monitoring + const progressInterval = setInterval(() => { + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1) + console.log(` Scanning consumer table... ${elapsed}s elapsed`) + }, 10000) // Update every 10 seconds + + // Wait for all segments to complete + const results = await Promise.all(scanPromises) + clearInterval(progressInterval) + + // Merge results from all segments + const customerSpacesMap = new Map() // customer -> Set(spaces) + let totalConsumerRecords = 0 + + for (const result of results) { + totalConsumerRecords += result.scanned + + for (const [customer, spaces] of result.customerSpacesMap.entries()) { + if (!customerSpacesMap.has(customer)) { + customerSpacesMap.set(customer, new Set()) + } + for (const space of spaces) { + customerSpacesMap.get(customer).add(space) + } + } + } + + const scanElapsed = ((Date.now() - startTime) / 1000).toFixed(1) + console.log() + console.log(`✓ Consumer table scan complete in ${scanElapsed}s`) + console.log(`✓ Total consumer records scanned: ${totalConsumerRecords.toLocaleString()}`) + console.log(`✓ Unique customers found: ${customerSpacesMap.size.toLocaleString()}`) + console.log() + + // Step 2: Count uploads for each customer's spaces (in parallel) + console.log('Step 2: Counting uploads per customer...') + console.log('='.repeat(70)) + + const totalCustomers = customerSpacesMap.size + const concurrency = 20 // Process 20 customers in parallel (reduced to avoid overwhelming DynamoDB) + + console.log(`Processing ${totalCustomers.toLocaleString()} customers with concurrency ${concurrency}...`) + console.log() + + const customerEntries = Array.from(customerSpacesMap.entries()) + const customers = [] + let processedCustomers = 0 + let totalSpacesProcessed = 0 + + // Create a shared DynamoDB Document Client for reuse + const baseClient = new DynamoDBClient({ + region: config.aws.region, + }) + const client = DynamoDBDocumentClient.from(baseClient) + + const countStartTime = Date.now() + + // Checkpoint file for resume capability + const checkpointFile = path.join(DISTRIBUTION_DIR, 'counting-checkpoint.json') + let checkpoint = { processedCustomers: 0, customers: [] } + + // Try to load existing checkpoint + try { + await fs.mkdir(DISTRIBUTION_DIR, { recursive: true }) + const checkpointData = await fs.readFile(checkpointFile, 'utf-8') + checkpoint = JSON.parse(checkpointData) + console.log(`Resuming from checkpoint: ${checkpoint.processedCustomers} customers already processed`) + customers.push(...checkpoint.customers) + processedCustomers = checkpoint.processedCustomers + console.log() + } catch (error) { + // No checkpoint exists, start fresh + } + + // Process customers in batches + for (let i = checkpoint.processedCustomers; i < customerEntries.length; i += concurrency) { + const batch = customerEntries.slice(i, i + concurrency) + + const batchStartTime = Date.now() + const batchResults = await Promise.all( + batch.map(async ([customer, spaces], batchIndex) => { + const customerStartTime = Date.now() + let totalUploads = 0 + let spacesProcessed = 0 + let emptySpaces = 0 + let spacesWithUploads = 0 + + // Count uploads for each space sequentially per customer to avoid too many parallel queries + for (const space of spaces) { + const count = await countUploadsForSpace(client, space) + totalUploads += count + spacesProcessed++ + + if (count === 0) { + emptySpaces++ + } else { + spacesWithUploads++ + } + + // Log progress for customers with many spaces + if (spaces.size > 10 && spacesProcessed % 50 === 0) { + const customerElapsed = ((Date.now() - customerStartTime) / 1000).toFixed(1) + console.log(` [Batch ${Math.floor(i / concurrency) + 1}, Customer ${batchIndex + 1}/${batch.length}] ${spacesProcessed}/${spaces.size} spaces (${emptySpaces} empty, ${spacesWithUploads} with uploads), ${totalUploads.toLocaleString()} uploads, ${customerElapsed}s`) + } + } + + return { + customer, + uploadCount: totalUploads, + spaceCount: spacesWithUploads, // Spaces with uploads + emptySpaceCount: emptySpaces, // Spaces without uploads + totalSpaceCount: spaces.size, // All spaces + } + }) + ) + + const batchElapsed = ((Date.now() - batchStartTime) / 1000).toFixed(1) + + customers.push(...batchResults) + processedCustomers += batch.length + totalSpacesProcessed += batch.reduce((sum, [, spaces]) => sum + spaces.size, 0) + + const pct = ((processedCustomers / totalCustomers) * 100).toFixed(1) + const elapsed = ((Date.now() - countStartTime) / 1000).toFixed(1) + const rate = (processedCustomers / (Date.now() - countStartTime) * 1000).toFixed(1) + console.log(` ${processedCustomers.toLocaleString()}/${totalCustomers.toLocaleString()} customers (${pct}%) | ${totalSpacesProcessed.toLocaleString()} spaces | ${elapsed}s | ${rate} customers/s | batch: ${batchElapsed}s`) + + // Save checkpoint every 10 batches + if (processedCustomers % (concurrency * 10) === 0) { + await fs.writeFile(checkpointFile, JSON.stringify({ + processedCustomers, + customers, + timestamp: new Date().toISOString() + }, null, 2)) + } + } + + // Clean up checkpoint file on completion + try { + await fs.unlink(checkpointFile) + } catch (error) { + // Ignore if file doesn't exist + } + + const totalElapsed = ((Date.now() - startTime) / 1000).toFixed(1) + console.log() + console.log(`✓ Upload counting complete in ${totalElapsed}s`) + console.log() + + // Filter and sort + const filteredCustomers = customers + .filter(c => c.uploadCount >= minUploads) + .sort((a, b) => b.uploadCount - a.uploadCount) // Sort by upload count descending + + if (minUploads > 0) { + const filtered = customers.length - filteredCustomers.length + console.log(`Filtered out ${filtered} customers with < ${minUploads} uploads`) + console.log() + } + + return filteredCustomers +} + +/** + * Analyze customer distribution statistics + */ +function analyzeDistribution(customers) { + console.log('Customer Distribution Analysis') + console.log('='.repeat(70)) + + const totalCustomers = customers.length + const totalUploads = customers.reduce((sum, c) => sum + c.uploadCount, 0) + const totalSpaces = customers.reduce((sum, c) => sum + c.spaceCount, 0) + const totalEmptySpaces = customers.reduce((sum, c) => sum + (c.emptySpaceCount || 0), 0) + const totalAllSpaces = customers.reduce((sum, c) => sum + (c.totalSpaceCount || c.spaceCount), 0) + const avgUploadsPerCustomer = totalUploads / totalCustomers + const avgSpacesPerCustomer = totalSpaces / totalCustomers + const emptySpacePct = ((totalEmptySpaces / totalAllSpaces) * 100).toFixed(1) + + console.log(`Total customers: ${totalCustomers.toLocaleString()}`) + console.log(`Total uploads: ${totalUploads.toLocaleString()}`) + console.log(`Total spaces (with uploads): ${totalSpaces.toLocaleString()}`) + console.log(`Total empty spaces: ${totalEmptySpaces.toLocaleString()} (${emptySpacePct}%)`) + console.log(`Total all spaces: ${totalAllSpaces.toLocaleString()}`) + console.log(`Average uploads/customer: ${Math.round(avgUploadsPerCustomer).toLocaleString()}`) + console.log(`Average spaces/customer: ${avgSpacesPerCustomer.toFixed(1)}`) + console.log() + + // Top customers + console.log('Top 100 Customers by Upload Count:') + console.log('-'.repeat(70)) + customers.slice(0, 100).forEach((c, i) => { + const pct = ((c.uploadCount / totalUploads) * 100).toFixed(2) + console.log(` ${String(i + 1).padStart(3)}. ${c.customer.padEnd(50)} | ${String(c.uploadCount.toLocaleString()).padStart(12)} uploads (${String(pct).padStart(5)}%) | ${String(c.spaceCount).padStart(6)} spaces`) + }) + console.log() + + // Distribution buckets + console.log('Upload Count Distribution:') + const buckets = [ + { label: '1-10', min: 1, max: 10 }, + { label: '11-100', min: 11, max: 100 }, + { label: '101-1K', min: 101, max: 1000 }, + { label: '1K-10K', min: 1001, max: 10000 }, + { label: '10K-100K', min: 10001, max: 100000 }, + { label: '100K+', min: 100001, max: Infinity }, + ] + + for (const bucket of buckets) { + const count = customers.filter(c => c.uploadCount >= bucket.min && c.uploadCount <= bucket.max).length + const pct = ((count / totalCustomers) * 100).toFixed(1) + console.log(` ${bucket.label.padEnd(10)}: ${count.toLocaleString().padStart(8)} customers (${pct}%)`) + } + console.log() + + return { totalCustomers, totalUploads, totalSpaces, avgUploadsPerCustomer } +} + +/** + * Distribute customers across instances using greedy load balancing + * + * @param {Array} customers - Array of customer objects + * @param {number} numInstances - Number of instances + * @returns {Array<{instanceId: number, customers: Array, totalUploads: number, totalSpaces: number}>} + */ +function distributeCustomers(customers, numInstances) { + // Initialize instances + const instances = Array(numInstances).fill(null).map((_, i) => ({ + instanceId: i + 1, + customers: [], + totalUploads: 0, + totalSpaces: 0, + totalEmptySpaces: 0, + totalAllSpaces: 0, + })) + + // Greedy assignment: assign each customer to instance with least load + for (const customer of customers) { + const lightestInstance = instances.reduce((minIdx, inst, idx) => + inst.totalUploads < instances[minIdx].totalUploads ? idx : minIdx + , 0) + + instances[lightestInstance].customers.push(customer.customer) + instances[lightestInstance].totalUploads += customer.uploadCount + instances[lightestInstance].totalSpaces += customer.spaceCount + instances[lightestInstance].totalEmptySpaces += customer.emptySpaceCount || 0 + instances[lightestInstance].totalAllSpaces += customer.totalSpaceCount || customer.spaceCount + } + + return instances +} + +/** + * Print distribution summary + */ +function printDistributionSummary(distribution, totalUploads, workersPerInstance = 10) { + console.log('Instance Distribution') + console.log('='.repeat(70)) + + const UPLOADS_PER_MIN_PER_WORKER = 27 + + for (const instance of distribution) { + const pct = ((instance.totalUploads / totalUploads) * 100).toFixed(1) + const avgUploadsPerCustomer = Math.round(instance.totalUploads / instance.customers.length) + const emptyPct = instance.totalAllSpaces > 0 + ? ((instance.totalEmptySpaces / instance.totalAllSpaces) * 100).toFixed(1) + : '0.0' + + // Time estimate for this instance + const uploadsPerWorker = instance.totalUploads / workersPerInstance + const minutesRequired = uploadsPerWorker / UPLOADS_PER_MIN_PER_WORKER + const hoursRequired = minutesRequired / 60 + const daysRequired = hoursRequired / 24 + + console.log(`Instance ${instance.instanceId}:`) + console.log(` Customers: ${instance.customers.length.toLocaleString()}`) + console.log(` Uploads: ${instance.totalUploads.toLocaleString()} (${pct}%)`) + console.log(` Spaces (with uploads): ${instance.totalSpaces.toLocaleString()}`) + console.log(` Empty spaces: ${instance.totalEmptySpaces.toLocaleString()} (${emptyPct}%)`) + console.log(` Total spaces: ${instance.totalAllSpaces.toLocaleString()}`) + console.log(` Avg uploads/customer: ${avgUploadsPerCustomer.toLocaleString()}`) + console.log(` Estimated time (${workersPerInstance} workers): ${daysRequired.toFixed(1)} days (${hoursRequired.toFixed(1)} hours)`) + console.log() + } + + // Load balance check + const uploadsPerInstance = distribution.map(i => i.totalUploads) + const minUploads = Math.min(...uploadsPerInstance) + const maxUploads = Math.max(...uploadsPerInstance) + const avgUploads = uploadsPerInstance.reduce((a, b) => a + b, 0) / uploadsPerInstance.length + const variance = ((maxUploads - minUploads) / avgUploads * 100).toFixed(1) + + console.log('Load Balance:') + console.log(` Min uploads/instance: ${minUploads.toLocaleString()}`) + console.log(` Max uploads/instance: ${maxUploads.toLocaleString()}`) + console.log(` Avg uploads/instance: ${Math.round(avgUploads).toLocaleString()}`) + console.log(` Variance: ${variance}%`) + console.log() +} + +/** + * Estimate migration time + */ +function estimateMigrationTime(distribution, workersPerInstance = 10) { + console.log('Migration Time Estimate') + console.log('='.repeat(70)) + + // From previous measurements: ~27 uploads/min per worker + const UPLOADS_PER_MIN_PER_WORKER = 27 + + const totalWorkers = distribution.length * workersPerInstance + const throughput = totalWorkers * UPLOADS_PER_MIN_PER_WORKER + + // Find the instance with most work (critical path) + const maxUploads = Math.max(...distribution.map(i => i.totalUploads)) + const uploadsPerWorker = maxUploads / workersPerInstance + const minutesRequired = uploadsPerWorker / UPLOADS_PER_MIN_PER_WORKER + const hoursRequired = minutesRequired / 60 + const daysRequired = hoursRequired / 24 + + console.log(`Workers per instance: ${workersPerInstance}`) + console.log(`Total workers: ${totalWorkers}`) + console.log(`Combined throughput: ${throughput.toLocaleString()} uploads/min`) + console.log() + console.log(`Critical path (slowest instance):`) + console.log(` Uploads: ${maxUploads.toLocaleString()}`) + console.log(` Time: ${minutesRequired.toLocaleString()} minutes`) + console.log(` = ${hoursRequired.toFixed(1)} hours`) + console.log(` = ${daysRequired.toFixed(1)} days`) + console.log() + + // Show estimates for different worker counts + console.log('Time estimates for different worker counts:') + console.log('-'.repeat(70)) + for (const workers of [5, 10, 15, 20]) { + const mins = (maxUploads / workers) / UPLOADS_PER_MIN_PER_WORKER + const hours = mins / 60 + const days = hours / 24 + console.log(` ${String(workers).padStart(2)} workers/instance: ${String(days.toFixed(1)).padStart(5)} days (${String(hours.toFixed(1)).padStart(6)} hours) | Total workers: ${workers * distribution.length}`) + } + console.log() + + // Workload distribution scenarios + console.log('Migration Workload Scenarios') + console.log('='.repeat(70)) + console.log() + + const scenarios = [ + { instances: 5, workers: 10, name: 'Baseline (Current Plan)' }, + { instances: 5, workers: 15, name: 'Increased Workers' }, + { instances: 5, workers: 20, name: 'Maximum Workers' }, + { instances: 10, workers: 10, name: 'Double Instances' }, + { instances: 10, workers: 15, name: 'Double Instances + More Workers' }, + ] + + for (const scenario of scenarios) { + const totalWorkers = scenario.instances * scenario.workers + const uploadsPerInstance = maxUploads // Assumes similar distribution + const uploadsPerWorker = uploadsPerInstance / scenario.workers + const mins = uploadsPerWorker / UPLOADS_PER_MIN_PER_WORKER + const hours = mins / 60 + const days = hours / 24 + const throughput = totalWorkers * UPLOADS_PER_MIN_PER_WORKER + + console.log(`Scenario: ${scenario.name}`) + console.log(` Configuration: ${scenario.instances} instances × ${scenario.workers} workers = ${totalWorkers} total workers`) + console.log(` Throughput: ${throughput.toLocaleString()} uploads/min`) + console.log(` Time to complete: ${days.toFixed(1)} days (${hours.toFixed(1)} hours)`) + console.log(` Cost (EC2 @ $0.10/hour): $${(scenario.instances * hours * 0.10).toFixed(2)}`) + console.log() + } +} + +/** + * Save distribution to files + */ +async function saveDistribution(distribution) { + // Ensure directory exists + await fs.mkdir(DISTRIBUTION_DIR, { recursive: true }) + + console.log('Saving distribution files...') + console.log('='.repeat(70)) + + for (const instance of distribution) { + const filename = `instance-${instance.instanceId}-customers.json` + const filepath = path.join(DISTRIBUTION_DIR, filename) + + const data = { + instanceId: instance.instanceId, + totalCustomers: instance.customers.length, + estimatedUploads: instance.totalUploads, + estimatedSpaces: instance.totalSpaces, + customers: instance.customers, + createdAt: new Date().toISOString(), + } + + await fs.writeFile(filepath, JSON.stringify(data, null, 2)) + console.log(`✓ ${filename}`) + } + + console.log() + console.log(`Distribution files saved to: ${DISTRIBUTION_DIR}/`) + console.log() +} + +/** + * Print usage instructions + */ +function printUsageInstructions(numInstances) { + console.log('Next Steps') + console.log('='.repeat(70)) + console.log() + console.log('To start migration, run these commands on each EC2 instance:') + console.log() + + for (let i = 1; i <= numInstances; i++) { + console.log(` EC2 Instance ${i}: node src/migrate-instance.js --instance ${i}`) + } + + console.log() + console.log('To monitor progress:') + console.log(' node src/monitor.js') + console.log() +} + +/** + * Main function + */ +async function main() { + const { values } = parseArgs({ + options: { + analyze: { + type: 'boolean', + default: false, + description: 'Analyze customer distribution without generating files', + }, + instances: { + type: 'string', + short: 'i', + description: 'Number of EC2 instances', + }, + 'min-uploads': { + type: 'string', + default: '0', + description: 'Minimum uploads to include customer', + }, + 'workers-per-instance': { + type: 'string', + short: 'w', + default: '10', + description: 'Workers per instance for time estimate', + }, + 'parallel-segments': { + type: 'string', + short: 'p', + default: '4', + description: 'Number of parallel scan segments (1-10)', + }, + }, + }) + + validateConfig() + + const analyzeOnly = values.analyze + const numInstances = values.instances ? parseInt(values.instances, 10) : null + const minUploads = parseInt(values['min-uploads'], 10) + const parallelSegments = Math.max(1, Math.min(10, parseInt(values['parallel-segments'], 10))) + const workersPerInstance = parseInt(values['workers-per-instance'], 10) + + console.log() + console.log('Legacy Content Migration - Setup Distribution') + console.log('='.repeat(70)) + console.log() + + // Discover customers + const customers = await discoverCustomers({ minUploads, parallelSegments }) + + // Analyze distribution + const stats = analyzeDistribution(customers) + + // If analyze-only mode, stop here + if (analyzeOnly) { + console.log('Analysis complete. Use --instances N to generate distribution.') + return + } + + // Generate distribution + if (!numInstances) { + console.log('Error: --instances required to generate distribution') + console.log('Example: node src/setup-distribution.js --instances 5') + return + } + + const distribution = distributeCustomers(customers, numInstances) + printDistributionSummary(distribution, stats.totalUploads, workersPerInstance) + estimateMigrationTime(distribution, workersPerInstance) + + // Save to files + await saveDistribution(distribution) + + // Print usage instructions + printUsageInstructions(numInstances) +} + +main().catch(error => { + console.error('Fatal error:', error) + process.exit(1) +})