@walkeros/server-destination-aws 4.8.1 → 4.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/firehose/config.ts","../src/lib/client.ts","../src/lib/credentials.ts","../src/lib/errors.ts","../src/firehose/lib/firehose.ts","../src/firehose/lib/deliver.ts","../src/lib/payload.ts","../src/firehose/lib/records.ts","../src/firehose/push.ts","../src/firehose/pushBatch.ts","../src/firehose/types/index.ts","../src/firehose/index.ts","../src/sns/lib/sns.ts","../src/sns/config.ts","../src/sns/push.ts","../src/sns/setup.ts","../src/sns/types/index.ts","../src/sns/index.ts"],"sourcesContent":["// AWS Firehose\nexport { destinationFirehose } from './firehose';\nexport * as DestinationFirehose from './firehose/types';\n\n// AWS SNS\nexport { destinationSNS } from './sns';\nexport * as DestinationSNS from './sns/types';\n\n// Default remains Firehose for backward compatibility.\nexport { destinationFirehose as default } from './firehose';\n","import type { Logger } from '@walkeros/core';\nimport { FirehoseClient } from '@aws-sdk/client-firehose';\nimport { isObject } from '@walkeros/core';\nimport type {\n Config,\n CredentialSource,\n Env,\n FirehoseConfig,\n InitSettings,\n PartialConfig,\n Settings,\n} from './types';\nimport {\n DEFAULT_REGION,\n InFlight,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport { configError } from '../lib/errors';\nimport { STREAM_NAME_PATTERN, createClient } from './lib/firehose';\n\n/**\n * Equal as written in a flow: the same value, or plain objects and arrays with\n * equal contents. Anything else (a client, a provider function, a handler\n * instance) is equal only to itself.\n */\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b))\n return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));\n if (!isObject(a) || !isObject(b)) return false;\n if (!isPlain(a) || !isPlain(b)) return false;\n const keys = Object.keys(a);\n return (\n keys.length === Object.keys(b).length &&\n keys.every((key) => key in b && sameValue(a[key], b[key]))\n );\n}\n\nfunction isPlain(value: object): boolean {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Reads a flat key and its deprecated `settings.firehose` alias. Both set to\n * different values throws: a silent winner could route events to the wrong\n * stream after a half-done migration.\n */\nfunction pick<K extends keyof FirehoseConfig>(\n key: K,\n flat: FirehoseConfig[K],\n alias: FirehoseConfig[K],\n): FirehoseConfig[K] {\n if (flat !== undefined && alias !== undefined && !sameValue(flat, alias))\n throw configError(\n `Firehose: settings.${key} and settings.firehose.${key} are both set and differ. Remove settings.firehose.`,\n );\n return flat ?? alias;\n}\n\n/**\n * Resolves the settings once, offline: no credentials are loaded and no AWS\n * call is made. The caller's config is never mutated.\n */\nexport async function getConfig(\n partialConfig: PartialConfig = {},\n env: Env | undefined,\n logger: Logger.Instance,\n): Promise<Config> {\n const input: InitSettings = partialConfig.settings ?? {};\n const alias: FirehoseConfig = input.firehose ?? {};\n\n const streamName = pick('streamName', input.streamName, alias.streamName);\n const sdkConfig = pick('config', input.config, alias.config);\n const userClient = pick('client', input.client, alias.client);\n const regionSetting = pick('region', input.region, alias.region);\n\n if (!streamName)\n throw configError(\n 'Firehose: settings.streamName is missing. Set it to the name of your Firehose stream.',\n );\n if (!STREAM_NAME_PATTERN.test(streamName))\n throw configError(\n `Firehose: settings.streamName \"${streamName}\" is not a valid stream name. Use 1 to 64 letters, digits, \"_\", \".\" or \"-\".`,\n );\n\n const sdkRegion =\n typeof sdkConfig?.region === 'string' ? sdkConfig.region : undefined;\n const region =\n firstString(regionSetting, sdkRegion, await loadSdkRegion()) ??\n DEFAULT_REGION;\n\n const credentials = parseCredentials(partialConfig.credentials);\n const credentialSource: CredentialSource = userClient\n ? 'client'\n : credentials\n ? 'config.credentials'\n : sdkConfig?.credentials !== undefined\n ? 'settings.config.credentials'\n : 'default chain';\n\n const client =\n userClient ??\n createClient(env?.AWS?.FirehoseClient ?? FirehoseClient, {\n region,\n config: sdkConfig,\n credentials,\n timeout: partialConfig.timeout,\n });\n\n const settings: Settings = {\n streamName,\n region,\n newline: input.newline !== false,\n client,\n runtime: {\n ownsClient: !userClient && !isHandlerInstance(sdkConfig?.requestHandler),\n inflight: new InFlight(),\n credentialSource,\n },\n };\n if (sdkConfig) settings.config = sdkConfig;\n\n logger.debug('Firehose client ready', {\n stream: streamName,\n region,\n credentials: credentialSource,\n });\n\n return { ...partialConfig, settings };\n}\n\n/**\n * Narrows the settings a push receives. Core types them as the slot a user\n * writes, so the fields `init` set are optional there.\n */\nexport function resolveSettings(settings: InitSettings | undefined): Settings {\n const { streamName, region, client, runtime } = settings ?? {};\n if (!streamName || !region || !client || !runtime)\n throw configError('Firehose: the destination was not initialized.');\n return {\n streamName,\n region,\n newline: settings?.newline !== false,\n client,\n runtime,\n };\n}\n","import {\n loadConfig,\n NODE_REGION_CONFIG_FILE_OPTIONS,\n NODE_REGION_CONFIG_OPTIONS,\n} from '@smithy/core/config';\n\n/** Region used when neither the flow nor the environment names one. */\nexport const DEFAULT_REGION = 'eu-central-1';\n\n/** The collector's per-delivery race when `config.timeout` is unset. */\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Share of `config.timeout` one SDK attempt may take. The SDK standard retry\n * makes up to three attempts; at a quarter each, three attempts plus the SDK's\n * longest documented waits end before the collector stops waiting.\n */\nexport const REQUEST_TIMEOUT_SHARE = 0.25;\n\n/**\n * The part of an AWS SDK v3 client this package calls: `send` with a command,\n * and `destroy` on a client it created. The SDK clients satisfy it, and so\n * does an injected mock (tests, simulate) without a cast.\n */\nexport interface SendClient {\n send(command: object): Promise<unknown>;\n destroy?(): void;\n}\n\n/**\n * True for a request handler instance (anything with a `handle` method), as\n * opposed to handler options. An instance the user passed belongs to the\n * user: a client closed by this package would close its sockets too.\n */\nexport function isHandlerInstance(handler: unknown): boolean {\n return (\n typeof handler === 'object' &&\n handler !== null &&\n 'handle' in handler &&\n typeof handler.handle === 'function'\n );\n}\n\n/** `requestHandler` options for a client this package builds. */\nexport interface RequestHandlerOptions {\n requestTimeout: number;\n throwOnRequestTimeout: true;\n}\n\n/** The collector's race as this package reads it: positive and finite. */\nexport function resolveTimeout(timeout?: number): number {\n if (timeout === undefined || !Number.isFinite(timeout) || timeout <= 0)\n return DEFAULT_TIMEOUT_MS;\n return timeout;\n}\n\n/**\n * Per-attempt timeout for a client this package builds. Without it the SDK\n * waits on a hung request forever (its default is 0, off), so the retry it\n * keeps never fires and the collector's race is the only bound.\n */\nexport function requestHandlerOptions(timeout?: number): RequestHandlerOptions {\n return {\n requestTimeout: Math.floor(resolveTimeout(timeout) * REQUEST_TIMEOUT_SHARE),\n throwOnRequestTimeout: true,\n };\n}\n\n/** The parts of an SNS topic ARN this package reads. */\nexport interface TopicArn {\n region: string;\n accountId: string;\n name: string;\n}\n\n/** Parses `arn:<partition>:sns:<region>:<account>:<name>`. */\nexport function parseTopicArn(arn: string): TopicArn | undefined {\n const parts = arn.split(':');\n if (parts.length !== 6) return undefined;\n const [prefix, partition, service, region, accountId, name] = parts;\n if (prefix !== 'arn' || !partition || service !== 'sns') return undefined;\n if (!region || !accountId || !name) return undefined;\n return { region, accountId, name };\n}\n\n/**\n * The region the SDK would pick on its own: `AWS_REGION`, else the active\n * profile's `region`. Local files and variables only, never the network.\n * `undefined` when neither names one.\n */\nexport async function loadSdkRegion(): Promise<string | undefined> {\n try {\n const region = await loadConfig(\n NODE_REGION_CONFIG_OPTIONS,\n NODE_REGION_CONFIG_FILE_OPTIONS,\n )();\n return typeof region === 'string' && region.length > 0 ? region : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The first non-empty string, used for the region order. */\nexport function firstString(\n ...candidates: Array<string | undefined>\n): string | undefined {\n return candidates.find(\n (candidate): candidate is string =>\n typeof candidate === 'string' && candidate.length > 0,\n );\n}\n\n/**\n * Tracks the sends an instance has in flight, so `destroy` can let them land\n * before it closes the client under them.\n */\nexport class InFlight {\n private readonly pending = new Set<Promise<unknown>>();\n\n track<T>(promise: Promise<T>): Promise<T> {\n this.pending.add(promise);\n const remove = () => {\n this.pending.delete(promise);\n };\n promise.then(remove, remove);\n return promise;\n }\n\n get size(): number {\n return this.pending.size;\n }\n\n /** Resolves once every tracked send settled, whatever its outcome. */\n async settle(): Promise<void> {\n while (this.pending.size > 0) {\n await Promise.allSettled([...this.pending]);\n }\n }\n}\n","import { isObject } from '@walkeros/core';\nimport { configError } from './errors';\n\n/**\n * Static AWS keys, the shape `config.credentials` takes. Back each value with\n * `$secret`; a literal key in a flow file is a leaked key.\n */\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\n/** `config.credentials` as written: the object, or the same object as JSON. */\nexport type CredentialsInput = AwsCredentials | string;\n\nfunction nonEmpty(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\n/**\n * Checks `config.credentials` offline and returns the keys, or `undefined`\n * when the slot is unset (the SDK's default chain then applies).\n *\n * Messages name the field that is wrong and never a value: a credentials\n * error ends up in logs, and the value it would echo is the secret.\n */\nexport function parseCredentials(input: unknown): AwsCredentials | undefined {\n if (input === undefined || input === null || input === '') return undefined;\n\n let value: unknown = input;\n if (typeof input === 'string') {\n try {\n value = JSON.parse(input);\n } catch {\n // The parser's own message quotes the input, so it is not passed on.\n throw configError(\n 'config.credentials is not valid JSON. Expected {\"accessKeyId\", \"secretAccessKey\", \"sessionToken\"?}.',\n );\n }\n }\n\n if (!isObject(value))\n throw configError(\n 'config.credentials must be an object with accessKeyId and secretAccessKey.',\n );\n\n const { accessKeyId, secretAccessKey, sessionToken } = value;\n\n for (const [field, fieldValue] of [\n ['accessKeyId', accessKeyId],\n ['secretAccessKey', secretAccessKey],\n ] as const) {\n if (!nonEmpty(fieldValue))\n throw configError(\n `config.credentials.${field} is missing or empty. Set it from a $secret.`,\n );\n }\n\n if (sessionToken !== undefined && !nonEmpty(sessionToken))\n throw configError(\n 'config.credentials.sessionToken must be a non-empty string when set.',\n );\n\n const credentials: AwsCredentials = {\n accessKeyId: String(accessKeyId),\n secretAccessKey: String(secretAccessKey),\n };\n if (sessionToken !== undefined) credentials.sessionToken = sessionToken;\n return credentials;\n}\n","import {\n isServerError,\n isThrottlingError,\n isTransientError,\n} from '@smithy/core/retry';\n\n/** The AWS service a delivery goes to, as it appears in every message. */\nexport type AwsService = 'Firehose' | 'SNS';\n\n/**\n * What a failure is about: the service, the stream or topic and the region.\n * Every message carries all three, because a wrong region and a wrong name\n * produce the same AWS answer.\n */\nexport interface DeliveryTarget {\n service: AwsService;\n /** Stream name or topic ARN (or topic name before the ARN is known). */\n resource: string;\n region: string;\n /** Destination id, used by the SNS setup hint. */\n id?: string;\n}\n\nexport interface AwsDeliveryErrorOptions {\n code: string;\n status?: number;\n retryable: boolean;\n cause?: unknown;\n}\n\n/**\n * Every failure this package reports or throws. `code` is the AWS error name\n * or one of the package's own codes (`RecordTooLarge`, `InvalidRecord`,\n * `InvalidResponse`, `InvalidConfig`), `status` the HTTP status when AWS\n * answered, and `retryable` whether sending the same record again can succeed.\n * The SDK error stays reachable as `cause`.\n */\nexport class AwsDeliveryError extends Error {\n readonly code: string;\n readonly status: number | undefined;\n readonly retryable: boolean;\n\n constructor(message: string, options: AwsDeliveryErrorOptions) {\n super(message, { cause: options.cause });\n this.name = 'AwsDeliveryError';\n this.code = options.code;\n this.status = options.status;\n this.retryable = options.retryable;\n }\n}\n\n/** Codes whose fix is a configuration change, each with that change. */\nfunction hintFor(code: string, target: DeliveryTarget): string | undefined {\n switch (code) {\n case 'ResourceNotFoundException':\n return target.service === 'Firehose'\n ? 'Check settings.streamName and settings.region.'\n : 'Check settings.topicArn and settings.region.';\n case 'AccessDeniedException':\n case 'AuthorizationErrorException':\n return target.service === 'Firehose'\n ? 'Allow firehose:PutRecordBatch on the stream for these credentials.'\n : 'Allow sns:Publish on the topic for these credentials.';\n case 'UnrecognizedClientException':\n case 'InvalidSignatureException':\n case 'ExpiredTokenException':\n case 'CredentialsProviderError':\n return 'Check config.credentials or the AWS credentials in the environment.';\n case 'NotFoundException':\n return target.service === 'SNS'\n ? `Run \"walkeros setup destination.${target.id ?? '<id>'}\" or set settings.topicArn.`\n : undefined;\n default:\n return undefined;\n }\n}\n\nfunction describe(target: DeliveryTarget): string {\n const kind = target.service === 'Firehose' ? 'stream' : 'topic';\n return `${target.service} ${kind} \"${target.resource}\" (${target.region})`;\n}\n\n/** Builds the message: target, code, the AWS text and, if listed, the fix. */\nexport function formatMessage(\n target: DeliveryTarget,\n code: string,\n text: string,\n): string {\n const hint = hintFor(code, target);\n const base = `${describe(target)}: ${code}: ${text}`;\n return hint ? `${base} ${hint}` : base;\n}\n\n/** The error shape the SDK classifier reads. */\ntype SdkError = Parameters<typeof isTransientError>[0];\n\nfunction isSdkError(error: unknown): error is SdkError {\n return error instanceof Error;\n}\n\nfunction readCode(error: SdkError): string {\n // Network errors from Node arrive as a plain `Error` whose cause is in the\n // errno `code` (ECONNRESET, ETIMEDOUT); the name alone would say nothing.\n if (error.name !== 'Error') return error.name;\n const code: unknown = Reflect.get(error, 'code');\n return typeof code === 'string' && code.length > 0 ? code : error.name;\n}\n\n/**\n * Wraps an error a whole call threw. `retryable` is the SDK's own public\n * classifier, the one its standard retry strategy uses, so this package keeps\n * no table of its own.\n */\nexport function fromSdkError(\n error: unknown,\n target: DeliveryTarget,\n): AwsDeliveryError {\n if (error instanceof AwsDeliveryError) return error;\n\n if (!isSdkError(error)) {\n return new AwsDeliveryError(\n formatMessage(target, 'UnknownError', String(error)),\n { code: 'UnknownError', retryable: false, cause: error },\n );\n }\n\n const code = readCode(error);\n const retryable =\n isThrottlingError(error) || isTransientError(error) || isServerError(error);\n\n return new AwsDeliveryError(formatMessage(target, code, error.message), {\n code,\n status: error.$metadata?.httpStatusCode,\n retryable,\n cause: error,\n });\n}\n\n/**\n * A record AWS rejected inside an accepted call. Both per-record codes AWS\n * documents for PutRecordBatch (`ServiceUnavailableException`,\n * `InternalFailure`) are retryable, so every such rejection is.\n */\nexport function recordError(\n target: DeliveryTarget,\n code: string,\n text: string,\n status?: number,\n): AwsDeliveryError {\n return new AwsDeliveryError(formatMessage(target, code, text), {\n code,\n status,\n retryable: true,\n });\n}\n\n/**\n * A failure this package detected itself, without an AWS answer to classify:\n * a record it refused to send, or a response it could not read.\n */\nexport function localError(\n target: DeliveryTarget,\n code: 'RecordTooLarge' | 'InvalidRecord' | 'InvalidResponse',\n text: string,\n cause?: unknown,\n): AwsDeliveryError {\n return new AwsDeliveryError(formatMessage(target, code, text), {\n code,\n retryable: code === 'InvalidResponse',\n cause,\n });\n}\n\n/** A configuration error found offline in `init` or `setup`. */\nexport function configError(message: string): AwsDeliveryError {\n return new AwsDeliveryError(message, {\n code: 'InvalidConfig',\n retryable: false,\n });\n}\n","import type { FirehoseClientConfig } from '@aws-sdk/client-firehose';\nimport type { AwsCredentials } from '../../lib/credentials';\nimport type { SendClient } from '../../lib/client';\nimport type { FirehoseClientConstructor } from '../types';\nimport { requestHandlerOptions } from '../../lib/client';\n\n/** Firehose stream names: 1 to 64 letters, digits, `_`, `.` or `-`. */\nexport const STREAM_NAME_PATTERN = /^[a-zA-Z0-9_.-]{1,64}$/;\n\nexport interface ClientOptions {\n region: string;\n /** Raw SDK options from `settings.config`. Never mutated. */\n config?: FirehoseClientConfig;\n /** Parsed `config.credentials`; wins over `config.credentials` of the SDK options. */\n credentials?: AwsCredentials;\n /** The collector's race, `config.timeout`. */\n timeout?: number;\n}\n\n/**\n * Builds the client the destination owns. The per-attempt timeout is added\n * only when the user passed no `requestHandler` of their own.\n */\nexport function createClient(\n Client: FirehoseClientConstructor,\n options: ClientOptions,\n): SendClient {\n const clientConfig: FirehoseClientConfig = {\n ...options.config,\n region: options.region,\n };\n if (options.credentials) clientConfig.credentials = options.credentials;\n if (clientConfig.requestHandler === undefined)\n clientConfig.requestHandler = requestHandlerOptions(options.timeout);\n\n return new Client(clientConfig);\n}\n","import type { Destination, Logger, WalkerOS } from '@walkeros/core';\nimport { PutRecordBatchCommand } from '@aws-sdk/client-firehose';\nimport type { Env, Settings } from '../types';\nimport type { DeliveryTarget } from '../../lib/errors';\nimport {\n AwsDeliveryError,\n fromSdkError,\n localError,\n recordError,\n} from '../../lib/errors';\nimport type { PlannedRecord } from './records';\nimport { planChunks, toPayload } from './records';\n\n/** One event to deliver, with what a mapping produced for it. */\nexport interface DeliveryItem {\n event: WalkerOS.Event;\n data?: unknown;\n}\n\n/** A record that did not land, by its index in the delivery. */\nexport interface DeliveryFailure {\n index: number;\n error: AwsDeliveryError;\n}\n\n/** The per-record answer of a `PutRecordBatch` call, as far as it is read. */\ninterface RecordResponse {\n errorCode?: string;\n errorMessage?: string;\n}\n\n/** A `PutRecordBatch` answer, read field by field from an unknown value. */\ninterface CallResponse {\n failedPutCount?: number;\n responses?: RecordResponse[];\n status?: number;\n attempts?: number;\n}\n\n/** Any non-null object, SDK errors included (`isObject` accepts plain ones only). */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readMetadata(value: unknown): { status?: number; attempts?: number } {\n if (!isRecord(value)) return {};\n const metadata = value.$metadata;\n if (!isRecord(metadata)) return {};\n const { httpStatusCode, attempts } = metadata;\n return {\n status: typeof httpStatusCode === 'number' ? httpStatusCode : undefined,\n attempts: typeof attempts === 'number' ? attempts : undefined,\n };\n}\n\nfunction readResponse(value: unknown): CallResponse {\n const response: CallResponse = readMetadata(value);\n if (!isRecord(value)) return response;\n\n if (typeof value.FailedPutCount === 'number')\n response.failedPutCount = value.FailedPutCount;\n\n if (Array.isArray(value.RequestResponses)) {\n response.responses = value.RequestResponses.map(\n (entry: unknown): RecordResponse => {\n if (!isRecord(entry)) return {};\n const { ErrorCode, ErrorMessage } = entry;\n return {\n errorCode:\n typeof ErrorCode === 'string' && ErrorCode.length > 0\n ? ErrorCode\n : undefined,\n errorMessage:\n typeof ErrorMessage === 'string' ? ErrorMessage : undefined,\n };\n },\n );\n }\n\n return response;\n}\n\n/**\n * Maps one call's answer onto its records. Per-record answers are trusted only\n * when they line up with the records sent and agree with `FailedPutCount`; a\n * reported failure they cannot place fails the whole chunk as\n * `InvalidResponse`, never as delivered.\n */\nfunction chunkFailures(\n chunk: readonly PlannedRecord[],\n response: CallResponse,\n target: DeliveryTarget,\n): DeliveryFailure[] {\n const { responses, failedPutCount, status } = response;\n\n if (responses && responses.length === chunk.length) {\n const failures: DeliveryFailure[] = [];\n responses.forEach((entry, local) => {\n if (!entry.errorCode) return;\n failures.push({\n index: chunk[local].index,\n error: recordError(\n target,\n entry.errorCode,\n entry.errorMessage ?? 'Record rejected.',\n status,\n ),\n });\n });\n // Entries that do not account for the reported count cannot place the\n // failures, so none of them is trusted.\n if (failedPutCount === undefined || failures.length === failedPutCount)\n return failures;\n } else if (failedPutCount === 0) return [];\n\n const error = localError(\n target,\n 'InvalidResponse',\n `PutRecordBatch answered without per-record results that match its FailedPutCount for ${chunk.length} records (FailedPutCount ${failedPutCount ?? 'missing'}).`,\n );\n return chunk.map(({ index }) => ({ index, error }));\n}\n\n/**\n * Sends records to the stream and says which did not land.\n *\n * `push` and `pushBatch` both arrive here. Each chunk is sent once, through\n * the SDK and its own retry, and all chunks go out together, so one failed\n * call never stops the rest. The answer lists every failed record by index,\n * sorted; an empty list means everything landed.\n */\nexport async function deliver(\n items: readonly DeliveryItem[],\n settings: Settings,\n env: Env | undefined,\n logger: Logger.Instance,\n): Promise<DeliveryFailure[]> {\n const target: DeliveryTarget = {\n service: 'Firehose',\n resource: settings.streamName,\n region: settings.region,\n };\n const Command = env?.AWS?.PutRecordBatchCommand ?? PutRecordBatchCommand;\n\n const plan = planChunks(\n items.map(({ event, data }) => toPayload(event, data)),\n settings.newline,\n );\n\n const failures: DeliveryFailure[] = plan.refused.map(\n ({ index, code, detail, cause }) => ({\n index,\n error: localError(target, code, detail, cause),\n }),\n );\n\n const sendChunk = async (\n chunk: PlannedRecord[],\n ): Promise<DeliveryFailure[]> => {\n const started = Date.now();\n const bytes = chunk.reduce(\n (sum, record) => sum + record.data.byteLength,\n 0,\n );\n const command = new Command({\n DeliveryStreamName: settings.streamName,\n Records: chunk.map(({ data }) => ({ Data: data })),\n });\n\n let result: DeliveryFailure[];\n let attempts: number | undefined;\n try {\n const raw = await settings.runtime.inflight.track(\n settings.client.send(command),\n );\n const response = readResponse(raw);\n attempts = response.attempts;\n result = chunkFailures(chunk, response, target);\n } catch (thrown) {\n attempts = readMetadata(thrown).attempts;\n const error = fromSdkError(thrown, target);\n result = chunk.map(({ index }) => ({ index, error }));\n }\n\n logger.debug('Firehose PutRecordBatch', {\n stream: settings.streamName,\n records: chunk.length,\n bytes,\n failed: result.length,\n attempts,\n ms: Date.now() - started,\n });\n\n return result;\n };\n\n const results = await Promise.all(plan.chunks.map(sendChunk));\n for (const result of results) failures.push(...result);\n\n return failures.sort((a, b) => a.index - b.index);\n}\n\n/**\n * The error thrown when nothing landed. One shared cause is thrown as is;\n * mixed causes give the most frequent code, retryable only if every record\n * was, with the counts per code in the message.\n */\nexport function nothingLanded(\n failures: readonly DeliveryFailure[],\n): AwsDeliveryError {\n const first = failures[0].error;\n if (failures.every(({ error }) => error === first)) return first;\n\n const byCode = new Map<string, { count: number; sample: AwsDeliveryError }>();\n for (const { error } of failures) {\n const entry = byCode.get(error.code);\n if (entry) entry.count += 1;\n else byCode.set(error.code, { count: 1, sample: error });\n }\n\n let dominant = { count: 0, sample: first };\n for (const entry of byCode.values())\n if (entry.count > dominant.count) dominant = entry;\n\n const counts = [...byCode.entries()]\n .map(([code, { count }]) => `${count} ${code}`)\n .join(', ');\n\n return new AwsDeliveryError(\n `${dominant.sample.message} (all ${failures.length} records failed: ${counts})`,\n {\n code: dominant.sample.code,\n status: dominant.sample.status,\n retryable: failures.every(({ error }) => error.retryable),\n cause: dominant.sample,\n },\n );\n}\n\n/**\n * The collector's reading of a delivery: resolve when everything landed,\n * the failed indices when some did, a throw when none did.\n */\nexport function toOutcome(\n total: number,\n failures: readonly DeliveryFailure[],\n): void | Destination.BatchOutcome {\n if (failures.length === 0) return;\n if (failures.length >= total) throw nothingLanded(failures);\n return {\n failed: failures.map(({ index, error }) => ({ index, error })),\n };\n}\n","import type { WalkerOS } from '@walkeros/core';\nimport { isObject } from '@walkeros/core';\n\n/**\n * What one record or message carries: the mapped `data` when the mapping\n * produced an object, else the full event.\n */\nexport function toPayload(event: WalkerOS.Event, data?: unknown): object {\n return isObject(data) ? data : event;\n}\n","export { toPayload } from '../../lib/payload';\n\n/** Most records one `PutRecordBatch` call accepts. */\nexport const MAX_RECORDS_PER_CALL = 500;\n\n/**\n * Raw bytes per call, kept below the documented 4 MiB request limit with room\n * for the base64 encoding and the JSON envelope of the request.\n */\nexport const MAX_BYTES_PER_CALL = 2_900_000;\n\n/** Largest record Firehose accepts, newline included. */\nexport const MAX_RECORD_BYTES = 1_000_000;\n\n/** The record bytes: the payload as JSON, plus `\\n` unless `newline` is false. */\nexport function toRecordData(payload: unknown, newline = true): Buffer {\n const json = JSON.stringify(payload);\n if (typeof json !== 'string')\n throw new TypeError('The payload does not serialize to JSON.');\n return Buffer.from(newline ? `${json}\\n` : json);\n}\n\n/** One record on its way out, with its index in the delivery. */\nexport interface PlannedRecord {\n index: number;\n data: Buffer;\n}\n\n/** A record refused before sending; it is never sent. */\nexport interface RefusedRecord {\n index: number;\n code: 'RecordTooLarge' | 'InvalidRecord';\n detail: string;\n cause?: unknown;\n}\n\nexport interface Plan {\n chunks: PlannedRecord[][];\n refused: RefusedRecord[];\n}\n\n/**\n * Turns payloads into records and splits them into calls of at most\n * `MAX_RECORDS_PER_CALL` records and `MAX_BYTES_PER_CALL` bytes, in order.\n * A record that cannot be serialized or is too large is refused on its own,\n * so it never fails its neighbours.\n */\nexport function planChunks(\n payloads: readonly unknown[],\n newline: boolean,\n): Plan {\n const chunks: PlannedRecord[][] = [];\n const refused: RefusedRecord[] = [];\n let current: PlannedRecord[] = [];\n let currentBytes = 0;\n\n payloads.forEach((payload, index) => {\n let data: Buffer;\n try {\n data = toRecordData(payload, newline);\n } catch (error) {\n refused.push({\n index,\n code: 'InvalidRecord',\n detail: `Record ${index} does not serialize to JSON: ${error instanceof Error ? error.message : String(error)}`,\n cause: error,\n });\n return;\n }\n\n if (data.byteLength > MAX_RECORD_BYTES) {\n refused.push({\n index,\n code: 'RecordTooLarge',\n detail: `Record ${index} is ${data.byteLength} bytes, over the ${MAX_RECORD_BYTES} byte limit.`,\n });\n return;\n }\n\n if (\n current.length >= MAX_RECORDS_PER_CALL ||\n currentBytes + data.byteLength > MAX_BYTES_PER_CALL\n ) {\n chunks.push(current);\n current = [];\n currentBytes = 0;\n }\n\n current.push({ index, data });\n currentBytes += data.byteLength;\n });\n\n if (current.length > 0) chunks.push(current);\n return { chunks, refused };\n}\n","import type { PushFn } from './types';\nimport { resolveSettings } from './config';\nimport { deliver } from './lib/deliver';\n\n/** Sends one event as one record. Resolves once AWS confirmed it, else throws. */\nexport const push: PushFn = async function (event, context) {\n const { config, data, env, logger } = context;\n const settings = resolveSettings(config.settings);\n\n const [failure] = await deliver([{ event, data }], settings, env, logger);\n if (failure) throw failure.error;\n};\n","import type { PushBatchFn } from './types';\nimport { resolveSettings } from './config';\nimport { deliver, toOutcome } from './lib/deliver';\n\n/**\n * Sends a flushed batch. Resolves when every record landed, returns the\n * failed indices when some did, and throws when none did.\n */\nexport const pushBatch: PushBatchFn = async (batch, context) => {\n const { config, env, logger } = context;\n if (batch.entries.length === 0) return;\n\n const settings = resolveSettings(config.settings);\n const failures = await deliver(\n batch.entries.map(({ event, data }) => ({ event, data })),\n settings,\n env,\n logger,\n );\n\n return toOutcome(batch.entries.length, failures);\n};\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type {\n FirehoseClientConfig,\n PutRecordBatchCommandInput,\n} from '@aws-sdk/client-firehose';\nimport type { InFlight, SendClient } from '../../lib/client';\nimport type { CredentialsInput } from '../../lib/credentials';\n\nexport type { SendClient } from '../../lib/client';\n\n/** Client and stream options, as the deprecated `settings.firehose` held them. */\nexport interface FirehoseConfig {\n streamName?: string;\n client?: SendClient;\n region?: string;\n config?: FirehoseClientConfig;\n}\n\n/**\n * Settings as a user writes them. Only `streamName` is required (here or in\n * the deprecated `firehose` alias); `init` fills in the rest.\n */\nexport interface InitSettings {\n /** Name of the Firehose stream. */\n streamName?: string;\n /** AWS region. Default: `AWS_REGION` or the profile, else `eu-central-1`. */\n region?: string;\n /** Append `\\n` to every record. Default `true`. */\n newline?: boolean;\n /** Raw `FirehoseClient` options, passed through to the SDK. */\n config?: FirehoseClientConfig;\n /** A client of your own. Used as is and never closed by the destination. */\n client?: SendClient;\n /** @deprecated Use the flat fields. Read at `init` as an alias. */\n firehose?: FirehoseConfig;\n /** Runtime-only, set by `init`: what the destination owns. Not user-facing. */\n runtime?: Runtime;\n}\n\n/** What `init` resolved. Every field is present. */\nexport interface Settings {\n streamName: string;\n region: string;\n newline: boolean;\n config?: FirehoseClientConfig;\n client: SendClient;\n runtime: Runtime;\n}\n\n/** Runtime-only state of one initialized instance. */\nexport interface Runtime {\n /**\n * True when the destination built the client from nothing the user owns,\n * and so closes it. A user client, or a user request handler instance in\n * `settings.config`, keeps it false.\n */\n ownsClient: boolean;\n /** Sends in flight, awaited by `destroy`. */\n inflight: InFlight;\n /** Where the client's credentials come from, for the debug line. */\n credentialSource: CredentialSource;\n}\n\nexport type CredentialSource =\n | 'config.credentials'\n | 'settings.config.credentials'\n | 'default chain'\n | 'client';\n\nexport interface Mapping {}\n\nexport type FirehoseClientConstructor = new (\n config: FirehoseClientConfig,\n) => SendClient;\n\nexport type PutRecordBatchCommandConstructor = new (\n input: PutRecordBatchCommandInput,\n) => { readonly input: PutRecordBatchCommandInput };\n\n/**\n * Optional SDK overrides. Each one present wins over the SDK the package\n * imports; tests and simulate inject mocks here.\n */\nexport interface Env extends DestinationServer.Env {\n AWS?: {\n FirehoseClient?: FirehoseClientConstructor;\n PutRecordBatchCommand?: PutRecordBatchCommandConstructor;\n };\n}\n\nexport type Types = CoreDestination.Types<\n Settings,\n Mapping,\n Env,\n InitSettings,\n unknown,\n CredentialsInput\n>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: InitFn;\n pushBatch: PushBatchFn;\n}\n\nexport type Config = {\n settings: InitSettings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PushBatchFn = CoreDestination.PushBatchFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { pushBatch } from './pushBatch';\n\n// Types\nexport * as DestinationFirehose from './types';\n\n/**\n * The Firehose destination. Batching is on by default: a flushed batch goes\n * out as `PutRecordBatch` calls of at most 500 records.\n */\nexport const destinationFirehose: Destination = {\n type: 'aws-firehose',\n\n config: { batch: { size: 500, age: 1000 } },\n\n async init({ config, env, logger }) {\n return getConfig(config, env, logger);\n },\n\n push,\n\n pushBatch,\n\n async destroy({ config, logger }) {\n const runtime = config.settings?.runtime;\n if (!runtime) return;\n\n // The collector awaits only the last flush per buffer, so earlier sends\n // can still be on the wire. Closing the client would cut them off.\n await runtime.inflight.settle();\n\n if (!runtime.ownsClient) return;\n runtime.ownsClient = false;\n config.settings?.client?.destroy?.();\n logger.debug('Firehose client closed');\n },\n};\n\nexport default destinationFirehose;\n","import type { SNSClientConfig } from '@aws-sdk/client-sns';\nimport type { STSClientConfig } from '@aws-sdk/client-sts';\nimport { SNSClient } from '@aws-sdk/client-sns';\nimport { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\nimport { isObject } from '@walkeros/core';\nimport type { AwsCredentials } from '../../lib/credentials';\nimport type { SendClient } from '../../lib/client';\nimport type { DeliveryTarget } from '../../lib/errors';\nimport type { Env } from '../types';\nimport { isHandlerInstance, requestHandlerOptions } from '../../lib/client';\nimport { fromSdkError } from '../../lib/errors';\n\nexport interface ClientOptions {\n region: string;\n /** Raw SDK options from `settings.config`. Never mutated. */\n config?: SNSClientConfig;\n /** Parsed `config.credentials`; wins over the credentials of the SDK options. */\n credentials?: AwsCredentials;\n /** The collector's race, `config.timeout`. */\n timeout?: number;\n}\n\n/**\n * The options of a client this package builds: the region, the credentials\n * slot, and the per-attempt timeout unless the user passed a handler.\n */\nexport function clientConfig(options: ClientOptions): SNSClientConfig {\n const resolved: SNSClientConfig = {\n ...options.config,\n region: options.region,\n };\n if (options.credentials) resolved.credentials = options.credentials;\n if (resolved.requestHandler === undefined)\n resolved.requestHandler = requestHandlerOptions(options.timeout);\n return resolved;\n}\n\n/** Builds the SNS client the destination owns, from env or the SDK. */\nexport function createSnsClient(\n env: Env | undefined,\n options: ClientOptions,\n): SendClient {\n const Client = env?.AWS?.SNSClient ?? SNSClient;\n return new Client(clientConfig(options));\n}\n\n/**\n * Builds the STS client for the account lookup, from env or the SDK. It shares\n * the region and credentials of the SNS client, never its endpoint (an SNS\n * endpoint does not answer STS) and never a user's handler instance (the\n * lookup client is closed after one call).\n *\n * One attempt only: the lookup runs before the first publish of a\n * `topicName`-only config, and one attempt of a quarter of `config.timeout`\n * plus the publish's own three still end inside the collector's race. A\n * failed lookup is not cached, so the next publish asks again.\n */\nexport function createStsClient(\n env: Env | undefined,\n options: ClientOptions,\n): SendClient {\n const Client = env?.AWS?.STSClient ?? STSClient;\n const config: STSClientConfig = { region: options.region };\n const credentials = options.credentials ?? options.config?.credentials;\n if (credentials) config.credentials = credentials;\n const userHandler = options.config?.requestHandler;\n config.requestHandler =\n userHandler === undefined || isHandlerInstance(userHandler)\n ? requestHandlerOptions(options.timeout)\n : userHandler;\n config.maxAttempts = 1;\n return new Client(config);\n}\n\n/** Account id and partition of the caller, from `GetCallerIdentity`. */\nexport interface CallerIdentity {\n accountId: string;\n partition: string;\n}\n\n/** Asks STS who the credentials belong to. One call, no retry of its own. */\nexport async function getCallerIdentity(\n client: SendClient,\n env: Env | undefined,\n target: DeliveryTarget,\n): Promise<CallerIdentity> {\n const Command =\n env?.AWS?.GetCallerIdentityCommand ?? GetCallerIdentityCommand;\n let response: unknown;\n try {\n response = await client.send(new Command({}));\n } catch (error) {\n throw fromSdkError(error, target);\n }\n\n if (!isObject(response) || typeof response.Account !== 'string')\n throw fromSdkError(\n new Error('STS GetCallerIdentity returned no Account.'),\n target,\n );\n\n // The caller ARN carries the partition (aws, aws-cn, aws-us-gov).\n const arn = typeof response.Arn === 'string' ? response.Arn : '';\n const partition = arn.split(':')[1] || 'aws';\n return { accountId: response.Account, partition };\n}\n","import type { Logger } from '@walkeros/core';\nimport type {\n Config,\n Env,\n InitSettings,\n PartialConfig,\n Runtime,\n Settings,\n} from './types';\nimport type { DeliveryTarget } from '../lib/errors';\nimport {\n DEFAULT_REGION,\n InFlight,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n parseTopicArn,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport { configError } from '../lib/errors';\nimport { createSnsClient, createStsClient, getCallerIdentity } from './lib/sns';\n\n/**\n * Resolves the settings once, offline: no credentials are loaded, no topic is\n * created and no AWS call is made. The caller's config is never mutated.\n */\nexport async function getConfig(\n partialConfig: PartialConfig = {},\n env: Env | undefined,\n logger: Logger.Instance,\n id: string,\n): Promise<Config> {\n const input: InitSettings = partialConfig.settings ?? {};\n const { topicArn, config: sdkConfig, client: userClient } = input;\n\n const arn = topicArn ? parseTopicArn(topicArn) : undefined;\n if (topicArn && !arn)\n throw configError(\n `SNS: settings.topicArn \"${topicArn}\" is not an SNS topic ARN (arn:aws:sns:<region>:<account>:<name>).`,\n );\n if (!arn && !input.topicName)\n throw configError(\n 'SNS: settings.topicArn is missing. Set it to the ARN of your topic (or set settings.topicName).',\n );\n if (arn && input.region && input.region !== arn.region)\n throw configError(\n `SNS: settings.region \"${input.region}\" differs from the region \"${arn.region}\" in settings.topicArn. Remove settings.region.`,\n );\n if (arn && input.topicName && input.topicName !== arn.name)\n throw configError(\n `SNS: settings.topicName \"${input.topicName}\" differs from the name \"${arn.name}\" in settings.topicArn. Remove settings.topicName.`,\n );\n\n const sdkRegion =\n typeof sdkConfig?.region === 'string' ? sdkConfig.region : undefined;\n const region =\n firstString(input.region, arn?.region, sdkRegion, await loadSdkRegion()) ??\n DEFAULT_REGION;\n const topicName = input.topicName ?? arn?.name;\n\n if (partialConfig.batch !== undefined)\n logger.warn(\n 'SNS publishes one message per event and does not batch. Remove config.batch from this destination.',\n );\n\n const credentials = parseCredentials(partialConfig.credentials);\n const clientOptions = {\n region,\n config: sdkConfig,\n credentials,\n timeout: partialConfig.timeout,\n };\n const client = userClient ?? createSnsClient(env, clientOptions);\n\n const runtime: Runtime = {\n ownsClient: !userClient && !isHandlerInstance(sdkConfig?.requestHandler),\n inflight: new InFlight(),\n topicArn: topicArnResolver(\n topicArn,\n topicName,\n {\n service: 'SNS',\n resource: topicName ?? '',\n region,\n id,\n },\n () => createStsClient(env, clientOptions),\n env,\n ),\n };\n\n const settings: Settings = { region, client, runtime };\n if (topicArn) settings.topicArn = topicArn;\n if (topicName) settings.topicName = topicName;\n if (sdkConfig) settings.config = sdkConfig;\n\n logger.debug('SNS client ready', {\n topic: topicArn ?? topicName,\n region,\n credentials: userClient\n ? 'client'\n : credentials\n ? 'config.credentials'\n : sdkConfig?.credentials !== undefined\n ? 'settings.config.credentials'\n : 'default chain',\n });\n\n return { ...partialConfig, settings };\n}\n\n/**\n * The topic ARN, as a function a publish awaits. A configured ARN is returned\n * as is. A name alone is completed once with the account id from STS, at the\n * first publish, and cached; a failed lookup is not cached, so the next\n * publish asks again.\n */\nfunction topicArnResolver(\n topicArn: string | undefined,\n topicName: string | undefined,\n target: DeliveryTarget,\n stsClient: () => ReturnType<typeof createStsClient>,\n env: Env | undefined,\n): () => Promise<string> {\n if (topicArn) return async () => topicArn;\n\n const lookup = async (): Promise<string> => {\n const client = stsClient();\n try {\n const { accountId, partition } = await getCallerIdentity(\n client,\n env,\n target,\n );\n return `arn:${partition}:sns:${target.region}:${accountId}:${topicName}`;\n } finally {\n client.destroy?.();\n }\n };\n\n let pending: Promise<string> | undefined;\n return () => {\n pending ??= lookup().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n}\n\n/**\n * Narrows the settings a push receives. Core types them as the slot a user\n * writes, so the fields `init` set are optional there.\n */\nexport function resolveSettings(settings: InitSettings | undefined): Settings {\n const { region, client, runtime } = settings ?? {};\n if (!region || !client || !runtime)\n throw configError('SNS: the destination was not initialized.');\n return {\n topicArn: settings?.topicArn,\n topicName: settings?.topicName,\n region,\n client,\n runtime,\n };\n}\n","import type { PushFn, Mapping } from './types';\nimport type { Mapping as WalkerOSMapping, Collector } from '@walkeros/core';\nimport { PublishCommand } from '@aws-sdk/client-sns';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { resolveSettings } from './config';\nimport type { DeliveryTarget } from '../lib/errors';\nimport { fromSdkError, localError } from '../lib/errors';\nimport { toPayload } from '../lib/payload';\n\ninterface MessageAttribute {\n DataType: string;\n StringValue?: string;\n BinaryValue?: Uint8Array;\n}\n\nfunction isMessageAttribute(value: unknown): value is MessageAttribute {\n return isObject(value) && typeof value.DataType === 'string';\n}\n\nasync function resolveMessageAttributes(\n event: Parameters<PushFn>[0],\n source: WalkerOSMapping.Map | undefined,\n collector: Collector.Instance,\n): Promise<Record<string, MessageAttribute> | undefined> {\n if (!source) return undefined;\n const result: Record<string, MessageAttribute> = {};\n let touched = false;\n for (const [key, value] of Object.entries(source)) {\n const resolved = await getMappingValue(event, value, { collector });\n if (isMessageAttribute(resolved)) {\n result[key] = resolved;\n touched = true;\n } else if (typeof resolved === 'string' && resolved.length > 0) {\n // Bare-string convenience: wrap into String DataType.\n result[key] = { DataType: 'String', StringValue: resolved };\n touched = true;\n } else if (resolved !== undefined && resolved !== null) {\n result[key] = { DataType: 'String', StringValue: String(resolved) };\n touched = true;\n }\n }\n return touched ? result : undefined;\n}\n\nfunction isMapping(value: unknown): value is Mapping {\n return typeof value === 'object' && value !== null;\n}\n\nasync function resolveStringMappingValue(\n event: Parameters<PushFn>[0],\n value: WalkerOSMapping.Value,\n collector: Collector.Instance,\n): Promise<string | undefined> {\n const resolved = await getMappingValue(event, value, { collector });\n if (typeof resolved === 'string' && resolved.length > 0) return resolved;\n if (resolved === undefined || resolved === null) return undefined;\n return String(resolved);\n}\n\n/**\n * Publishes one event. Resolves once SNS accepted the message, else throws an\n * `AwsDeliveryError`; the collector logs it, so SNS logs no error of its own.\n */\nexport const push: PushFn = async function (event, context) {\n const { config, data, env, rule, collector, logger, id } = context;\n const settings = resolveSettings(config.settings);\n const { runtime } = settings;\n\n // Tracked, so destroy waits for a first-publish account lookup too.\n const topicArn = await runtime.inflight.track(runtime.topicArn());\n const target: DeliveryTarget = {\n service: 'SNS',\n resource: topicArn,\n region: settings.region,\n id,\n };\n\n const ruleSettings: Mapping = isMapping(rule?.settings) ? rule.settings : {};\n\n const messageGroupId =\n ruleSettings.messageGroupId !== undefined\n ? await resolveStringMappingValue(\n event,\n ruleSettings.messageGroupId,\n collector,\n )\n : undefined;\n\n const fifo = topicArn.endsWith('.fifo');\n const mappedDeduplicationId =\n ruleSettings.messageDeduplicationId !== undefined\n ? await resolveStringMappingValue(\n event,\n ruleSettings.messageDeduplicationId,\n collector,\n )\n : undefined;\n // The event id is the dedup key everywhere, so a FIFO topic gets it unless\n // a rule maps its own.\n const messageDeduplicationId =\n mappedDeduplicationId ?? (fifo && event.id ? event.id : undefined);\n\n const messageAttributes = await resolveMessageAttributes(\n event,\n ruleSettings.messageAttributes,\n collector,\n );\n\n let message: string;\n try {\n message = JSON.stringify(toPayload(event, data));\n } catch (error) {\n throw localError(\n target,\n 'InvalidRecord',\n `The message does not serialize to JSON: ${error instanceof Error ? error.message : String(error)}`,\n error,\n );\n }\n\n const input: {\n TopicArn: string;\n Message: string;\n MessageGroupId?: string;\n MessageDeduplicationId?: string;\n MessageAttributes?: Record<string, MessageAttribute>;\n } = { TopicArn: topicArn, Message: message };\n if (messageGroupId) input.MessageGroupId = messageGroupId;\n if (messageDeduplicationId)\n input.MessageDeduplicationId = messageDeduplicationId;\n if (messageAttributes) input.MessageAttributes = messageAttributes;\n\n const Command = env?.AWS?.PublishCommand ?? PublishCommand;\n const started = Date.now();\n let attempts: number | undefined;\n try {\n const response = await runtime.inflight.track(\n settings.client.send(new Command(input)),\n );\n attempts = readAttempts(response);\n } catch (error) {\n attempts = readAttempts(error);\n throw fromSdkError(error, target);\n } finally {\n logger.debug('SNS Publish', {\n topicArn,\n fifo,\n event: event.name,\n attempts,\n ms: Date.now() - started,\n });\n }\n};\n\nfunction readAttempts(value: unknown): number | undefined {\n if (typeof value !== 'object' || value === null || !('$metadata' in value))\n return undefined;\n const metadata = value.$metadata;\n if (typeof metadata !== 'object' || metadata === null) return undefined;\n return 'attempts' in metadata && typeof metadata.attempts === 'number'\n ? metadata.attempts\n : undefined;\n}\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type { Env, SendClient, Setup, SetupSubscription, Types } from './types';\nimport type { LifecycleContext } from '@walkeros/core';\nimport {\n CreateTopicCommand,\n GetTopicAttributesCommand,\n SubscribeCommand,\n} from '@aws-sdk/client-sns';\nimport { resolveSetup } from '@walkeros/core';\nimport {\n DEFAULT_REGION,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n parseTopicArn,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport type { DeliveryTarget } from '../lib/errors';\nimport type { CallerIdentity, ClientOptions } from './lib/sns';\nimport { createSnsClient, createStsClient, getCallerIdentity } from './lib/sns';\n\n// Setup is wired to the destination's `setup` slot which uses the broader\n// `DestinationServer.Config<Types>` (settings is optional). We runtime-narrow\n// instead of using the local Config alias so the assignment in index.ts\n// type-checks without contravariance issues.\ntype WideConfig = DestinationServer.Config<Types>;\n\n// No region default here: an unset `setup.region` falls through to the\n// runtime region order, so setup and publish target the same region.\nexport const DEFAULT_SETUP: Required<Pick<Setup, 'fifoTopic'>> & Setup = {\n fifoTopic: false,\n};\n\nexport interface SetupResult {\n topicArn: string;\n /** True when the GetTopicAttributes probe returned 404 (topic absent before this run). */\n topicCreated: boolean;\n tagsApplied: number;\n subscriptionsCreated: number;\n}\n\n// Module-level caller-identity cache so re-running setup in the same process\n// is a no-op for the STS GetCallerIdentity call. Keyed by region (in case\n// future versions support cross-region setup in one process).\nconst identityCache: Map<string, CallerIdentity> = new Map();\n\n/** Test-only: clear the caller-identity cache between test cases. */\nexport function __resetAccountIdCache(): void {\n identityCache.clear();\n}\n\ninterface CreateTopicResponse {\n TopicArn?: string;\n}\n\ninterface AwsErrorMeta {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n}\n\nfunction isAwsError(err: unknown): err is AwsErrorMeta {\n return typeof err === 'object' && err !== null;\n}\n\nfunction isNotFound(err: unknown): boolean {\n if (!isAwsError(err)) return false;\n if (err.name === 'NotFoundException' || err.name === 'NotFound') return true;\n if (err.$metadata && err.$metadata.httpStatusCode === 404) return true;\n return false;\n}\n\nfunction resolveTopicName(name: string, fifo: boolean): string {\n if (fifo) {\n if (name.endsWith('.fifo')) return name;\n return `${name}.fifo`;\n }\n if (name.endsWith('.fifo')) {\n throw new Error(\n `FIFO suffix '.fifo' on standard topic name '${name}'. Set setup.fifoTopic: true or rename the topic.`,\n );\n }\n return name;\n}\n\n/**\n * Account id and partition of the setup credentials, from STS once per region.\n * The partition (aws, aws-cn, aws-us-gov) comes from the caller ARN, so the\n * candidate topic ARN is right outside the standard partition too.\n */\nasync function getIdentity(\n env: Env | undefined,\n options: ClientOptions,\n target: DeliveryTarget,\n): Promise<CallerIdentity> {\n const cached = identityCache.get(options.region);\n if (cached) return cached;\n const stsClient = createStsClient(env, options);\n try {\n const identity = await getCallerIdentity(stsClient, env, target);\n identityCache.set(options.region, identity);\n return identity;\n } finally {\n stsClient.destroy?.();\n }\n}\n\nfunction isCreateTopicResponse(v: unknown): v is CreateTopicResponse {\n return typeof v === 'object' && v !== null;\n}\n\nfunction extractTopicArn(res: unknown): string {\n if (isCreateTopicResponse(res) && typeof res.TopicArn === 'string')\n return res.TopicArn;\n throw new Error('SNS CreateTopic returned no TopicArn');\n}\n\nfunction buildSubscribeAttributes(\n sub: SetupSubscription,\n): Record<string, string> | undefined {\n const attrs: Record<string, string> = {};\n if (sub.rawMessageDelivery !== undefined) {\n attrs.RawMessageDelivery = String(sub.rawMessageDelivery);\n }\n if (sub.filterPolicy !== undefined) {\n attrs.FilterPolicy = JSON.stringify(sub.filterPolicy);\n }\n if (sub.deadLetterTargetArn !== undefined) {\n attrs.RedrivePolicy = JSON.stringify({\n deadLetterTargetArn: sub.deadLetterTargetArn,\n });\n }\n return Object.keys(attrs).length > 0 ? attrs : undefined;\n}\n\nasync function applyDeclaredSubscriptions(\n client: SendClient,\n env: Env | undefined,\n topicArn: string,\n declared: SetupSubscription[],\n): Promise<number> {\n let count = 0;\n for (const sub of declared) {\n const input: {\n TopicArn: string;\n Protocol: string;\n Endpoint: string;\n Attributes?: Record<string, string>;\n } = {\n TopicArn: topicArn,\n Protocol: sub.protocol,\n Endpoint: sub.endpoint,\n };\n const attrs = buildSubscribeAttributes(sub);\n if (attrs) input.Attributes = attrs;\n const Command = env?.AWS?.SubscribeCommand ?? SubscribeCommand;\n await client.send(new Command(input));\n count += 1;\n }\n return count;\n}\n\nexport async function setup(\n ctx: LifecycleContext<WideConfig, Env | undefined>,\n): Promise<SetupResult | undefined> {\n const { config, env, logger } = ctx;\n const merged = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!merged) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n const settings = config.settings ?? {};\n const arn = settings.topicArn ? parseTopicArn(settings.topicArn) : undefined;\n const topicName = settings.topicName ?? arn?.name;\n if (!topicName) {\n logger.throw(\n 'setup: settings.topicName (or settings.topicArn) is required. There is no safe default for the SNS topic name.',\n );\n return;\n }\n\n const sdkRegion =\n typeof settings.config?.region === 'string'\n ? settings.config.region\n : undefined;\n const region =\n firstString(\n merged.region,\n settings.region,\n arn?.region,\n sdkRegion,\n await loadSdkRegion(),\n ) ?? DEFAULT_REGION;\n const fifo = merged.fifoTopic ?? false;\n const finalName = resolveTopicName(topicName, fifo);\n if (fifo && finalName !== topicName) {\n logger.info(\n `setup: appended .fifo suffix to FIFO topic name '${topicName}' -> '${finalName}'`,\n );\n }\n\n // Topic-existence probe via candidate ARN derived from STS account ID.\n const options: ClientOptions = {\n region,\n config: settings.config,\n credentials: parseCredentials(config.credentials),\n timeout: config.timeout,\n };\n const client = settings.client ?? createSnsClient(env, options);\n // A client setup built is closed on every path, unless something in it is\n // the user's (their client, or their request handler instance).\n const closesClient =\n !settings.client && !isHandlerInstance(settings.config?.requestHandler);\n\n try {\n const { accountId, partition } = await getIdentity(env, options, {\n service: 'SNS',\n resource: finalName,\n region,\n id: ctx.id,\n });\n const candidateArn = `arn:${partition}:sns:${region}:${accountId}:${finalName}`;\n\n let topicCreated = false;\n try {\n const Command =\n env?.AWS?.GetTopicAttributesCommand ?? GetTopicAttributesCommand;\n await client.send(new Command({ TopicArn: candidateArn }));\n } catch (err) {\n if (isNotFound(err)) {\n topicCreated = true;\n } else {\n throw err;\n }\n }\n\n // Authoritative-apply: full declared state in one CreateTopic call.\n // SNS:CreateTopic is idempotent on identical Name+Attributes+Tags inputs.\n const attributes: Record<string, string> = {};\n if (fifo) {\n attributes.FifoTopic = 'true';\n attributes.ContentBasedDeduplication = 'true';\n }\n if (merged.displayName !== undefined)\n attributes.DisplayName = merged.displayName;\n if (merged.kmsMasterKeyId !== undefined)\n attributes.KmsMasterKeyId = merged.kmsMasterKeyId;\n\n const tagEntries = merged.tags\n ? Object.entries(merged.tags).map(([Key, Value]) => ({ Key, Value }))\n : undefined;\n\n const createInput: {\n Name: string;\n Attributes?: Record<string, string>;\n Tags?: Array<{ Key: string; Value: string }>;\n } = { Name: finalName };\n if (Object.keys(attributes).length > 0) createInput.Attributes = attributes;\n if (tagEntries && tagEntries.length > 0) createInput.Tags = tagEntries;\n\n const CreateCommand = env?.AWS?.CreateTopicCommand ?? CreateTopicCommand;\n const createRes = await client.send(new CreateCommand(createInput));\n const topicArn = extractTopicArn(createRes);\n\n if (topicCreated) {\n logger.info('setup: topic created', { topicArn, region });\n } else {\n logger.debug('setup: topic exists; declared state re-applied', {\n topicArn,\n region,\n });\n }\n\n const subscriptionsCreated = await applyDeclaredSubscriptions(\n client,\n env,\n topicArn,\n merged.subscriptions ?? [],\n );\n\n return {\n topicArn,\n topicCreated,\n tagsApplied: merged.tags ? Object.keys(merged.tags).length : 0,\n subscriptionsCreated,\n };\n } finally {\n if (closesClient) client.destroy?.();\n }\n}\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n SetupFn as CoreSetupFn,\n} from '@walkeros/core';\nimport type {\n SNSClientConfig,\n CreateTopicCommandInput,\n PublishCommandInput,\n GetTopicAttributesCommandInput,\n SubscribeCommandInput,\n} from '@aws-sdk/client-sns';\nimport type {\n STSClientConfig,\n GetCallerIdentityCommandInput,\n} from '@aws-sdk/client-sts';\nimport type { InFlight, SendClient } from '../../lib/client';\nimport type { CredentialsInput } from '../../lib/credentials';\n\nexport type { SendClient } from '../../lib/client';\n\n/** An AWS SDK v3 command class, as far as this destination builds one. */\nexport type CommandConstructor<Input> = new (input: Input) => {\n readonly input: Input;\n};\n\n/**\n * Settings as a user writes them. `topicArn` alone is enough: region and\n * topic name derive from it. `topicName` (with `region`) also works, and the\n * ARN is then completed with the account id at the first publish.\n */\nexport interface InitSettings {\n /** Topic ARN. Region and topic name derive from it. */\n topicArn?: string;\n /** Topic name (with `.fifo` for FIFO topics). Needed by `walkeros setup`. */\n topicName?: string;\n /** AWS region. Default: the ARN's region, `AWS_REGION` or the profile, else `eu-central-1`. */\n region?: string;\n /** Raw `SNSClient` options, passed through to the SDK. */\n config?: SNSClientConfig;\n /** A client of your own. Used as is and never closed by the destination. */\n client?: SendClient;\n /** Runtime-only, set by `init`: what the destination owns. Not user-facing. */\n runtime?: Runtime;\n}\n\n/** What `init` resolved. */\nexport interface Settings {\n topicArn?: string;\n topicName?: string;\n region: string;\n config?: SNSClientConfig;\n client: SendClient;\n runtime: Runtime;\n}\n\n/** Runtime-only state of one initialized instance. */\nexport interface Runtime {\n /**\n * True when the destination built the client from nothing the user owns,\n * and so closes it. A user client, or a user request handler instance in\n * `settings.config`, keeps it false.\n */\n ownsClient: boolean;\n /** Publishes in flight, awaited by `destroy`. */\n inflight: InFlight;\n /** The topic ARN, completed once with the account id when only a name was given. */\n topicArn: () => Promise<string>;\n}\n\nexport interface Mapping {\n /**\n * Per-event message attributes resolved via mapping. Each attribute value\n * resolves to the SDK's expected `{ DataType, StringValue }` shape; operators\n * write either a literal `{ DataType: 'String', StringValue: '$path' }` value\n * or use `{ value: { DataType: 'String', StringValue: '$user.id' } }` style\n * to drive the StringValue from event data.\n */\n messageAttributes?: WalkerOSMapping.Map;\n /**\n * FIFO group ID for FIFO topics. `Mapping.Value` so operators can write\n * `messageGroupId: 'user.id'` (path) instead of hard-coding a literal.\n */\n messageGroupId?: WalkerOSMapping.Value;\n /**\n * FIFO deduplication ID. `Mapping.Value`, same reasoning as\n * `messageGroupId`. Defaults to the event id on FIFO topics.\n */\n messageDeduplicationId?: WalkerOSMapping.Value;\n}\n\n/**\n * Optional SDK overrides. Each one present wins over the SDK the package\n * imports; tests and simulate inject mocks here.\n */\nexport interface Env extends DestinationServer.Env {\n AWS?: {\n SNSClient?: new (config: SNSClientConfig) => SendClient;\n CreateTopicCommand?: CommandConstructor<CreateTopicCommandInput>;\n PublishCommand?: CommandConstructor<PublishCommandInput>;\n GetTopicAttributesCommand?: CommandConstructor<GetTopicAttributesCommandInput>;\n SubscribeCommand?: CommandConstructor<SubscribeCommandInput>;\n STSClient?: new (config: STSClientConfig) => SendClient;\n GetCallerIdentityCommand?: CommandConstructor<GetCallerIdentityCommandInput>;\n };\n}\n\n/**\n * Provisioning options for `walkeros setup destination.<id>` when targeting SNS.\n * Triggered only by the explicit CLI command. Idempotent, never auto-run.\n *\n * `topicName` lives in Settings (one source of truth for setup AND runtime publish).\n */\nexport interface Setup {\n /** AWS region for the topic. Default: the runtime region order. */\n region?: string;\n /** Display name. Optional. */\n displayName?: string;\n /** FIFO topic with content-based deduplication. Default: false. */\n fifoTopic?: boolean;\n /** KMS key for at-rest encryption. Optional. */\n kmsMasterKeyId?: string;\n /** Tags for cost allocation. Optional. */\n tags?: Record<string, string>;\n /** Subscriptions to create or update on the topic. Each is opt-in. */\n subscriptions?: SetupSubscription[];\n}\n\nexport interface SetupSubscription {\n protocol: 'sqs' | 'lambda' | 'https' | 'http' | 'email' | 'sms';\n endpoint: string;\n rawMessageDelivery?: boolean;\n filterPolicy?: Record<string, unknown>;\n deadLetterTargetArn?: string;\n}\n\nexport type Types = CoreDestination.Types<\n Settings,\n Mapping,\n Env,\n InitSettings,\n Setup,\n CredentialsInput\n>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: InitSettings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type SetupFn = CoreSetupFn<Config, Env>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { setup } from './setup';\n\n// Types\nexport * as DestinationSNS from './types';\n\nexport const destinationSNS: Destination = {\n type: 'aws-sns',\n\n config: {},\n\n setup,\n\n async init({ config, env, logger, id }) {\n return getConfig(config, env, logger, id);\n },\n\n push,\n\n async destroy({ config, logger }) {\n const runtime = config.settings?.runtime;\n if (!runtime) return;\n\n // Let publishes on the wire land before closing the client under them.\n await runtime.inflight.settle();\n\n if (!runtime.ownsClient) return;\n runtime.ownsClient = false;\n config.settings?.client?.destroy?.();\n logger.debug('SNS client closed');\n },\n};\n\nexport default destinationSNS;\n"],"mappings":"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","names":["types_exports","import_core","clientConfig","import_client_firehose","import_core","import_core","getConfig","resolveSettings","import_client_sns","import_core","push","resolveSettings","import_client_sns","import_core","types_exports","getConfig","push"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/firehose/config.ts","../src/lib/client.ts","../src/lib/credentials.ts","../src/lib/errors.ts","../src/firehose/lib/firehose.ts","../src/firehose/lib/deliver.ts","../src/lib/payload.ts","../src/firehose/lib/records.ts","../src/firehose/push.ts","../src/firehose/pushBatch.ts","../src/firehose/types/index.ts","../src/firehose/index.ts","../src/sns/config.ts","../src/sns/lib/sns.ts","../src/sns/push.ts","../src/sns/setup.ts","../src/sns/types/index.ts","../src/sns/index.ts"],"sourcesContent":["// AWS Firehose\nexport { destinationFirehose } from './firehose';\nexport * as DestinationFirehose from './firehose/types';\n\n// AWS SNS\nexport { destinationSNS } from './sns';\nexport * as DestinationSNS from './sns/types';\n\n// Default remains Firehose for backward compatibility.\nexport { destinationFirehose as default } from './firehose';\n","import type { Logger } from '@walkeros/core';\nimport { FirehoseClient } from '@aws-sdk/client-firehose';\nimport { StepError, isObject } from '@walkeros/core';\nimport type {\n Config,\n CredentialSource,\n Env,\n FirehoseConfig,\n InitSettings,\n PartialConfig,\n Settings,\n} from './types';\nimport {\n DEFAULT_REGION,\n InFlight,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport { configError } from '../lib/errors';\nimport { STREAM_NAME_PATTERN, createClient } from './lib/firehose';\n\n/**\n * Equal as written in a flow: the same value, or plain objects and arrays with\n * equal contents. Anything else (a client, a provider function, a handler\n * instance) is equal only to itself.\n */\nfunction sameValue(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (Array.isArray(a) && Array.isArray(b))\n return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));\n if (!isObject(a) || !isObject(b)) return false;\n if (!isPlain(a) || !isPlain(b)) return false;\n const keys = Object.keys(a);\n return (\n keys.length === Object.keys(b).length &&\n keys.every((key) => key in b && sameValue(a[key], b[key]))\n );\n}\n\nfunction isPlain(value: object): boolean {\n const proto: unknown = Object.getPrototypeOf(value);\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Reads a flat key and its deprecated `settings.firehose` alias. Both set to\n * different values throws: a silent winner could route events to the wrong\n * stream after a half-done migration.\n */\nfunction pick<K extends keyof FirehoseConfig>(\n key: K,\n flat: FirehoseConfig[K],\n alias: FirehoseConfig[K],\n): FirehoseConfig[K] {\n if (flat !== undefined && alias !== undefined && !sameValue(flat, alias))\n throw configError(\n `Firehose: settings.${key} and settings.firehose.${key} are both set and differ. Remove settings.firehose.`,\n );\n return flat ?? alias;\n}\n\n/**\n * Resolves the settings once, offline: no credentials are loaded and no AWS\n * call is made. The caller's config is never mutated.\n */\nexport async function getConfig(\n partialConfig: PartialConfig = {},\n env: Env | undefined,\n logger: Logger.Instance,\n): Promise<Config> {\n const input: InitSettings = partialConfig.settings ?? {};\n const alias: FirehoseConfig = input.firehose ?? {};\n\n const streamName = pick('streamName', input.streamName, alias.streamName);\n const sdkConfig = pick('config', input.config, alias.config);\n const userClient = pick('client', input.client, alias.client);\n const regionSetting = pick('region', input.region, alias.region);\n\n if (!streamName)\n throw configError(\n 'Firehose: settings.streamName is missing. Set it to the name of your Firehose stream (settings.firehose.streamName is the deprecated form).',\n );\n if (!STREAM_NAME_PATTERN.test(streamName))\n throw configError(\n `Firehose: settings.streamName \"${streamName}\" is not a valid stream name. Use 1 to 64 letters, digits, \"_\", \".\" or \"-\".`,\n );\n\n const sdkRegion =\n typeof sdkConfig?.region === 'string' ? sdkConfig.region : undefined;\n const region =\n firstString(regionSetting, sdkRegion, await loadSdkRegion()) ??\n DEFAULT_REGION;\n\n const credentials = parseCredentials(partialConfig.credentials);\n const credentialSource: CredentialSource = userClient\n ? 'client'\n : credentials\n ? 'config.credentials'\n : sdkConfig?.credentials !== undefined\n ? 'settings.config.credentials'\n : 'default chain';\n\n const client =\n userClient ??\n createClient(env?.AWS?.FirehoseClient ?? FirehoseClient, {\n region,\n config: sdkConfig,\n credentials,\n timeout: partialConfig.timeout,\n });\n\n const settings: Settings = {\n streamName,\n region,\n newline: input.newline !== false,\n client,\n runtime: {\n ownsClient: !userClient && !isHandlerInstance(sdkConfig?.requestHandler),\n inflight: new InFlight(),\n credentialSource,\n },\n };\n if (sdkConfig) settings.config = sdkConfig;\n\n logger.debug('Firehose client ready', {\n stream: streamName,\n region,\n credentials: credentialSource,\n });\n\n return { ...partialConfig, settings };\n}\n\n/**\n * Narrows the settings a push receives. Core types them as the slot a user\n * writes, so the fields `init` set are optional there.\n */\nexport function resolveSettings(settings: InitSettings | undefined): Settings {\n const { streamName, region, client, runtime } = settings ?? {};\n if (!streamName || !region || !client || !runtime)\n throw new StepError('Firehose: the destination was not initialized.', {\n code: 'not-ready',\n });\n return {\n streamName,\n region,\n newline: settings?.newline !== false,\n client,\n runtime,\n };\n}\n","import {\n loadConfig,\n NODE_REGION_CONFIG_FILE_OPTIONS,\n NODE_REGION_CONFIG_OPTIONS,\n} from '@smithy/core/config';\n\n/** Region used when neither the flow nor the environment names one. */\nexport const DEFAULT_REGION = 'eu-central-1';\n\n/** The collector's per-delivery race when `config.timeout` is unset. */\nexport const DEFAULT_TIMEOUT_MS = 10_000;\n\n/**\n * Share of `config.timeout` one SDK attempt may take. The SDK standard retry\n * makes up to three attempts; at a quarter each, three attempts plus the SDK's\n * longest documented waits end before the collector stops waiting.\n */\nexport const REQUEST_TIMEOUT_SHARE = 0.25;\n\n/**\n * The part of an AWS SDK v3 client this package calls: `send` with a command,\n * and `destroy` on a client it created. The SDK clients satisfy it, and so\n * does an injected mock (tests, simulate) without a cast.\n */\nexport interface SendClient {\n send(command: object): Promise<unknown>;\n destroy?(): void;\n}\n\n/**\n * True for a request handler instance (anything with a `handle` method), as\n * opposed to handler options. An instance the user passed belongs to the\n * user: a client closed by this package would close its sockets too.\n */\nexport function isHandlerInstance(handler: unknown): boolean {\n return (\n typeof handler === 'object' &&\n handler !== null &&\n 'handle' in handler &&\n typeof handler.handle === 'function'\n );\n}\n\n/** `requestHandler` options for a client this package builds. */\nexport interface RequestHandlerOptions {\n requestTimeout: number;\n throwOnRequestTimeout: true;\n}\n\n/** The collector's race as this package reads it: positive and finite. */\nexport function resolveTimeout(timeout?: number): number {\n if (timeout === undefined || !Number.isFinite(timeout) || timeout <= 0)\n return DEFAULT_TIMEOUT_MS;\n return timeout;\n}\n\n/**\n * Per-attempt timeout for a client this package builds. Without it the SDK\n * waits on a hung request forever (its default is 0, off), so the retry it\n * keeps never fires and the collector's race is the only bound.\n */\nexport function requestHandlerOptions(timeout?: number): RequestHandlerOptions {\n return {\n requestTimeout: Math.floor(resolveTimeout(timeout) * REQUEST_TIMEOUT_SHARE),\n throwOnRequestTimeout: true,\n };\n}\n\n/** The parts of an SNS topic ARN this package reads. */\nexport interface TopicArn {\n region: string;\n accountId: string;\n name: string;\n}\n\n/** Parses `arn:<partition>:sns:<region>:<account>:<name>`. */\nexport function parseTopicArn(arn: string): TopicArn | undefined {\n const parts = arn.split(':');\n if (parts.length !== 6) return undefined;\n const [prefix, partition, service, region, accountId, name] = parts;\n if (prefix !== 'arn' || !partition || service !== 'sns') return undefined;\n if (!region || !accountId || !name) return undefined;\n return { region, accountId, name };\n}\n\n/**\n * The region the SDK would pick on its own: `AWS_REGION`, else the active\n * profile's `region`. Local files and variables only, never the network.\n * `undefined` when neither names one.\n */\nexport async function loadSdkRegion(): Promise<string | undefined> {\n try {\n const region = await loadConfig(\n NODE_REGION_CONFIG_OPTIONS,\n NODE_REGION_CONFIG_FILE_OPTIONS,\n )();\n return typeof region === 'string' && region.length > 0 ? region : undefined;\n } catch {\n return undefined;\n }\n}\n\n/** The first non-empty string, used for the region order. */\nexport function firstString(\n ...candidates: Array<string | undefined>\n): string | undefined {\n return candidates.find(\n (candidate): candidate is string =>\n typeof candidate === 'string' && candidate.length > 0,\n );\n}\n\n/**\n * Tracks the sends an instance has in flight, so `destroy` can let them land\n * before it closes the client under them.\n */\nexport class InFlight {\n private readonly pending = new Set<Promise<unknown>>();\n\n track<T>(promise: Promise<T>): Promise<T> {\n this.pending.add(promise);\n const remove = () => {\n this.pending.delete(promise);\n };\n promise.then(remove, remove);\n return promise;\n }\n\n get size(): number {\n return this.pending.size;\n }\n\n /** Resolves once every tracked send settled, whatever its outcome. */\n async settle(): Promise<void> {\n while (this.pending.size > 0) {\n await Promise.allSettled([...this.pending]);\n }\n }\n}\n","import { isObject } from '@walkeros/core';\nimport { configError } from './errors';\n\n/**\n * Static AWS keys, the shape `config.credentials` takes. Back each value with\n * `$secret`; a literal key in a flow file is a leaked key.\n */\nexport interface AwsCredentials {\n accessKeyId: string;\n secretAccessKey: string;\n sessionToken?: string;\n}\n\n/** `config.credentials` as written: the object, or the same object as JSON. */\nexport type CredentialsInput = AwsCredentials | string;\n\nfunction nonEmpty(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n\n/**\n * Checks `config.credentials` offline and returns the keys, or `undefined`\n * when the slot is unset (the SDK's default chain then applies).\n *\n * Messages name the field that is wrong and never a value: a credentials\n * error ends up in logs, and the value it would echo is the secret.\n */\nexport function parseCredentials(input: unknown): AwsCredentials | undefined {\n if (input === undefined || input === null || input === '') return undefined;\n\n let value: unknown = input;\n if (typeof input === 'string') {\n try {\n value = JSON.parse(input);\n } catch {\n // The parser's own message quotes the input, so it is not passed on.\n throw configError(\n 'config.credentials is not valid JSON. Expected {\"accessKeyId\", \"secretAccessKey\", \"sessionToken\"?}.',\n );\n }\n }\n\n if (!isObject(value))\n throw configError(\n 'config.credentials must be an object with accessKeyId and secretAccessKey.',\n );\n\n const { accessKeyId, secretAccessKey, sessionToken } = value;\n\n for (const [field, fieldValue] of [\n ['accessKeyId', accessKeyId],\n ['secretAccessKey', secretAccessKey],\n ] as const) {\n if (!nonEmpty(fieldValue))\n throw configError(\n `config.credentials.${field} is missing or empty. Set it from a $secret.`,\n );\n }\n\n if (sessionToken !== undefined && !nonEmpty(sessionToken))\n throw configError(\n 'config.credentials.sessionToken must be a non-empty string when set.',\n );\n\n const credentials: AwsCredentials = {\n accessKeyId: String(accessKeyId),\n secretAccessKey: String(secretAccessKey),\n };\n if (sessionToken !== undefined) credentials.sessionToken = sessionToken;\n return credentials;\n}\n","import {\n isServerError,\n isThrottlingError,\n isTransientError,\n} from '@smithy/core/retry';\nimport { StepError } from '@walkeros/core';\n\n/** The AWS service a delivery goes to, as it appears in every message. */\nexport type AwsService = 'Firehose' | 'SNS';\n\n/**\n * What a failure is about: the service, the stream or topic and the region.\n * Every message carries all three, because a wrong region and a wrong name\n * produce the same AWS answer.\n */\nexport interface DeliveryTarget {\n service: AwsService;\n /** Stream name or topic ARN (or topic name before the ARN is known). */\n resource: string;\n region: string;\n /** Destination id, used by the SNS setup hint. */\n id?: string;\n}\n\n/*\n * Every failure this package throws is a `StepError`. `code` is the AWS error\n * name or one of the package's own codes (`RecordTooLarge`, `InvalidRecord`,\n * `InvalidResponse`, `config`), `status` the HTTP status when AWS answered,\n * and `retryable` whether sending the same record again can succeed. The\n * message is the package's own: AWS's text can quote the request, so it stays\n * on the SDK error, kept as `cause`, or on a rejected record's `body`.\n */\n\n/** Codes whose fix is a configuration change, each with that change. */\nfunction hintFor(code: string, target: DeliveryTarget): string | undefined {\n switch (code) {\n case 'ResourceNotFoundException':\n return target.service === 'Firehose'\n ? 'Check settings.streamName and settings.region.'\n : 'Check settings.topicArn and settings.region.';\n case 'AccessDeniedException':\n case 'AuthorizationErrorException':\n return target.service === 'Firehose'\n ? 'Allow firehose:PutRecordBatch on the stream for these credentials.'\n : 'Allow sns:Publish on the topic for these credentials.';\n case 'UnrecognizedClientException':\n case 'InvalidSignatureException':\n case 'ExpiredTokenException':\n case 'CredentialsProviderError':\n return 'Check config.credentials or the AWS credentials in the environment.';\n case 'NotFoundException':\n return target.service === 'SNS'\n ? `Run \"walkeros setup destination.${target.id ?? '<id>'}\" or set settings.topicArn.`\n : undefined;\n default:\n return undefined;\n }\n}\n\nfunction describe(target: DeliveryTarget): string {\n const kind = target.service === 'Firehose' ? 'stream' : 'topic';\n return `${target.service} ${kind} \"${target.resource}\" (${target.region})`;\n}\n\n/** Builds the message: target, code, the package's detail and, if listed, the fix. */\nexport function formatMessage(\n target: DeliveryTarget,\n code: string,\n detail?: string,\n): string {\n const hint = hintFor(code, target);\n const base = detail\n ? `${describe(target)}: ${code}: ${detail}`\n : `${describe(target)}: ${code}`;\n return hint ? `${base} ${hint}` : base;\n}\n\n/** The error shape the SDK classifier reads. */\ntype SdkError = Parameters<typeof isTransientError>[0];\n\nfunction isSdkError(error: unknown): error is SdkError {\n return error instanceof Error;\n}\n\nfunction readCode(error: SdkError): string {\n // Network errors from Node arrive as a plain `Error` whose cause is in the\n // errno `code` (ECONNRESET, ETIMEDOUT); the name alone would say nothing.\n if (error.name !== 'Error') return error.name;\n const code: unknown = Reflect.get(error, 'code');\n return typeof code === 'string' && code.length > 0 ? code : error.name;\n}\n\n/**\n * Errors of the providers that resolve credentials before a request goes\n * out: a setup problem, never fixed by sending again.\n */\nconst PROVIDER_ERRORS = [\n 'ProviderError',\n 'CredentialsProviderError',\n 'TokenProviderError',\n];\n\n/**\n * Wraps an error a whole call threw. `retryable` is the SDK's own public\n * classifier, the one its standard retry strategy uses, so this package keeps\n * no table of its own. An error it does not know that carries no AWS answer\n * never reached AWS (a refused connection) and is retryable like any\n * transport failure, unless no credentials could be found.\n */\nexport function fromSdkError(\n error: unknown,\n target: DeliveryTarget,\n): StepError {\n if (error instanceof StepError) return error;\n\n if (!isSdkError(error)) {\n return new StepError(formatMessage(target, 'UnknownError'), {\n code: 'UnknownError',\n retryable: false,\n cause: error,\n });\n }\n\n const code = readCode(error);\n const status = error.$metadata?.httpStatusCode;\n const retryable =\n isThrottlingError(error) ||\n isTransientError(error) ||\n isServerError(error) ||\n (status === undefined && !PROVIDER_ERRORS.includes(code));\n\n return new StepError(formatMessage(target, code), {\n code,\n status,\n retryable,\n cause: error,\n });\n}\n\n/**\n * A record AWS rejected inside an accepted call. Both per-record codes AWS\n * documents for PutRecordBatch (`ServiceUnavailableException`,\n * `InternalFailure`) are retryable, so every such rejection is. AWS's answer\n * for the record stays on the error as `body`.\n */\nexport function recordError(\n target: DeliveryTarget,\n code: string,\n status?: number,\n body?: unknown,\n): StepError {\n return new StepError(formatMessage(target, code), {\n code,\n status,\n retryable: true,\n body,\n });\n}\n\n/**\n * A failure this package detected itself, without an AWS answer to classify:\n * a record it refused to send, or a response it could not read.\n */\nexport function localError(\n target: DeliveryTarget,\n code: 'RecordTooLarge' | 'InvalidRecord' | 'InvalidResponse',\n detail: string,\n cause?: unknown,\n): StepError {\n return new StepError(formatMessage(target, code, detail), {\n code,\n retryable: code === 'InvalidResponse',\n cause,\n });\n}\n\n/** A configuration error found offline in `init` or `setup`. */\nexport function configError(message: string): StepError {\n return new StepError(message, { code: 'config' });\n}\n","import type { FirehoseClientConfig } from '@aws-sdk/client-firehose';\nimport type { AwsCredentials } from '../../lib/credentials';\nimport type { SendClient } from '../../lib/client';\nimport type { FirehoseClientConstructor } from '../types';\nimport { requestHandlerOptions } from '../../lib/client';\n\n/** Firehose stream names: 1 to 64 letters, digits, `_`, `.` or `-`. */\nexport const STREAM_NAME_PATTERN = /^[a-zA-Z0-9_.-]{1,64}$/;\n\nexport interface ClientOptions {\n region: string;\n /** Raw SDK options from `settings.config`. Never mutated. */\n config?: FirehoseClientConfig;\n /** Parsed `config.credentials`; wins over `config.credentials` of the SDK options. */\n credentials?: AwsCredentials;\n /** The collector's race, `config.timeout`. */\n timeout?: number;\n}\n\n/**\n * Builds the client the destination owns. The per-attempt timeout is added\n * only when the user passed no `requestHandler` of their own.\n */\nexport function createClient(\n Client: FirehoseClientConstructor,\n options: ClientOptions,\n): SendClient {\n const clientConfig: FirehoseClientConfig = {\n ...options.config,\n region: options.region,\n };\n if (options.credentials) clientConfig.credentials = options.credentials;\n if (clientConfig.requestHandler === undefined)\n clientConfig.requestHandler = requestHandlerOptions(options.timeout);\n\n return new Client(clientConfig);\n}\n","import type { Logger, StepError, WalkerOS } from '@walkeros/core';\nimport { PutRecordBatchCommand } from '@aws-sdk/client-firehose';\nimport type { Env, Settings } from '../types';\nimport type { DeliveryTarget } from '../../lib/errors';\nimport { fromSdkError, localError, recordError } from '../../lib/errors';\nimport type { PlannedRecord } from './records';\nimport { planChunks, toPayload } from './records';\n\n/** One event to deliver, with what a mapping produced for it. */\nexport interface DeliveryItem {\n event: WalkerOS.Event;\n data?: unknown;\n}\n\n/** A record that did not land, by its index in the delivery. */\nexport interface DeliveryFailure {\n index: number;\n error: StepError;\n}\n\n/** The per-record answer of a `PutRecordBatch` call, as far as it is read. */\ninterface RecordResponse {\n errorCode?: string;\n errorMessage?: string;\n}\n\n/** A `PutRecordBatch` answer, read field by field from an unknown value. */\ninterface CallResponse {\n failedPutCount?: number;\n responses?: RecordResponse[];\n status?: number;\n attempts?: number;\n}\n\n/** Any non-null object, SDK errors included (`isObject` accepts plain ones only). */\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction readMetadata(value: unknown): { status?: number; attempts?: number } {\n if (!isRecord(value)) return {};\n const metadata = value.$metadata;\n if (!isRecord(metadata)) return {};\n const { httpStatusCode, attempts } = metadata;\n return {\n status: typeof httpStatusCode === 'number' ? httpStatusCode : undefined,\n attempts: typeof attempts === 'number' ? attempts : undefined,\n };\n}\n\nfunction readResponse(value: unknown): CallResponse {\n const response: CallResponse = readMetadata(value);\n if (!isRecord(value)) return response;\n\n if (typeof value.FailedPutCount === 'number')\n response.failedPutCount = value.FailedPutCount;\n\n if (Array.isArray(value.RequestResponses)) {\n response.responses = value.RequestResponses.map(\n (entry: unknown): RecordResponse => {\n if (!isRecord(entry)) return {};\n const { ErrorCode, ErrorMessage } = entry;\n return {\n errorCode:\n typeof ErrorCode === 'string' && ErrorCode.length > 0\n ? ErrorCode\n : undefined,\n errorMessage:\n typeof ErrorMessage === 'string' ? ErrorMessage : undefined,\n };\n },\n );\n }\n\n return response;\n}\n\n/**\n * Maps one call's answer onto its records. Per-record answers are trusted only\n * when they line up with the records sent and agree with `FailedPutCount`; a\n * reported failure they cannot place fails the whole chunk as\n * `InvalidResponse`, never as delivered.\n */\nfunction chunkFailures(\n chunk: readonly PlannedRecord[],\n response: CallResponse,\n target: DeliveryTarget,\n): DeliveryFailure[] {\n const { responses, failedPutCount, status } = response;\n\n if (responses && responses.length === chunk.length) {\n const failures: DeliveryFailure[] = [];\n responses.forEach((entry, local) => {\n if (!entry.errorCode) return;\n failures.push({\n index: chunk[local].index,\n error: recordError(target, entry.errorCode, status, entry),\n });\n });\n // Entries that do not account for the reported count cannot place the\n // failures, so none of them is trusted.\n if (failedPutCount === undefined || failures.length === failedPutCount)\n return failures;\n } else if (failedPutCount === 0) return [];\n\n const error = localError(\n target,\n 'InvalidResponse',\n `PutRecordBatch answered without per-record results that match its FailedPutCount for ${chunk.length} records (FailedPutCount ${failedPutCount ?? 'missing'}).`,\n );\n return chunk.map(({ index }) => ({ index, error }));\n}\n\n/**\n * Sends records to the stream and says which did not land.\n *\n * `push` and `pushBatch` both arrive here. Each chunk is sent once, through\n * the SDK and its own retry, and all chunks go out together, so one failed\n * call never stops the rest. The answer lists every failed record by index,\n * sorted; an empty list means everything landed.\n */\nexport async function deliver(\n items: readonly DeliveryItem[],\n settings: Settings,\n env: Env | undefined,\n logger: Logger.Instance,\n): Promise<DeliveryFailure[]> {\n const target: DeliveryTarget = {\n service: 'Firehose',\n resource: settings.streamName,\n region: settings.region,\n };\n const Command = env?.AWS?.PutRecordBatchCommand ?? PutRecordBatchCommand;\n\n const plan = planChunks(\n items.map(({ event, data }) => toPayload(event, data)),\n settings.newline,\n );\n\n const failures: DeliveryFailure[] = plan.refused.map(\n ({ index, code, detail, cause }) => ({\n index,\n error: localError(target, code, detail, cause),\n }),\n );\n\n const sendChunk = async (\n chunk: PlannedRecord[],\n ): Promise<DeliveryFailure[]> => {\n const started = Date.now();\n const bytes = chunk.reduce(\n (sum, record) => sum + record.data.byteLength,\n 0,\n );\n const command = new Command({\n DeliveryStreamName: settings.streamName,\n Records: chunk.map(({ data }) => ({ Data: data })),\n });\n\n let result: DeliveryFailure[];\n let attempts: number | undefined;\n try {\n const raw = await settings.runtime.inflight.track(\n settings.client.send(command),\n );\n const response = readResponse(raw);\n attempts = response.attempts;\n result = chunkFailures(chunk, response, target);\n } catch (thrown) {\n attempts = readMetadata(thrown).attempts;\n const error = fromSdkError(thrown, target);\n result = chunk.map(({ index }) => ({ index, error }));\n }\n\n logger.debug('Firehose PutRecordBatch', {\n stream: settings.streamName,\n records: chunk.length,\n bytes,\n failed: result.length,\n attempts,\n ms: Date.now() - started,\n });\n\n return result;\n };\n\n const results = await Promise.all(plan.chunks.map(sendChunk));\n for (const result of results) failures.push(...result);\n\n return failures.sort((a, b) => a.index - b.index);\n}\n","import type { WalkerOS } from '@walkeros/core';\nimport { isObject } from '@walkeros/core';\n\n/**\n * What one record or message carries: the mapped `data` when the mapping\n * produced an object, else the full event.\n */\nexport function toPayload(event: WalkerOS.Event, data?: unknown): object {\n return isObject(data) ? data : event;\n}\n","export { toPayload } from '../../lib/payload';\n\n/** Most records one `PutRecordBatch` call accepts. */\nexport const MAX_RECORDS_PER_CALL = 500;\n\n/**\n * Raw bytes per call, kept below the documented 4 MiB request limit with room\n * for the base64 encoding and the JSON envelope of the request.\n */\nexport const MAX_BYTES_PER_CALL = 2_900_000;\n\n/** Largest record Firehose accepts, newline included. */\nexport const MAX_RECORD_BYTES = 1_000_000;\n\n/** The record bytes: the payload as JSON, plus `\\n` unless `newline` is false. */\nexport function toRecordData(payload: unknown, newline = true): Buffer {\n const json = JSON.stringify(payload);\n if (typeof json !== 'string')\n throw new TypeError('The payload does not serialize to JSON.');\n return Buffer.from(newline ? `${json}\\n` : json);\n}\n\n/** One record on its way out, with its index in the delivery. */\nexport interface PlannedRecord {\n index: number;\n data: Buffer;\n}\n\n/** A record refused before sending; it is never sent. */\nexport interface RefusedRecord {\n index: number;\n code: 'RecordTooLarge' | 'InvalidRecord';\n detail: string;\n cause?: unknown;\n}\n\nexport interface Plan {\n chunks: PlannedRecord[][];\n refused: RefusedRecord[];\n}\n\n/**\n * Turns payloads into records and splits them into calls of at most\n * `MAX_RECORDS_PER_CALL` records and `MAX_BYTES_PER_CALL` bytes, in order.\n * A record that cannot be serialized or is too large is refused on its own,\n * so it never fails its neighbours.\n */\nexport function planChunks(\n payloads: readonly unknown[],\n newline: boolean,\n): Plan {\n const chunks: PlannedRecord[][] = [];\n const refused: RefusedRecord[] = [];\n let current: PlannedRecord[] = [];\n let currentBytes = 0;\n\n payloads.forEach((payload, index) => {\n let data: Buffer;\n try {\n data = toRecordData(payload, newline);\n } catch (error) {\n refused.push({\n index,\n code: 'InvalidRecord',\n detail: `Record ${index} does not serialize to JSON.`,\n cause: error,\n });\n return;\n }\n\n if (data.byteLength > MAX_RECORD_BYTES) {\n refused.push({\n index,\n code: 'RecordTooLarge',\n detail: `Record ${index} is ${data.byteLength} bytes, over the ${MAX_RECORD_BYTES} byte limit.`,\n });\n return;\n }\n\n if (\n current.length >= MAX_RECORDS_PER_CALL ||\n currentBytes + data.byteLength > MAX_BYTES_PER_CALL\n ) {\n chunks.push(current);\n current = [];\n currentBytes = 0;\n }\n\n current.push({ index, data });\n currentBytes += data.byteLength;\n });\n\n if (current.length > 0) chunks.push(current);\n return { chunks, refused };\n}\n","import type { PushFn } from './types';\nimport { resolveSettings } from './config';\nimport { deliver } from './lib/deliver';\n\n/**\n * Sends one event as one record. Resolves once AWS confirmed it, else throws.\n * A silent rule has no record of its own: nothing to send.\n */\nexport const push: PushFn = async function (event, context) {\n const { config, data, env, logger, rule } = context;\n const settings = resolveSettings(config.settings);\n\n if (!rule?.silent) {\n const [failure] = await deliver([{ event, data }], settings, env, logger);\n if (failure) throw failure.error;\n }\n};\n","import type { PushBatchFn } from './types';\nimport { resolveSettings } from './config';\nimport { batchOutcome } from '@walkeros/server-core';\nimport { deliver } from './lib/deliver';\n\n/**\n * Sends a flushed batch. Resolves when every record landed, returns the\n * failed indices when some did, and throws when none did. Silent rules have\n * no record of their own: they are not sent and count as delivered.\n */\nexport const pushBatch: PushBatchFn = async (batch, context) => {\n const { config, env, logger } = context;\n\n const settings = resolveSettings(config.settings);\n // Each record keeps its index in the flushed batch for the outcome.\n const sending = batch.entries\n .map((entry, index) => ({ entry, index }))\n .filter(({ entry }) => !entry.rule?.silent);\n const failures = sending.length\n ? await deliver(\n sending.map(({ entry: { event, data } }) => ({ event, data })),\n settings,\n env,\n logger,\n )\n : [];\n\n return batchOutcome(\n batch.entries.length,\n failures.map(({ index, error }) => ({\n index: sending[index].index,\n error,\n })),\n );\n};\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n} from '@walkeros/core';\nimport type {\n FirehoseClientConfig,\n PutRecordBatchCommandInput,\n} from '@aws-sdk/client-firehose';\nimport type { InFlight, SendClient } from '../../lib/client';\nimport type { CredentialsInput } from '../../lib/credentials';\n\nexport type { SendClient } from '../../lib/client';\n\n/** Client and stream options, as the deprecated `settings.firehose` held them. */\nexport interface FirehoseConfig {\n streamName?: string;\n client?: SendClient;\n region?: string;\n config?: FirehoseClientConfig;\n}\n\n/**\n * Settings as a user writes them. Only `streamName` is required (here or in\n * the deprecated `firehose` alias); `init` fills in the rest.\n */\nexport interface InitSettings {\n /** Name of the Firehose stream. */\n streamName?: string;\n /** AWS region. Default: `AWS_REGION` or the profile, else `eu-central-1`. */\n region?: string;\n /** Append `\\n` to every record. Default `true`. */\n newline?: boolean;\n /** Raw `FirehoseClient` options, passed through to the SDK. */\n config?: FirehoseClientConfig;\n /** A client of your own. Used as is and never closed by the destination. */\n client?: SendClient;\n /** @deprecated Use the flat fields. Read at `init` as an alias. */\n firehose?: FirehoseConfig;\n /** Runtime-only, set by `init`: what the destination owns. Not user-facing. */\n runtime?: Runtime;\n}\n\n/** What `init` resolved. Every field is present. */\nexport interface Settings {\n streamName: string;\n region: string;\n newline: boolean;\n config?: FirehoseClientConfig;\n client: SendClient;\n runtime: Runtime;\n}\n\n/** Runtime-only state of one initialized instance. */\nexport interface Runtime {\n /**\n * True when the destination built the client from nothing the user owns,\n * and so closes it. A user client, or a user request handler instance in\n * `settings.config`, keeps it false.\n */\n ownsClient: boolean;\n /** Sends in flight, awaited by `destroy`. */\n inflight: InFlight;\n /** Where the client's credentials come from, for the debug line. */\n credentialSource: CredentialSource;\n}\n\nexport type CredentialSource =\n | 'config.credentials'\n | 'settings.config.credentials'\n | 'default chain'\n | 'client';\n\nexport interface Mapping {}\n\nexport type FirehoseClientConstructor = new (\n config: FirehoseClientConfig,\n) => SendClient;\n\nexport type PutRecordBatchCommandConstructor = new (\n input: PutRecordBatchCommandInput,\n) => { readonly input: PutRecordBatchCommandInput };\n\n/**\n * Optional SDK overrides. Each one present wins over the SDK the package\n * imports; tests and simulate inject mocks here.\n */\nexport interface Env extends DestinationServer.Env {\n AWS?: {\n FirehoseClient?: FirehoseClientConstructor;\n PutRecordBatchCommand?: PutRecordBatchCommandConstructor;\n };\n}\n\nexport type Types = CoreDestination.Types<\n Settings,\n Mapping,\n Env,\n InitSettings,\n unknown,\n CredentialsInput\n>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: InitFn;\n pushBatch: PushBatchFn;\n}\n\nexport type Config = {\n settings: InitSettings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type PushBatchFn = CoreDestination.PushBatchFn<Types>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { pushBatch } from './pushBatch';\n\n// Types\nexport * as DestinationFirehose from './types';\n\n/**\n * The Firehose destination. Batching is on by default: a flushed batch goes\n * out as `PutRecordBatch` calls of at most 500 records.\n */\nexport const destinationFirehose: Destination = {\n type: 'aws-firehose',\n\n config: { batch: { size: 500, age: 1000 } },\n\n async init({ config, env, logger }) {\n return getConfig(config, env, logger);\n },\n\n push,\n\n pushBatch,\n\n async destroy({ config, logger }) {\n const runtime = config.settings?.runtime;\n if (!runtime) return;\n\n // The collector awaits only the last flush per buffer, so earlier sends\n // can still be on the wire. Closing the client would cut them off.\n await runtime.inflight.settle();\n\n if (!runtime.ownsClient) return;\n runtime.ownsClient = false;\n config.settings?.client?.destroy?.();\n logger.debug('Firehose client closed');\n },\n};\n\nexport default destinationFirehose;\n","import type { Logger } from '@walkeros/core';\nimport { StepError } from '@walkeros/core';\nimport type {\n Config,\n Env,\n InitSettings,\n PartialConfig,\n Runtime,\n Settings,\n} from './types';\nimport type { DeliveryTarget } from '../lib/errors';\nimport {\n DEFAULT_REGION,\n InFlight,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n parseTopicArn,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport { configError } from '../lib/errors';\nimport { createSnsClient, createStsClient, getCallerIdentity } from './lib/sns';\n\n/**\n * Resolves the settings once, offline: no credentials are loaded, no topic is\n * created and no AWS call is made. The caller's config is never mutated.\n */\nexport async function getConfig(\n partialConfig: PartialConfig = {},\n env: Env | undefined,\n logger: Logger.Instance,\n id: string,\n): Promise<Config> {\n const input: InitSettings = partialConfig.settings ?? {};\n const { topicArn, config: sdkConfig, client: userClient } = input;\n\n const arn = topicArn ? parseTopicArn(topicArn) : undefined;\n if (topicArn && !arn)\n throw configError(\n `SNS: settings.topicArn \"${topicArn}\" is not an SNS topic ARN (arn:aws:sns:<region>:<account>:<name>).`,\n );\n if (!arn && !input.topicName)\n throw configError(\n 'SNS: settings.topicArn is missing. Set it to the ARN of your topic (or set settings.topicName).',\n );\n if (arn && input.region && input.region !== arn.region)\n throw configError(\n `SNS: settings.region \"${input.region}\" differs from the region \"${arn.region}\" in settings.topicArn. Remove settings.region.`,\n );\n if (arn && input.topicName && input.topicName !== arn.name)\n throw configError(\n `SNS: settings.topicName \"${input.topicName}\" differs from the name \"${arn.name}\" in settings.topicArn. Remove settings.topicName.`,\n );\n\n const sdkRegion =\n typeof sdkConfig?.region === 'string' ? sdkConfig.region : undefined;\n const region =\n firstString(input.region, arn?.region, sdkRegion, await loadSdkRegion()) ??\n DEFAULT_REGION;\n const topicName = input.topicName ?? arn?.name;\n\n if (partialConfig.batch !== undefined)\n logger.warn(\n 'SNS publishes one message per event and does not batch. Remove config.batch from this destination.',\n );\n\n const credentials = parseCredentials(partialConfig.credentials);\n const clientOptions = {\n region,\n config: sdkConfig,\n credentials,\n timeout: partialConfig.timeout,\n };\n const client = userClient ?? createSnsClient(env, clientOptions);\n\n const runtime: Runtime = {\n ownsClient: !userClient && !isHandlerInstance(sdkConfig?.requestHandler),\n inflight: new InFlight(),\n topicArn: topicArnResolver(\n topicArn,\n topicName,\n {\n service: 'SNS',\n resource: topicName ?? '',\n region,\n id,\n },\n () => createStsClient(env, clientOptions),\n env,\n ),\n };\n\n const settings: Settings = { region, client, runtime };\n if (topicArn) settings.topicArn = topicArn;\n if (topicName) settings.topicName = topicName;\n if (sdkConfig) settings.config = sdkConfig;\n\n logger.debug('SNS client ready', {\n topic: topicArn ?? topicName,\n region,\n credentials: userClient\n ? 'client'\n : credentials\n ? 'config.credentials'\n : sdkConfig?.credentials !== undefined\n ? 'settings.config.credentials'\n : 'default chain',\n });\n\n return { ...partialConfig, settings };\n}\n\n/**\n * The topic ARN, as a function a publish awaits. A configured ARN is returned\n * as is. A name alone is completed once with the account id from STS, at the\n * first publish, and cached; a failed lookup is not cached, so the next\n * publish asks again.\n */\nfunction topicArnResolver(\n topicArn: string | undefined,\n topicName: string | undefined,\n target: DeliveryTarget,\n stsClient: () => ReturnType<typeof createStsClient>,\n env: Env | undefined,\n): () => Promise<string> {\n if (topicArn) return async () => topicArn;\n\n const lookup = async (): Promise<string> => {\n const client = stsClient();\n try {\n const { accountId, partition } = await getCallerIdentity(\n client,\n env,\n target,\n );\n return `arn:${partition}:sns:${target.region}:${accountId}:${topicName}`;\n } finally {\n client.destroy?.();\n }\n };\n\n let pending: Promise<string> | undefined;\n return () => {\n pending ??= lookup().catch((error: unknown) => {\n pending = undefined;\n throw error;\n });\n return pending;\n };\n}\n\n/**\n * Narrows the settings a push receives. Core types them as the slot a user\n * writes, so the fields `init` set are optional there.\n */\nexport function resolveSettings(settings: InitSettings | undefined): Settings {\n const { region, client, runtime } = settings ?? {};\n if (!region || !client || !runtime)\n throw new StepError('SNS: the destination was not initialized.', {\n code: 'not-ready',\n });\n return {\n topicArn: settings?.topicArn,\n topicName: settings?.topicName,\n region,\n client,\n runtime,\n };\n}\n","import type { SNSClientConfig } from '@aws-sdk/client-sns';\nimport type { STSClientConfig } from '@aws-sdk/client-sts';\nimport { SNSClient } from '@aws-sdk/client-sns';\nimport { GetCallerIdentityCommand, STSClient } from '@aws-sdk/client-sts';\nimport { isObject } from '@walkeros/core';\nimport type { AwsCredentials } from '../../lib/credentials';\nimport type { SendClient } from '../../lib/client';\nimport type { DeliveryTarget } from '../../lib/errors';\nimport type { Env } from '../types';\nimport { isHandlerInstance, requestHandlerOptions } from '../../lib/client';\nimport { fromSdkError, localError } from '../../lib/errors';\n\nexport interface ClientOptions {\n region: string;\n /** Raw SDK options from `settings.config`. Never mutated. */\n config?: SNSClientConfig;\n /** Parsed `config.credentials`; wins over the credentials of the SDK options. */\n credentials?: AwsCredentials;\n /** The collector's race, `config.timeout`. */\n timeout?: number;\n}\n\n/**\n * The options of a client this package builds: the region, the credentials\n * slot, and the per-attempt timeout unless the user passed a handler.\n */\nexport function clientConfig(options: ClientOptions): SNSClientConfig {\n const resolved: SNSClientConfig = {\n ...options.config,\n region: options.region,\n };\n if (options.credentials) resolved.credentials = options.credentials;\n if (resolved.requestHandler === undefined)\n resolved.requestHandler = requestHandlerOptions(options.timeout);\n return resolved;\n}\n\n/** Builds the SNS client the destination owns, from env or the SDK. */\nexport function createSnsClient(\n env: Env | undefined,\n options: ClientOptions,\n): SendClient {\n const Client = env?.AWS?.SNSClient ?? SNSClient;\n return new Client(clientConfig(options));\n}\n\n/**\n * Builds the STS client for the account lookup, from env or the SDK. It shares\n * the region and credentials of the SNS client, never its endpoint (an SNS\n * endpoint does not answer STS) and never a user's handler instance (the\n * lookup client is closed after one call).\n *\n * One attempt only: the lookup runs before the first publish of a\n * `topicName`-only config, and one attempt of a quarter of `config.timeout`\n * plus the publish's own three still end inside the collector's race. A\n * failed lookup is not cached, so the next publish asks again.\n */\nexport function createStsClient(\n env: Env | undefined,\n options: ClientOptions,\n): SendClient {\n const Client = env?.AWS?.STSClient ?? STSClient;\n const config: STSClientConfig = { region: options.region };\n const credentials = options.credentials ?? options.config?.credentials;\n if (credentials) config.credentials = credentials;\n const userHandler = options.config?.requestHandler;\n config.requestHandler =\n userHandler === undefined || isHandlerInstance(userHandler)\n ? requestHandlerOptions(options.timeout)\n : userHandler;\n config.maxAttempts = 1;\n return new Client(config);\n}\n\n/** Account id and partition of the caller, from `GetCallerIdentity`. */\nexport interface CallerIdentity {\n accountId: string;\n partition: string;\n}\n\n/** Asks STS who the credentials belong to. One call, no retry of its own. */\nexport async function getCallerIdentity(\n client: SendClient,\n env: Env | undefined,\n target: DeliveryTarget,\n): Promise<CallerIdentity> {\n const Command =\n env?.AWS?.GetCallerIdentityCommand ?? GetCallerIdentityCommand;\n let response: unknown;\n try {\n response = await client.send(new Command({}));\n } catch (error) {\n throw fromSdkError(error, target);\n }\n\n if (!isObject(response) || typeof response.Account !== 'string')\n throw localError(\n target,\n 'InvalidResponse',\n 'STS GetCallerIdentity returned no Account.',\n );\n\n // The caller ARN carries the partition (aws, aws-cn, aws-us-gov).\n const arn = typeof response.Arn === 'string' ? response.Arn : '';\n const partition = arn.split(':')[1] || 'aws';\n return { accountId: response.Account, partition };\n}\n","import type { PushFn, Mapping } from './types';\nimport type { Mapping as WalkerOSMapping, Collector } from '@walkeros/core';\nimport { PublishCommand } from '@aws-sdk/client-sns';\nimport { getMappingValue, isObject } from '@walkeros/core';\nimport { resolveSettings } from './config';\nimport type { DeliveryTarget } from '../lib/errors';\nimport { fromSdkError, localError } from '../lib/errors';\nimport { toPayload } from '../lib/payload';\n\ninterface MessageAttribute {\n DataType: string;\n StringValue?: string;\n BinaryValue?: Uint8Array;\n}\n\nfunction isMessageAttribute(value: unknown): value is MessageAttribute {\n return isObject(value) && typeof value.DataType === 'string';\n}\n\nasync function resolveMessageAttributes(\n event: Parameters<PushFn>[0],\n source: WalkerOSMapping.Map | undefined,\n collector: Collector.Instance,\n): Promise<Record<string, MessageAttribute> | undefined> {\n if (!source) return undefined;\n const result: Record<string, MessageAttribute> = {};\n let touched = false;\n for (const [key, value] of Object.entries(source)) {\n const resolved = await getMappingValue(event, value, { collector });\n if (isMessageAttribute(resolved)) {\n result[key] = resolved;\n touched = true;\n } else if (typeof resolved === 'string' && resolved.length > 0) {\n // Bare-string convenience: wrap into String DataType.\n result[key] = { DataType: 'String', StringValue: resolved };\n touched = true;\n } else if (resolved !== undefined && resolved !== null) {\n result[key] = { DataType: 'String', StringValue: String(resolved) };\n touched = true;\n }\n }\n return touched ? result : undefined;\n}\n\nfunction isMapping(value: unknown): value is Mapping {\n return typeof value === 'object' && value !== null;\n}\n\nasync function resolveStringMappingValue(\n event: Parameters<PushFn>[0],\n value: WalkerOSMapping.Value,\n collector: Collector.Instance,\n): Promise<string | undefined> {\n const resolved = await getMappingValue(event, value, { collector });\n if (typeof resolved === 'string' && resolved.length > 0) return resolved;\n if (resolved === undefined || resolved === null) return undefined;\n return String(resolved);\n}\n\n/**\n * Publishes one event. Resolves once SNS accepted the message, else throws an\n * `StepError`; the collector logs it, so SNS logs no error of its own.\n */\nexport const push: PushFn = async function (event, context) {\n // A silent rule has no message of its own: nothing to publish.\n if (!context.rule?.silent) await publish(event, context);\n};\n\nconst publish: PushFn = async function (event, context) {\n const { config, data, env, rule, collector, logger, id } = context;\n const settings = resolveSettings(config.settings);\n const { runtime } = settings;\n\n // Tracked, so destroy waits for a first-publish account lookup too.\n const topicArn = await runtime.inflight.track(runtime.topicArn());\n const target: DeliveryTarget = {\n service: 'SNS',\n resource: topicArn,\n region: settings.region,\n id,\n };\n\n const ruleSettings: Mapping = isMapping(rule?.settings) ? rule.settings : {};\n\n const messageGroupId =\n ruleSettings.messageGroupId !== undefined\n ? await resolveStringMappingValue(\n event,\n ruleSettings.messageGroupId,\n collector,\n )\n : undefined;\n\n const fifo = topicArn.endsWith('.fifo');\n const mappedDeduplicationId =\n ruleSettings.messageDeduplicationId !== undefined\n ? await resolveStringMappingValue(\n event,\n ruleSettings.messageDeduplicationId,\n collector,\n )\n : undefined;\n // The event id is the dedup key everywhere, so a FIFO topic gets it unless\n // a rule maps its own.\n const messageDeduplicationId =\n mappedDeduplicationId ?? (fifo && event.id ? event.id : undefined);\n\n const messageAttributes = await resolveMessageAttributes(\n event,\n ruleSettings.messageAttributes,\n collector,\n );\n\n let message: string;\n try {\n message = JSON.stringify(toPayload(event, data));\n } catch (error) {\n throw localError(\n target,\n 'InvalidRecord',\n 'The message does not serialize to JSON.',\n error,\n );\n }\n\n const input: {\n TopicArn: string;\n Message: string;\n MessageGroupId?: string;\n MessageDeduplicationId?: string;\n MessageAttributes?: Record<string, MessageAttribute>;\n } = { TopicArn: topicArn, Message: message };\n if (messageGroupId) input.MessageGroupId = messageGroupId;\n if (messageDeduplicationId)\n input.MessageDeduplicationId = messageDeduplicationId;\n if (messageAttributes) input.MessageAttributes = messageAttributes;\n\n const Command = env?.AWS?.PublishCommand ?? PublishCommand;\n const started = Date.now();\n let attempts: number | undefined;\n try {\n const response = await runtime.inflight.track(\n settings.client.send(new Command(input)),\n );\n attempts = readAttempts(response);\n } catch (error) {\n attempts = readAttempts(error);\n throw fromSdkError(error, target);\n } finally {\n logger.debug('SNS Publish', {\n topicArn,\n fifo,\n event: event.name,\n attempts,\n ms: Date.now() - started,\n });\n }\n};\n\nfunction readAttempts(value: unknown): number | undefined {\n if (typeof value !== 'object' || value === null || !('$metadata' in value))\n return undefined;\n const metadata = value.$metadata;\n if (typeof metadata !== 'object' || metadata === null) return undefined;\n return 'attempts' in metadata && typeof metadata.attempts === 'number'\n ? metadata.attempts\n : undefined;\n}\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type { Env, SendClient, Setup, SetupSubscription, Types } from './types';\nimport type { LifecycleContext } from '@walkeros/core';\nimport {\n CreateTopicCommand,\n GetTopicAttributesCommand,\n SubscribeCommand,\n} from '@aws-sdk/client-sns';\nimport { resolveSetup } from '@walkeros/core';\nimport {\n DEFAULT_REGION,\n firstString,\n isHandlerInstance,\n loadSdkRegion,\n parseTopicArn,\n} from '../lib/client';\nimport { parseCredentials } from '../lib/credentials';\nimport type { DeliveryTarget } from '../lib/errors';\nimport type { CallerIdentity, ClientOptions } from './lib/sns';\nimport { createSnsClient, createStsClient, getCallerIdentity } from './lib/sns';\n\n// Setup is wired to the destination's `setup` slot which uses the broader\n// `DestinationServer.Config<Types>` (settings is optional). We runtime-narrow\n// instead of using the local Config alias so the assignment in index.ts\n// type-checks without contravariance issues.\ntype WideConfig = DestinationServer.Config<Types>;\n\n// No region default here: an unset `setup.region` falls through to the\n// runtime region order, so setup and publish target the same region.\nexport const DEFAULT_SETUP: Required<Pick<Setup, 'fifoTopic'>> & Setup = {\n fifoTopic: false,\n};\n\nexport interface SetupResult {\n topicArn: string;\n /** True when the GetTopicAttributes probe returned 404 (topic absent before this run). */\n topicCreated: boolean;\n tagsApplied: number;\n subscriptionsCreated: number;\n}\n\n// Module-level caller-identity cache so re-running setup in the same process\n// is a no-op for the STS GetCallerIdentity call. Keyed by region (in case\n// future versions support cross-region setup in one process).\nconst identityCache: Map<string, CallerIdentity> = new Map();\n\n/** Test-only: clear the caller-identity cache between test cases. */\nexport function __resetAccountIdCache(): void {\n identityCache.clear();\n}\n\ninterface CreateTopicResponse {\n TopicArn?: string;\n}\n\ninterface AwsErrorMeta {\n name?: string;\n $metadata?: { httpStatusCode?: number };\n}\n\nfunction isAwsError(err: unknown): err is AwsErrorMeta {\n return typeof err === 'object' && err !== null;\n}\n\nfunction isNotFound(err: unknown): boolean {\n if (!isAwsError(err)) return false;\n if (err.name === 'NotFoundException' || err.name === 'NotFound') return true;\n if (err.$metadata && err.$metadata.httpStatusCode === 404) return true;\n return false;\n}\n\nfunction resolveTopicName(name: string, fifo: boolean): string {\n if (fifo) {\n if (name.endsWith('.fifo')) return name;\n return `${name}.fifo`;\n }\n if (name.endsWith('.fifo')) {\n throw new Error(\n `FIFO suffix '.fifo' on standard topic name '${name}'. Set setup.fifoTopic: true or rename the topic.`,\n );\n }\n return name;\n}\n\n/**\n * Account id and partition of the setup credentials, from STS once per region.\n * The partition (aws, aws-cn, aws-us-gov) comes from the caller ARN, so the\n * candidate topic ARN is right outside the standard partition too.\n */\nasync function getIdentity(\n env: Env | undefined,\n options: ClientOptions,\n target: DeliveryTarget,\n): Promise<CallerIdentity> {\n const cached = identityCache.get(options.region);\n if (cached) return cached;\n const stsClient = createStsClient(env, options);\n try {\n const identity = await getCallerIdentity(stsClient, env, target);\n identityCache.set(options.region, identity);\n return identity;\n } finally {\n stsClient.destroy?.();\n }\n}\n\nfunction isCreateTopicResponse(v: unknown): v is CreateTopicResponse {\n return typeof v === 'object' && v !== null;\n}\n\nfunction extractTopicArn(res: unknown): string {\n if (isCreateTopicResponse(res) && typeof res.TopicArn === 'string')\n return res.TopicArn;\n throw new Error('SNS CreateTopic returned no TopicArn');\n}\n\nfunction buildSubscribeAttributes(\n sub: SetupSubscription,\n): Record<string, string> | undefined {\n const attrs: Record<string, string> = {};\n if (sub.rawMessageDelivery !== undefined) {\n attrs.RawMessageDelivery = String(sub.rawMessageDelivery);\n }\n if (sub.filterPolicy !== undefined) {\n attrs.FilterPolicy = JSON.stringify(sub.filterPolicy);\n }\n if (sub.deadLetterTargetArn !== undefined) {\n attrs.RedrivePolicy = JSON.stringify({\n deadLetterTargetArn: sub.deadLetterTargetArn,\n });\n }\n return Object.keys(attrs).length > 0 ? attrs : undefined;\n}\n\nasync function applyDeclaredSubscriptions(\n client: SendClient,\n env: Env | undefined,\n topicArn: string,\n declared: SetupSubscription[],\n): Promise<number> {\n let count = 0;\n for (const sub of declared) {\n const input: {\n TopicArn: string;\n Protocol: string;\n Endpoint: string;\n Attributes?: Record<string, string>;\n } = {\n TopicArn: topicArn,\n Protocol: sub.protocol,\n Endpoint: sub.endpoint,\n };\n const attrs = buildSubscribeAttributes(sub);\n if (attrs) input.Attributes = attrs;\n const Command = env?.AWS?.SubscribeCommand ?? SubscribeCommand;\n await client.send(new Command(input));\n count += 1;\n }\n return count;\n}\n\nexport async function setup(\n ctx: LifecycleContext<WideConfig, Env | undefined>,\n): Promise<SetupResult | undefined> {\n const { config, env, logger } = ctx;\n const merged = resolveSetup(config.setup, DEFAULT_SETUP);\n if (!merged) {\n logger.debug('setup: skipped (config.setup is false or unset)');\n return;\n }\n\n const settings = config.settings ?? {};\n const arn = settings.topicArn ? parseTopicArn(settings.topicArn) : undefined;\n const topicName = settings.topicName ?? arn?.name;\n if (!topicName) {\n logger.throw(\n 'setup: settings.topicName (or settings.topicArn) is required. There is no safe default for the SNS topic name.',\n );\n return;\n }\n\n const sdkRegion =\n typeof settings.config?.region === 'string'\n ? settings.config.region\n : undefined;\n const region =\n firstString(\n merged.region,\n settings.region,\n arn?.region,\n sdkRegion,\n await loadSdkRegion(),\n ) ?? DEFAULT_REGION;\n const fifo = merged.fifoTopic ?? false;\n const finalName = resolveTopicName(topicName, fifo);\n if (fifo && finalName !== topicName) {\n logger.info(\n `setup: appended .fifo suffix to FIFO topic name '${topicName}' -> '${finalName}'`,\n );\n }\n\n // Topic-existence probe via candidate ARN derived from STS account ID.\n const options: ClientOptions = {\n region,\n config: settings.config,\n credentials: parseCredentials(config.credentials),\n timeout: config.timeout,\n };\n const client = settings.client ?? createSnsClient(env, options);\n // A client setup built is closed on every path, unless something in it is\n // the user's (their client, or their request handler instance).\n const closesClient =\n !settings.client && !isHandlerInstance(settings.config?.requestHandler);\n\n try {\n const { accountId, partition } = await getIdentity(env, options, {\n service: 'SNS',\n resource: finalName,\n region,\n id: ctx.id,\n });\n const candidateArn = `arn:${partition}:sns:${region}:${accountId}:${finalName}`;\n\n let topicCreated = false;\n try {\n const Command =\n env?.AWS?.GetTopicAttributesCommand ?? GetTopicAttributesCommand;\n await client.send(new Command({ TopicArn: candidateArn }));\n } catch (err) {\n if (isNotFound(err)) {\n topicCreated = true;\n } else {\n throw err;\n }\n }\n\n // Authoritative-apply: full declared state in one CreateTopic call.\n // SNS:CreateTopic is idempotent on identical Name+Attributes+Tags inputs.\n const attributes: Record<string, string> = {};\n if (fifo) {\n attributes.FifoTopic = 'true';\n attributes.ContentBasedDeduplication = 'true';\n }\n if (merged.displayName !== undefined)\n attributes.DisplayName = merged.displayName;\n if (merged.kmsMasterKeyId !== undefined)\n attributes.KmsMasterKeyId = merged.kmsMasterKeyId;\n\n const tagEntries = merged.tags\n ? Object.entries(merged.tags).map(([Key, Value]) => ({ Key, Value }))\n : undefined;\n\n const createInput: {\n Name: string;\n Attributes?: Record<string, string>;\n Tags?: Array<{ Key: string; Value: string }>;\n } = { Name: finalName };\n if (Object.keys(attributes).length > 0) createInput.Attributes = attributes;\n if (tagEntries && tagEntries.length > 0) createInput.Tags = tagEntries;\n\n const CreateCommand = env?.AWS?.CreateTopicCommand ?? CreateTopicCommand;\n const createRes = await client.send(new CreateCommand(createInput));\n const topicArn = extractTopicArn(createRes);\n\n if (topicCreated) {\n logger.info('setup: topic created', { topicArn, region });\n } else {\n logger.debug('setup: topic exists; declared state re-applied', {\n topicArn,\n region,\n });\n }\n\n const subscriptionsCreated = await applyDeclaredSubscriptions(\n client,\n env,\n topicArn,\n merged.subscriptions ?? [],\n );\n\n return {\n topicArn,\n topicCreated,\n tagsApplied: merged.tags ? Object.keys(merged.tags).length : 0,\n subscriptionsCreated,\n };\n } finally {\n if (closesClient) client.destroy?.();\n }\n}\n","import type { DestinationServer } from '@walkeros/server-core';\nimport type {\n Mapping as WalkerOSMapping,\n Destination as CoreDestination,\n SetupFn as CoreSetupFn,\n} from '@walkeros/core';\nimport type {\n SNSClientConfig,\n CreateTopicCommandInput,\n PublishCommandInput,\n GetTopicAttributesCommandInput,\n SubscribeCommandInput,\n} from '@aws-sdk/client-sns';\nimport type {\n STSClientConfig,\n GetCallerIdentityCommandInput,\n} from '@aws-sdk/client-sts';\nimport type { InFlight, SendClient } from '../../lib/client';\nimport type { CredentialsInput } from '../../lib/credentials';\n\nexport type { SendClient } from '../../lib/client';\n\n/** An AWS SDK v3 command class, as far as this destination builds one. */\nexport type CommandConstructor<Input> = new (input: Input) => {\n readonly input: Input;\n};\n\n/**\n * Settings as a user writes them. `topicArn` alone is enough: region and\n * topic name derive from it. `topicName` (with `region`) also works, and the\n * ARN is then completed with the account id at the first publish.\n */\nexport interface InitSettings {\n /** Topic ARN. Region and topic name derive from it. */\n topicArn?: string;\n /** Topic name (with `.fifo` for FIFO topics). Needed by `walkeros setup`. */\n topicName?: string;\n /** AWS region. Default: the ARN's region, `AWS_REGION` or the profile, else `eu-central-1`. */\n region?: string;\n /** Raw `SNSClient` options, passed through to the SDK. */\n config?: SNSClientConfig;\n /** A client of your own. Used as is and never closed by the destination. */\n client?: SendClient;\n /** Runtime-only, set by `init`: what the destination owns. Not user-facing. */\n runtime?: Runtime;\n}\n\n/** What `init` resolved. */\nexport interface Settings {\n topicArn?: string;\n topicName?: string;\n region: string;\n config?: SNSClientConfig;\n client: SendClient;\n runtime: Runtime;\n}\n\n/** Runtime-only state of one initialized instance. */\nexport interface Runtime {\n /**\n * True when the destination built the client from nothing the user owns,\n * and so closes it. A user client, or a user request handler instance in\n * `settings.config`, keeps it false.\n */\n ownsClient: boolean;\n /** Publishes in flight, awaited by `destroy`. */\n inflight: InFlight;\n /** The topic ARN, completed once with the account id when only a name was given. */\n topicArn: () => Promise<string>;\n}\n\nexport interface Mapping {\n /**\n * Per-event message attributes resolved via mapping. Each attribute value\n * resolves to the SDK's expected `{ DataType, StringValue }` shape; operators\n * write either a literal `{ DataType: 'String', StringValue: '$path' }` value\n * or use `{ value: { DataType: 'String', StringValue: '$user.id' } }` style\n * to drive the StringValue from event data.\n */\n messageAttributes?: WalkerOSMapping.Map;\n /**\n * FIFO group ID for FIFO topics. `Mapping.Value` so operators can write\n * `messageGroupId: 'user.id'` (path) instead of hard-coding a literal.\n */\n messageGroupId?: WalkerOSMapping.Value;\n /**\n * FIFO deduplication ID. `Mapping.Value`, same reasoning as\n * `messageGroupId`. Defaults to the event id on FIFO topics.\n */\n messageDeduplicationId?: WalkerOSMapping.Value;\n}\n\n/**\n * Optional SDK overrides. Each one present wins over the SDK the package\n * imports; tests and simulate inject mocks here.\n */\nexport interface Env extends DestinationServer.Env {\n AWS?: {\n SNSClient?: new (config: SNSClientConfig) => SendClient;\n CreateTopicCommand?: CommandConstructor<CreateTopicCommandInput>;\n PublishCommand?: CommandConstructor<PublishCommandInput>;\n GetTopicAttributesCommand?: CommandConstructor<GetTopicAttributesCommandInput>;\n SubscribeCommand?: CommandConstructor<SubscribeCommandInput>;\n STSClient?: new (config: STSClientConfig) => SendClient;\n GetCallerIdentityCommand?: CommandConstructor<GetCallerIdentityCommandInput>;\n };\n}\n\n/**\n * Provisioning options for `walkeros setup destination.<id>` when targeting SNS.\n * Triggered only by the explicit CLI command. Idempotent, never auto-run.\n *\n * `topicName` lives in Settings (one source of truth for setup AND runtime publish).\n */\nexport interface Setup {\n /** AWS region for the topic. Default: the runtime region order. */\n region?: string;\n /** Display name. Optional. */\n displayName?: string;\n /** FIFO topic with content-based deduplication. Default: false. */\n fifoTopic?: boolean;\n /** KMS key for at-rest encryption. Optional. */\n kmsMasterKeyId?: string;\n /** Tags for cost allocation. Optional. */\n tags?: Record<string, string>;\n /** Subscriptions to create or update on the topic. Each is opt-in. */\n subscriptions?: SetupSubscription[];\n}\n\nexport interface SetupSubscription {\n protocol: 'sqs' | 'lambda' | 'https' | 'http' | 'email' | 'sms';\n endpoint: string;\n rawMessageDelivery?: boolean;\n filterPolicy?: Record<string, unknown>;\n deadLetterTargetArn?: string;\n}\n\nexport type Types = CoreDestination.Types<\n Settings,\n Mapping,\n Env,\n InitSettings,\n Setup,\n CredentialsInput\n>;\n\nexport interface Destination extends DestinationServer.Destination<Types> {\n init: DestinationServer.InitFn<Types>;\n}\n\nexport type Config = {\n settings: InitSettings;\n} & DestinationServer.Config<Types>;\n\nexport type InitFn = DestinationServer.InitFn<Types>;\nexport type PushFn = DestinationServer.PushFn<Types>;\nexport type SetupFn = CoreSetupFn<Config, Env>;\n\nexport type PartialConfig = DestinationServer.PartialConfig<Types>;\n\nexport type PushEvents = DestinationServer.PushEvents<Mapping>;\n\nexport type Rule = WalkerOSMapping.Rule<Mapping>;\nexport type Rules = WalkerOSMapping.Rules<Rule>;\n","import type { Destination } from './types';\nimport { getConfig } from './config';\nimport { push } from './push';\nimport { setup } from './setup';\n\n// Types\nexport * as DestinationSNS from './types';\n\nexport const destinationSNS: Destination = {\n type: 'aws-sns',\n\n config: {},\n\n setup,\n\n async init({ config, env, logger, id }) {\n return getConfig(config, env, logger, id);\n },\n\n push,\n\n async destroy({ config, logger }) {\n const runtime = config.settings?.runtime;\n if (!runtime) return;\n\n // Let publishes on the wire land before closing the client under them.\n await runtime.inflight.settle();\n\n if (!runtime.ownsClient) return;\n runtime.ownsClient = false;\n config.settings?.client?.destroy?.();\n logger.debug('SNS client closed');\n },\n};\n\nexport default destinationSNS;\n"],"mappings":"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","names":["types_exports","import_core","import_core","clientConfig","import_client_firehose","import_core","import_core","import_core","getConfig","resolveSettings","import_client_sns","import_core","push","resolveSettings","import_client_sns","import_core","types_exports","getConfig","push"]}
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{FirehoseClient}from"@aws-sdk/client-firehose";import{isObject as isObject2}from"@walkeros/core";import{loadConfig,NODE_REGION_CONFIG_FILE_OPTIONS,NODE_REGION_CONFIG_OPTIONS}from"@smithy/core/config";function isHandlerInstance(handler){return"object"==typeof handler&&null!==handler&&"handle"in handler&&"function"==typeof handler.handle}function resolveTimeout(timeout){return void 0===timeout||!Number.isFinite(timeout)||timeout<=0?1e4:timeout}function requestHandlerOptions(timeout){return{requestTimeout:Math.floor(.25*resolveTimeout(timeout)),throwOnRequestTimeout:!0}}function parseTopicArn(arn){const parts=arn.split(":");if(6!==parts.length)return;const[prefix,partition,service,region,accountId,name]=parts;return"arn"===prefix&&partition&&"sns"===service&&region&&accountId&&name?{region:region,accountId:accountId,name:name}:void 0}async function loadSdkRegion(){try{const region=await loadConfig(NODE_REGION_CONFIG_OPTIONS,NODE_REGION_CONFIG_FILE_OPTIONS)();return"string"==typeof region&&region.length>0?region:void 0}catch{return}}function firstString(...candidates){return candidates.find(candidate=>"string"==typeof candidate&&candidate.length>0)}var InFlight=class{pending=new Set;track(promise){this.pending.add(promise);const remove=()=>{this.pending.delete(promise)};return promise.then(remove,remove),promise}get size(){return this.pending.size}async settle(){for(;this.pending.size>0;)await Promise.allSettled([...this.pending])}};import{isObject}from"@walkeros/core";import{isServerError,isThrottlingError,isTransientError}from"@smithy/core/retry";var AwsDeliveryError=class extends Error{code;status;retryable;constructor(message,options){super(message,{cause:options.cause}),this.name="AwsDeliveryError",this.code=options.code,this.status=options.status,this.retryable=options.retryable}};function formatMessage(target,code,text){const hint=function(code,target){switch(code){case"ResourceNotFoundException":return"Firehose"===target.service?"Check settings.streamName and settings.region.":"Check settings.topicArn and settings.region.";case"AccessDeniedException":case"AuthorizationErrorException":return"Firehose"===target.service?"Allow firehose:PutRecordBatch on the stream for these credentials.":"Allow sns:Publish on the topic for these credentials.";case"UnrecognizedClientException":case"InvalidSignatureException":case"ExpiredTokenException":case"CredentialsProviderError":return"Check config.credentials or the AWS credentials in the environment.";case"NotFoundException":return"SNS"===target.service?`Run "walkeros setup destination.${target.id??"<id>"}" or set settings.topicArn.`:void 0;default:return}}(code,target),base=`${function(target){const kind="Firehose"===target.service?"stream":"topic";return`${target.service} ${kind} "${target.resource}" (${target.region})`}(target)}: ${code}: ${text}`;return hint?`${base} ${hint}`:base}function fromSdkError(error,target){if(error instanceof AwsDeliveryError)return error;if(!function(error){return error instanceof Error}(error))return new AwsDeliveryError(formatMessage(target,"UnknownError",String(error)),{code:"UnknownError",retryable:!1,cause:error});const code=function(error){if("Error"!==error.name)return error.name;const code=Reflect.get(error,"code");return"string"==typeof code&&code.length>0?code:error.name}(error),retryable=isThrottlingError(error)||isTransientError(error)||isServerError(error);return new AwsDeliveryError(formatMessage(target,code,error.message),{code:code,status:error.$metadata?.httpStatusCode,retryable:retryable,cause:error})}function recordError(target,code,text,status){return new AwsDeliveryError(formatMessage(target,code,text),{code:code,status:status,retryable:!0})}function localError(target,code,text,cause){return new AwsDeliveryError(formatMessage(target,code,text),{code:code,retryable:"InvalidResponse"===code,cause:cause})}function configError(message){return new AwsDeliveryError(message,{code:"InvalidConfig",retryable:!1})}function nonEmpty(value){return"string"==typeof value&&value.length>0}function parseCredentials(input){if(null==input||""===input)return;let value=input;if("string"==typeof input)try{value=JSON.parse(input)}catch{throw configError('config.credentials is not valid JSON. Expected {"accessKeyId", "secretAccessKey", "sessionToken"?}.')}if(!isObject(value))throw configError("config.credentials must be an object with accessKeyId and secretAccessKey.");const{accessKeyId:accessKeyId,secretAccessKey:secretAccessKey,sessionToken:sessionToken}=value;for(const[field,fieldValue]of[["accessKeyId",accessKeyId],["secretAccessKey",secretAccessKey]])if(!nonEmpty(fieldValue))throw configError(`config.credentials.${field} is missing or empty. Set it from a $secret.`);if(void 0!==sessionToken&&!nonEmpty(sessionToken))throw configError("config.credentials.sessionToken must be a non-empty string when set.");const credentials={accessKeyId:String(accessKeyId),secretAccessKey:String(secretAccessKey)};return void 0!==sessionToken&&(credentials.sessionToken=sessionToken),credentials}var STREAM_NAME_PATTERN=/^[a-zA-Z0-9_.-]{1,64}$/;function sameValue(a,b){if(a===b)return!0;if(Array.isArray(a)&&Array.isArray(b))return a.length===b.length&&a.every((v,i)=>sameValue(v,b[i]));if(!isObject2(a)||!isObject2(b))return!1;if(!isPlain(a)||!isPlain(b))return!1;const keys=Object.keys(a);return keys.length===Object.keys(b).length&&keys.every(key=>key in b&&sameValue(a[key],b[key]))}function isPlain(value){const proto=Object.getPrototypeOf(value);return proto===Object.prototype||null===proto}function pick(key,flat,alias){if(void 0!==flat&&void 0!==alias&&!sameValue(flat,alias))throw configError(`Firehose: settings.${key} and settings.firehose.${key} are both set and differ. Remove settings.firehose.`);return flat??alias}async function getConfig(partialConfig={},env,logger){const input=partialConfig.settings??{},alias=input.firehose??{},streamName=pick("streamName",input.streamName,alias.streamName),sdkConfig=pick("config",input.config,alias.config),userClient=pick("client",input.client,alias.client),regionSetting=pick("region",input.region,alias.region);if(!streamName)throw configError("Firehose: settings.streamName is missing. Set it to the name of your Firehose stream.");if(!STREAM_NAME_PATTERN.test(streamName))throw configError(`Firehose: settings.streamName "${streamName}" is not a valid stream name. Use 1 to 64 letters, digits, "_", "." or "-".`);const region=firstString(regionSetting,"string"==typeof sdkConfig?.region?sdkConfig.region:void 0,await loadSdkRegion())??"eu-central-1",credentials=parseCredentials(partialConfig.credentials),credentialSource=userClient?"client":credentials?"config.credentials":void 0!==sdkConfig?.credentials?"settings.config.credentials":"default chain",client=userClient??function(Client,options){const clientConfig2={...options.config,region:options.region};return options.credentials&&(clientConfig2.credentials=options.credentials),void 0===clientConfig2.requestHandler&&(clientConfig2.requestHandler=requestHandlerOptions(options.timeout)),new Client(clientConfig2)}(env?.AWS?.FirehoseClient??FirehoseClient,{region:region,config:sdkConfig,credentials:credentials,timeout:partialConfig.timeout}),settings={streamName:streamName,region:region,newline:!1!==input.newline,client:client,runtime:{ownsClient:!userClient&&!isHandlerInstance(sdkConfig?.requestHandler),inflight:new InFlight,credentialSource:credentialSource}};return sdkConfig&&(settings.config=sdkConfig),logger.debug("Firehose client ready",{stream:streamName,region:region,credentials:credentialSource}),{...partialConfig,settings:settings}}function resolveSettings(settings){const{streamName:streamName,region:region,client:client,runtime:runtime}=settings??{};if(!(streamName&&region&&client&&runtime))throw configError("Firehose: the destination was not initialized.");return{streamName:streamName,region:region,newline:!1!==settings?.newline,client:client,runtime:runtime}}import{PutRecordBatchCommand}from"@aws-sdk/client-firehose";import{isObject as isObject3}from"@walkeros/core";function toPayload(event,data){return isObject3(data)?data:event}function planChunks(payloads,newline){const chunks=[],refused=[];let current=[],currentBytes=0;return payloads.forEach((payload,index)=>{let data;try{data=function(payload,newline=!0){const json=JSON.stringify(payload);if("string"!=typeof json)throw new TypeError("The payload does not serialize to JSON.");return Buffer.from(newline?`${json}\n`:json)}(payload,newline)}catch(error){return void refused.push({index:index,code:"InvalidRecord",detail:`Record ${index} does not serialize to JSON: ${error instanceof Error?error.message:String(error)}`,cause:error})}data.byteLength>1e6?refused.push({index:index,code:"RecordTooLarge",detail:`Record ${index} is ${data.byteLength} bytes, over the 1000000 byte limit.`}):((current.length>=500||currentBytes+data.byteLength>29e5)&&(chunks.push(current),current=[],currentBytes=0),current.push({index:index,data:data}),currentBytes+=data.byteLength)}),current.length>0&&chunks.push(current),{chunks:chunks,refused:refused}}function isRecord(value){return"object"==typeof value&&null!==value}function readMetadata(value){if(!isRecord(value))return{};const metadata=value.$metadata;if(!isRecord(metadata))return{};const{httpStatusCode:httpStatusCode,attempts:attempts}=metadata;return{status:"number"==typeof httpStatusCode?httpStatusCode:void 0,attempts:"number"==typeof attempts?attempts:void 0}}async function deliver(items,settings,env,logger){const target={service:"Firehose",resource:settings.streamName,region:settings.region},Command=env?.AWS?.PutRecordBatchCommand??PutRecordBatchCommand,plan=planChunks(items.map(({event:event,data:data})=>toPayload(event,data)),settings.newline),failures=plan.refused.map(({index:index,code:code,detail:detail,cause:cause})=>({index:index,error:localError(target,code,detail,cause)})),results=await Promise.all(plan.chunks.map(async chunk=>{const started=Date.now(),bytes=chunk.reduce((sum,record)=>sum+record.data.byteLength,0),command=new Command({DeliveryStreamName:settings.streamName,Records:chunk.map(({data:data})=>({Data:data}))});let result,attempts;try{const response=function(value){const response=readMetadata(value);return isRecord(value)?("number"==typeof value.FailedPutCount&&(response.failedPutCount=value.FailedPutCount),Array.isArray(value.RequestResponses)&&(response.responses=value.RequestResponses.map(entry=>{if(!isRecord(entry))return{};const{ErrorCode:ErrorCode,ErrorMessage:ErrorMessage}=entry;return{errorCode:"string"==typeof ErrorCode&&ErrorCode.length>0?ErrorCode:void 0,errorMessage:"string"==typeof ErrorMessage?ErrorMessage:void 0}})),response):response}(await settings.runtime.inflight.track(settings.client.send(command)));attempts=response.attempts,result=function(chunk,response,target){const{responses:responses,failedPutCount:failedPutCount,status:status}=response;if(responses&&responses.length===chunk.length){const failures=[];if(responses.forEach((entry,local)=>{entry.errorCode&&failures.push({index:chunk[local].index,error:recordError(target,entry.errorCode,entry.errorMessage??"Record rejected.",status)})}),void 0===failedPutCount||failures.length===failedPutCount)return failures}else if(0===failedPutCount)return[];const error=localError(target,"InvalidResponse",`PutRecordBatch answered without per-record results that match its FailedPutCount for ${chunk.length} records (FailedPutCount ${failedPutCount??"missing"}).`);return chunk.map(({index:index})=>({index:index,error:error}))}(chunk,response,target)}catch(thrown){attempts=readMetadata(thrown).attempts;const error=fromSdkError(thrown,target);result=chunk.map(({index:index})=>({index:index,error:error}))}return logger.debug("Firehose PutRecordBatch",{stream:settings.streamName,records:chunk.length,bytes:bytes,failed:result.length,attempts:attempts,ms:Date.now()-started}),result}));for(const result of results)failures.push(...result);return failures.sort((a,b)=>a.index-b.index)}function toOutcome(total,failures){if(0!==failures.length){if(failures.length>=total)throw function(failures){const first=failures[0].error;if(failures.every(({error:error})=>error===first))return first;const byCode=new Map;for(const{error:error}of failures){const entry=byCode.get(error.code);entry?entry.count+=1:byCode.set(error.code,{count:1,sample:error})}let dominant={count:0,sample:first};for(const entry of byCode.values())entry.count>dominant.count&&(dominant=entry);const counts=[...byCode.entries()].map(([code,{count:count}])=>`${count} ${code}`).join(", ");return new AwsDeliveryError(`${dominant.sample.message} (all ${failures.length} records failed: ${counts})`,{code:dominant.sample.code,status:dominant.sample.status,retryable:failures.every(({error:error})=>error.retryable),cause:dominant.sample})}(failures);return{failed:failures.map(({index:index,error:error})=>({index:index,error:error}))}}}var types_exports={},destinationFirehose={type:"aws-firehose",config:{batch:{size:500,age:1e3}},init:async({config:config,env:env,logger:logger})=>getConfig(config,env,logger),push:async function(event,context){const{config:config,data:data,env:env,logger:logger}=context,settings=resolveSettings(config.settings),[failure]=await deliver([{event:event,data:data}],settings,env,logger);if(failure)throw failure.error},pushBatch:async(batch,context)=>{const{config:config,env:env,logger:logger}=context;if(0===batch.entries.length)return;const settings=resolveSettings(config.settings),failures=await deliver(batch.entries.map(({event:event,data:data})=>({event:event,data:data})),settings,env,logger);return toOutcome(batch.entries.length,failures)},async destroy({config:config,logger:logger}){const runtime=config.settings?.runtime;runtime&&(await runtime.inflight.settle(),runtime.ownsClient&&(runtime.ownsClient=!1,config.settings?.client?.destroy?.(),logger.debug("Firehose client closed")))}};import{SNSClient}from"@aws-sdk/client-sns";import{GetCallerIdentityCommand,STSClient}from"@aws-sdk/client-sts";import{isObject as isObject4}from"@walkeros/core";function createSnsClient(env,options){return new(env?.AWS?.SNSClient??SNSClient)(function(options){const resolved={...options.config,region:options.region};return options.credentials&&(resolved.credentials=options.credentials),void 0===resolved.requestHandler&&(resolved.requestHandler=requestHandlerOptions(options.timeout)),resolved}(options))}function createStsClient(env,options){const Client=env?.AWS?.STSClient??STSClient,config={region:options.region},credentials=options.credentials??options.config?.credentials;credentials&&(config.credentials=credentials);const userHandler=options.config?.requestHandler;return config.requestHandler=void 0===userHandler||isHandlerInstance(userHandler)?requestHandlerOptions(options.timeout):userHandler,config.maxAttempts=1,new Client(config)}async function getCallerIdentity(client,env,target){const Command=env?.AWS?.GetCallerIdentityCommand??GetCallerIdentityCommand;let response;try{response=await client.send(new Command({}))}catch(error){throw fromSdkError(error,target)}if(!isObject4(response)||"string"!=typeof response.Account)throw fromSdkError(new Error("STS GetCallerIdentity returned no Account."),target);const partition=("string"==typeof response.Arn?response.Arn:"").split(":")[1]||"aws";return{accountId:response.Account,partition:partition}}function topicArnResolver(topicArn,topicName,target,stsClient,env){if(topicArn)return async()=>topicArn;let pending;return()=>(pending??=(async()=>{const client=stsClient();try{const{accountId:accountId,partition:partition}=await getCallerIdentity(client,env,target);return`arn:${partition}:sns:${target.region}:${accountId}:${topicName}`}finally{client.destroy?.()}})().catch(error=>{throw pending=void 0,error}),pending)}import{PublishCommand}from"@aws-sdk/client-sns";import{getMappingValue,isObject as isObject5}from"@walkeros/core";function isMessageAttribute(value){return isObject5(value)&&"string"==typeof value.DataType}async function resolveStringMappingValue(event,value,collector){const resolved=await getMappingValue(event,value,{collector:collector});return"string"==typeof resolved&&resolved.length>0?resolved:null!=resolved?String(resolved):void 0}function readAttempts(value){if("object"!=typeof value||null===value||!("$metadata"in value))return;const metadata=value.$metadata;return"object"==typeof metadata&&null!==metadata&&"attempts"in metadata&&"number"==typeof metadata.attempts?metadata.attempts:void 0}import{CreateTopicCommand,GetTopicAttributesCommand,SubscribeCommand}from"@aws-sdk/client-sns";import{resolveSetup}from"@walkeros/core";var DEFAULT_SETUP={fifoTopic:!1},identityCache=new Map;function buildSubscribeAttributes(sub){const attrs={};return void 0!==sub.rawMessageDelivery&&(attrs.RawMessageDelivery=String(sub.rawMessageDelivery)),void 0!==sub.filterPolicy&&(attrs.FilterPolicy=JSON.stringify(sub.filterPolicy)),void 0!==sub.deadLetterTargetArn&&(attrs.RedrivePolicy=JSON.stringify({deadLetterTargetArn:sub.deadLetterTargetArn})),Object.keys(attrs).length>0?attrs:void 0}var types_exports2={},destinationSNS={type:"aws-sns",config:{},setup:async function(ctx){const{config:config,env:env,logger:logger}=ctx,merged=resolveSetup(config.setup,DEFAULT_SETUP);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const settings=config.settings??{},arn=settings.topicArn?parseTopicArn(settings.topicArn):void 0,topicName=settings.topicName??arn?.name;if(!topicName)return void logger.throw("setup: settings.topicName (or settings.topicArn) is required. There is no safe default for the SNS topic name.");const sdkRegion="string"==typeof settings.config?.region?settings.config.region:void 0,region=firstString(merged.region,settings.region,arn?.region,sdkRegion,await loadSdkRegion())??"eu-central-1",fifo=merged.fifoTopic??!1,finalName=function(name,fifo){if(fifo)return name.endsWith(".fifo")?name:`${name}.fifo`;if(name.endsWith(".fifo"))throw new Error(`FIFO suffix '.fifo' on standard topic name '${name}'. Set setup.fifoTopic: true or rename the topic.`);return name}(topicName,fifo);fifo&&finalName!==topicName&&logger.info(`setup: appended .fifo suffix to FIFO topic name '${topicName}' -> '${finalName}'`);const options={region:region,config:settings.config,credentials:parseCredentials(config.credentials),timeout:config.timeout},client=settings.client??createSnsClient(env,options),closesClient=!settings.client&&!isHandlerInstance(settings.config?.requestHandler);try{const{accountId:accountId,partition:partition}=await async function(env,options,target){const cached=identityCache.get(options.region);if(cached)return cached;const stsClient=createStsClient(env,options);try{const identity=await getCallerIdentity(stsClient,env,target);return identityCache.set(options.region,identity),identity}finally{stsClient.destroy?.()}}(env,options,{service:"SNS",resource:finalName,region:region,id:ctx.id}),candidateArn=`arn:${partition}:sns:${region}:${accountId}:${finalName}`;let topicCreated=!1;try{const Command=env?.AWS?.GetTopicAttributesCommand??GetTopicAttributesCommand;await client.send(new Command({TopicArn:candidateArn}))}catch(err){if(!function(err){return!(!function(err){return"object"==typeof err&&null!==err}(err)||"NotFoundException"!==err.name&&"NotFound"!==err.name&&(!err.$metadata||404!==err.$metadata.httpStatusCode))}(err))throw err;topicCreated=!0}const attributes={};fifo&&(attributes.FifoTopic="true",attributes.ContentBasedDeduplication="true"),void 0!==merged.displayName&&(attributes.DisplayName=merged.displayName),void 0!==merged.kmsMasterKeyId&&(attributes.KmsMasterKeyId=merged.kmsMasterKeyId);const tagEntries=merged.tags?Object.entries(merged.tags).map(([Key,Value])=>({Key:Key,Value:Value})):void 0,createInput={Name:finalName};Object.keys(attributes).length>0&&(createInput.Attributes=attributes),tagEntries&&tagEntries.length>0&&(createInput.Tags=tagEntries);const CreateCommand=env?.AWS?.CreateTopicCommand??CreateTopicCommand,topicArn=function(res){if("object"==typeof(v=res)&&null!==v&&"string"==typeof res.TopicArn)return res.TopicArn;var v;throw new Error("SNS CreateTopic returned no TopicArn")}(await client.send(new CreateCommand(createInput)));topicCreated?logger.info("setup: topic created",{topicArn:topicArn,region:region}):logger.debug("setup: topic exists; declared state re-applied",{topicArn:topicArn,region:region});const subscriptionsCreated=await async function(client,env,topicArn,declared){let count=0;for(const sub of declared){const input={TopicArn:topicArn,Protocol:sub.protocol,Endpoint:sub.endpoint},attrs=buildSubscribeAttributes(sub);attrs&&(input.Attributes=attrs);const Command=env?.AWS?.SubscribeCommand??SubscribeCommand;await client.send(new Command(input)),count+=1}return count}(client,env,topicArn,merged.subscriptions??[]);return{topicArn:topicArn,topicCreated:topicCreated,tagsApplied:merged.tags?Object.keys(merged.tags).length:0,subscriptionsCreated:subscriptionsCreated}}finally{closesClient&&client.destroy?.()}},init:async({config:config,env:env,logger:logger,id:id})=>async function(partialConfig={},env,logger,id){const input=partialConfig.settings??{},{topicArn:topicArn,config:sdkConfig,client:userClient}=input,arn=topicArn?parseTopicArn(topicArn):void 0;if(topicArn&&!arn)throw configError(`SNS: settings.topicArn "${topicArn}" is not an SNS topic ARN (arn:aws:sns:<region>:<account>:<name>).`);if(!arn&&!input.topicName)throw configError("SNS: settings.topicArn is missing. Set it to the ARN of your topic (or set settings.topicName).");if(arn&&input.region&&input.region!==arn.region)throw configError(`SNS: settings.region "${input.region}" differs from the region "${arn.region}" in settings.topicArn. Remove settings.region.`);if(arn&&input.topicName&&input.topicName!==arn.name)throw configError(`SNS: settings.topicName "${input.topicName}" differs from the name "${arn.name}" in settings.topicArn. Remove settings.topicName.`);const sdkRegion="string"==typeof sdkConfig?.region?sdkConfig.region:void 0,region=firstString(input.region,arn?.region,sdkRegion,await loadSdkRegion())??"eu-central-1",topicName=input.topicName??arn?.name;void 0!==partialConfig.batch&&logger.warn("SNS publishes one message per event and does not batch. Remove config.batch from this destination.");const credentials=parseCredentials(partialConfig.credentials),clientOptions={region:region,config:sdkConfig,credentials:credentials,timeout:partialConfig.timeout},settings={region:region,client:userClient??createSnsClient(env,clientOptions),runtime:{ownsClient:!userClient&&!isHandlerInstance(sdkConfig?.requestHandler),inflight:new InFlight,topicArn:topicArnResolver(topicArn,topicName,{service:"SNS",resource:topicName??"",region:region,id:id},()=>createStsClient(env,clientOptions),env)}};return topicArn&&(settings.topicArn=topicArn),topicName&&(settings.topicName=topicName),sdkConfig&&(settings.config=sdkConfig),logger.debug("SNS client ready",{topic:topicArn??topicName,region:region,credentials:userClient?"client":credentials?"config.credentials":void 0!==sdkConfig?.credentials?"settings.config.credentials":"default chain"}),{...partialConfig,settings:settings}}(config,env,logger,id),push:async function(event,context){const{config:config,data:data,env:env,rule:rule,collector:collector,logger:logger,id:id}=context,settings=function(settings){const{region:region,client:client,runtime:runtime}=settings??{};if(!region||!client||!runtime)throw configError("SNS: the destination was not initialized.");return{topicArn:settings?.topicArn,topicName:settings?.topicName,region:region,client:client,runtime:runtime}}(config.settings),{runtime:runtime}=settings,topicArn=await runtime.inflight.track(runtime.topicArn()),target={service:"SNS",resource:topicArn,region:settings.region,id:id},ruleSettings=(value=rule?.settings,"object"==typeof value&&null!==value?rule.settings:{});var value;const messageGroupId=void 0!==ruleSettings.messageGroupId?await resolveStringMappingValue(event,ruleSettings.messageGroupId,collector):void 0,fifo=topicArn.endsWith(".fifo"),messageDeduplicationId=(void 0!==ruleSettings.messageDeduplicationId?await resolveStringMappingValue(event,ruleSettings.messageDeduplicationId,collector):void 0)??(fifo&&event.id?event.id:void 0),messageAttributes=await async function(event,source,collector){if(!source)return;const result={};let touched=!1;for(const[key,value]of Object.entries(source)){const resolved=await getMappingValue(event,value,{collector:collector});isMessageAttribute(resolved)?(result[key]=resolved,touched=!0):"string"==typeof resolved&&resolved.length>0?(result[key]={DataType:"String",StringValue:resolved},touched=!0):null!=resolved&&(result[key]={DataType:"String",StringValue:String(resolved)},touched=!0)}return touched?result:void 0}(event,ruleSettings.messageAttributes,collector);let message;try{message=JSON.stringify(toPayload(event,data))}catch(error){throw localError(target,"InvalidRecord",`The message does not serialize to JSON: ${error instanceof Error?error.message:String(error)}`,error)}const input={TopicArn:topicArn,Message:message};messageGroupId&&(input.MessageGroupId=messageGroupId),messageDeduplicationId&&(input.MessageDeduplicationId=messageDeduplicationId),messageAttributes&&(input.MessageAttributes=messageAttributes);const Command=env?.AWS?.PublishCommand??PublishCommand,started=Date.now();let attempts;try{attempts=readAttempts(await runtime.inflight.track(settings.client.send(new Command(input))))}catch(error){throw attempts=readAttempts(error),fromSdkError(error,target)}finally{logger.debug("SNS Publish",{topicArn:topicArn,fifo:fifo,event:event.name,attempts:attempts,ms:Date.now()-started})}},async destroy({config:config,logger:logger}){const runtime=config.settings?.runtime;runtime&&(await runtime.inflight.settle(),runtime.ownsClient&&(runtime.ownsClient=!1,config.settings?.client?.destroy?.(),logger.debug("SNS client closed")))}};export{types_exports as DestinationFirehose,types_exports2 as DestinationSNS,destinationFirehose as default,destinationFirehose,destinationSNS};//# sourceMappingURL=index.mjs.map
1
+ import{createRequire}from"module";createRequire(import.meta.url);import{FirehoseClient}from"@aws-sdk/client-firehose";import{StepError as StepError2,isObject as isObject2}from"@walkeros/core";import{loadConfig,NODE_REGION_CONFIG_FILE_OPTIONS,NODE_REGION_CONFIG_OPTIONS}from"@smithy/core/config";function isHandlerInstance(handler){return"object"==typeof handler&&null!==handler&&"handle"in handler&&"function"==typeof handler.handle}function resolveTimeout(timeout){return void 0===timeout||!Number.isFinite(timeout)||timeout<=0?1e4:timeout}function requestHandlerOptions(timeout){return{requestTimeout:Math.floor(.25*resolveTimeout(timeout)),throwOnRequestTimeout:!0}}function parseTopicArn(arn){const parts=arn.split(":");if(6!==parts.length)return;const[prefix,partition,service,region,accountId,name]=parts;return"arn"===prefix&&partition&&"sns"===service&&region&&accountId&&name?{region:region,accountId:accountId,name:name}:void 0}async function loadSdkRegion(){try{const region=await loadConfig(NODE_REGION_CONFIG_OPTIONS,NODE_REGION_CONFIG_FILE_OPTIONS)();return"string"==typeof region&&region.length>0?region:void 0}catch{return}}function firstString(...candidates){return candidates.find(candidate=>"string"==typeof candidate&&candidate.length>0)}var InFlight=class{pending=new Set;track(promise){this.pending.add(promise);const remove=()=>{this.pending.delete(promise)};return promise.then(remove,remove),promise}get size(){return this.pending.size}async settle(){for(;this.pending.size>0;)await Promise.allSettled([...this.pending])}};import{isObject}from"@walkeros/core";import{isServerError,isThrottlingError,isTransientError}from"@smithy/core/retry";import{StepError}from"@walkeros/core";function describe(target){const kind="Firehose"===target.service?"stream":"topic";return`${target.service} ${kind} "${target.resource}" (${target.region})`}function formatMessage(target,code,detail){const hint=function(code,target){switch(code){case"ResourceNotFoundException":return"Firehose"===target.service?"Check settings.streamName and settings.region.":"Check settings.topicArn and settings.region.";case"AccessDeniedException":case"AuthorizationErrorException":return"Firehose"===target.service?"Allow firehose:PutRecordBatch on the stream for these credentials.":"Allow sns:Publish on the topic for these credentials.";case"UnrecognizedClientException":case"InvalidSignatureException":case"ExpiredTokenException":case"CredentialsProviderError":return"Check config.credentials or the AWS credentials in the environment.";case"NotFoundException":return"SNS"===target.service?`Run "walkeros setup destination.${target.id??"<id>"}" or set settings.topicArn.`:void 0;default:return}}(code,target),base=detail?`${describe(target)}: ${code}: ${detail}`:`${describe(target)}: ${code}`;return hint?`${base} ${hint}`:base}var PROVIDER_ERRORS=["ProviderError","CredentialsProviderError","TokenProviderError"];function fromSdkError(error,target){if(error instanceof StepError)return error;if(!function(error){return error instanceof Error}(error))return new StepError(formatMessage(target,"UnknownError"),{code:"UnknownError",retryable:!1,cause:error});const code=function(error){if("Error"!==error.name)return error.name;const code=Reflect.get(error,"code");return"string"==typeof code&&code.length>0?code:error.name}(error),status=error.$metadata?.httpStatusCode,retryable=isThrottlingError(error)||isTransientError(error)||isServerError(error)||void 0===status&&!PROVIDER_ERRORS.includes(code);return new StepError(formatMessage(target,code),{code:code,status:status,retryable:retryable,cause:error})}function recordError(target,code,status,body){return new StepError(formatMessage(target,code),{code:code,status:status,retryable:!0,body:body})}function localError(target,code,detail,cause){return new StepError(formatMessage(target,code,detail),{code:code,retryable:"InvalidResponse"===code,cause:cause})}function configError(message){return new StepError(message,{code:"config"})}function nonEmpty(value){return"string"==typeof value&&value.length>0}function parseCredentials(input){if(null==input||""===input)return;let value=input;if("string"==typeof input)try{value=JSON.parse(input)}catch{throw configError('config.credentials is not valid JSON. Expected {"accessKeyId", "secretAccessKey", "sessionToken"?}.')}if(!isObject(value))throw configError("config.credentials must be an object with accessKeyId and secretAccessKey.");const{accessKeyId:accessKeyId,secretAccessKey:secretAccessKey,sessionToken:sessionToken}=value;for(const[field,fieldValue]of[["accessKeyId",accessKeyId],["secretAccessKey",secretAccessKey]])if(!nonEmpty(fieldValue))throw configError(`config.credentials.${field} is missing or empty. Set it from a $secret.`);if(void 0!==sessionToken&&!nonEmpty(sessionToken))throw configError("config.credentials.sessionToken must be a non-empty string when set.");const credentials={accessKeyId:String(accessKeyId),secretAccessKey:String(secretAccessKey)};return void 0!==sessionToken&&(credentials.sessionToken=sessionToken),credentials}var STREAM_NAME_PATTERN=/^[a-zA-Z0-9_.-]{1,64}$/;function sameValue(a,b){if(a===b)return!0;if(Array.isArray(a)&&Array.isArray(b))return a.length===b.length&&a.every((v,i)=>sameValue(v,b[i]));if(!isObject2(a)||!isObject2(b))return!1;if(!isPlain(a)||!isPlain(b))return!1;const keys=Object.keys(a);return keys.length===Object.keys(b).length&&keys.every(key=>key in b&&sameValue(a[key],b[key]))}function isPlain(value){const proto=Object.getPrototypeOf(value);return proto===Object.prototype||null===proto}function pick(key,flat,alias){if(void 0!==flat&&void 0!==alias&&!sameValue(flat,alias))throw configError(`Firehose: settings.${key} and settings.firehose.${key} are both set and differ. Remove settings.firehose.`);return flat??alias}async function getConfig(partialConfig={},env,logger){const input=partialConfig.settings??{},alias=input.firehose??{},streamName=pick("streamName",input.streamName,alias.streamName),sdkConfig=pick("config",input.config,alias.config),userClient=pick("client",input.client,alias.client),regionSetting=pick("region",input.region,alias.region);if(!streamName)throw configError("Firehose: settings.streamName is missing. Set it to the name of your Firehose stream (settings.firehose.streamName is the deprecated form).");if(!STREAM_NAME_PATTERN.test(streamName))throw configError(`Firehose: settings.streamName "${streamName}" is not a valid stream name. Use 1 to 64 letters, digits, "_", "." or "-".`);const region=firstString(regionSetting,"string"==typeof sdkConfig?.region?sdkConfig.region:void 0,await loadSdkRegion())??"eu-central-1",credentials=parseCredentials(partialConfig.credentials),credentialSource=userClient?"client":credentials?"config.credentials":void 0!==sdkConfig?.credentials?"settings.config.credentials":"default chain",client=userClient??function(Client,options){const clientConfig2={...options.config,region:options.region};return options.credentials&&(clientConfig2.credentials=options.credentials),void 0===clientConfig2.requestHandler&&(clientConfig2.requestHandler=requestHandlerOptions(options.timeout)),new Client(clientConfig2)}(env?.AWS?.FirehoseClient??FirehoseClient,{region:region,config:sdkConfig,credentials:credentials,timeout:partialConfig.timeout}),settings={streamName:streamName,region:region,newline:!1!==input.newline,client:client,runtime:{ownsClient:!userClient&&!isHandlerInstance(sdkConfig?.requestHandler),inflight:new InFlight,credentialSource:credentialSource}};return sdkConfig&&(settings.config=sdkConfig),logger.debug("Firehose client ready",{stream:streamName,region:region,credentials:credentialSource}),{...partialConfig,settings:settings}}function resolveSettings(settings){const{streamName:streamName,region:region,client:client,runtime:runtime}=settings??{};if(!(streamName&&region&&client&&runtime))throw new StepError2("Firehose: the destination was not initialized.",{code:"not-ready"});return{streamName:streamName,region:region,newline:!1!==settings?.newline,client:client,runtime:runtime}}import{PutRecordBatchCommand}from"@aws-sdk/client-firehose";import{isObject as isObject3}from"@walkeros/core";function toPayload(event,data){return isObject3(data)?data:event}function planChunks(payloads,newline){const chunks=[],refused=[];let current=[],currentBytes=0;return payloads.forEach((payload,index)=>{let data;try{data=function(payload,newline=!0){const json=JSON.stringify(payload);if("string"!=typeof json)throw new TypeError("The payload does not serialize to JSON.");return Buffer.from(newline?`${json}\n`:json)}(payload,newline)}catch(error){return void refused.push({index:index,code:"InvalidRecord",detail:`Record ${index} does not serialize to JSON.`,cause:error})}data.byteLength>1e6?refused.push({index:index,code:"RecordTooLarge",detail:`Record ${index} is ${data.byteLength} bytes, over the 1000000 byte limit.`}):((current.length>=500||currentBytes+data.byteLength>29e5)&&(chunks.push(current),current=[],currentBytes=0),current.push({index:index,data:data}),currentBytes+=data.byteLength)}),current.length>0&&chunks.push(current),{chunks:chunks,refused:refused}}function isRecord(value){return"object"==typeof value&&null!==value}function readMetadata(value){if(!isRecord(value))return{};const metadata=value.$metadata;if(!isRecord(metadata))return{};const{httpStatusCode:httpStatusCode,attempts:attempts}=metadata;return{status:"number"==typeof httpStatusCode?httpStatusCode:void 0,attempts:"number"==typeof attempts?attempts:void 0}}async function deliver(items,settings,env,logger){const target={service:"Firehose",resource:settings.streamName,region:settings.region},Command=env?.AWS?.PutRecordBatchCommand??PutRecordBatchCommand,plan=planChunks(items.map(({event:event,data:data})=>toPayload(event,data)),settings.newline),failures=plan.refused.map(({index:index,code:code,detail:detail,cause:cause})=>({index:index,error:localError(target,code,detail,cause)})),results=await Promise.all(plan.chunks.map(async chunk=>{const started=Date.now(),bytes=chunk.reduce((sum,record)=>sum+record.data.byteLength,0),command=new Command({DeliveryStreamName:settings.streamName,Records:chunk.map(({data:data})=>({Data:data}))});let result,attempts;try{const response=function(value){const response=readMetadata(value);return isRecord(value)?("number"==typeof value.FailedPutCount&&(response.failedPutCount=value.FailedPutCount),Array.isArray(value.RequestResponses)&&(response.responses=value.RequestResponses.map(entry=>{if(!isRecord(entry))return{};const{ErrorCode:ErrorCode,ErrorMessage:ErrorMessage}=entry;return{errorCode:"string"==typeof ErrorCode&&ErrorCode.length>0?ErrorCode:void 0,errorMessage:"string"==typeof ErrorMessage?ErrorMessage:void 0}})),response):response}(await settings.runtime.inflight.track(settings.client.send(command)));attempts=response.attempts,result=function(chunk,response,target){const{responses:responses,failedPutCount:failedPutCount,status:status}=response;if(responses&&responses.length===chunk.length){const failures=[];if(responses.forEach((entry,local)=>{entry.errorCode&&failures.push({index:chunk[local].index,error:recordError(target,entry.errorCode,status,entry)})}),void 0===failedPutCount||failures.length===failedPutCount)return failures}else if(0===failedPutCount)return[];const error=localError(target,"InvalidResponse",`PutRecordBatch answered without per-record results that match its FailedPutCount for ${chunk.length} records (FailedPutCount ${failedPutCount??"missing"}).`);return chunk.map(({index:index})=>({index:index,error:error}))}(chunk,response,target)}catch(thrown){attempts=readMetadata(thrown).attempts;const error=fromSdkError(thrown,target);result=chunk.map(({index:index})=>({index:index,error:error}))}return logger.debug("Firehose PutRecordBatch",{stream:settings.streamName,records:chunk.length,bytes:bytes,failed:result.length,attempts:attempts,ms:Date.now()-started}),result}));for(const result of results)failures.push(...result);return failures.sort((a,b)=>a.index-b.index)}import{batchOutcome}from"@walkeros/server-core";var types_exports={},destinationFirehose={type:"aws-firehose",config:{batch:{size:500,age:1e3}},init:async({config:config,env:env,logger:logger})=>getConfig(config,env,logger),push:async function(event,context){const{config:config,data:data,env:env,logger:logger,rule:rule}=context,settings=resolveSettings(config.settings);if(!rule?.silent){const[failure]=await deliver([{event:event,data:data}],settings,env,logger);if(failure)throw failure.error}},pushBatch:async(batch,context)=>{const{config:config,env:env,logger:logger}=context,settings=resolveSettings(config.settings),sending=batch.entries.map((entry,index)=>({entry:entry,index:index})).filter(({entry:entry})=>!entry.rule?.silent),failures=sending.length?await deliver(sending.map(({entry:{event:event,data:data}})=>({event:event,data:data})),settings,env,logger):[];return batchOutcome(batch.entries.length,failures.map(({index:index,error:error})=>({index:sending[index].index,error:error})))},async destroy({config:config,logger:logger}){const runtime=config.settings?.runtime;runtime&&(await runtime.inflight.settle(),runtime.ownsClient&&(runtime.ownsClient=!1,config.settings?.client?.destroy?.(),logger.debug("Firehose client closed")))}};import{StepError as StepError3}from"@walkeros/core";import{SNSClient}from"@aws-sdk/client-sns";import{GetCallerIdentityCommand,STSClient}from"@aws-sdk/client-sts";import{isObject as isObject4}from"@walkeros/core";function createSnsClient(env,options){return new(env?.AWS?.SNSClient??SNSClient)(function(options){const resolved={...options.config,region:options.region};return options.credentials&&(resolved.credentials=options.credentials),void 0===resolved.requestHandler&&(resolved.requestHandler=requestHandlerOptions(options.timeout)),resolved}(options))}function createStsClient(env,options){const Client=env?.AWS?.STSClient??STSClient,config={region:options.region},credentials=options.credentials??options.config?.credentials;credentials&&(config.credentials=credentials);const userHandler=options.config?.requestHandler;return config.requestHandler=void 0===userHandler||isHandlerInstance(userHandler)?requestHandlerOptions(options.timeout):userHandler,config.maxAttempts=1,new Client(config)}async function getCallerIdentity(client,env,target){const Command=env?.AWS?.GetCallerIdentityCommand??GetCallerIdentityCommand;let response;try{response=await client.send(new Command({}))}catch(error){throw fromSdkError(error,target)}if(!isObject4(response)||"string"!=typeof response.Account)throw localError(target,"InvalidResponse","STS GetCallerIdentity returned no Account.");const partition=("string"==typeof response.Arn?response.Arn:"").split(":")[1]||"aws";return{accountId:response.Account,partition:partition}}function topicArnResolver(topicArn,topicName,target,stsClient,env){if(topicArn)return async()=>topicArn;let pending;return()=>(pending??=(async()=>{const client=stsClient();try{const{accountId:accountId,partition:partition}=await getCallerIdentity(client,env,target);return`arn:${partition}:sns:${target.region}:${accountId}:${topicName}`}finally{client.destroy?.()}})().catch(error=>{throw pending=void 0,error}),pending)}import{PublishCommand}from"@aws-sdk/client-sns";import{getMappingValue,isObject as isObject5}from"@walkeros/core";function isMessageAttribute(value){return isObject5(value)&&"string"==typeof value.DataType}async function resolveStringMappingValue(event,value,collector){const resolved=await getMappingValue(event,value,{collector:collector});return"string"==typeof resolved&&resolved.length>0?resolved:null!=resolved?String(resolved):void 0}var publish=async function(event,context){const{config:config,data:data,env:env,rule:rule,collector:collector,logger:logger,id:id}=context,settings=function(settings){const{region:region,client:client,runtime:runtime}=settings??{};if(!region||!client||!runtime)throw new StepError3("SNS: the destination was not initialized.",{code:"not-ready"});return{topicArn:settings?.topicArn,topicName:settings?.topicName,region:region,client:client,runtime:runtime}}(config.settings),{runtime:runtime}=settings,topicArn=await runtime.inflight.track(runtime.topicArn()),target={service:"SNS",resource:topicArn,region:settings.region,id:id},ruleSettings=(value=rule?.settings,"object"==typeof value&&null!==value?rule.settings:{});var value;const messageGroupId=void 0!==ruleSettings.messageGroupId?await resolveStringMappingValue(event,ruleSettings.messageGroupId,collector):void 0,fifo=topicArn.endsWith(".fifo"),messageDeduplicationId=(void 0!==ruleSettings.messageDeduplicationId?await resolveStringMappingValue(event,ruleSettings.messageDeduplicationId,collector):void 0)??(fifo&&event.id?event.id:void 0),messageAttributes=await async function(event,source,collector){if(!source)return;const result={};let touched=!1;for(const[key,value]of Object.entries(source)){const resolved=await getMappingValue(event,value,{collector:collector});isMessageAttribute(resolved)?(result[key]=resolved,touched=!0):"string"==typeof resolved&&resolved.length>0?(result[key]={DataType:"String",StringValue:resolved},touched=!0):null!=resolved&&(result[key]={DataType:"String",StringValue:String(resolved)},touched=!0)}return touched?result:void 0}(event,ruleSettings.messageAttributes,collector);let message;try{message=JSON.stringify(toPayload(event,data))}catch(error){throw localError(target,"InvalidRecord","The message does not serialize to JSON.",error)}const input={TopicArn:topicArn,Message:message};messageGroupId&&(input.MessageGroupId=messageGroupId),messageDeduplicationId&&(input.MessageDeduplicationId=messageDeduplicationId),messageAttributes&&(input.MessageAttributes=messageAttributes);const Command=env?.AWS?.PublishCommand??PublishCommand,started=Date.now();let attempts;try{attempts=readAttempts(await runtime.inflight.track(settings.client.send(new Command(input))))}catch(error){throw attempts=readAttempts(error),fromSdkError(error,target)}finally{logger.debug("SNS Publish",{topicArn:topicArn,fifo:fifo,event:event.name,attempts:attempts,ms:Date.now()-started})}};function readAttempts(value){if("object"!=typeof value||null===value||!("$metadata"in value))return;const metadata=value.$metadata;return"object"==typeof metadata&&null!==metadata&&"attempts"in metadata&&"number"==typeof metadata.attempts?metadata.attempts:void 0}import{CreateTopicCommand,GetTopicAttributesCommand,SubscribeCommand}from"@aws-sdk/client-sns";import{resolveSetup}from"@walkeros/core";var DEFAULT_SETUP={fifoTopic:!1},identityCache=new Map;function buildSubscribeAttributes(sub){const attrs={};return void 0!==sub.rawMessageDelivery&&(attrs.RawMessageDelivery=String(sub.rawMessageDelivery)),void 0!==sub.filterPolicy&&(attrs.FilterPolicy=JSON.stringify(sub.filterPolicy)),void 0!==sub.deadLetterTargetArn&&(attrs.RedrivePolicy=JSON.stringify({deadLetterTargetArn:sub.deadLetterTargetArn})),Object.keys(attrs).length>0?attrs:void 0}var types_exports2={},destinationSNS={type:"aws-sns",config:{},setup:async function(ctx){const{config:config,env:env,logger:logger}=ctx,merged=resolveSetup(config.setup,DEFAULT_SETUP);if(!merged)return void logger.debug("setup: skipped (config.setup is false or unset)");const settings=config.settings??{},arn=settings.topicArn?parseTopicArn(settings.topicArn):void 0,topicName=settings.topicName??arn?.name;if(!topicName)return void logger.throw("setup: settings.topicName (or settings.topicArn) is required. There is no safe default for the SNS topic name.");const sdkRegion="string"==typeof settings.config?.region?settings.config.region:void 0,region=firstString(merged.region,settings.region,arn?.region,sdkRegion,await loadSdkRegion())??"eu-central-1",fifo=merged.fifoTopic??!1,finalName=function(name,fifo){if(fifo)return name.endsWith(".fifo")?name:`${name}.fifo`;if(name.endsWith(".fifo"))throw new Error(`FIFO suffix '.fifo' on standard topic name '${name}'. Set setup.fifoTopic: true or rename the topic.`);return name}(topicName,fifo);fifo&&finalName!==topicName&&logger.info(`setup: appended .fifo suffix to FIFO topic name '${topicName}' -> '${finalName}'`);const options={region:region,config:settings.config,credentials:parseCredentials(config.credentials),timeout:config.timeout},client=settings.client??createSnsClient(env,options),closesClient=!settings.client&&!isHandlerInstance(settings.config?.requestHandler);try{const{accountId:accountId,partition:partition}=await async function(env,options,target){const cached=identityCache.get(options.region);if(cached)return cached;const stsClient=createStsClient(env,options);try{const identity=await getCallerIdentity(stsClient,env,target);return identityCache.set(options.region,identity),identity}finally{stsClient.destroy?.()}}(env,options,{service:"SNS",resource:finalName,region:region,id:ctx.id}),candidateArn=`arn:${partition}:sns:${region}:${accountId}:${finalName}`;let topicCreated=!1;try{const Command=env?.AWS?.GetTopicAttributesCommand??GetTopicAttributesCommand;await client.send(new Command({TopicArn:candidateArn}))}catch(err){if(!function(err){return!(!function(err){return"object"==typeof err&&null!==err}(err)||"NotFoundException"!==err.name&&"NotFound"!==err.name&&(!err.$metadata||404!==err.$metadata.httpStatusCode))}(err))throw err;topicCreated=!0}const attributes={};fifo&&(attributes.FifoTopic="true",attributes.ContentBasedDeduplication="true"),void 0!==merged.displayName&&(attributes.DisplayName=merged.displayName),void 0!==merged.kmsMasterKeyId&&(attributes.KmsMasterKeyId=merged.kmsMasterKeyId);const tagEntries=merged.tags?Object.entries(merged.tags).map(([Key,Value])=>({Key:Key,Value:Value})):void 0,createInput={Name:finalName};Object.keys(attributes).length>0&&(createInput.Attributes=attributes),tagEntries&&tagEntries.length>0&&(createInput.Tags=tagEntries);const CreateCommand=env?.AWS?.CreateTopicCommand??CreateTopicCommand,topicArn=function(res){if("object"==typeof(v=res)&&null!==v&&"string"==typeof res.TopicArn)return res.TopicArn;var v;throw new Error("SNS CreateTopic returned no TopicArn")}(await client.send(new CreateCommand(createInput)));topicCreated?logger.info("setup: topic created",{topicArn:topicArn,region:region}):logger.debug("setup: topic exists; declared state re-applied",{topicArn:topicArn,region:region});const subscriptionsCreated=await async function(client,env,topicArn,declared){let count=0;for(const sub of declared){const input={TopicArn:topicArn,Protocol:sub.protocol,Endpoint:sub.endpoint},attrs=buildSubscribeAttributes(sub);attrs&&(input.Attributes=attrs);const Command=env?.AWS?.SubscribeCommand??SubscribeCommand;await client.send(new Command(input)),count+=1}return count}(client,env,topicArn,merged.subscriptions??[]);return{topicArn:topicArn,topicCreated:topicCreated,tagsApplied:merged.tags?Object.keys(merged.tags).length:0,subscriptionsCreated:subscriptionsCreated}}finally{closesClient&&client.destroy?.()}},init:async({config:config,env:env,logger:logger,id:id})=>async function(partialConfig={},env,logger,id){const input=partialConfig.settings??{},{topicArn:topicArn,config:sdkConfig,client:userClient}=input,arn=topicArn?parseTopicArn(topicArn):void 0;if(topicArn&&!arn)throw configError(`SNS: settings.topicArn "${topicArn}" is not an SNS topic ARN (arn:aws:sns:<region>:<account>:<name>).`);if(!arn&&!input.topicName)throw configError("SNS: settings.topicArn is missing. Set it to the ARN of your topic (or set settings.topicName).");if(arn&&input.region&&input.region!==arn.region)throw configError(`SNS: settings.region "${input.region}" differs from the region "${arn.region}" in settings.topicArn. Remove settings.region.`);if(arn&&input.topicName&&input.topicName!==arn.name)throw configError(`SNS: settings.topicName "${input.topicName}" differs from the name "${arn.name}" in settings.topicArn. Remove settings.topicName.`);const sdkRegion="string"==typeof sdkConfig?.region?sdkConfig.region:void 0,region=firstString(input.region,arn?.region,sdkRegion,await loadSdkRegion())??"eu-central-1",topicName=input.topicName??arn?.name;void 0!==partialConfig.batch&&logger.warn("SNS publishes one message per event and does not batch. Remove config.batch from this destination.");const credentials=parseCredentials(partialConfig.credentials),clientOptions={region:region,config:sdkConfig,credentials:credentials,timeout:partialConfig.timeout},settings={region:region,client:userClient??createSnsClient(env,clientOptions),runtime:{ownsClient:!userClient&&!isHandlerInstance(sdkConfig?.requestHandler),inflight:new InFlight,topicArn:topicArnResolver(topicArn,topicName,{service:"SNS",resource:topicName??"",region:region,id:id},()=>createStsClient(env,clientOptions),env)}};return topicArn&&(settings.topicArn=topicArn),topicName&&(settings.topicName=topicName),sdkConfig&&(settings.config=sdkConfig),logger.debug("SNS client ready",{topic:topicArn??topicName,region:region,credentials:userClient?"client":credentials?"config.credentials":void 0!==sdkConfig?.credentials?"settings.config.credentials":"default chain"}),{...partialConfig,settings:settings}}(config,env,logger,id),push:async function(event,context){context.rule?.silent||await publish(event,context)},async destroy({config:config,logger:logger}){const runtime=config.settings?.runtime;runtime&&(await runtime.inflight.settle(),runtime.ownsClient&&(runtime.ownsClient=!1,config.settings?.client?.destroy?.(),logger.debug("SNS client closed")))}};export{types_exports as DestinationFirehose,types_exports2 as DestinationSNS,destinationFirehose as default,destinationFirehose,destinationSNS};//# sourceMappingURL=index.mjs.map