diff --git a/.claude/commands/test-destination-e2e.md b/.claude/commands/test-destination-e2e.md new file mode 100644 index 00000000000..d9740911669 --- /dev/null +++ b/.claude/commands/test-destination-e2e.md @@ -0,0 +1,350 @@ +--- +name: test-destination-e2e +description: Generate a Jest e2e test file for a Segment Actions destination. Reads destination code, derives all test cases, and writes a test file that makes real HTTP calls to the local serve server. +argument-hint: [destination-slug] +allowed-tools: Read, Bash, Glob, Grep, AskUserQuestion, Agent, Write +--- + +# Segment Destination E2E Test Suite Generator + +Reads the destination code and generates a Jest e2e test file covering every testable code path. The test file makes real HTTP calls to a running local serve server (`./bin/run serve`). + +## Step 1: Get the Destination Slug + +If `$ARGUMENTS` is provided use it as the slug. Otherwise ask the user for it. + +## Step 2: Build a Complete Map of the Destination + +Read EVERY file in `packages/destination-actions/src/destinations//` and any shared libraries it imports. The goal is to understand everything the code does so that the test file tests all of it. + +### 2a. Root `index.ts` + +Path: `packages/destination-actions/src/destinations//index.ts` + +Extract: + +- **Authentication scheme** — custom, oauth2, basic +- **Every authentication field** — name, type, required, description +- **`testAuthentication` function** — what it calls, what errors it can throw +- **`extendRequest`** — if present, what it adds to requests +- **Every action registered** — list of action names + +### 2b. Each Action `index.ts` + +Path: `packages/destination-actions/src/destinations///index.ts` + +Extract: + +- **`defaultSubscription`** — which event types +- **Every field** — name, type, required, default, min, max, choices, description +- **`perform` function** — what it calls, what arguments it passes +- **`performBatch` function** — whether it exists, what it calls +- **`batch_size`** — default and max values + +### 2c. Utils / Helper Files / All Other Source Files + +Path: `packages/destination-actions/src/destinations//utils.ts` and any other `.ts` files + +Read EVERY source file. Extract: + +- **Every function** — name, parameters, what it does +- **Every code branch** — every `if/else`, `try/catch`, `switch`, ternary, `||`, `??` +- **Every error map/class** — retryable errors, non-retryable errors, status codes +- **Every external call** — SDK clients, HTTP requests, what they send +- **Every metric** — `statsContext?.statsClient` calls with metric names +- **Every log statement** — what gets logged, whether any variables could contain sensitive data + +### 2d. Shared Libraries + +Follow every import to shared code (e.g. `src/lib/AWS/sts.ts`, `packages/core/src/errors.ts`). Extract: + +- **What the shared code does** — function signatures, error types, credential chains +- **Multi-step flows** — e.g. credential assumption chains with multiple steps + +### 2e. Generated Types + +Path: `packages/destination-actions/src/destinations//generated-types.ts` and `/generated-types.ts` + +Extract: + +- **Settings interface** — field names and types +- **Payload interface** — field names and types + +### 2f. Generate Numbered Test Plan + +For every item found in 2a–2e, generate one numbered test line. Items that cannot be triggered via HTTP get a SKIP line with the reason. + +**Coverage requirement:** The goal is full code coverage via HTTP requests. Every line of code that can be reached by an HTTP request must have a test. This means independently deriving from the code itself — every auth field variation, every required/optional field, every event type in `defaultSubscription`, every `if/else`/`try/catch`/ternary branch, every entry in every error map, every min/max constraint, every `perform` and `performBatch` path. + +### 2g. Self-Verify the Test Plan + +Before generating any files, cross-reference the test plan against the code: + +- Go back through every file read in 2a–2e +- For each function, branch, field, error map entry, and event type — confirm there is a corresponding test line or SKIP +- If anything is missing, add it now + +Do not proceed to Step 3 until this cross-check is complete. + +## Step 3: Identify Required Variables + +From the code map, identify every value that varies per environment: + +- Every **authentication field** (e.g. `IAM_ROLE_ARN`, `IAM_EXTERNAL_ID`, `API_KEY`) +- Every **destination-specific mapping field** (e.g. `STREAM_NAME`, `QUEUE_URL`, `AWS_REGION`) +- **Base URL** → always `BASE_URL`, defaults to `http://localhost:3000` + +These will be read from environment variables in the test file. + +## Step 4: Generate the Jest E2E Test File + +Write the test file to: +`packages/destination-actions/src/destinations//__tests__/e2e.test.ts` + +### Test file structure + +```typescript +/** + * E2E tests for + * + * These tests make real HTTP calls to a running local serve server. + * They are NOT run in CI — they require real infrastructure credentials. + * + * Prerequisites: + * 1. Start the serve server: + * ./bin/run serve --destination --noUI + * + * 2. Set environment variables (or create a .env file): + * export BASE_URL=http://localhost:3000 + * export = + * export = + * + * 3. Run the tests: + * yarn cloud jest --testPathPattern="/__tests__/e2e" + */ + +const BASE_URL = process.env.BASE_URL ?? 'http://localhost:3000' +// Extract all other env vars needed + +// Helper to POST JSON to the serve server +async function post(path: string, body: Record) { + const res = await fetch(`${BASE_URL}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + return { + status: res.status, + body: await res.json() + } +} + +// Helper to GET from the serve server +async function get(path: string) { + const res = await fetch(`${BASE_URL}${path}`) + return { + status: res.status, + body: await res.json() + } +} + +describe(' E2E', () => { + // Check server is running before all tests + beforeAll(async () => { + try { + await get('/manifest') + } catch { + throw new Error('Serve server is not running. Start it with:\n' + ' ./bin/run serve --destination --noUI') + } + }) + + describe('Authentication', () => { + // Auth tests here — POST to /authenticate + }) + + describe('', () => { + // Action tests here — POST to / + // Group by: single events, batch events, validation errors, error paths + }) + + describe('Skipped (not testable via HTTP)', () => { + // Skipped tests with .skip and comments explaining why + }) +}) +``` + +### Test patterns + +#### Authentication tests + +```typescript +it('returns ok:true for valid credentials', async () => { + const res = await post('/authenticate', { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(true) +}) + +it('returns ok:false for invalid ARN format', async () => { + const res = await post('/authenticate', { + iamRoleArn: 'not-an-arn', + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(false) +}) +``` + +#### Action tests (single event) + +```typescript +it('delivers a track event via perform()', async () => { + const res = await post('/send', { + settings: { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }, + payload: { + type: 'track', + event: 'Test Event', + messageId: 'msg-e2e-track', + userId: 'user-1' + }, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION + } + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) +}) +``` + +#### Action tests (batch) + +```typescript +it('delivers batch events via performBatch()', async () => { + const res = await post('/send', { + settings: { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }, + payload: [ + { type: 'track', event: 'Event 1', messageId: 'msg-1', userId: 'user-1' }, + { type: 'track', event: 'Event 2', messageId: 'msg-2', userId: 'user-2' } + ], + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION + } + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) +}) +``` + +#### Validation error tests + +```typescript +it('returns validation error for missing required field', async () => { + const res = await post('/send', { + settings: { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }, + payload: { + type: 'track', + event: 'Test Event', + messageId: 'msg-1', + userId: 'user-1' + }, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + // streamName intentionally omitted + awsRegion: AWS_REGION + } + }) + expect(res.status).toBe(200) + expect(res.body[0].message).toContain('streamName') +}) +``` + +#### Skipped tests + +```typescript +// Cannot trigger AccessDeniedException from Kinesis via HTTP. +// This branch fires when error.name === 'AccessDeniedException' from the Kinesis PutRecordsCommand. +it.skip('handleError AccessDeniedException branch → 403', () => {}) +``` + +### Test assertions + +- For successful auth: `expect(res.body.ok).toBe(true)` +- For failed auth: `expect(res.body.ok).toBe(false)` and optionally check `res.body.error` +- For successful send: `expect(res.status).toBe(200)` and `expect(Array.isArray(res.body)).toBe(true)` +- For validation errors: check `res.body[0].message` contains the expected field name +- For error paths: check `res.body[0].message` contains the expected error string and `res.body[0].statusCode` +- Use Jest matchers (`toBe`, `toContain`, `toEqual`, `toHaveProperty`) — NOT Chai-style (`to.equal`, `to.include`) + +### Test timeouts + +Set a generous timeout for the entire test suite since e2e tests make real network calls: + +```typescript +// At the top level of describe, or in jest config +jest.setTimeout(30000) // 30 seconds per test +``` + +## Step 5: Print Usage Instructions + +After writing the test file, print to the user: + +``` +Generated: packages/destination-actions/src/destinations//__tests__/e2e.test.ts + +1. Start the serve server (in a separate terminal): + ./bin/run serve --destination --noUI + +2. Set environment variables: + export BASE_URL=http://localhost:3000 + export = + ... + +3. Run the tests: + yarn cloud jest --testPathPattern="/__tests__/e2e" + +Environment variables: + +``` + +## Rules + +### Testing philosophy + +- **The code is the source of truth.** Every test must be derived from something found in Step 2. If the code doesn't do it, don't test it. +- **Every item in the code map gets a test.** Every auth field, every action field, every error map entry, every code branch — if it exists in the code, it gets tested or marked as untestable with a reason. +- **Do not follow a fixed checklist.** The number and type of tests depends entirely on what the code contains. + +### Serve endpoint contract + +- For `/authenticate`, send settings fields at the **root level** of the request body (not nested under `settings`) +- For `/`, send `payload`, `settings`, and `mapping` as separate keys in the request body +- For batch tests, send `payload` as an **array** — the serve endpoint auto-detects batch vs single +- The serve endpoint returns `[]` for successful SDK-based calls + +### File rules + +- **One test file per destination.** All e2e tests go in `__tests__/e2e.test.ts`. +- **Never hardcode credentials.** All auth/config values must come from environment variables. +- **Hardcode everything else.** Event payloads, field names, error message patterns come from the code — not the user. +- **Do NOT read or reference existing test files** (`__tests__/*.test.ts`) — derive everything from source code directly. +- **Do NOT generate or modify destination source code** — this skill only generates e2e test files. +- **Use `fetch` for HTTP calls** — it's available natively in Node 18+. Do not add dependencies. +- **Group tests logically** — by auth, then by action, then by test type (single, batch, validation, error, skipped). diff --git a/packages/destination-actions/package.json b/packages/destination-actions/package.json index 87123460085..17359b2e53d 100644 --- a/packages/destination-actions/package.json +++ b/packages/destination-actions/package.json @@ -43,6 +43,7 @@ "@amplitude/ua-parser-js": "^0.7.25", "@aws-sdk/client-eventbridge": "^3.894.0", "@aws-sdk/client-kinesis": "3.974.0", + "@aws-sdk/client-sqs": "3.974.0", "@aws-sdk/client-s3": "^3.894.0", "@aws-sdk/client-sts": "^3.894.0", "@bufbuild/protobuf": "^2.2.3", diff --git a/packages/destination-actions/src/destinations/aws-kinesis/__tests__/e2e.test.ts b/packages/destination-actions/src/destinations/aws-kinesis/__tests__/e2e.test.ts new file mode 100644 index 00000000000..a7a62fb5b6e --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-kinesis/__tests__/e2e.test.ts @@ -0,0 +1,419 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/// +import fetch from 'node-fetch' + +/** + * E2E tests for AWS Kinesis + * + * These tests make real HTTP calls to a running local serve server. + * They are NOT run in CI — they require real infrastructure credentials. + * + * Prerequisites: + * 1. Start the serve server: + * ./bin/run serve --destination aws-kinesis --noUI + * + * 2. Set environment variables (or create a .env file): + * export BASE_URL=http://localhost:3000 + * export IAM_ROLE_ARN=arn:aws:iam::123456789012:role/your-role + * export IAM_EXTERNAL_ID=your-external-id + * export STREAM_NAME=your-kinesis-stream + * export AWS_REGION=us-west-2 + * + * 3. Run the tests: + * yarn cloud jest --testPathPattern="aws-kinesis/__tests__/e2e" + */ + +const BASE_URL = process.env.BASE_URL ?? 'http://127.0.0.1:3000' +const IAM_ROLE_ARN = + process.env.IAM_ROLE_ARN ?? 'arn:aws:iam::355207333203:role/mm2e-e2e-service-stage-us-west-2-forwarder' +const IAM_EXTERNAL_ID = process.env.IAM_EXTERNAL_ID ?? 'mm2e-e2e-service-stage-us-west-2' +const STREAM_NAME = process.env.STREAM_NAME ?? 'mm2e-e2e-service-stage-us-west-2' +const AWS_REGION = process.env.AWS_REGION ?? 'us-west-2' + +async function post(path: string, body: Record) { + const res = await fetch(`${BASE_URL}${path}`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }) + return { + status: res.status, + body: await res.json() + } +} + +async function get(path: string) { + const res = await fetch(`${BASE_URL}${path}`) + return { + status: res.status, + body: await res.json() + } +} + +function sendPayload( + payload: Record | Record[], + mappingOverrides?: Record +) { + return post('/send', { + settings: { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }, + payload, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION, + max_batch_size: 500, + batch_bytes: 1000000, + ...mappingOverrides + } + }) +} + +describe('AWS Kinesis E2E', () => { + beforeAll(async () => { + try { + await get('/manifest') + } catch (err) { + throw new Error( + 'Serve server is not running. Start it with:\n' + + ' ./bin/run serve --destination aws-kinesis --noUI\n' + + ' Error: ' + + (err as Error).message + ) + } + }) + + describe('Authentication', () => { + it('01 - returns ok:true for valid credentials', async () => { + const res = await post('/authenticate', { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(true) + }) + + it('02 - returns ok:false for invalid ARN format', async () => { + const res = await post('/authenticate', { + iamRoleArn: 'not-a-valid-arn', + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(false) + }) + + it('03 - returns ok:false for empty ARN', async () => { + const res = await post('/authenticate', { + iamRoleArn: '', + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(false) + }) + + it('04 - ARN with special chars +=,.@_-/ passes regex validation', async () => { + const res = await post('/authenticate', { + iamRoleArn: 'arn:aws:iam::355207333203:role/test+role=name,with.special@chars_and-slash/path', + iamExternalId: IAM_EXTERNAL_ID + }) + expect(res.status).toBe(200) + expect(res.body).toHaveProperty('ok') + }) + + it('05 - wrong external ID rejected by trust policy', async () => { + const res = await post('/authenticate', { + iamRoleArn: IAM_ROLE_ARN, + iamExternalId: 'totally-wrong-external-id' + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(false) + }) + + it('06 - nonexistent account/role returns ok:false', async () => { + const res = await post('/authenticate', { + iamRoleArn: 'arn:aws:iam::000000000000:role/nonexistent-role', + iamExternalId: 'some-id' + }) + expect(res.status).toBe(200) + expect(res.body.ok).toBe(false) + }) + }) + + describe('Send - Single Events', () => { + it('07 - track event delivered via perform()', async () => { + const res = await sendPayload({ + type: 'track', + event: 'Test Event', + messageId: 'msg-e2e-track', + userId: 'user-1' + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('08 - identify event delivered via perform()', async () => { + const res = await sendPayload({ + type: 'identify', + messageId: 'msg-e2e-identify', + userId: 'user-1', + traits: { name: 'Test User' } + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('09 - page event delivered via perform()', async () => { + const res = await sendPayload({ + type: 'page', + name: 'Home', + messageId: 'msg-e2e-page', + userId: 'user-1' + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('10 - screen event delivered via perform()', async () => { + const res = await sendPayload({ + type: 'screen', + name: 'Dashboard', + messageId: 'msg-e2e-screen', + userId: 'user-1' + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('11 - group event delivered via perform()', async () => { + const res = await sendPayload({ + type: 'group', + groupId: 'group-1', + messageId: 'msg-e2e-group', + userId: 'user-1' + }) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + }) + + describe('Send - Validation Errors', () => { + it('12 - missing partitionKey returns AggregateAjvError', async () => { + const res = await post('/send', { + settings: { iamRoleArn: IAM_ROLE_ARN, iamExternalId: IAM_EXTERNAL_ID }, + payload: { type: 'track', event: 'Test', userId: 'user-1' }, + mapping: { + payload: { '@path': '$.' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION, + max_batch_size: 500, + batch_bytes: 1000000 + } + }) + expect(res.status).toBe(200) + expect(res.body[0].message).toContain('partitionKey') + }) + + it('13 - missing streamName returns AggregateAjvError', async () => { + const res = await post('/send', { + settings: { iamRoleArn: IAM_ROLE_ARN, iamExternalId: IAM_EXTERNAL_ID }, + payload: { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + awsRegion: AWS_REGION, + max_batch_size: 500, + batch_bytes: 1000000 + } + }) + expect(res.status).toBe(200) + expect(res.body[0].message).toContain('streamName') + }) + + it('14 - missing awsRegion returns AggregateAjvError', async () => { + const res = await post('/send', { + settings: { iamRoleArn: IAM_ROLE_ARN, iamExternalId: IAM_EXTERNAL_ID }, + payload: { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + max_batch_size: 500, + batch_bytes: 1000000 + } + }) + expect(res.status).toBe(200) + expect(res.body[0].message).toContain('awsRegion') + }) + + it('15 - missing payload returns AggregateAjvError', async () => { + const res = await post('/send', { + settings: { iamRoleArn: IAM_ROLE_ARN, iamExternalId: IAM_EXTERNAL_ID }, + payload: { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + mapping: { + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION, + max_batch_size: 500, + batch_bytes: 1000000 + } + }) + expect(res.status).toBe(200) + expect(res.body[0].message).toContain('payload') + }) + + it('16 - batch_size below min (0) fails constraint', async () => { + const res = await sendPayload( + { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + { batch_size: 0 } + ) + expect(res.status).toBe(200) + expect(res.body[0].message).toBeDefined() + }) + + it('17 - batch_size above max (501) fails constraint', async () => { + const res = await sendPayload( + { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + { batch_size: 501 } + ) + expect(res.status).toBe(200) + expect(res.body[0].message).toBeDefined() + }) + + it('18 - max_batch_size below min (0) fails constraint', async () => { + const res = await sendPayload( + { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + { max_batch_size: 0 } + ) + expect(res.status).toBe(200) + expect(res.body[0].message).toBeDefined() + }) + + it('19 - max_batch_size above max (501) fails constraint', async () => { + const res = await sendPayload( + { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + { max_batch_size: 501 } + ) + expect(res.status).toBe(200) + expect(res.body[0].message).toBeDefined() + }) + }) + + describe('Send - Batching', () => { + it('20 - enable_batching=false routes through perform()', async () => { + const res = await sendPayload( + { type: 'track', event: 'Test', messageId: 'msg-1', userId: 'user-1' }, + { enable_batching: false } + ) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('21 - 2 track events delivered via performBatch()', async () => { + const res = await sendPayload([ + { type: 'track', event: 'Event 1', messageId: 'msg-batch-1', userId: 'user-1' }, + { type: 'track', event: 'Event 2', messageId: 'msg-batch-2', userId: 'user-2' } + ]) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('22 - mixed track+identify via performBatch()', async () => { + const res = await sendPayload([ + { type: 'track', event: 'Test Event', messageId: 'msg-mix-1', userId: 'user-1' }, + { type: 'identify', messageId: 'msg-mix-2', userId: 'user-2', traits: { name: 'User' } } + ]) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('23 - page+screen+group via performBatch()', async () => { + const res = await sendPayload([ + { type: 'page', name: 'Home', messageId: 'msg-psg-1', userId: 'user-1' }, + { type: 'screen', name: 'Dashboard', messageId: 'msg-psg-2', userId: 'user-2' }, + { type: 'group', groupId: 'grp-1', messageId: 'msg-psg-3', userId: 'user-3' } + ]) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + + it('24 - custom batch_size accepted via performBatch()', async () => { + const res = await sendPayload( + [ + { type: 'track', event: 'Event 1', messageId: 'msg-bs-1', userId: 'user-1' }, + { type: 'track', event: 'Event 2', messageId: 'msg-bs-2', userId: 'user-2' } + ], + { batch_size: 100 } + ) + expect(res.status).toBe(200) + expect(Array.isArray(res.body)).toBe(true) + }) + }) + + describe('Send - Error Paths', () => { + it('25 - invalid role triggers handleError generic branch → DEPENDENCY_ERROR 500', async () => { + const res = await post('/send', { + settings: { + iamRoleArn: 'arn:aws:iam::000000000000:role/nonexistent-role', + iamExternalId: 'bad-id' + }, + payload: { type: 'track', event: 'Test', messageId: 'msg-err', userId: 'user-1' }, + mapping: { + payload: { '@path': '$.' }, + partitionKey: { '@path': '$.messageId' }, + streamName: STREAM_NAME, + awsRegion: AWS_REGION, + max_batch_size: 500, + batch_bytes: 1000000 + } + }) + expect(res.status).toBe(200) + expect(res.body[0].statusCode).toBe(500) + }) + }) + + describe('Skipped (not testable via HTTP)', () => { + // Cannot trigger AccessDeniedException from Kinesis via HTTP. + // This branch fires when error.name === 'AccessDeniedException' from the Kinesis PutRecordsCommand. + // Requires a role that can be assumed via STS but has no kinesis:PutRecords permission on the stream. + it.skip('26 - handleError AccessDeniedException branch → 403', () => {}) + + // Cannot trigger AbortError via HTTP request. + // This branch fires when the AbortSignal passed to client.send(command, { abortSignal: signal }) + // is triggered. The signal comes from Segment runtime infrastructure, not from the request payload. + it.skip('27 - AbortError → RequestTimeoutError', () => {}) + + // Cannot force Kinesis to return partial failures via HTTP. + // This branch fires when response.FailedRecordCount > 0 and some records have ErrorCode set. + // Per-record error codes are set by Kinesis based on real infrastructure conditions. + it.skip('28 - handleMultiStatusResponse partial batch failure', () => {}) + + // Cannot force Kinesis to return all records failed via HTTP. + // Same reason as test 28 — requires Kinesis to return FailedRecordCount equal to total records. + it.skip('29 - handleMultiStatusResponse all records failed', () => {}) + + // Cannot trigger per-record error codes via HTTP. + // The ERROR_CODE_STATUS_MAP maps 20 Kinesis/KMS/STS error codes to HTTP status codes. + // These codes appear in response.Records[i].ErrorCode — set by Kinesis, not by the request. + it.skip('30 - convertErrorCodeToStatus all 20 ERROR_CODE_STATUS_MAP entries', () => {}) + + // Cannot trigger unknown error code via HTTP. + // This path fires when Kinesis returns an unrecognized ErrorCode on a per-record failure. + it.skip('31 - convertErrorCodeToStatus unknown code → 500', () => {}) + + // Cannot trigger undefined error code via HTTP. + // This path fires when Kinesis omits ErrorCode on a failed record (!code guard → return 500). + it.skip('32 - convertErrorCodeToStatus undefined code → 500', () => {}) + + // statsContext is not injected by the local serve environment. + // Metrics are only observable in Segment deployed infrastructure (Datadog). + it.skip('33 - stats metrics (statsContext.statsClient)', () => {}) + + // Cannot force STS to return empty credentials via HTTP. + // This guard fires when STS returns a successful AssumeRole response but with + // missing AccessKeyId, SecretAccessKey, or SessionToken. + it.skip('34 - getSTSCredentials returns empty credentials → 403', () => {}) + }) +}) diff --git a/packages/destination-actions/src/destinations/aws-sqs/__tests__/index.test.ts b/packages/destination-actions/src/destinations/aws-sqs/__tests__/index.test.ts new file mode 100644 index 00000000000..66670c2f9d2 --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/__tests__/index.test.ts @@ -0,0 +1,38 @@ +import { createTestIntegration } from '@segment/actions-core' +import Definition from '../index' + +const testDestination = createTestIntegration(Definition) + +describe('AWS SQS', () => { + describe('destination definition', () => { + it('has the correct name', () => { + expect(Definition.name).toBe('AWS SQS') + }) + + it('has the correct slug', () => { + expect(Definition.slug).toBe('actions-aws-sqs') + }) + + it('has cloud mode', () => { + expect(Definition.mode).toBe('cloud') + }) + + it('has the send action', () => { + expect(Definition.actions.send).toBeDefined() + }) + + it('has correct authentication fields', () => { + const authFields = Definition.authentication?.fields + expect(authFields?.iamRoleArn).toBeDefined() + expect(authFields?.iamRoleArn.required).toBe(true) + expect(authFields?.iamExternalId).toBeDefined() + expect(authFields?.iamExternalId.type).toBe('password') + }) + }) + + describe('testAuthentication', () => { + it('destination is defined', () => { + expect(testDestination).toBeDefined() + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/aws-sqs/generated-types.ts b/packages/destination-actions/src/destinations/aws-sqs/generated-types.ts new file mode 100644 index 00000000000..050f1b0dace --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/generated-types.ts @@ -0,0 +1,12 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Settings { + /** + * The ARN of the IAM role to assume for SQS access. Format: arn:aws:iam:::role/. Must have sqs:SendMessage and sqs:SendMessageBatch permissions. + */ + iamRoleArn: string + /** + * The external ID for cross-account role assumption. Used as a shared secret between Segment and the customer's IAM trust policy. + */ + iamExternalId: string +} diff --git a/packages/destination-actions/src/destinations/aws-sqs/index.ts b/packages/destination-actions/src/destinations/aws-sqs/index.ts new file mode 100644 index 00000000000..e932bdac9a0 --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/index.ts @@ -0,0 +1,47 @@ +import type { DestinationDefinition } from '@segment/actions-core' +import type { Settings } from './generated-types' +import { DEFAULT_REQUEST_TIMEOUT } from '@segment/actions-core' +import { assumeRole } from '../../lib/AWS/sts' +import { APP_AWS_REGION } from '../../lib/AWS/utils' +import send from './send' + +const destination: DestinationDefinition = { + name: 'AWS SQS', + slug: 'actions-aws-sqs', + mode: 'cloud', + description: + 'Amazon Simple Queue Service (SQS) is a fully managed message queuing service. This destination enables delivery of Segment events to SQS queues for asynchronous processing.', + authentication: { + scheme: 'custom', + fields: { + iamRoleArn: { + type: 'string', + label: 'IAM Role ARN', + description: + 'The ARN of the IAM role to assume for SQS access. Format: arn:aws:iam:::role/. Must have sqs:SendMessage and sqs:SendMessageBatch permissions.', + required: true + }, + iamExternalId: { + type: 'password', + label: 'External ID', + description: + "The external ID for cross-account role assumption. Used as a shared secret between Segment and the customer's IAM trust policy.", + required: true + } + }, + testAuthentication: async (_, { settings }) => { + await assumeRole(settings.iamRoleArn, settings.iamExternalId, APP_AWS_REGION) + return true + } + }, + extendRequest() { + return { + timeout: Math.max(30_000, DEFAULT_REQUEST_TIMEOUT) + } + }, + actions: { + send + } +} + +export default destination diff --git a/packages/destination-actions/src/destinations/aws-sqs/send/__tests__/index.test.ts b/packages/destination-actions/src/destinations/aws-sqs/send/__tests__/index.test.ts new file mode 100644 index 00000000000..7be4de633cd --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/send/__tests__/index.test.ts @@ -0,0 +1,226 @@ +import { createTestEvent, createTestIntegration, SegmentEvent } from '@segment/actions-core' +import Definition from '../../index' +import type { Settings } from '../../generated-types' + +let testDestination = createTestIntegration(Definition) + +const mockSend = jest.fn() +const mockAssumeRole = jest.fn() + +jest.mock('@aws-sdk/client-sqs', () => ({ + SQSClient: jest.fn(() => ({ + send: mockSend + })), + SendMessageBatchCommand: class { + constructor(public input: any) {} + } +})) + +jest.mock('../../../../lib/AWS/sts', () => ({ + assumeRole: (...args: unknown[]) => mockAssumeRole(...args) +})) + +const settings: Settings = { + iamRoleArn: 'arn:aws:iam::123456789012:role/test-role', + iamExternalId: 'test-external-id' +} + +const mapping = { + payload: { '@path': '$.' }, + queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue', + awsRegion: 'us-east-1', + messageDeduplicationId: { '@path': '$.messageId' }, + enable_batching: true, + batch_size: 10 +} + +const basePayload: Partial = { + userId: 'user-123', + anonymousId: 'anon-456', + event: 'Test Event', + type: 'track', + timestamp: '2026-04-06T10:00:00.000Z', + properties: { + product_id: 'prod-789', + price: 99.99 + }, + messageId: 'msg-abc-123' +} + +describe('AWS SQS Send', () => { + beforeEach(() => { + testDestination = createTestIntegration(Definition) + jest.clearAllMocks() + mockAssumeRole.mockResolvedValue({ + accessKeyId: 'AKIA...', + secretAccessKey: 'SECRET...', + sessionToken: 'TOKEN...' + }) + }) + + describe('successful send', () => { + it('sends a single event with correct payload', async () => { + mockSend.mockResolvedValueOnce({ + Successful: [{ Id: '0', MessageId: 'sqs-msg-001', MD5OfMessageBody: 'abc123' }], + Failed: [] + }) + + const event = createTestEvent(basePayload) + + const response = await testDestination.testAction('send', { + event, + settings, + useDefaultMappings: true, + mapping + }) + + expect(response).toBeDefined() + expect(mockAssumeRole).toHaveBeenCalledWith( + 'arn:aws:iam::123456789012:role/test-role', + 'test-external-id', + 'us-west-2' + ) + expect(mockSend).toHaveBeenCalledTimes(1) + + const command = mockSend.mock.calls[0][0] + expect(command.input.QueueUrl).toBe('https://sqs.us-east-1.amazonaws.com/123456789012/test-queue') + expect(command.input.Entries).toHaveLength(1) + expect(command.input.Entries[0].Id).toBe('0') + + const messageBody = JSON.parse(command.input.Entries[0].MessageBody) + expect(messageBody.userId).toBe('user-123') + expect(messageBody.event).toBe('Test Event') + expect(messageBody.properties.product_id).toBe('prod-789') + }) + + it('sends a batch of events', async () => { + mockSend.mockResolvedValueOnce({ + Successful: [ + { Id: '0', MessageId: 'sqs-msg-001', MD5OfMessageBody: 'abc123' }, + { Id: '1', MessageId: 'sqs-msg-002', MD5OfMessageBody: 'def456' } + ], + Failed: [] + }) + + const events = [ + createTestEvent({ ...basePayload, messageId: 'msg-1' }), + createTestEvent({ ...basePayload, messageId: 'msg-2', event: 'Second Event' }) + ] + + const response = await testDestination.testBatchAction('send', { + events, + settings, + useDefaultMappings: true, + mapping + }) + + expect(response).toBeDefined() + expect(mockSend).toHaveBeenCalledTimes(1) + + const command = mockSend.mock.calls[0][0] + expect(command.input.Entries).toHaveLength(2) + expect(command.input.Entries[0].Id).toBe('0') + expect(command.input.Entries[1].Id).toBe('1') + }) + }) + + describe('FIFO queue support', () => { + it('includes messageGroupId when provided', async () => { + mockSend.mockResolvedValueOnce({ + Successful: [{ Id: '0', MessageId: 'sqs-msg-001', MD5OfMessageBody: 'abc123' }], + Failed: [] + }) + + const event = createTestEvent(basePayload) + + await testDestination.testAction('send', { + event, + settings, + useDefaultMappings: true, + mapping: { + ...mapping, + queueUrl: 'https://sqs.us-east-1.amazonaws.com/123456789012/test-queue.fifo', + messageGroupId: 'user-123' + } + }) + + const command = mockSend.mock.calls[0][0] + expect(command.input.Entries[0].MessageGroupId).toBe('user-123') + }) + }) + + describe('error handling', () => { + it('handles partial batch failure with MultiStatusResponse', async () => { + mockSend.mockResolvedValueOnce({ + Successful: [{ Id: '0', MessageId: 'sqs-msg-001', MD5OfMessageBody: 'abc123' }], + Failed: [{ Id: '1', Code: 'InternalError', Message: 'Internal service error', SenderFault: false }] + }) + + const events = [ + createTestEvent({ ...basePayload, messageId: 'msg-1' }), + createTestEvent({ ...basePayload, messageId: 'msg-2' }) + ] + + const response = await testDestination.testBatchAction('send', { + events, + settings, + useDefaultMappings: true, + mapping + }) + + expect(response).toBeDefined() + expect(mockSend).toHaveBeenCalledTimes(1) + }) + + it('throws RetryableError on RequestThrottled', async () => { + const throttleError = new Error('Rate exceeded') + throttleError.name = 'RequestThrottled' + mockSend.mockRejectedValueOnce(throttleError) + + const event = createTestEvent(basePayload) + + await expect( + testDestination.testAction('send', { + event, + settings, + useDefaultMappings: true, + mapping + }) + ).rejects.toThrowError(/Retryable error RequestThrottled/) + }) + + it('throws IntegrationError on QueueDoesNotExist', async () => { + const notFoundError = new Error('Queue not found') + notFoundError.name = 'QueueDoesNotExist' + mockSend.mockRejectedValueOnce(notFoundError) + + const event = createTestEvent(basePayload) + + await expect( + testDestination.testAction('send', { + event, + settings, + useDefaultMappings: true, + mapping + }) + ).rejects.toThrowError(/Non-retryable error QueueDoesNotExist/) + }) + + it('throws IntegrationError on InvalidSecurity', async () => { + const securityError = new Error('Invalid security token') + securityError.name = 'InvalidSecurity' + mockSend.mockRejectedValueOnce(securityError) + + const event = createTestEvent(basePayload) + + await expect( + testDestination.testAction('send', { + event, + settings, + useDefaultMappings: true, + mapping + }) + ).rejects.toThrowError(/Non-retryable error InvalidSecurity/) + }) + }) +}) diff --git a/packages/destination-actions/src/destinations/aws-sqs/send/generated-types.ts b/packages/destination-actions/src/destinations/aws-sqs/send/generated-types.ts new file mode 100644 index 00000000000..313c836f135 --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/send/generated-types.ts @@ -0,0 +1,50 @@ +// Generated file. DO NOT MODIFY IT BY HAND. + +export interface Payload { + /** + * The event data to send as the SQS message body. Maps the entire Segment event by default. + */ + payload: { + [k: string]: unknown + } + /** + * The URL of the SQS queue to send messages to. Format: https://sqs..amazonaws.com// + */ + queueUrl: string + /** + * The AWS region where the SQS queue is located. + */ + awsRegion: string + /** + * Required for FIFO queues. Specifies the message group for ordering. Recommended: userId or anonymousId for user-level ordering. + */ + messageGroupId?: string + /** + * Used for FIFO queues with content-based deduplication disabled. Defaults to Segment messageId. + */ + messageDeduplicationId?: string + /** + * The number of seconds to delay message delivery (0-900). Only applies to Standard queues. + */ + delaySeconds?: number + /** + * Enable batching of messages using SendMessageBatch API. + */ + enable_batching: boolean + /** + * Maximum number of messages per SendMessageBatch request. SQS API limit is 10. + */ + batch_size?: number + /** + * Fields used to group events into separate batches. Events targeting different queues/regions are batched separately. + */ + batch_keys?: string[] + /** + * Framework-level cap on batch size. Must not exceed 10 (SQS SendMessageBatch limit). + */ + max_batch_size: number + /** + * Maximum total bytes per batch. SQS limits each SendMessageBatch to 1MB (1,048,576 bytes). + */ + batch_bytes: number +} diff --git a/packages/destination-actions/src/destinations/aws-sqs/send/index.ts b/packages/destination-actions/src/destinations/aws-sqs/send/index.ts new file mode 100644 index 00000000000..c2ee5cff863 --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/send/index.ts @@ -0,0 +1,109 @@ +import type { ActionDefinition } from '@segment/actions-core' +import type { Settings } from '../generated-types' +import type { Payload } from './generated-types' +import { AWS_REGIONS } from '../../../lib/AWS/utils' +import { send } from '../utils' + +const action: ActionDefinition = { + title: 'Send', + description: 'Send event data to an Amazon SQS queue.', + defaultSubscription: 'type = "track" or type = "identify" or type = "page" or type = "group" or type = "alias"', + fields: { + payload: { + label: 'Payload', + description: 'The event data to send as the SQS message body. Maps the entire Segment event by default.', + type: 'object', + default: { '@path': '$.' }, + required: true + }, + queueUrl: { + label: 'Queue URL', + description: + 'The URL of the SQS queue to send messages to. Format: https://sqs..amazonaws.com//', + type: 'string', + required: true + }, + awsRegion: { + label: 'AWS Region', + description: 'The AWS region where the SQS queue is located.', + type: 'string', + required: true, + choices: AWS_REGIONS + }, + messageGroupId: { + label: 'Message Group ID', + description: + 'Required for FIFO queues. Specifies the message group for ordering. Recommended: userId or anonymousId for user-level ordering.', + type: 'string', + required: false + }, + messageDeduplicationId: { + label: 'Message Deduplication ID', + description: 'Used for FIFO queues with content-based deduplication disabled. Defaults to Segment messageId.', + type: 'string', + required: false + }, + delaySeconds: { + label: 'Delay Seconds', + description: 'The number of seconds to delay message delivery (0-900). Only applies to Standard queues.', + type: 'number', + default: 0, + required: false, + minimum: 0, + maximum: 900 + }, + enable_batching: { + type: 'boolean', + label: 'Enable Batching', + description: 'Enable batching of messages using SendMessageBatch API.', + unsafe_hidden: true, + required: true, + default: true + }, + batch_size: { + label: 'Batch Size', + description: 'Maximum number of messages per SendMessageBatch request. SQS API limit is 10.', + type: 'number', + unsafe_hidden: true, + required: false, + default: 10, + minimum: 1, + maximum: 10 + }, + batch_keys: { + label: 'Batch Keys', + description: + 'Fields used to group events into separate batches. Events targeting different queues/regions are batched separately.', + type: 'string', + unsafe_hidden: true, + default: ['awsRegion', 'queueUrl'], + multiple: true + }, + max_batch_size: { + label: 'Max Batch Size', + description: 'Framework-level cap on batch size. Must not exceed 10 (SQS SendMessageBatch limit).', + type: 'number', + required: true, + minimum: 1, + maximum: 10, + default: 10, + unsafe_hidden: true + }, + batch_bytes: { + type: 'number', + label: 'Batch Bytes', + description: 'Maximum total bytes per batch. SQS limits each SendMessageBatch to 1MB (1,048,576 bytes).', + default: 1048576, + required: true, + unsafe_hidden: true + } + }, + perform: (_, { payload, settings, statsContext, logger, signal }) => { + return send([payload], settings, statsContext, logger, signal) + }, + performBatch: (_, { payload, settings, statsContext, logger, signal }) => { + return send(payload, settings, statsContext, logger, signal) + } +} + +export default action diff --git a/packages/destination-actions/src/destinations/aws-sqs/utils.ts b/packages/destination-actions/src/destinations/aws-sqs/utils.ts new file mode 100644 index 00000000000..af9f010cb7e --- /dev/null +++ b/packages/destination-actions/src/destinations/aws-sqs/utils.ts @@ -0,0 +1,165 @@ +import type { Payload } from './send/generated-types' +import type { Settings } from './generated-types' +import { MultiStatusResponse, RetryableError, IntegrationError, RequestTimeoutError } from '@segment/actions-core' +import type { Logger, StatsContext } from '@segment/actions-core/destination-kit' +import { SQSClient, SendMessageBatchCommand } from '@aws-sdk/client-sqs' +import type { + SendMessageBatchCommandOutput, + BatchResultErrorEntry, + SendMessageBatchResultEntry +} from '@aws-sdk/client-sqs' +import { assumeRole } from '../../lib/AWS/sts' +import { APP_AWS_REGION } from '../../lib/AWS/utils' + +const SQSRetryableErrors: Record = { + RequestThrottled: 'RETRYABLE', + KmsThrottled: 'RETRYABLE' +} + +const SQSNonRetryableErrors: Record = { + QueueDoesNotExist: 404, + InvalidAddress: 400, + InvalidSecurity: 403, + BatchEntryIdsNotDistinct: 400, + BatchRequestTooLong: 400, + EmptyBatchRequest: 400, + InvalidBatchEntryId: 400, + TooManyEntriesInBatchRequest: 400, + UnsupportedOperation: 400, + KmsAccessDenied: 403, + KmsDisabled: 503, + KmsInvalidState: 400, + KmsInvalidKeyUsage: 400, + KmsNotFound: 404, + KmsOptInRequired: 403 +} + +export async function send( + payloads: Payload[], + settings: Settings, + statsContext?: StatsContext, + logger?: Logger, + signal?: AbortSignal +): Promise { + const { awsRegion } = payloads[0] + console.log( + '[SQS] Starting send. Region:', + awsRegion, + 'Queue:', + payloads[0].queueUrl, + 'Payload count:', + payloads.length + ) + + console.log('[SQS] Assuming role:', settings.iamRoleArn) + const credentials = await assumeRole(settings.iamRoleArn, settings.iamExternalId, APP_AWS_REGION) + console.log('[SQS] Role assumed successfully') + + const client = new SQSClient({ + region: awsRegion, + credentials: { + accessKeyId: credentials.accessKeyId, + secretAccessKey: credentials.secretAccessKey, + sessionToken: credentials.sessionToken + } + }) + + const command = new SendMessageBatchCommand({ + QueueUrl: payloads[0].queueUrl, + Entries: payloads.map((p, index) => ({ + Id: String(index), + MessageBody: JSON.stringify(p.payload), + MessageGroupId: p.messageGroupId || undefined, + MessageDeduplicationId: p.messageDeduplicationId || undefined, + DelaySeconds: p.delaySeconds || undefined + })) + }) + + statsContext?.statsClient?.histogram('actions_sqs.batch_size', payloads.length, statsContext?.tags) + statsContext?.statsClient?.incr('actions_sqs.request_hit', 1, statsContext?.tags) + + let response: SendMessageBatchCommandOutput + + try { + response = await client.send(command, { abortSignal: signal }) + console.log( + '[SQS] Send successful. Successful:', + response.Successful?.length ?? 0, + 'Failed:', + response.Failed?.length ?? 0 + ) + if (response.Failed?.length) { + console.log('[SQS] Failed entries:', JSON.stringify(response.Failed)) + } + } catch (error) { + console.log('[SQS] Send failed:', (error as Error).name, (error as Error).message) + if ((error as Error).name === 'AbortError') { + throw new RequestTimeoutError() + } + if (logger && typeof logger.crit === 'function') { + logger.crit('Failed to send batch to SQS:', error) + } + throwError(error, 'client.send') + } + + return buildMultiStatusResponse(response, payloads) +} + +function buildMultiStatusResponse(response: SendMessageBatchCommandOutput, payloads: Payload[]): MultiStatusResponse { + const successful: SendMessageBatchResultEntry[] = response.Successful ?? [] + const failed: BatchResultErrorEntry[] = response.Failed ?? [] + const multiStatusResponse = new MultiStatusResponse() + + for (const entry of successful) { + const index = Number(entry.Id) + multiStatusResponse.setSuccessResponseAtIndex(index, { + status: 200, + body: JSON.stringify(entry), + sent: JSON.stringify(payloads[index]) + }) + } + + for (const entry of failed) { + const index = Number(entry.Id) + const status = entry.SenderFault ? 400 : 429 + multiStatusResponse.setErrorResponseAtIndex(index, { + status, + errormessage: entry.Message ?? 'Unknown Error', + sent: JSON.stringify(payloads[index]), + body: JSON.stringify(entry) + }) + } + + return multiStatusResponse +} + +function isRetryableError(error: unknown): boolean { + if (typeof error === 'object' && error !== null && 'name' in error) { + const err = error as { name: string } + return err.name in SQSRetryableErrors + } + return false +} + +function isNonRetryableError(error: unknown): boolean { + if (typeof error === 'object' && error !== null && 'name' in error) { + const err = error as { name: string } + return err.name in SQSNonRetryableErrors + } + return false +} + +function throwError(error: unknown, context: string): never { + if (isRetryableError(error)) { + const err = error as { name: string; message?: string } + const message = err.message ?? 'No error message returned' + throw new RetryableError(`Retryable error ${err.name} in ${context}. Message: ${message}`) + } else if (isNonRetryableError(error)) { + const err = error as { name: string; message?: string } + const message = err.message ?? 'No error message returned' + const status = SQSNonRetryableErrors[err.name] ?? 400 + throw new IntegrationError(`Non-retryable error ${err.name} in ${context}. Message: ${message}`, err.name, status) + } else { + throw new IntegrationError(`Unknown error in ${context}: ${JSON.stringify(error)}`, 'UnknownError', 400) + } +} diff --git a/yarn.lock b/yarn.lock index 218956924fa..4b67b866536 100644 --- a/yarn.lock +++ b/yarn.lock @@ -283,6 +283,53 @@ "@smithy/util-waiter" "^4.2.8" tslib "^2.6.2" +"@aws-sdk/client-sqs@3.974.0": + version "3.974.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sqs/-/client-sqs-3.974.0.tgz#a1cb0db59dfd64ebfd3e79fb5e158be572243451" + integrity sha512-dfJcrDwEGxOB2fea4IhhMdr7WL7OKY+fWegrlf91ewG7h7Lk0Zws/6yLQP2GCNECoLwveGQkYNZTUT2rfF52Gw== + dependencies: + "@aws-crypto/sha256-browser" "5.2.0" + "@aws-crypto/sha256-js" "5.2.0" + "@aws-sdk/core" "^3.973.0" + "@aws-sdk/credential-provider-node" "^3.972.1" + "@aws-sdk/middleware-host-header" "^3.972.1" + "@aws-sdk/middleware-logger" "^3.972.1" + "@aws-sdk/middleware-recursion-detection" "^3.972.1" + "@aws-sdk/middleware-sdk-sqs" "^3.972.1" + "@aws-sdk/middleware-user-agent" "^3.972.1" + "@aws-sdk/region-config-resolver" "^3.972.1" + "@aws-sdk/types" "^3.973.0" + "@aws-sdk/util-endpoints" "3.972.0" + "@aws-sdk/util-user-agent-browser" "^3.972.1" + "@aws-sdk/util-user-agent-node" "^3.972.1" + "@smithy/config-resolver" "^4.4.6" + "@smithy/core" "^3.21.0" + "@smithy/fetch-http-handler" "^5.3.9" + "@smithy/hash-node" "^4.2.8" + "@smithy/invalid-dependency" "^4.2.8" + "@smithy/md5-js" "^4.2.8" + "@smithy/middleware-content-length" "^4.2.8" + "@smithy/middleware-endpoint" "^4.4.10" + "@smithy/middleware-retry" "^4.4.26" + "@smithy/middleware-serde" "^4.2.9" + "@smithy/middleware-stack" "^4.2.8" + "@smithy/node-config-provider" "^4.3.8" + "@smithy/node-http-handler" "^4.4.8" + "@smithy/protocol-http" "^5.3.8" + "@smithy/smithy-client" "^4.10.11" + "@smithy/types" "^4.12.0" + "@smithy/url-parser" "^4.2.8" + "@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.25" + "@smithy/util-defaults-mode-node" "^4.2.28" + "@smithy/util-endpoints" "^3.2.8" + "@smithy/util-middleware" "^4.2.8" + "@smithy/util-retry" "^4.2.8" + "@smithy/util-utf8" "^4.2.0" + tslib "^2.6.2" + "@aws-sdk/client-sso@3.982.0": version "3.982.0" resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.982.0.tgz#36ea3868045c6d0ade03bf7a0119ac3a1abf79a8" @@ -820,6 +867,18 @@ "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" +"@aws-sdk/middleware-sdk-sqs@^3.972.1": + version "3.972.18" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-sqs/-/middleware-sdk-sqs-3.972.18.tgz#652935d71d0b7b4351b38f3f506ea2291724fd29" + integrity sha512-BdsGFuBJUX5PnuZkEV6JRB5g/6ts7iGmN3pXwyoiGCCM2HHXrlFqjkBs+iPX7yO884WqYeQJpme7nwn4DzU5xw== + dependencies: + "@aws-sdk/types" "^3.973.6" + "@smithy/smithy-client" "^4.12.8" + "@smithy/types" "^4.13.1" + "@smithy/util-hex-encoding" "^4.2.2" + "@smithy/util-utf8" "^4.2.2" + tslib "^2.6.2" + "@aws-sdk/middleware-ssec@^3.972.3": version "3.972.3" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-ssec/-/middleware-ssec-3.972.3.tgz#4f81d310fd91164e6e18ba3adab6bcf906920333" @@ -1028,6 +1087,14 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@aws-sdk/types@^3.973.6": + version "3.973.6" + resolved "https://registry.yarnpkg.com/@aws-sdk/types/-/types-3.973.6.tgz#1964a7c01b5cb18befa445998ad1d02f86c5432d" + integrity sha512-Atfcy4E++beKtwJHiDln2Nby8W/mam64opFPTiHEqgsthqeydFS1pY+OUlN1ouNOmf8ArPU/6cDS65anOP3KQw== + dependencies: + "@smithy/types" "^4.13.1" + tslib "^2.6.2" + "@aws-sdk/util-arn-parser@^3.972.2": version "3.972.2" resolved "https://registry.yarnpkg.com/@aws-sdk/util-arn-parser/-/util-arn-parser-3.972.2.tgz#ef18ba889e8ef35f083f1e962018bc0ce70acef3" @@ -4459,6 +4526,22 @@ "@smithy/uuid" "^1.1.0" tslib "^2.6.2" +"@smithy/core@^3.23.14": + version "3.23.14" + resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.23.14.tgz#29c3b6cf771ee8898018a1cc34c0fe3f418468e5" + integrity sha512-vJ0IhpZxZAkFYOegMKSrxw7ujhhT2pass/1UEcZ4kfl5srTAqtPU5I7MdYQoreVas3204ykCiNhY1o7Xlz6Yyg== + dependencies: + "@smithy/protocol-http" "^5.3.13" + "@smithy/types" "^4.14.0" + "@smithy/url-parser" "^4.2.13" + "@smithy/util-base64" "^4.3.2" + "@smithy/util-body-length-browser" "^4.2.2" + "@smithy/util-middleware" "^4.2.13" + "@smithy/util-stream" "^4.5.22" + "@smithy/util-utf8" "^4.2.2" + "@smithy/uuid" "^1.1.2" + tslib "^2.6.2" + "@smithy/core@^3.23.2": version "3.23.2" resolved "https://registry.yarnpkg.com/@smithy/core/-/core-3.23.2.tgz#9300fe6fa6e8ceb19ecbbb9090ccea04942a37f0" @@ -4531,6 +4614,17 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/fetch-http-handler@^5.3.16": + version "5.3.16" + resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.16.tgz#2cd94de19ac2bcdb51682259cf6dcacbb1b382a9" + integrity sha512-nYDRUIvNd4mFmuXraRWt6w5UsZTNqtj4hXJA/iiOD4tuseIdLP9Lq38teH/SZTcIFCa2f+27o7hYpIsWktJKEQ== + dependencies: + "@smithy/protocol-http" "^5.3.13" + "@smithy/querystring-builder" "^4.2.13" + "@smithy/types" "^4.14.0" + "@smithy/util-base64" "^4.3.2" + tslib "^2.6.2" + "@smithy/fetch-http-handler@^5.3.9": version "5.3.9" resolved "https://registry.yarnpkg.com/@smithy/fetch-http-handler/-/fetch-http-handler-5.3.9.tgz#edfc9e90e0c7538c81e22e748d62c0066cc91d58" @@ -4593,6 +4687,13 @@ dependencies: tslib "^2.6.2" +"@smithy/is-array-buffer@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/is-array-buffer/-/is-array-buffer-4.2.2.tgz#c401ce54b12a16529eb1c938a0b6c2247cb763b8" + integrity sha512-n6rQ4N8Jj4YTQO3YFrlgZuwKodf4zUFs7EJIWH86pSCWBaAtAGBFfCM7Wx6D2bBJ2xqFNxGBSrUWswT3M0VJow== + dependencies: + tslib "^2.6.2" + "@smithy/md5-js@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/md5-js/-/md5-js-4.2.8.tgz#d354dbf9aea7a580be97598a581e35eef324ce22" @@ -4639,6 +4740,20 @@ "@smithy/util-middleware" "^4.2.8" tslib "^2.6.2" +"@smithy/middleware-endpoint@^4.4.29": + version "4.4.29" + resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-4.4.29.tgz#86fa2f206469e48bff1b30b2c35e433b5f453119" + integrity sha512-R9Q/58U+qBiSARGWbAbFLczECg/RmysRksX6Q8BaQEpt75I7LI6WGDZnjuC9GXSGKljEbA7N118LhGaMbfrTXw== + dependencies: + "@smithy/core" "^3.23.14" + "@smithy/middleware-serde" "^4.2.17" + "@smithy/node-config-provider" "^4.3.13" + "@smithy/shared-ini-file-loader" "^4.4.8" + "@smithy/types" "^4.14.0" + "@smithy/url-parser" "^4.2.13" + "@smithy/util-middleware" "^4.2.13" + tslib "^2.6.2" + "@smithy/middleware-retry@^4.4.26", "@smithy/middleware-retry@^4.4.29": version "4.4.30" resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-4.4.30.tgz#a0548803044069b53a332606d4b4f803f07f8963" @@ -4669,6 +4784,16 @@ "@smithy/uuid" "^1.1.0" tslib "^2.6.2" +"@smithy/middleware-serde@^4.2.17": + version "4.2.17" + resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.2.17.tgz#45b1eaa99c3b536042eb56365096e6681f2a347b" + integrity sha512-0T2mcaM6v9W1xku86Dk0bEW7aEseG6KenFkPK98XNw0ZhOqOiD1MrMsdnQw9QsL3/Oa85T53iSMlm0SZdSuIEQ== + dependencies: + "@smithy/core" "^3.23.14" + "@smithy/protocol-http" "^5.3.13" + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/middleware-serde@^4.2.9": version "4.2.9" resolved "https://registry.yarnpkg.com/@smithy/middleware-serde/-/middleware-serde-4.2.9.tgz#fd9d9b02b265aef67c9a30f55c2a5038fc9ca791" @@ -4678,6 +4803,14 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/middleware-stack@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.2.13.tgz#88007ea7eb40ab3ff632701c21149e0e8a57b55f" + integrity sha512-g72jN/sGDLyTanrCLH9fhg3oysO3f7tQa6eWWsMyn2BiYNCgjF24n4/I9wff/5XidFvjj9ilipAoQrurTUrLvw== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/middleware-stack@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/middleware-stack/-/middleware-stack-4.2.8.tgz#4fa9cfaaa05f664c9bb15d45608f3cb4f6da2b76" @@ -4686,6 +4819,16 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/node-config-provider@^4.3.13": + version "4.3.13" + resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.3.13.tgz#a65c696a38a0c2e7012652b1c1138799882b12bc" + integrity sha512-iGxQ04DsKXLckbgnX4ipElrOTk+IHgTyu0q0WssZfYhDm9CQWHmu6cOeI5wmWRxpXbBDhIIfXMWz5tPEtcVqbw== + dependencies: + "@smithy/property-provider" "^4.2.13" + "@smithy/shared-ini-file-loader" "^4.4.8" + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/node-config-provider@^4.3.8": version "4.3.8" resolved "https://registry.yarnpkg.com/@smithy/node-config-provider/-/node-config-provider-4.3.8.tgz#85a0683448262b2eb822f64c14278d4887526377" @@ -4718,6 +4861,24 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/node-http-handler@^4.5.2": + version "4.5.2" + resolved "https://registry.yarnpkg.com/@smithy/node-http-handler/-/node-http-handler-4.5.2.tgz#21d70f4c9cf1ce59921567bab59ae1177b6c60b1" + integrity sha512-/oD7u8M0oj2ZTFw7GkuuHWpIxtWdLlnyNkbrWcyVYhd5RJNDuczdkb0wfnQICyNFrVPlr8YHOhamjNy3zidhmA== + dependencies: + "@smithy/protocol-http" "^5.3.13" + "@smithy/querystring-builder" "^4.2.13" + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + +"@smithy/property-provider@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.2.13.tgz#4859f887414f2c251517125258870a70509f8bbd" + integrity sha512-bGzUCthxRmezuxkbu9wD33wWg9KX3hJpCXpQ93vVkPrHn9ZW6KNNdY5xAUWNuRCwQ+VyboFuWirG1lZhhkcyRQ== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/property-provider@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/property-provider/-/property-provider-4.2.8.tgz#6e37b30923d2d31370c50ce303a4339020031472" @@ -4726,6 +4887,14 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/protocol-http@^5.3.13": + version "5.3.13" + resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.3.13.tgz#1e8fcacd61282cafc2c783ab002cb0debe763588" + integrity sha512-+HsmuJUF4u8POo6s8/a2Yb/AQ5t/YgLovCuHF9oxbocqv+SZ6gd8lC2duBFiCA/vFHoHQhoq7QjqJqZC6xOxxg== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/protocol-http@^5.3.8": version "5.3.8" resolved "https://registry.yarnpkg.com/@smithy/protocol-http/-/protocol-http-5.3.8.tgz#0938f69a3c3673694c2f489a640fce468ce75006" @@ -4734,6 +4903,15 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/querystring-builder@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.2.13.tgz#1f3c009493a06d83f998da70f5920246dfcd88dd" + integrity sha512-tG4aOYFCZdPMjbgfhnIQ322H//ojujldp1SrHPHpBSb3NqgUp3dwiUGRJzie87hS1DYwWGqDuPaowoDF+rYCbQ== + dependencies: + "@smithy/types" "^4.14.0" + "@smithy/util-uri-escape" "^4.2.2" + tslib "^2.6.2" + "@smithy/querystring-builder@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/querystring-builder/-/querystring-builder-4.2.8.tgz#2fa72d29eb1844a6a9933038bbbb14d6fe385e93" @@ -4743,6 +4921,14 @@ "@smithy/util-uri-escape" "^4.2.0" tslib "^2.6.2" +"@smithy/querystring-parser@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.2.13.tgz#c2ab4446a50d0de232bbffdab534b3e0023bf879" + integrity sha512-hqW3Q4P+CDzUyQ87GrboGMeD7XYNMOF+CuTwu936UQRB/zeYn3jys8C3w+wMkDfY7CyyyVwZQ5cNFoG0x1pYmA== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/querystring-parser@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/querystring-parser/-/querystring-parser-4.2.8.tgz#aa3f2456180ce70242e89018d0b1ebd4782a6347" @@ -4766,6 +4952,14 @@ "@smithy/types" "^4.12.0" tslib "^2.6.2" +"@smithy/shared-ini-file-loader@^4.4.8": + version "4.4.8" + resolved "https://registry.yarnpkg.com/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-4.4.8.tgz#c45099e8aea8f48af97d05be91ab6ae93d105ae7" + integrity sha512-VZCZx2bZasxdqxVgEAhREvDSlkatTPnkdWy1+Kiy8w7kYPBosW0V5IeDwzDUMvWBt56zpK658rx1cOBFOYaPaw== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/signature-v4@^5.3.8": version "5.3.8" resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-5.3.8.tgz#796619b10b7cc9467d0625b0ebd263ae04fdfb76" @@ -4806,6 +5000,19 @@ "@smithy/util-stream" "^4.5.12" tslib "^2.6.2" +"@smithy/smithy-client@^4.12.8": + version "4.12.9" + resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-4.12.9.tgz#2eb54ee07050a8bcd3792f8b8c4e03fac4bfb422" + integrity sha512-ovaLEcTU5olSeHcRXcxV6viaKtpkHZumn6Ps0yn7dRf2rRSfy794vpjOtrWDO0d1auDSvAqxO+lyhERSXQ03EQ== + dependencies: + "@smithy/core" "^3.23.14" + "@smithy/middleware-endpoint" "^4.4.29" + "@smithy/middleware-stack" "^4.2.13" + "@smithy/protocol-http" "^5.3.13" + "@smithy/types" "^4.14.0" + "@smithy/util-stream" "^4.5.22" + tslib "^2.6.2" + "@smithy/types@^3.3.0": version "3.3.0" resolved "https://registry.yarnpkg.com/@smithy/types/-/types-3.3.0.tgz#fae037c733d09bc758946a01a3de0ef6e210b16b" @@ -4820,6 +5027,22 @@ dependencies: tslib "^2.6.2" +"@smithy/types@^4.13.1", "@smithy/types@^4.14.0": + version "4.14.0" + resolved "https://registry.yarnpkg.com/@smithy/types/-/types-4.14.0.tgz#72fb6fd315f2eff7d4878142db2d1db4ef94f9bc" + integrity sha512-OWgntFLW88kx2qvf/c/67Vno1yuXm/f9M7QFAtVkkO29IJXGBIg0ycEaBTH0kvCtwmvZxRujrgP5a86RvsXJAQ== + dependencies: + tslib "^2.6.2" + +"@smithy/url-parser@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.2.13.tgz#cc582733d1181e1a135b05bb600f12c9889be7f4" + integrity sha512-2G03yoboIRZlZze2+PT4GZEjgwQsJjUgn6iTsvxA02bVceHR6vp4Cuk7TUnPFWKF+ffNUk3kj4COwkENS2K3vw== + dependencies: + "@smithy/querystring-parser" "^4.2.13" + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/url-parser@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/url-parser/-/url-parser-4.2.8.tgz#b44267cd704abe114abcd00580acdd9e4acc1177" @@ -4838,6 +5061,15 @@ "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" +"@smithy/util-base64@^4.3.2": + version "4.3.2" + resolved "https://registry.yarnpkg.com/@smithy/util-base64/-/util-base64-4.3.2.tgz#be02bcb29a87be744356467ea25ffa413e695cea" + integrity sha512-XRH6b0H/5A3SgblmMa5ErXQ2XKhfbQB+Fm/oyLZ2O2kCUrwgg55bU0RekmzAhuwOjA9qdN5VU2BprOvGGUkOOQ== + dependencies: + "@smithy/util-buffer-from" "^4.2.2" + "@smithy/util-utf8" "^4.2.2" + tslib "^2.6.2" + "@smithy/util-body-length-browser@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.0.tgz#04e9fc51ee7a3e7f648a4b4bcdf96c350cfa4d61" @@ -4845,6 +5077,13 @@ dependencies: tslib "^2.6.2" +"@smithy/util-body-length-browser@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-body-length-browser/-/util-body-length-browser-4.2.2.tgz#c4404277d22039872abdb80e7800f9a63f263862" + integrity sha512-JKCrLNOup3OOgmzeaKQwi4ZCTWlYR5H4Gm1r2uTMVBXoemo1UEghk5vtMi1xSu2ymgKVGW631e2fp9/R610ZjQ== + dependencies: + tslib "^2.6.2" + "@smithy/util-body-length-node@^4.2.1": version "4.2.1" resolved "https://registry.yarnpkg.com/@smithy/util-body-length-node/-/util-body-length-node-4.2.1.tgz#79c8a5d18e010cce6c42d5cbaf6c1958523e6fec" @@ -4868,6 +5107,14 @@ "@smithy/is-array-buffer" "^4.2.0" tslib "^2.6.2" +"@smithy/util-buffer-from@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-buffer-from/-/util-buffer-from-4.2.2.tgz#2c6b7857757dfd88f6cd2d36016179a40ccc913b" + integrity sha512-FDXD7cvUoFWwN6vtQfEta540Y/YBe5JneK3SoZg9bThSoOAC/eGeYEua6RkBgKjGa/sz6Y+DuBZj3+YEY21y4Q== + dependencies: + "@smithy/is-array-buffer" "^4.2.2" + tslib "^2.6.2" + "@smithy/util-config-provider@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@smithy/util-config-provider/-/util-config-provider-4.2.0.tgz#2e4722937f8feda4dcb09672c59925a4e6286cfc" @@ -4937,6 +5184,21 @@ dependencies: tslib "^2.6.2" +"@smithy/util-hex-encoding@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-hex-encoding/-/util-hex-encoding-4.2.2.tgz#4abf3335dd1eb884041d8589ca7628d81a6fd1d3" + integrity sha512-Qcz3W5vuHK4sLQdyT93k/rfrUwdJ8/HZ+nMUOyGdpeGA1Wxt65zYwi3oEl9kOM+RswvYq90fzkNDahPS8K0OIg== + dependencies: + tslib "^2.6.2" + +"@smithy/util-middleware@^4.2.13": + version "4.2.13" + resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.2.13.tgz#fda5518f95cc3f4a3086d9ee46cc42797baaedf8" + integrity sha512-GTooyrlmRTqvUen4eK7/K1p6kryF7bnDfq6XsAbIsf2mo51B/utaH+XThY6dKgNCWzMAaH/+OLmqaBuLhLWRow== + dependencies: + "@smithy/types" "^4.14.0" + tslib "^2.6.2" + "@smithy/util-middleware@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/util-middleware/-/util-middleware-4.2.8.tgz#1da33f29a74c7ebd9e584813cb7e12881600a80a" @@ -4982,6 +5244,20 @@ "@smithy/util-utf8" "^4.2.0" tslib "^2.6.2" +"@smithy/util-stream@^4.5.22": + version "4.5.22" + resolved "https://registry.yarnpkg.com/@smithy/util-stream/-/util-stream-4.5.22.tgz#16e449bbd174243b9e202f0f75d33a1d700c2020" + integrity sha512-3H8iq/0BfQjUs2/4fbHZ9aG9yNzcuZs24LPkcX1Q7Z+qpqaGM8+qbGmE8zo9m2nCRgamyvS98cHdcWvR6YUsew== + dependencies: + "@smithy/fetch-http-handler" "^5.3.16" + "@smithy/node-http-handler" "^4.5.2" + "@smithy/types" "^4.14.0" + "@smithy/util-base64" "^4.3.2" + "@smithy/util-buffer-from" "^4.2.2" + "@smithy/util-hex-encoding" "^4.2.2" + "@smithy/util-utf8" "^4.2.2" + tslib "^2.6.2" + "@smithy/util-uri-escape@^4.2.0": version "4.2.0" resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.0.tgz#096a4cec537d108ac24a68a9c60bee73fc7e3a9e" @@ -4989,6 +5265,13 @@ dependencies: tslib "^2.6.2" +"@smithy/util-uri-escape@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-uri-escape/-/util-uri-escape-4.2.2.tgz#48e40206e7fe9daefc8d44bb43a1ab17e76abf4a" + integrity sha512-2kAStBlvq+lTXHyAZYfJRb/DfS3rsinLiwb+69SstC9Vb0s9vNWkRwpnj918Pfi85mzi42sOqdV72OLxWAISnw== + dependencies: + tslib "^2.6.2" + "@smithy/util-utf8@^2.0.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-2.3.0.tgz#dd96d7640363259924a214313c3cf16e7dd329c5" @@ -5005,6 +5288,14 @@ "@smithy/util-buffer-from" "^4.2.0" tslib "^2.6.2" +"@smithy/util-utf8@^4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@smithy/util-utf8/-/util-utf8-4.2.2.tgz#21db686982e6f3393ac262e49143b42370130f13" + integrity sha512-75MeYpjdWRe8M5E3AW0O4Cx3UadweS+cwdXjwYGBW5h/gxxnbeZ877sLPX/ZJA9GVTlL/qG0dXP29JWFCD1Ayw== + dependencies: + "@smithy/util-buffer-from" "^4.2.2" + tslib "^2.6.2" + "@smithy/util-waiter@^4.2.8": version "4.2.8" resolved "https://registry.yarnpkg.com/@smithy/util-waiter/-/util-waiter-4.2.8.tgz#35d7bd8b2be7a2ebc12d8c38a0818c501b73e928" @@ -5021,6 +5312,13 @@ dependencies: tslib "^2.6.2" +"@smithy/uuid@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@smithy/uuid/-/uuid-1.1.2.tgz#b6e97c7158615e4a3c775e809c00d8c269b5a12e" + integrity sha512-O/IEdcCUKkubz60tFbGA7ceITTAJsty+lBjNoorP4Z6XRqaFb/OjQjZODophEcuq68nKm6/0r+6/lLQ+XVpk8g== + dependencies: + tslib "^2.6.2" + "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"