@celerity-sdk/topic 0.4.0 → 0.6.0
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/chunk-T4LCI5X5.js +20 -0
- package/dist/chunk-T4LCI5X5.js.map +1 -0
- package/dist/index.cjs +348 -280
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -22
- package/dist/index.js +20 -328
- package/dist/index.js.map +1 -1
- package/dist/redis-topic-client-SPF63T34.js +158 -0
- package/dist/redis-topic-client-SPF63T34.js.map +1 -0
- package/dist/sns-topic-client-643MNMBF.js +161 -0
- package/dist/sns-topic-client-643MNMBF.js.map +1 -0
- package/package.json +4 -4
- package/dist/index.d.cts +0 -215
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/types.ts","../src/providers/sns/sns-topic-client.ts","../src/providers/sns/sns-topic.ts","../src/errors.ts","../src/providers/sns/config.ts","../src/providers/redis/redis-topic.ts","../src/providers/redis/config.ts","../src/providers/redis/redis-topic-client.ts","../src/factory.ts","../src/decorators.ts","../src/helpers.ts","../src/layer.ts"],"sourcesContent":["// Core types\nexport {\n TopicClient,\n type PublishOptions,\n type PublishResult,\n type BatchPublishEntry,\n type BatchPublishResult,\n type BatchPublishSuccess,\n type BatchPublishFailure,\n} from \"./types\";\n\nexport { SNSTopicClient } from \"./providers/sns/sns-topic-client\";\nexport type { SNSTopicConfig } from \"./providers/sns/types\";\nexport { RedisTopicClient } from \"./providers/redis/redis-topic-client\";\nexport type { RedisTopicConfig } from \"./providers/redis/types\";\n\nexport { createTopicClient } from \"./factory\";\nexport type { CreateTopicClientOptions } from \"./factory\";\n\nexport { Topic, topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\nexport { getTopic } from \"./helpers\";\n\nexport { TopicLayer } from \"./layer\";\n\nexport { TopicError } from \"./errors\";\n","import type { Closeable } from \"@celerity-sdk/types\";\n\nexport const TopicClient = Symbol.for(\"TopicClient\");\n\n/**\n * A topic client abstraction for pub/sub topics. Provides access to named\n * topics, each representing a logical destination for published messages.\n */\nexport interface TopicClient extends Closeable {\n /**\n * Retrieves a topic instance by its name. The returned topic is a lightweight\n * handle — no network calls are made until an operation is invoked.\n *\n * @param name The topic identifier (SNS topic ARN, Redis channel name, etc.).\n */\n topic(name: string): Topic;\n}\n\n/**\n * A topic represents a logical pub/sub destination. It provides methods for\n * publishing messages individually or in batches. The Celerity runtime handles\n * all subscription and consumption — this interface is producer-only.\n */\nexport interface Topic {\n /**\n * Publish a single message to the topic. The body is serialized to JSON.\n *\n * @param body The message body (serialized to JSON string for transport).\n * @param options Optional publish parameters such as FIFO ordering or subject.\n * @returns A promise that resolves to the publish result with the provider-assigned message ID.\n */\n publish<T = Record<string, unknown>>(body: T, options?: PublishOptions): Promise<PublishResult>;\n\n /**\n * Publish multiple messages in a single batch. The provider may impose batch\n * size limits (e.g. SNS limits to 10 per request) — the implementation\n * handles chunking transparently.\n *\n * @param entries The batch of messages to publish, each with a caller-assigned ID for correlation.\n * @returns A promise that resolves to the batch result with successful and failed entries.\n */\n publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult>;\n}\n\n/**\n * Options for the publish and publishBatch operations.\n */\nexport type PublishOptions = {\n /** Message group ID for FIFO topics (SNS: MessageGroupId, Pub/Sub: ordering key). */\n groupId?: string;\n /** Deduplication ID for FIFO topics. */\n deduplicationId?: string;\n /** Message subject (SNS: Subject). */\n subject?: string;\n /** String key-value message attributes (metadata sent alongside the body). */\n attributes?: Record<string, string>;\n};\n\n/**\n * The result of a single publish call.\n */\nexport type PublishResult = {\n /** The provider-assigned message identifier. */\n messageId: string;\n};\n\n/**\n * A single entry in a batch publish request.\n */\nexport type BatchPublishEntry<T = Record<string, unknown>> = {\n /** Caller-assigned ID for correlating results within the batch. */\n id: string;\n /** The message body (serialized to JSON). */\n body: T;\n /** Per-message publish options. */\n options?: PublishOptions;\n};\n\n/**\n * The result of a batch publish operation.\n */\nexport type BatchPublishResult = {\n /** Entries that were published successfully. */\n successful: BatchPublishSuccess[];\n /** Entries that failed. */\n failed: BatchPublishFailure[];\n};\n\n/**\n * A successfully published entry from a batch operation.\n */\nexport type BatchPublishSuccess = {\n /** The caller-assigned entry ID. */\n id: string;\n /** The provider-assigned message ID. */\n messageId: string;\n};\n\n/**\n * A failed entry from a batch operation.\n */\nexport type BatchPublishFailure = {\n /** The caller-assigned entry ID. */\n id: string;\n /** Provider error code. */\n code: string;\n /** Human-readable error message. */\n message: string;\n};\n","import { SNSClient } from \"@aws-sdk/client-sns\";\nimport type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient, Topic } from \"../../types\";\nimport { SNSTopic } from \"./sns-topic\";\nimport type { SNSTopicConfig } from \"./types\";\nimport { captureSNSConfig } from \"./config\";\n\nexport class SNSTopicClient implements TopicClient {\n private client: SNSClient | null = null;\n private readonly config: SNSTopicConfig;\n\n constructor(\n config?: SNSTopicConfig,\n private readonly tracer?: CelerityTracer,\n ) {\n this.config = config ?? captureSNSConfig();\n }\n\n topic(name: string): Topic {\n return new SNSTopic(name, this.getClient(), this.tracer);\n }\n\n close(): void {\n this.client?.destroy();\n this.client = null;\n }\n\n private getClient(): SNSClient {\n if (!this.client) {\n this.client = new SNSClient({\n region: this.config.region,\n endpoint: this.config.endpoint,\n credentials: this.config.credentials,\n });\n }\n return this.client;\n }\n}\n","import createDebug from \"debug\";\nimport {\n type SNSClient,\n PublishCommand,\n PublishBatchCommand,\n type MessageAttributeValue,\n type PublishBatchRequestEntry,\n} from \"@aws-sdk/client-sns\";\nimport type { CelerityTracer, CeleritySpan } from \"@celerity-sdk/types\";\nimport type {\n Topic,\n PublishOptions,\n PublishResult,\n BatchPublishEntry,\n BatchPublishResult,\n BatchPublishSuccess,\n BatchPublishFailure,\n} from \"../../types\";\nimport { TopicError } from \"../../errors\";\n\nconst debug = createDebug(\"celerity:topic:sns\");\n\nconst SNS_MAX_BATCH_SIZE = 10;\n\nexport class SNSTopic implements Topic {\n constructor(\n private readonly topicArn: string,\n private readonly client: SNSClient,\n private readonly tracer?: CelerityTracer,\n ) {}\n\n async publish<T = Record<string, unknown>>(\n body: T,\n options?: PublishOptions,\n ): Promise<PublishResult> {\n debug(\"publish %s\", this.topicArn);\n return this.traced(\"celerity.topic.publish\", { \"topic.arn\": this.topicArn }, async () => {\n try {\n const result = await this.client.send(\n new PublishCommand({\n TopicArn: this.topicArn,\n Message: JSON.stringify(body),\n MessageGroupId: options?.groupId,\n MessageDeduplicationId: options?.deduplicationId,\n Subject: options?.subject,\n MessageAttributes: options?.attributes\n ? toSNSAttributes(options.attributes)\n : undefined,\n }),\n );\n return { messageId: result.MessageId! };\n } catch (error) {\n throw new TopicError(\n `Failed to publish message to topic \"${this.topicArn}\"`,\n this.topicArn,\n { cause: error },\n );\n }\n });\n }\n\n async publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult> {\n debug(\"publishBatch %s (%d entries)\", this.topicArn, entries.length);\n return this.traced(\n \"celerity.topic.publish_batch\",\n { \"topic.arn\": this.topicArn, \"topic.message_count\": entries.length },\n async () => {\n const successful: BatchPublishSuccess[] = [];\n const failed: BatchPublishFailure[] = [];\n\n // Auto-chunk into groups of 10 (SNS limit)\n for (let i = 0; i < entries.length; i += SNS_MAX_BATCH_SIZE) {\n const chunk = entries.slice(i, i + SNS_MAX_BATCH_SIZE);\n const snsEntries: PublishBatchRequestEntry[] = chunk.map((entry) => ({\n Id: entry.id,\n Message: JSON.stringify(entry.body),\n MessageGroupId: entry.options?.groupId,\n MessageDeduplicationId: entry.options?.deduplicationId,\n Subject: entry.options?.subject,\n MessageAttributes: entry.options?.attributes\n ? toSNSAttributes(entry.options.attributes)\n : undefined,\n }));\n\n try {\n const result = await this.client.send(\n new PublishBatchCommand({\n TopicArn: this.topicArn,\n PublishBatchRequestEntries: snsEntries,\n }),\n );\n\n for (const s of result.Successful ?? []) {\n successful.push({ id: s.Id!, messageId: s.MessageId! });\n }\n for (const f of result.Failed ?? []) {\n failed.push({ id: f.Id!, code: f.Code!, message: f.Message ?? \"Unknown error\" });\n }\n } catch (error) {\n throw new TopicError(\n `Failed to publish message batch to topic \"${this.topicArn}\"`,\n this.topicArn,\n { cause: error },\n );\n }\n }\n\n return { successful, failed };\n },\n );\n }\n\n private traced<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span?: CeleritySpan) => Promise<T>,\n ): Promise<T> {\n if (!this.tracer) return fn();\n return this.tracer.withSpan(name, (span) => fn(span), attributes);\n }\n}\n\nfunction toSNSAttributes(attrs: Record<string, string>): Record<string, MessageAttributeValue> {\n const result: Record<string, MessageAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n result[key] = { DataType: \"String\", StringValue: value };\n }\n return result;\n}\n","/**\n * Base error for topic operations. Wraps provider SDK errors and includes\n * the topic identifier for context.\n */\nexport class TopicError extends Error {\n constructor(\n message: string,\n public readonly topic: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.name = \"TopicError\";\n }\n}\n","import type { SNSTopicConfig } from \"./types\";\n\n/**\n * Captures SNS configuration from environment variables.\n * This is the only place that reads `process.env` for SNS config.\n */\nexport function captureSNSConfig(): SNSTopicConfig {\n return {\n region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,\n endpoint: process.env.AWS_ENDPOINT_URL,\n credentials:\n process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY\n ? {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n }\n : undefined,\n };\n}\n","import { randomUUID } from \"node:crypto\";\nimport createDebug from \"debug\";\nimport type Redis from \"ioredis\";\nimport type { CelerityTracer, CeleritySpan } from \"@celerity-sdk/types\";\nimport type {\n Topic,\n PublishOptions,\n PublishResult,\n BatchPublishEntry,\n BatchPublishResult,\n BatchPublishSuccess,\n BatchPublishFailure,\n} from \"../../types\";\nimport { TopicError } from \"../../errors\";\n\nconst debug = createDebug(\"celerity:topic:redis\");\n\nexport class RedisTopic implements Topic {\n constructor(\n private readonly channelName: string,\n private readonly client: Redis,\n private readonly tracer?: CelerityTracer,\n ) {}\n\n async publish<T = Record<string, unknown>>(\n body: T,\n options?: PublishOptions,\n ): Promise<PublishResult> {\n debug(\"publish %s\", this.channelName);\n return this.traced(\n \"celerity.topic.publish\",\n { \"topic.channel\": this.channelName },\n async () => {\n try {\n const messageId = randomUUID();\n const payload = buildEnvelope(body, messageId, options);\n await this.client.publish(this.channelName, payload);\n return { messageId };\n } catch (error) {\n throw new TopicError(\n `Failed to publish message to channel \"${this.channelName}\"`,\n this.channelName,\n { cause: error },\n );\n }\n },\n );\n }\n\n async publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult> {\n debug(\"publishBatch %s (%d entries)\", this.channelName, entries.length);\n return this.traced(\n \"celerity.topic.publish_batch\",\n { \"topic.channel\": this.channelName, \"topic.message_count\": entries.length },\n async () => {\n const successful: BatchPublishSuccess[] = [];\n const failed: BatchPublishFailure[] = [];\n\n const messageIds: string[] = [];\n const pipeline = this.client.pipeline();\n for (const entry of entries) {\n const messageId = randomUUID();\n messageIds.push(messageId);\n const payload = buildEnvelope(entry.body, messageId, entry.options);\n pipeline.publish(this.channelName, payload);\n }\n\n try {\n const results = await pipeline.exec();\n if (!results) {\n throw new Error(\"Pipeline returned null\");\n }\n\n for (let i = 0; i < entries.length; i++) {\n const [err] = results[i];\n if (err) {\n failed.push({\n id: entries[i].id,\n code: err.name ?? \"PipelineError\",\n message: err.message,\n });\n } else {\n successful.push({\n id: entries[i].id,\n messageId: messageIds[i],\n });\n }\n }\n } catch (error) {\n throw new TopicError(\n `Failed to publish message batch to channel \"${this.channelName}\"`,\n this.channelName,\n { cause: error },\n );\n }\n\n return { successful, failed };\n },\n );\n }\n\n private traced<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span?: CeleritySpan) => Promise<T>,\n ): Promise<T> {\n if (!this.tracer) return fn();\n return this.tracer.withSpan(name, (span) => fn(span), attributes);\n }\n}\n\n/**\n * Builds the JSON envelope published to the pub/sub channel.\n * The local-events bridge parses this envelope and extracts individual\n * fields when writing to target consumer streams.\n *\n * `messageId`, `subject`, and `attributes` are included in the envelope.\n * `groupId` and `deduplicationId` are silently ignored — ordering is\n * inherent in the single-instance bridge architecture.\n */\nfunction buildEnvelope<T>(body: T, messageId: string, options?: PublishOptions): string {\n const envelope: Record<string, unknown> = {\n body: JSON.stringify(body),\n messageId,\n };\n\n if (options?.subject) {\n envelope.subject = options.subject;\n }\n if (options?.attributes && Object.keys(options.attributes).length > 0) {\n envelope.attributes = options.attributes;\n }\n\n return JSON.stringify(envelope);\n}\n","import type { RedisTopicConfig } from \"./types\";\n\nconst DEFAULT_REDIS_URL = \"redis://localhost:6379\";\n\n/**\n * Captures Redis configuration from environment variables.\n * This is the only place that reads `process.env` for Redis config.\n */\nexport function captureRedisConfig(): RedisTopicConfig {\n return {\n url: process.env.CELERITY_LOCAL_REDIS_URL ?? DEFAULT_REDIS_URL,\n };\n}\n","import type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient, Topic } from \"../../types\";\nimport { RedisTopic } from \"./redis-topic\";\nimport type { RedisTopicConfig } from \"./types\";\nimport { captureRedisConfig } from \"./config\";\n\nexport class RedisTopicClient implements TopicClient {\n private client: import(\"ioredis\").default | null = null;\n private readonly config: RedisTopicConfig;\n\n constructor(\n config?: RedisTopicConfig,\n private readonly tracer?: CelerityTracer,\n ) {\n this.config = config ?? captureRedisConfig();\n }\n\n topic(name: string): Topic {\n return new RedisTopic(name, this.getClient(), this.tracer);\n }\n\n async close(): Promise<void> {\n if (this.client) {\n await this.client.quit();\n this.client = null;\n }\n }\n\n private getClient(): import(\"ioredis\").default {\n if (!this.client) {\n // Dynamic require to avoid bundling ioredis when not used.\n // eslint-disable-next-line @typescript-eslint/no-require-imports\n const Redis = require(\"ioredis\").default ?? require(\"ioredis\");\n this.client = new Redis(this.config.url);\n }\n return this.client!;\n }\n}\n","import { resolveConfig } from \"@celerity-sdk/config\";\nimport type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient } from \"./types\";\nimport type { SNSTopicConfig } from \"./providers/sns/types\";\nimport type { RedisTopicConfig } from \"./providers/redis/types\";\nimport { SNSTopicClient } from \"./providers/sns/sns-topic-client\";\nimport { RedisTopicClient } from \"./providers/redis/redis-topic-client\";\n\nexport type CreateTopicClientOptions = {\n /** Override provider selection. If omitted, derived from platform config. */\n provider?: \"aws\" | \"local\" | \"gcp\" | \"azure\";\n /** SNS-specific configuration overrides. */\n aws?: SNSTopicConfig;\n /** Redis-specific configuration overrides (local environment). */\n local?: RedisTopicConfig;\n /** Optional tracer for Celerity-level span instrumentation. */\n tracer?: CelerityTracer;\n};\n\nexport function createTopicClient(options?: CreateTopicClientOptions): TopicClient {\n const resolved = resolveConfig(\"topic\");\n const provider = options?.provider ?? resolved.provider;\n\n switch (provider) {\n case \"aws\":\n return new SNSTopicClient(options?.aws, options?.tracer);\n // Local environments always use Redis pub/sub regardless of deploy target.\n // The Celerity CLI manages the Redis instance and local-events sidecar.\n case \"local\":\n return new RedisTopicClient(options?.local, options?.tracer);\n // case \"gcp\":\n // v1: Google Cloud Pub/Sub\n // case \"azure\":\n // v1: Azure Service Bus Topics\n default:\n throw new Error(`Unsupported topic provider: \"${provider}\"`);\n }\n}\n","import \"reflect-metadata\";\nimport { INJECT_METADATA, USE_RESOURCE_METADATA } from \"@celerity-sdk/common\";\nimport type { Topic as TopicType } from \"./types\";\n\n// Re-declare as interface so the type merges with the decorator function below.\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface Topic extends TopicType {}\n\nexport function topicToken(resourceName: string): symbol {\n return Symbol.for(`celerity:topic:${resourceName}`);\n}\n\nexport const DEFAULT_TOPIC_TOKEN = Symbol.for(\"celerity:topic:default\");\n\n/**\n * Parameter decorator that injects a {@link Topic} instance for the given\n * blueprint resource. Writes both DI injection metadata and CLI resource-ref\n * metadata using well-known `Symbol.for()` keys (no dependency on core).\n *\n * When `resourceName` is omitted, the default topic token is used — this\n * auto-resolves when exactly one topic resource exists.\n *\n * @example\n * ```ts\n * @Controller(\"/orders\")\n * class OrderController {\n * constructor(@Topic(\"orderEvents\") private events: Topic) {}\n * }\n * ```\n */\nexport function Topic(resourceName?: string): ParameterDecorator {\n return (target, _propertyKey, parameterIndex) => {\n const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;\n const existing: Map<number, unknown> =\n Reflect.getOwnMetadata(INJECT_METADATA, target) ?? new Map();\n existing.set(parameterIndex, token);\n Reflect.defineMetadata(INJECT_METADATA, existing, target);\n\n if (resourceName) {\n const resources: string[] = Reflect.getOwnMetadata(USE_RESOURCE_METADATA, target) ?? [];\n if (!resources.includes(resourceName)) {\n Reflect.defineMetadata(USE_RESOURCE_METADATA, [...resources, resourceName], target);\n }\n }\n };\n}\n","import type { ServiceContainer } from \"@celerity-sdk/types\";\nimport type { Topic } from \"./types\";\nimport { topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\n/**\n * Resolves a {@link Topic} instance from the DI container.\n * For function-based handlers where parameter decorators aren't available.\n *\n * @param container - The service container (typically `context.container`).\n * @param resourceName - The blueprint resource name. Omit when exactly one\n * topic resource exists to use the default.\n *\n * @example\n * ```ts\n * const handler = createHttpHandler(async (req, ctx) => {\n * const events = await getTopic(ctx.container, \"orderEvents\");\n * await events.publish({ orderId: \"123\", action: \"created\" });\n * });\n * ```\n */\nexport function getTopic(container: ServiceContainer, resourceName?: string): Promise<Topic> {\n const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;\n return container.resolve<Topic>(token);\n}\n","import createDebug from \"debug\";\nimport type { CelerityLayer, BaseHandlerContext, CelerityTracer } from \"@celerity-sdk/types\";\nimport { TRACER_TOKEN, CONFIG_SERVICE_TOKEN } from \"@celerity-sdk/common\";\nimport {\n type ConfigService,\n captureResourceLinks,\n getLinksOfType,\n RESOURCE_CONFIG_NAMESPACE,\n} from \"@celerity-sdk/config\";\nimport { createTopicClient } from \"./factory\";\nimport { topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\nconst debug = createDebug(\"celerity:topic\");\n\n/**\n * System layer that auto-registers {@link TopicClient} and per-resource\n * {@link Topic} handles in the DI container.\n *\n * Reads resource link topology from `CELERITY_RESOURCE_LINKS` and resolves\n * actual topic ARNs / channel names from the ConfigService \"resources\" namespace.\n * Must run after ConfigLayer in the layer pipeline.\n */\nexport class TopicLayer implements CelerityLayer<BaseHandlerContext> {\n private initialized = false;\n\n async handle(context: BaseHandlerContext, next: () => Promise<unknown>): Promise<unknown> {\n if (!this.initialized) {\n const tracer = context.container.has(TRACER_TOKEN)\n ? await context.container.resolve<CelerityTracer>(TRACER_TOKEN)\n : undefined;\n\n const client = createTopicClient({ tracer });\n debug(\"registering TopicClient\");\n context.container.register(\"TopicClient\", { useValue: client });\n\n const links = captureResourceLinks();\n const topicLinks = getLinksOfType(links, \"topic\");\n\n if (topicLinks.size > 0) {\n const configService = await context.container.resolve<ConfigService>(CONFIG_SERVICE_TOKEN);\n const resourceConfig = configService.namespace(RESOURCE_CONFIG_NAMESPACE);\n\n for (const [resourceName, configKey] of topicLinks) {\n const actualName = await resourceConfig.getOrThrow(configKey);\n debug(\"registered topic resource %s → %s\", resourceName, actualName);\n context.container.register(topicToken(resourceName), {\n useValue: client.topic(actualName),\n });\n }\n\n if (topicLinks.size === 1) {\n const [, configKey] = [...topicLinks.entries()][0];\n const actualName = await resourceConfig.getOrThrow(configKey);\n debug(\"registered default topic → %s\", actualName);\n context.container.register(DEFAULT_TOPIC_TOKEN, {\n useValue: client.topic(actualName),\n });\n }\n }\n\n this.initialized = true;\n }\n\n return next();\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;ACEO,IAAMA,cAAcC,uBAAOC,IAAI,aAAA;;;ACFtC,IAAAC,qBAA0B;;;ACA1B,mBAAwB;AACxB,wBAMO;;;ACHA,IAAMC,aAAN,cAAyBC,MAAAA;EAJhC,OAIgCA;;;;EAC9B,YACEC,SACgBC,OAChBC,SACA;AACA,UAAMF,SAASE,OAAAA,GAAAA,KAHCD,QAAAA;AAIhB,SAAKE,OAAO;EACd;AACF;;;ADOA,IAAMC,YAAQC,aAAAA,SAAY,oBAAA;AAE1B,IAAMC,qBAAqB;AAEpB,IAAMC,WAAN,MAAMA;EAxBb,OAwBaA;;;;;;EACX,YACmBC,UACAC,QACAC,QACjB;SAHiBF,WAAAA;SACAC,SAAAA;SACAC,SAAAA;EAChB;EAEH,MAAMC,QACJC,MACAC,SACwB;AACxBT,UAAM,cAAc,KAAKI,QAAQ;AACjC,WAAO,KAAKM,OAAO,0BAA0B;MAAE,aAAa,KAAKN;IAAS,GAAG,YAAA;AAC3E,UAAI;AACF,cAAMO,SAAS,MAAM,KAAKN,OAAOO,KAC/B,IAAIC,iCAAe;UACjBC,UAAU,KAAKV;UACfW,SAASC,KAAKC,UAAUT,IAAAA;UACxBU,gBAAgBT,SAASU;UACzBC,wBAAwBX,SAASY;UACjCC,SAASb,SAASc;UAClBC,mBAAmBf,SAASgB,aACxBC,gBAAgBjB,QAAQgB,UAAU,IAClCE;QACN,CAAA,CAAA;AAEF,eAAO;UAAEC,WAAWjB,OAAOkB;QAAW;MACxC,SAASC,OAAO;AACd,cAAM,IAAIC,WACR,uCAAuC,KAAK3B,QAAQ,KACpD,KAAKA,UACL;UAAE4B,OAAOF;QAAM,CAAA;MAEnB;IACF,CAAA;EACF;EAEA,MAAMG,aACJC,SAC6B;AAC7BlC,UAAM,gCAAgC,KAAKI,UAAU8B,QAAQC,MAAM;AACnE,WAAO,KAAKzB,OACV,gCACA;MAAE,aAAa,KAAKN;MAAU,uBAAuB8B,QAAQC;IAAO,GACpE,YAAA;AACE,YAAMC,aAAoC,CAAA;AAC1C,YAAMC,SAAgC,CAAA;AAGtC,eAASC,IAAI,GAAGA,IAAIJ,QAAQC,QAAQG,KAAKpC,oBAAoB;AAC3D,cAAMqC,QAAQL,QAAQM,MAAMF,GAAGA,IAAIpC,kBAAAA;AACnC,cAAMuC,aAAyCF,MAAMG,IAAI,CAACC,WAAW;UACnEC,IAAID,MAAME;UACV9B,SAASC,KAAKC,UAAU0B,MAAMnC,IAAI;UAClCU,gBAAgByB,MAAMlC,SAASU;UAC/BC,wBAAwBuB,MAAMlC,SAASY;UACvCC,SAASqB,MAAMlC,SAASc;UACxBC,mBAAmBmB,MAAMlC,SAASgB,aAC9BC,gBAAgBiB,MAAMlC,QAAQgB,UAAU,IACxCE;QACN,EAAA;AAEA,YAAI;AACF,gBAAMhB,SAAS,MAAM,KAAKN,OAAOO,KAC/B,IAAIkC,sCAAoB;YACtBhC,UAAU,KAAKV;YACf2C,4BAA4BN;UAC9B,CAAA,CAAA;AAGF,qBAAWO,KAAKrC,OAAOsC,cAAc,CAAA,GAAI;AACvCb,uBAAWc,KAAK;cAAEL,IAAIG,EAAEJ;cAAKhB,WAAWoB,EAAEnB;YAAW,CAAA;UACvD;AACA,qBAAWsB,KAAKxC,OAAOyC,UAAU,CAAA,GAAI;AACnCf,mBAAOa,KAAK;cAAEL,IAAIM,EAAEP;cAAKS,MAAMF,EAAEG;cAAOC,SAASJ,EAAEpC,WAAW;YAAgB,CAAA;UAChF;QACF,SAASe,OAAO;AACd,gBAAM,IAAIC,WACR,6CAA6C,KAAK3B,QAAQ,KAC1D,KAAKA,UACL;YAAE4B,OAAOF;UAAM,CAAA;QAEnB;MACF;AAEA,aAAO;QAAEM;QAAYC;MAAO;IAC9B,CAAA;EAEJ;EAEQ3B,OACN8C,MACA/B,YACAgC,IACY;AACZ,QAAI,CAAC,KAAKnD,OAAQ,QAAOmD,GAAAA;AACzB,WAAO,KAAKnD,OAAOoD,SAASF,MAAM,CAACG,SAASF,GAAGE,IAAAA,GAAOlC,UAAAA;EACxD;AACF;AAEA,SAASC,gBAAgBkC,OAA6B;AACpD,QAAMjD,SAAgD,CAAC;AACvD,aAAW,CAACkD,KAAKC,KAAAA,KAAUC,OAAO7B,QAAQ0B,KAAAA,GAAQ;AAChDjD,WAAOkD,GAAAA,IAAO;MAAEG,UAAU;MAAUC,aAAaH;IAAM;EACzD;AACA,SAAOnD;AACT;AANSe;;;AEtHF,SAASwC,mBAAAA;AACd,SAAO;IACLC,QAAQC,QAAQC,IAAIC,cAAcF,QAAQC,IAAIE;IAC9CC,UAAUJ,QAAQC,IAAII;IACtBC,aACEN,QAAQC,IAAIM,qBAAqBP,QAAQC,IAAIO,wBACzC;MACEC,aAAaT,QAAQC,IAAIM;MACzBG,iBAAiBV,QAAQC,IAAIO;IAC/B,IACAG;EACR;AACF;AAZgBb;;;AHCT,IAAMc,iBAAN,MAAMA;EAPb,OAOaA;;;;EACHC,SAA2B;EAClBC;EAEjB,YACEA,QACiBC,QACjB;SADiBA,SAAAA;AAEjB,SAAKD,SAASA,UAAUE,iBAAAA;EAC1B;EAEAC,MAAMC,MAAqB;AACzB,WAAO,IAAIC,SAASD,MAAM,KAAKE,UAAS,GAAI,KAAKL,MAAM;EACzD;EAEAM,QAAc;AACZ,SAAKR,QAAQS,QAAAA;AACb,SAAKT,SAAS;EAChB;EAEQO,YAAuB;AAC7B,QAAI,CAAC,KAAKP,QAAQ;AAChB,WAAKA,SAAS,IAAIU,6BAAU;QAC1BC,QAAQ,KAAKV,OAAOU;QACpBC,UAAU,KAAKX,OAAOW;QACtBC,aAAa,KAAKZ,OAAOY;MAC3B,CAAA;IACF;AACA,WAAO,KAAKb;EACd;AACF;;;AIrCA,yBAA2B;AAC3B,IAAAc,gBAAwB;AAcxB,IAAMC,aAAQC,cAAAA,SAAY,sBAAA;AAEnB,IAAMC,aAAN,MAAMA;EAjBb,OAiBaA;;;;;;EACX,YACmBC,aACAC,QACAC,QACjB;SAHiBF,cAAAA;SACAC,SAAAA;SACAC,SAAAA;EAChB;EAEH,MAAMC,QACJC,MACAC,SACwB;AACxBR,IAAAA,OAAM,cAAc,KAAKG,WAAW;AACpC,WAAO,KAAKM,OACV,0BACA;MAAE,iBAAiB,KAAKN;IAAY,GACpC,YAAA;AACE,UAAI;AACF,cAAMO,gBAAYC,+BAAAA;AAClB,cAAMC,UAAUC,cAAcN,MAAMG,WAAWF,OAAAA;AAC/C,cAAM,KAAKJ,OAAOE,QAAQ,KAAKH,aAAaS,OAAAA;AAC5C,eAAO;UAAEF;QAAU;MACrB,SAASI,OAAO;AACd,cAAM,IAAIC,WACR,yCAAyC,KAAKZ,WAAW,KACzD,KAAKA,aACL;UAAEa,OAAOF;QAAM,CAAA;MAEnB;IACF,CAAA;EAEJ;EAEA,MAAMG,aACJC,SAC6B;AAC7BlB,IAAAA,OAAM,gCAAgC,KAAKG,aAAae,QAAQC,MAAM;AACtE,WAAO,KAAKV,OACV,gCACA;MAAE,iBAAiB,KAAKN;MAAa,uBAAuBe,QAAQC;IAAO,GAC3E,YAAA;AACE,YAAMC,aAAoC,CAAA;AAC1C,YAAMC,SAAgC,CAAA;AAEtC,YAAMC,aAAuB,CAAA;AAC7B,YAAMC,WAAW,KAAKnB,OAAOmB,SAAQ;AACrC,iBAAWC,SAASN,SAAS;AAC3B,cAAMR,gBAAYC,+BAAAA;AAClBW,mBAAWG,KAAKf,SAAAA;AAChB,cAAME,UAAUC,cAAcW,MAAMjB,MAAMG,WAAWc,MAAMhB,OAAO;AAClEe,iBAASjB,QAAQ,KAAKH,aAAaS,OAAAA;MACrC;AAEA,UAAI;AACF,cAAMc,UAAU,MAAMH,SAASI,KAAI;AACnC,YAAI,CAACD,SAAS;AACZ,gBAAM,IAAIE,MAAM,wBAAA;QAClB;AAEA,iBAASC,IAAI,GAAGA,IAAIX,QAAQC,QAAQU,KAAK;AACvC,gBAAM,CAACC,GAAAA,IAAOJ,QAAQG,CAAAA;AACtB,cAAIC,KAAK;AACPT,mBAAOI,KAAK;cACVM,IAAIb,QAAQW,CAAAA,EAAGE;cACfC,MAAMF,IAAIG,QAAQ;cAClBC,SAASJ,IAAII;YACf,CAAA;UACF,OAAO;AACLd,uBAAWK,KAAK;cACdM,IAAIb,QAAQW,CAAAA,EAAGE;cACfrB,WAAWY,WAAWO,CAAAA;YACxB,CAAA;UACF;QACF;MACF,SAASf,OAAO;AACd,cAAM,IAAIC,WACR,+CAA+C,KAAKZ,WAAW,KAC/D,KAAKA,aACL;UAAEa,OAAOF;QAAM,CAAA;MAEnB;AAEA,aAAO;QAAEM;QAAYC;MAAO;IAC9B,CAAA;EAEJ;EAEQZ,OACNwB,MACAE,YACAC,IACY;AACZ,QAAI,CAAC,KAAK/B,OAAQ,QAAO+B,GAAAA;AACzB,WAAO,KAAK/B,OAAOgC,SAASJ,MAAM,CAACK,SAASF,GAAGE,IAAAA,GAAOH,UAAAA;EACxD;AACF;AAWA,SAAStB,cAAiBN,MAASG,WAAmBF,SAAwB;AAC5E,QAAM+B,WAAoC;IACxChC,MAAMiC,KAAKC,UAAUlC,IAAAA;IACrBG;EACF;AAEA,MAAIF,SAASkC,SAAS;AACpBH,aAASG,UAAUlC,QAAQkC;EAC7B;AACA,MAAIlC,SAAS2B,cAAcQ,OAAOC,KAAKpC,QAAQ2B,UAAU,EAAEhB,SAAS,GAAG;AACrEoB,aAASJ,aAAa3B,QAAQ2B;EAChC;AAEA,SAAOK,KAAKC,UAAUF,QAAAA;AACxB;AAdS1B;;;ACxHT,IAAMgC,oBAAoB;AAMnB,SAASC,qBAAAA;AACd,SAAO;IACLC,KAAKC,QAAQC,IAAIC,4BAA4BL;EAC/C;AACF;AAJgBC;;;ACFT,IAAMK,mBAAN,MAAMA;EAJb,OAIaA;;;;EACHC,SAA2C;EAClCC;EAEjB,YACEA,QACiBC,QACjB;SADiBA,SAAAA;AAEjB,SAAKD,SAASA,UAAUE,mBAAAA;EAC1B;EAEAC,MAAMC,MAAqB;AACzB,WAAO,IAAIC,WAAWD,MAAM,KAAKE,UAAS,GAAI,KAAKL,MAAM;EAC3D;EAEA,MAAMM,QAAuB;AAC3B,QAAI,KAAKR,QAAQ;AACf,YAAM,KAAKA,OAAOS,KAAI;AACtB,WAAKT,SAAS;IAChB;EACF;EAEQO,YAAuC;AAC7C,QAAI,CAAC,KAAKP,QAAQ;AAGhB,YAAMU,QAAQC,QAAQ,SAAA,EAAWC,WAAWD,QAAQ,SAAA;AACpD,WAAKX,SAAS,IAAIU,MAAM,KAAKT,OAAOY,GAAG;IACzC;AACA,WAAO,KAAKb;EACd;AACF;;;ACrCA,IAAAc,iBAA8B;AAmBvB,SAASC,kBAAkBC,SAAkC;AAClE,QAAMC,eAAWC,8BAAc,OAAA;AAC/B,QAAMC,WAAWH,SAASG,YAAYF,SAASE;AAE/C,UAAQA,UAAAA;IACN,KAAK;AACH,aAAO,IAAIC,eAAeJ,SAASK,KAAKL,SAASM,MAAAA;;;IAGnD,KAAK;AACH,aAAO,IAAIC,iBAAiBP,SAASQ,OAAOR,SAASM,MAAAA;;;;;IAKvD;AACE,YAAM,IAAIG,MAAM,gCAAgCN,QAAAA,GAAW;EAC/D;AACF;AAlBgBJ;;;ACnBhB,8BAAO;AACP,oBAAuD;AAOhD,SAASW,WAAWC,cAAoB;AAC7C,SAAOC,uBAAOC,IAAI,kBAAkBF,YAAAA,EAAc;AACpD;AAFgBD;AAIT,IAAMI,sBAAsBF,uBAAOC,IAAI,wBAAA;AAkBvC,SAASE,MAAMJ,cAAqB;AACzC,SAAO,CAACK,QAAQC,cAAcC,mBAAAA;AAC5B,UAAMC,QAAQR,eAAeD,WAAWC,YAAAA,IAAgBG;AACxD,UAAMM,WACJC,QAAQC,eAAeC,+BAAiBP,MAAAA,KAAW,oBAAIQ,IAAAA;AACzDJ,aAASK,IAAIP,gBAAgBC,KAAAA;AAC7BE,YAAQK,eAAeH,+BAAiBH,UAAUJ,MAAAA;AAElD,QAAIL,cAAc;AAChB,YAAMgB,YAAsBN,QAAQC,eAAeM,qCAAuBZ,MAAAA,KAAW,CAAA;AACrF,UAAI,CAACW,UAAUE,SAASlB,YAAAA,GAAe;AACrCU,gBAAQK,eAAeE,qCAAuB;aAAID;UAAWhB;WAAeK,MAAAA;MAC9E;IACF;EACF;AACF;AAfgBD;;;ACVT,SAASe,SAASC,WAA6BC,cAAqB;AACzE,QAAMC,QAAQD,eAAeE,WAAWF,YAAAA,IAAgBG;AACxD,SAAOJ,UAAUK,QAAeH,KAAAA;AAClC;AAHgBH;;;ACpBhB,IAAAO,gBAAwB;AAExB,IAAAC,iBAAmD;AACnD,IAAAC,iBAKO;AAIP,IAAMC,aAAQC,cAAAA,SAAY,gBAAA;AAUnB,IAAMC,aAAN,MAAMA;EAtBb,OAsBaA;;;EACHC,cAAc;EAEtB,MAAMC,OAAOC,SAA6BC,MAAgD;AACxF,QAAI,CAAC,KAAKH,aAAa;AACrB,YAAMI,SAASF,QAAQG,UAAUC,IAAIC,2BAAAA,IACjC,MAAML,QAAQG,UAAUG,QAAwBD,2BAAAA,IAChDE;AAEJ,YAAMC,SAASC,kBAAkB;QAAEP;MAAO,CAAA;AAC1CP,MAAAA,OAAM,yBAAA;AACNK,cAAQG,UAAUO,SAAS,eAAe;QAAEC,UAAUH;MAAO,CAAA;AAE7D,YAAMI,YAAQC,qCAAAA;AACd,YAAMC,iBAAaC,+BAAeH,OAAO,OAAA;AAEzC,UAAIE,WAAWE,OAAO,GAAG;AACvB,cAAMC,gBAAgB,MAAMjB,QAAQG,UAAUG,QAAuBY,mCAAAA;AACrE,cAAMC,iBAAiBF,cAAcG,UAAUC,wCAAAA;AAE/C,mBAAW,CAACC,cAAcC,SAAAA,KAAcT,YAAY;AAClD,gBAAMU,aAAa,MAAML,eAAeM,WAAWF,SAAAA;AACnD5B,UAAAA,OAAM,0CAAqC2B,cAAcE,UAAAA;AACzDxB,kBAAQG,UAAUO,SAASgB,WAAWJ,YAAAA,GAAe;YACnDX,UAAUH,OAAOmB,MAAMH,UAAAA;UACzB,CAAA;QACF;AAEA,YAAIV,WAAWE,SAAS,GAAG;AACzB,gBAAM,CAAA,EAAGO,SAAAA,IAAa;eAAIT,WAAWc,QAAO;YAAI,CAAA;AAChD,gBAAMJ,aAAa,MAAML,eAAeM,WAAWF,SAAAA;AACnD5B,UAAAA,OAAM,sCAAiC6B,UAAAA;AACvCxB,kBAAQG,UAAUO,SAASmB,qBAAqB;YAC9ClB,UAAUH,OAAOmB,MAAMH,UAAAA;UACzB,CAAA;QACF;MACF;AAEA,WAAK1B,cAAc;IACrB;AAEA,WAAOG,KAAAA;EACT;AACF;","names":["TopicClient","Symbol","for","import_client_sns","TopicError","Error","message","topic","options","name","debug","createDebug","SNS_MAX_BATCH_SIZE","SNSTopic","topicArn","client","tracer","publish","body","options","traced","result","send","PublishCommand","TopicArn","Message","JSON","stringify","MessageGroupId","groupId","MessageDeduplicationId","deduplicationId","Subject","subject","MessageAttributes","attributes","toSNSAttributes","undefined","messageId","MessageId","error","TopicError","cause","publishBatch","entries","length","successful","failed","i","chunk","slice","snsEntries","map","entry","Id","id","PublishBatchCommand","PublishBatchRequestEntries","s","Successful","push","f","Failed","code","Code","message","name","fn","withSpan","span","attrs","key","value","Object","DataType","StringValue","captureSNSConfig","region","process","env","AWS_REGION","AWS_DEFAULT_REGION","endpoint","AWS_ENDPOINT_URL","credentials","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","accessKeyId","secretAccessKey","undefined","SNSTopicClient","client","config","tracer","captureSNSConfig","topic","name","SNSTopic","getClient","close","destroy","SNSClient","region","endpoint","credentials","import_debug","debug","createDebug","RedisTopic","channelName","client","tracer","publish","body","options","traced","messageId","randomUUID","payload","buildEnvelope","error","TopicError","cause","publishBatch","entries","length","successful","failed","messageIds","pipeline","entry","push","results","exec","Error","i","err","id","code","name","message","attributes","fn","withSpan","span","envelope","JSON","stringify","subject","Object","keys","DEFAULT_REDIS_URL","captureRedisConfig","url","process","env","CELERITY_LOCAL_REDIS_URL","RedisTopicClient","client","config","tracer","captureRedisConfig","topic","name","RedisTopic","getClient","close","quit","Redis","require","default","url","import_config","createTopicClient","options","resolved","resolveConfig","provider","SNSTopicClient","aws","tracer","RedisTopicClient","local","Error","topicToken","resourceName","Symbol","for","DEFAULT_TOPIC_TOKEN","Topic","target","_propertyKey","parameterIndex","token","existing","Reflect","getOwnMetadata","INJECT_METADATA","Map","set","defineMetadata","resources","USE_RESOURCE_METADATA","includes","getTopic","container","resourceName","token","topicToken","DEFAULT_TOPIC_TOKEN","resolve","import_debug","import_common","import_config","debug","createDebug","TopicLayer","initialized","handle","context","next","tracer","container","has","TRACER_TOKEN","resolve","undefined","client","createTopicClient","register","useValue","links","captureResourceLinks","topicLinks","getLinksOfType","size","configService","CONFIG_SERVICE_TOKEN","resourceConfig","namespace","RESOURCE_CONFIG_NAMESPACE","resourceName","configKey","actualName","getOrThrow","topicToken","topic","entries","DEFAULT_TOPIC_TOKEN"]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/providers/sns/sns-topic.ts","../src/providers/sns/config.ts","../src/providers/sns/sns-topic-client.ts","../src/providers/redis/redis-topic.ts","../src/providers/redis/config.ts","../src/providers/redis/redis-topic-client.ts","../src/index.ts","../src/types.ts","../src/factory.ts","../src/decorators.ts","../src/helpers.ts","../src/layer.ts"],"sourcesContent":["/**\n * Base error for topic operations. Wraps provider SDK errors and includes\n * the topic identifier for context.\n */\nexport class TopicError extends Error {\n constructor(\n message: string,\n public readonly topic: string,\n options?: { cause?: unknown },\n ) {\n super(message, options);\n this.name = \"TopicError\";\n }\n}\n","import createDebug from \"debug\";\nimport {\n type SNSClient,\n PublishCommand,\n PublishBatchCommand,\n type MessageAttributeValue,\n type PublishBatchRequestEntry,\n} from \"@aws-sdk/client-sns\";\nimport type { CelerityTracer, CeleritySpan } from \"@celerity-sdk/types\";\nimport type {\n Topic,\n PublishOptions,\n PublishResult,\n BatchPublishEntry,\n BatchPublishResult,\n BatchPublishSuccess,\n BatchPublishFailure,\n} from \"../../types\";\nimport { TopicError } from \"../../errors\";\n\nconst debug = createDebug(\"celerity:topic:sns\");\n\nconst SNS_MAX_BATCH_SIZE = 10;\n\nexport class SNSTopic implements Topic {\n constructor(\n private readonly topicArn: string,\n private readonly client: SNSClient,\n private readonly tracer?: CelerityTracer,\n ) {}\n\n async publish<T = Record<string, unknown>>(\n body: T,\n options?: PublishOptions,\n ): Promise<PublishResult> {\n debug(\"publish %s\", this.topicArn);\n return this.traced(\"celerity.topic.publish\", { \"topic.arn\": this.topicArn }, async () => {\n try {\n const result = await this.client.send(\n new PublishCommand({\n TopicArn: this.topicArn,\n Message: JSON.stringify(body),\n MessageGroupId: options?.groupId,\n MessageDeduplicationId: options?.deduplicationId,\n Subject: options?.subject,\n MessageAttributes: options?.attributes\n ? toSNSAttributes(options.attributes)\n : undefined,\n }),\n );\n return { messageId: result.MessageId! };\n } catch (error) {\n throw new TopicError(\n `Failed to publish message to topic \"${this.topicArn}\"`,\n this.topicArn,\n { cause: error },\n );\n }\n });\n }\n\n async publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult> {\n debug(\"publishBatch %s (%d entries)\", this.topicArn, entries.length);\n return this.traced(\n \"celerity.topic.publish_batch\",\n { \"topic.arn\": this.topicArn, \"topic.message_count\": entries.length },\n async () => {\n const successful: BatchPublishSuccess[] = [];\n const failed: BatchPublishFailure[] = [];\n\n // Auto-chunk into groups of 10 (SNS limit)\n for (let i = 0; i < entries.length; i += SNS_MAX_BATCH_SIZE) {\n const chunk = entries.slice(i, i + SNS_MAX_BATCH_SIZE);\n const snsEntries: PublishBatchRequestEntry[] = chunk.map((entry) => ({\n Id: entry.id,\n Message: JSON.stringify(entry.body),\n MessageGroupId: entry.options?.groupId,\n MessageDeduplicationId: entry.options?.deduplicationId,\n Subject: entry.options?.subject,\n MessageAttributes: entry.options?.attributes\n ? toSNSAttributes(entry.options.attributes)\n : undefined,\n }));\n\n try {\n const result = await this.client.send(\n new PublishBatchCommand({\n TopicArn: this.topicArn,\n PublishBatchRequestEntries: snsEntries,\n }),\n );\n\n for (const s of result.Successful ?? []) {\n successful.push({ id: s.Id!, messageId: s.MessageId! });\n }\n for (const f of result.Failed ?? []) {\n failed.push({ id: f.Id!, code: f.Code!, message: f.Message ?? \"Unknown error\" });\n }\n } catch (error) {\n throw new TopicError(\n `Failed to publish message batch to topic \"${this.topicArn}\"`,\n this.topicArn,\n { cause: error },\n );\n }\n }\n\n return { successful, failed };\n },\n );\n }\n\n private traced<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span?: CeleritySpan) => Promise<T>,\n ): Promise<T> {\n if (!this.tracer) return fn();\n return this.tracer.withSpan(name, (span) => fn(span), attributes);\n }\n}\n\nfunction toSNSAttributes(attrs: Record<string, string>): Record<string, MessageAttributeValue> {\n const result: Record<string, MessageAttributeValue> = {};\n for (const [key, value] of Object.entries(attrs)) {\n result[key] = { DataType: \"String\", StringValue: value };\n }\n return result;\n}\n","import type { SNSTopicConfig } from \"./types\";\n\n/**\n * Captures SNS configuration from environment variables.\n * This is the only place that reads `process.env` for SNS config.\n */\nexport function captureSNSConfig(): SNSTopicConfig {\n return {\n region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,\n endpoint: process.env.CELERITY_AWS_SNS_ENDPOINT ?? process.env.AWS_ENDPOINT_URL,\n credentials:\n process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY\n ? {\n accessKeyId: process.env.AWS_ACCESS_KEY_ID,\n secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,\n }\n : undefined,\n };\n}\n","import { SNSClient } from \"@aws-sdk/client-sns\";\nimport type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient, Topic } from \"../../types\";\nimport { SNSTopic } from \"./sns-topic\";\nimport type { SNSTopicConfig } from \"./types\";\nimport { captureSNSConfig } from \"./config\";\n\nexport class SNSTopicClient implements TopicClient {\n private client: SNSClient | null = null;\n private readonly config: SNSTopicConfig;\n\n constructor(\n config?: SNSTopicConfig,\n private readonly tracer?: CelerityTracer,\n ) {\n this.config = config ?? captureSNSConfig();\n }\n\n topic(name: string): Topic {\n return new SNSTopic(name, this.getClient(), this.tracer);\n }\n\n close(): void {\n this.client?.destroy();\n this.client = null;\n }\n\n private getClient(): SNSClient {\n if (!this.client) {\n this.client = new SNSClient({\n region: this.config.region,\n endpoint: this.config.endpoint,\n credentials: this.config.credentials,\n });\n }\n return this.client;\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport createDebug from \"debug\";\nimport type Redis from \"ioredis\";\nimport type { CelerityTracer, CeleritySpan } from \"@celerity-sdk/types\";\nimport type {\n Topic,\n PublishOptions,\n PublishResult,\n BatchPublishEntry,\n BatchPublishResult,\n BatchPublishSuccess,\n BatchPublishFailure,\n} from \"../../types\";\nimport { TopicError } from \"../../errors\";\n\nconst debug = createDebug(\"celerity:topic:redis\");\n\nexport class RedisTopic implements Topic {\n constructor(\n private readonly channelName: string,\n private readonly client: Redis,\n private readonly tracer?: CelerityTracer,\n ) {}\n\n async publish<T = Record<string, unknown>>(\n body: T,\n options?: PublishOptions,\n ): Promise<PublishResult> {\n debug(\"publish %s\", this.channelName);\n return this.traced(\n \"celerity.topic.publish\",\n { \"topic.channel\": this.channelName },\n async () => {\n try {\n const messageId = randomUUID();\n const payload = buildEnvelope(body, messageId, options);\n await this.client.publish(this.channelName, payload);\n return { messageId };\n } catch (error) {\n throw new TopicError(\n `Failed to publish message to channel \"${this.channelName}\"`,\n this.channelName,\n { cause: error },\n );\n }\n },\n );\n }\n\n async publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult> {\n debug(\"publishBatch %s (%d entries)\", this.channelName, entries.length);\n return this.traced(\n \"celerity.topic.publish_batch\",\n { \"topic.channel\": this.channelName, \"topic.message_count\": entries.length },\n async () => {\n const successful: BatchPublishSuccess[] = [];\n const failed: BatchPublishFailure[] = [];\n\n const messageIds: string[] = [];\n const pipeline = this.client.pipeline();\n for (const entry of entries) {\n const messageId = randomUUID();\n messageIds.push(messageId);\n const payload = buildEnvelope(entry.body, messageId, entry.options);\n pipeline.publish(this.channelName, payload);\n }\n\n try {\n const results = await pipeline.exec();\n if (!results) {\n throw new Error(\"Pipeline returned null\");\n }\n\n for (let i = 0; i < entries.length; i++) {\n const [err] = results[i];\n if (err) {\n failed.push({\n id: entries[i].id,\n code: err.name ?? \"PipelineError\",\n message: err.message,\n });\n } else {\n successful.push({\n id: entries[i].id,\n messageId: messageIds[i],\n });\n }\n }\n } catch (error) {\n throw new TopicError(\n `Failed to publish message batch to channel \"${this.channelName}\"`,\n this.channelName,\n { cause: error },\n );\n }\n\n return { successful, failed };\n },\n );\n }\n\n private traced<T>(\n name: string,\n attributes: Record<string, string | number | boolean>,\n fn: (span?: CeleritySpan) => Promise<T>,\n ): Promise<T> {\n if (!this.tracer) return fn();\n return this.tracer.withSpan(name, (span) => fn(span), attributes);\n }\n}\n\n/**\n * Builds the JSON envelope published to the pub/sub channel.\n * The local-events bridge parses this envelope and extracts individual\n * fields when writing to target consumer streams.\n *\n * `messageId`, `subject`, and `attributes` are included in the envelope.\n * `groupId` and `deduplicationId` are silently ignored — ordering is\n * inherent in the single-instance bridge architecture.\n */\nfunction buildEnvelope<T>(body: T, messageId: string, options?: PublishOptions): string {\n const envelope: Record<string, unknown> = {\n body: JSON.stringify(body),\n messageId,\n };\n\n if (options?.subject) {\n envelope.subject = options.subject;\n }\n if (options?.attributes && Object.keys(options.attributes).length > 0) {\n envelope.attributes = options.attributes;\n }\n\n return JSON.stringify(envelope);\n}\n","import type { RedisTopicConfig } from \"./types\";\n\nconst DEFAULT_REDIS_URL = \"redis://localhost:6379\";\n\n/**\n * Captures Redis configuration from environment variables.\n * This is the only place that reads `process.env` for Redis config.\n */\nexport function captureRedisConfig(): RedisTopicConfig {\n return {\n url: process.env.CELERITY_REDIS_ENDPOINT ?? DEFAULT_REDIS_URL,\n };\n}\n","import type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient, Topic } from \"../../types\";\nimport { RedisTopic } from \"./redis-topic\";\nimport type { RedisTopicConfig } from \"./types\";\nimport { captureRedisConfig } from \"./config\";\n\nexport class RedisTopicClient implements TopicClient {\n private client: import(\"ioredis\").default | null = null;\n private ioredisModule: typeof import(\"ioredis\") | null = null;\n private readonly config: RedisTopicConfig;\n\n constructor(\n config?: RedisTopicConfig,\n private readonly tracer?: CelerityTracer,\n ) {\n this.config = config ?? captureRedisConfig();\n }\n\n topic(name: string): Topic {\n const channel = `celerity:topic:channel:${name}`;\n return new RedisTopic(channel, this.getClient(), this.tracer);\n }\n\n async close(): Promise<void> {\n if (this.client) {\n await this.client.quit();\n this.client = null;\n }\n }\n\n async ensureIoRedis(): Promise<void> {\n if (!this.ioredisModule) {\n const pkg = \"ioredis\";\n this.ioredisModule = (await import(pkg)) as typeof import(\"ioredis\");\n }\n }\n\n private getClient(): import(\"ioredis\").default {\n if (!this.client) {\n const ioredis = this.ioredisModule!;\n const Redis = ioredis.default ?? ioredis;\n this.client = new Redis(this.config.url as string);\n }\n return this.client!;\n }\n}\n","// Core types\nexport {\n TopicClient,\n type PublishOptions,\n type PublishResult,\n type BatchPublishEntry,\n type BatchPublishResult,\n type BatchPublishSuccess,\n type BatchPublishFailure,\n} from \"./types\";\n\nexport type { SNSTopicConfig } from \"./providers/sns/types\";\nexport type { RedisTopicConfig } from \"./providers/redis/types\";\n\nexport { createTopicClient } from \"./factory\";\nexport type { CreateTopicClientOptions } from \"./factory\";\n\nexport { Topic, topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\nexport { getTopic } from \"./helpers\";\n\nexport { TopicLayer } from \"./layer\";\n\nexport { TopicError } from \"./errors\";\n","import type { Closeable } from \"@celerity-sdk/types\";\n\nexport const TopicClient = Symbol.for(\"TopicClient\");\n\n/**\n * A topic client abstraction for pub/sub topics. Provides access to named\n * topics, each representing a logical destination for published messages.\n */\nexport interface TopicClient extends Closeable {\n /**\n * Retrieves a topic instance by its name. The returned topic is a lightweight\n * handle — no network calls are made until an operation is invoked.\n *\n * @param name The topic identifier (SNS topic ARN, Redis channel name, etc.).\n */\n topic(name: string): Topic;\n}\n\n/**\n * A topic represents a logical pub/sub destination. It provides methods for\n * publishing messages individually or in batches. The Celerity runtime handles\n * all subscription and consumption — this interface is producer-only.\n */\nexport interface Topic {\n /**\n * Publish a single message to the topic. The body is serialized to JSON.\n *\n * @param body The message body (serialized to JSON string for transport).\n * @param options Optional publish parameters such as FIFO ordering or subject.\n * @returns A promise that resolves to the publish result with the provider-assigned message ID.\n */\n publish<T = Record<string, unknown>>(body: T, options?: PublishOptions): Promise<PublishResult>;\n\n /**\n * Publish multiple messages in a single batch. The provider may impose batch\n * size limits (e.g. SNS limits to 10 per request) — the implementation\n * handles chunking transparently.\n *\n * @param entries The batch of messages to publish, each with a caller-assigned ID for correlation.\n * @returns A promise that resolves to the batch result with successful and failed entries.\n */\n publishBatch<T = Record<string, unknown>>(\n entries: BatchPublishEntry<T>[],\n ): Promise<BatchPublishResult>;\n}\n\n/**\n * Options for the publish and publishBatch operations.\n */\nexport type PublishOptions = {\n /** Message group ID for FIFO topics (SNS: MessageGroupId, Pub/Sub: ordering key). */\n groupId?: string;\n /** Deduplication ID for FIFO topics. */\n deduplicationId?: string;\n /** Message subject (SNS: Subject). */\n subject?: string;\n /** String key-value message attributes (metadata sent alongside the body). */\n attributes?: Record<string, string>;\n};\n\n/**\n * The result of a single publish call.\n */\nexport type PublishResult = {\n /** The provider-assigned message identifier. */\n messageId: string;\n};\n\n/**\n * A single entry in a batch publish request.\n */\nexport type BatchPublishEntry<T = Record<string, unknown>> = {\n /** Caller-assigned ID for correlating results within the batch. */\n id: string;\n /** The message body (serialized to JSON). */\n body: T;\n /** Per-message publish options. */\n options?: PublishOptions;\n};\n\n/**\n * The result of a batch publish operation.\n */\nexport type BatchPublishResult = {\n /** Entries that were published successfully. */\n successful: BatchPublishSuccess[];\n /** Entries that failed. */\n failed: BatchPublishFailure[];\n};\n\n/**\n * A successfully published entry from a batch operation.\n */\nexport type BatchPublishSuccess = {\n /** The caller-assigned entry ID. */\n id: string;\n /** The provider-assigned message ID. */\n messageId: string;\n};\n\n/**\n * A failed entry from a batch operation.\n */\nexport type BatchPublishFailure = {\n /** The caller-assigned entry ID. */\n id: string;\n /** Provider error code. */\n code: string;\n /** Human-readable error message. */\n message: string;\n};\n","import { resolveConfig } from \"@celerity-sdk/config\";\nimport type { CelerityTracer } from \"@celerity-sdk/types\";\nimport type { TopicClient } from \"./types\";\nimport type { SNSTopicConfig } from \"./providers/sns/types\";\nimport type { RedisTopicConfig } from \"./providers/redis/types\";\n\nexport type CreateTopicClientOptions = {\n /** Override provider selection. If omitted, derived from platform config. */\n provider?: \"aws\" | \"local\" | \"gcp\" | \"azure\";\n /** SNS-specific configuration overrides. */\n aws?: SNSTopicConfig;\n /** Redis-specific configuration overrides (local environment). */\n local?: RedisTopicConfig;\n /** Optional tracer for Celerity-level span instrumentation. */\n tracer?: CelerityTracer;\n};\n\nexport async function createTopicClient(options?: CreateTopicClientOptions): Promise<TopicClient> {\n const resolved = resolveConfig(\"topic\");\n const provider = options?.provider ?? resolved.provider;\n\n switch (provider) {\n case \"aws\": {\n const { SNSTopicClient } = await import(\"./providers/sns/sns-topic-client.js\");\n return new SNSTopicClient(options?.aws, options?.tracer);\n }\n // Local environments always use Redis pub/sub regardless of deploy target.\n // The Celerity CLI manages the Redis instance and local-events sidecar.\n case \"local\": {\n const { RedisTopicClient } = await import(\"./providers/redis/redis-topic-client.js\");\n const client = new RedisTopicClient(options?.local, options?.tracer);\n await client.ensureIoRedis();\n return client;\n }\n // case \"gcp\":\n // v1: Google Cloud Pub/Sub\n // case \"azure\":\n // v1: Azure Service Bus Topics\n default:\n throw new Error(`Unsupported topic provider: \"${provider}\"`);\n }\n}\n","import \"reflect-metadata\";\nimport { INJECT_METADATA, USE_RESOURCE_METADATA } from \"@celerity-sdk/common\";\nimport type { Topic as TopicType } from \"./types\";\n\n// Re-declare as interface so the type merges with the decorator function below.\n// eslint-disable-next-line @typescript-eslint/no-empty-object-type\nexport interface Topic extends TopicType {}\n\nexport function topicToken(resourceName: string): symbol {\n return Symbol.for(`celerity:topic:${resourceName}`);\n}\n\nexport const DEFAULT_TOPIC_TOKEN = Symbol.for(\"celerity:topic:default\");\n\n/**\n * Parameter decorator that injects a {@link Topic} instance for the given\n * blueprint resource. Writes both DI injection metadata and CLI resource-ref\n * metadata using well-known `Symbol.for()` keys (no dependency on core).\n *\n * When `resourceName` is omitted, the default topic token is used — this\n * auto-resolves when exactly one topic resource exists.\n *\n * @example\n * ```ts\n * @Controller(\"/orders\")\n * class OrderController {\n * constructor(@Topic(\"orderEvents\") private events: Topic) {}\n * }\n * ```\n */\nexport function Topic(resourceName?: string): ParameterDecorator {\n return (target, _propertyKey, parameterIndex) => {\n const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;\n const existing: Map<number, unknown> =\n Reflect.getOwnMetadata(INJECT_METADATA, target) ?? new Map();\n existing.set(parameterIndex, token);\n Reflect.defineMetadata(INJECT_METADATA, existing, target);\n\n if (resourceName) {\n const resources: string[] = Reflect.getOwnMetadata(USE_RESOURCE_METADATA, target) ?? [];\n if (!resources.includes(resourceName)) {\n Reflect.defineMetadata(USE_RESOURCE_METADATA, [...resources, resourceName], target);\n }\n }\n };\n}\n","import type { ServiceContainer } from \"@celerity-sdk/types\";\nimport type { Topic } from \"./types\";\nimport { topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\n/**\n * Resolves a {@link Topic} instance from the DI container.\n * For function-based handlers where parameter decorators aren't available.\n *\n * @param container - The service container (typically `context.container`).\n * @param resourceName - The blueprint resource name. Omit when exactly one\n * topic resource exists to use the default.\n *\n * @example\n * ```ts\n * const handler = createHttpHandler(async (req, ctx) => {\n * const events = await getTopic(ctx.container, \"orderEvents\");\n * await events.publish({ orderId: \"123\", action: \"created\" });\n * });\n * ```\n */\nexport function getTopic(container: ServiceContainer, resourceName?: string): Promise<Topic> {\n const token = resourceName ? topicToken(resourceName) : DEFAULT_TOPIC_TOKEN;\n return container.resolve<Topic>(token);\n}\n","import createDebug from \"debug\";\nimport type { CelerityLayer, BaseHandlerContext, CelerityTracer } from \"@celerity-sdk/types\";\nimport { TRACER_TOKEN, CONFIG_SERVICE_TOKEN } from \"@celerity-sdk/common\";\nimport {\n type ConfigService,\n captureResourceLinks,\n getLinksOfType,\n RESOURCE_CONFIG_NAMESPACE,\n} from \"@celerity-sdk/config\";\nimport { createTopicClient } from \"./factory\";\nimport { topicToken, DEFAULT_TOPIC_TOKEN } from \"./decorators\";\n\nconst debug = createDebug(\"celerity:topic\");\n\n/**\n * System layer that auto-registers {@link TopicClient} and per-resource\n * {@link Topic} handles in the DI container.\n *\n * Reads resource link topology from `CELERITY_RESOURCE_LINKS` and resolves\n * actual topic ARNs / channel names from the ConfigService \"resources\" namespace.\n * Must run after ConfigLayer in the layer pipeline.\n */\nexport class TopicLayer implements CelerityLayer<BaseHandlerContext> {\n private initialized = false;\n\n async handle(context: BaseHandlerContext, next: () => Promise<unknown>): Promise<unknown> {\n if (!this.initialized) {\n const tracer = context.container.has(TRACER_TOKEN)\n ? await context.container.resolve<CelerityTracer>(TRACER_TOKEN)\n : undefined;\n\n const client = await createTopicClient({ tracer });\n debug(\"registering TopicClient\");\n context.container.register(\"TopicClient\", { useValue: client });\n\n const links = captureResourceLinks();\n const topicLinks = getLinksOfType(links, \"topic\");\n\n if (topicLinks.size > 0) {\n const configService = await context.container.resolve<ConfigService>(CONFIG_SERVICE_TOKEN);\n const resourceConfig = configService.namespace(RESOURCE_CONFIG_NAMESPACE);\n\n for (const [resourceName, configKey] of topicLinks) {\n const actualName = await resourceConfig.getOrThrow(configKey);\n debug(\"registered topic resource %s → %s\", resourceName, actualName);\n context.container.register(topicToken(resourceName), {\n useValue: client.topic(actualName),\n });\n }\n\n if (topicLinks.size === 1) {\n const [, configKey] = [...topicLinks.entries()][0];\n const actualName = await resourceConfig.getOrThrow(configKey);\n debug(\"registered default topic → %s\", actualName);\n context.container.register(DEFAULT_TOPIC_TOKEN, {\n useValue: client.topic(actualName),\n });\n }\n }\n\n this.initialized = true;\n }\n\n return next();\n }\n}\n"],"mappings":"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","names":["TopicError","Error","message","topic","options","name","toSNSAttributes","attrs","result","key","value","Object","entries","DataType","StringValue","debug","SNS_MAX_BATCH_SIZE","SNSTopic","createDebug","topicArn","client","tracer","publish","body","options","traced","send","PublishCommand","TopicArn","Message","JSON","stringify","MessageGroupId","groupId","MessageDeduplicationId","deduplicationId","Subject","subject","MessageAttributes","attributes","undefined","messageId","MessageId","error","TopicError","cause","publishBatch","length","successful","failed","i","chunk","slice","snsEntries","map","entry","Id","id","PublishBatchCommand","PublishBatchRequestEntries","s","Successful","push","f","Failed","code","Code","message","name","fn","withSpan","span","captureSNSConfig","region","process","env","AWS_REGION","AWS_DEFAULT_REGION","endpoint","CELERITY_AWS_SNS_ENDPOINT","AWS_ENDPOINT_URL","credentials","AWS_ACCESS_KEY_ID","AWS_SECRET_ACCESS_KEY","accessKeyId","secretAccessKey","undefined","import_client_sns","SNSTopicClient","client","config","tracer","captureSNSConfig","topic","name","SNSTopic","getClient","close","destroy","SNSClient","region","endpoint","credentials","buildEnvelope","body","messageId","options","envelope","JSON","stringify","subject","attributes","Object","keys","length","import_debug","debug","RedisTopic","createDebug","channelName","client","tracer","publish","traced","randomUUID","payload","error","TopicError","cause","publishBatch","entries","successful","failed","messageIds","pipeline","entry","push","results","exec","Error","i","err","id","code","name","message","fn","withSpan","span","captureRedisConfig","url","process","env","CELERITY_REDIS_ENDPOINT","DEFAULT_REDIS_URL","init_config","RedisTopicClient","init_config","client","ioredisModule","config","tracer","captureRedisConfig","topic","name","channel","RedisTopic","getClient","close","quit","ensureIoRedis","pkg","ioredis","Redis","default","url","TopicClient","Symbol","for","import_config","createTopicClient","options","resolved","resolveConfig","provider","SNSTopicClient","aws","tracer","RedisTopicClient","client","local","ensureIoRedis","Error","topicToken","resourceName","Symbol","for","DEFAULT_TOPIC_TOKEN","Topic","target","_propertyKey","parameterIndex","token","existing","Reflect","getOwnMetadata","INJECT_METADATA","Map","set","defineMetadata","resources","USE_RESOURCE_METADATA","includes","getTopic","container","resourceName","token","topicToken","DEFAULT_TOPIC_TOKEN","resolve","import_debug","import_common","import_config","debug","createDebug","TopicLayer","initialized","handle","context","next","tracer","container","has","TRACER_TOKEN","resolve","undefined","client","createTopicClient","register","useValue","links","captureResourceLinks","topicLinks","getLinksOfType","size","configService","CONFIG_SERVICE_TOKEN","resourceConfig","namespace","RESOURCE_CONFIG_NAMESPACE","resourceName","configKey","actualName","getOrThrow","topicToken","topic","entries","DEFAULT_TOPIC_TOKEN"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -111,31 +111,11 @@ type SNSTopicConfig = {
|
|
|
111
111
|
};
|
|
112
112
|
};
|
|
113
113
|
|
|
114
|
-
declare class SNSTopicClient implements TopicClient {
|
|
115
|
-
private readonly tracer?;
|
|
116
|
-
private client;
|
|
117
|
-
private readonly config;
|
|
118
|
-
constructor(config?: SNSTopicConfig, tracer?: CelerityTracer | undefined);
|
|
119
|
-
topic(name: string): Topic$1;
|
|
120
|
-
close(): void;
|
|
121
|
-
private getClient;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
114
|
type RedisTopicConfig = {
|
|
125
115
|
/** Redis/Valkey connection URL. Defaults to "redis://localhost:6379". */
|
|
126
116
|
url?: string;
|
|
127
117
|
};
|
|
128
118
|
|
|
129
|
-
declare class RedisTopicClient implements TopicClient {
|
|
130
|
-
private readonly tracer?;
|
|
131
|
-
private client;
|
|
132
|
-
private readonly config;
|
|
133
|
-
constructor(config?: RedisTopicConfig, tracer?: CelerityTracer | undefined);
|
|
134
|
-
topic(name: string): Topic$1;
|
|
135
|
-
close(): Promise<void>;
|
|
136
|
-
private getClient;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
119
|
type CreateTopicClientOptions = {
|
|
140
120
|
/** Override provider selection. If omitted, derived from platform config. */
|
|
141
121
|
provider?: "aws" | "local" | "gcp" | "azure";
|
|
@@ -146,7 +126,7 @@ type CreateTopicClientOptions = {
|
|
|
146
126
|
/** Optional tracer for Celerity-level span instrumentation. */
|
|
147
127
|
tracer?: CelerityTracer;
|
|
148
128
|
};
|
|
149
|
-
declare function createTopicClient(options?: CreateTopicClientOptions): TopicClient
|
|
129
|
+
declare function createTopicClient(options?: CreateTopicClientOptions): Promise<TopicClient>;
|
|
150
130
|
|
|
151
131
|
declare function topicToken(resourceName: string): symbol;
|
|
152
132
|
declare const DEFAULT_TOPIC_TOKEN: unique symbol;
|
|
@@ -212,4 +192,4 @@ declare class TopicError extends Error {
|
|
|
212
192
|
});
|
|
213
193
|
}
|
|
214
194
|
|
|
215
|
-
export { type BatchPublishEntry, type BatchPublishFailure, type BatchPublishResult, type BatchPublishSuccess, type CreateTopicClientOptions, DEFAULT_TOPIC_TOKEN, type PublishOptions, type PublishResult,
|
|
195
|
+
export { type BatchPublishEntry, type BatchPublishFailure, type BatchPublishResult, type BatchPublishSuccess, type CreateTopicClientOptions, DEFAULT_TOPIC_TOKEN, type PublishOptions, type PublishResult, type RedisTopicConfig, type SNSTopicConfig, Topic, TopicClient, TopicError, TopicLayer, createTopicClient, getTopic, topicToken };
|
package/dist/index.js
CHANGED
|
@@ -1,335 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
}) : x)(function(x) {
|
|
6
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
7
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
8
|
-
});
|
|
1
|
+
import {
|
|
2
|
+
TopicError,
|
|
3
|
+
__name
|
|
4
|
+
} from "./chunk-T4LCI5X5.js";
|
|
9
5
|
|
|
10
6
|
// src/types.ts
|
|
11
7
|
var TopicClient = /* @__PURE__ */ Symbol.for("TopicClient");
|
|
12
8
|
|
|
13
|
-
// src/providers/sns/sns-topic-client.ts
|
|
14
|
-
import { SNSClient } from "@aws-sdk/client-sns";
|
|
15
|
-
|
|
16
|
-
// src/providers/sns/sns-topic.ts
|
|
17
|
-
import createDebug from "debug";
|
|
18
|
-
import { PublishCommand, PublishBatchCommand } from "@aws-sdk/client-sns";
|
|
19
|
-
|
|
20
|
-
// src/errors.ts
|
|
21
|
-
var TopicError = class extends Error {
|
|
22
|
-
static {
|
|
23
|
-
__name(this, "TopicError");
|
|
24
|
-
}
|
|
25
|
-
topic;
|
|
26
|
-
constructor(message, topic, options) {
|
|
27
|
-
super(message, options), this.topic = topic;
|
|
28
|
-
this.name = "TopicError";
|
|
29
|
-
}
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// src/providers/sns/sns-topic.ts
|
|
33
|
-
var debug = createDebug("celerity:topic:sns");
|
|
34
|
-
var SNS_MAX_BATCH_SIZE = 10;
|
|
35
|
-
var SNSTopic = class {
|
|
36
|
-
static {
|
|
37
|
-
__name(this, "SNSTopic");
|
|
38
|
-
}
|
|
39
|
-
topicArn;
|
|
40
|
-
client;
|
|
41
|
-
tracer;
|
|
42
|
-
constructor(topicArn, client, tracer) {
|
|
43
|
-
this.topicArn = topicArn;
|
|
44
|
-
this.client = client;
|
|
45
|
-
this.tracer = tracer;
|
|
46
|
-
}
|
|
47
|
-
async publish(body, options) {
|
|
48
|
-
debug("publish %s", this.topicArn);
|
|
49
|
-
return this.traced("celerity.topic.publish", {
|
|
50
|
-
"topic.arn": this.topicArn
|
|
51
|
-
}, async () => {
|
|
52
|
-
try {
|
|
53
|
-
const result = await this.client.send(new PublishCommand({
|
|
54
|
-
TopicArn: this.topicArn,
|
|
55
|
-
Message: JSON.stringify(body),
|
|
56
|
-
MessageGroupId: options?.groupId,
|
|
57
|
-
MessageDeduplicationId: options?.deduplicationId,
|
|
58
|
-
Subject: options?.subject,
|
|
59
|
-
MessageAttributes: options?.attributes ? toSNSAttributes(options.attributes) : void 0
|
|
60
|
-
}));
|
|
61
|
-
return {
|
|
62
|
-
messageId: result.MessageId
|
|
63
|
-
};
|
|
64
|
-
} catch (error) {
|
|
65
|
-
throw new TopicError(`Failed to publish message to topic "${this.topicArn}"`, this.topicArn, {
|
|
66
|
-
cause: error
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
async publishBatch(entries) {
|
|
72
|
-
debug("publishBatch %s (%d entries)", this.topicArn, entries.length);
|
|
73
|
-
return this.traced("celerity.topic.publish_batch", {
|
|
74
|
-
"topic.arn": this.topicArn,
|
|
75
|
-
"topic.message_count": entries.length
|
|
76
|
-
}, async () => {
|
|
77
|
-
const successful = [];
|
|
78
|
-
const failed = [];
|
|
79
|
-
for (let i = 0; i < entries.length; i += SNS_MAX_BATCH_SIZE) {
|
|
80
|
-
const chunk = entries.slice(i, i + SNS_MAX_BATCH_SIZE);
|
|
81
|
-
const snsEntries = chunk.map((entry) => ({
|
|
82
|
-
Id: entry.id,
|
|
83
|
-
Message: JSON.stringify(entry.body),
|
|
84
|
-
MessageGroupId: entry.options?.groupId,
|
|
85
|
-
MessageDeduplicationId: entry.options?.deduplicationId,
|
|
86
|
-
Subject: entry.options?.subject,
|
|
87
|
-
MessageAttributes: entry.options?.attributes ? toSNSAttributes(entry.options.attributes) : void 0
|
|
88
|
-
}));
|
|
89
|
-
try {
|
|
90
|
-
const result = await this.client.send(new PublishBatchCommand({
|
|
91
|
-
TopicArn: this.topicArn,
|
|
92
|
-
PublishBatchRequestEntries: snsEntries
|
|
93
|
-
}));
|
|
94
|
-
for (const s of result.Successful ?? []) {
|
|
95
|
-
successful.push({
|
|
96
|
-
id: s.Id,
|
|
97
|
-
messageId: s.MessageId
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
for (const f of result.Failed ?? []) {
|
|
101
|
-
failed.push({
|
|
102
|
-
id: f.Id,
|
|
103
|
-
code: f.Code,
|
|
104
|
-
message: f.Message ?? "Unknown error"
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
} catch (error) {
|
|
108
|
-
throw new TopicError(`Failed to publish message batch to topic "${this.topicArn}"`, this.topicArn, {
|
|
109
|
-
cause: error
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return {
|
|
114
|
-
successful,
|
|
115
|
-
failed
|
|
116
|
-
};
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
traced(name, attributes, fn) {
|
|
120
|
-
if (!this.tracer) return fn();
|
|
121
|
-
return this.tracer.withSpan(name, (span) => fn(span), attributes);
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
function toSNSAttributes(attrs) {
|
|
125
|
-
const result = {};
|
|
126
|
-
for (const [key, value] of Object.entries(attrs)) {
|
|
127
|
-
result[key] = {
|
|
128
|
-
DataType: "String",
|
|
129
|
-
StringValue: value
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
return result;
|
|
133
|
-
}
|
|
134
|
-
__name(toSNSAttributes, "toSNSAttributes");
|
|
135
|
-
|
|
136
|
-
// src/providers/sns/config.ts
|
|
137
|
-
function captureSNSConfig() {
|
|
138
|
-
return {
|
|
139
|
-
region: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION,
|
|
140
|
-
endpoint: process.env.AWS_ENDPOINT_URL,
|
|
141
|
-
credentials: process.env.AWS_ACCESS_KEY_ID && process.env.AWS_SECRET_ACCESS_KEY ? {
|
|
142
|
-
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
|
|
143
|
-
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
|
|
144
|
-
} : void 0
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
__name(captureSNSConfig, "captureSNSConfig");
|
|
148
|
-
|
|
149
|
-
// src/providers/sns/sns-topic-client.ts
|
|
150
|
-
var SNSTopicClient = class {
|
|
151
|
-
static {
|
|
152
|
-
__name(this, "SNSTopicClient");
|
|
153
|
-
}
|
|
154
|
-
tracer;
|
|
155
|
-
client = null;
|
|
156
|
-
config;
|
|
157
|
-
constructor(config, tracer) {
|
|
158
|
-
this.tracer = tracer;
|
|
159
|
-
this.config = config ?? captureSNSConfig();
|
|
160
|
-
}
|
|
161
|
-
topic(name) {
|
|
162
|
-
return new SNSTopic(name, this.getClient(), this.tracer);
|
|
163
|
-
}
|
|
164
|
-
close() {
|
|
165
|
-
this.client?.destroy();
|
|
166
|
-
this.client = null;
|
|
167
|
-
}
|
|
168
|
-
getClient() {
|
|
169
|
-
if (!this.client) {
|
|
170
|
-
this.client = new SNSClient({
|
|
171
|
-
region: this.config.region,
|
|
172
|
-
endpoint: this.config.endpoint,
|
|
173
|
-
credentials: this.config.credentials
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
return this.client;
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
|
|
180
|
-
// src/providers/redis/redis-topic.ts
|
|
181
|
-
import { randomUUID } from "crypto";
|
|
182
|
-
import createDebug2 from "debug";
|
|
183
|
-
var debug2 = createDebug2("celerity:topic:redis");
|
|
184
|
-
var RedisTopic = class {
|
|
185
|
-
static {
|
|
186
|
-
__name(this, "RedisTopic");
|
|
187
|
-
}
|
|
188
|
-
channelName;
|
|
189
|
-
client;
|
|
190
|
-
tracer;
|
|
191
|
-
constructor(channelName, client, tracer) {
|
|
192
|
-
this.channelName = channelName;
|
|
193
|
-
this.client = client;
|
|
194
|
-
this.tracer = tracer;
|
|
195
|
-
}
|
|
196
|
-
async publish(body, options) {
|
|
197
|
-
debug2("publish %s", this.channelName);
|
|
198
|
-
return this.traced("celerity.topic.publish", {
|
|
199
|
-
"topic.channel": this.channelName
|
|
200
|
-
}, async () => {
|
|
201
|
-
try {
|
|
202
|
-
const messageId = randomUUID();
|
|
203
|
-
const payload = buildEnvelope(body, messageId, options);
|
|
204
|
-
await this.client.publish(this.channelName, payload);
|
|
205
|
-
return {
|
|
206
|
-
messageId
|
|
207
|
-
};
|
|
208
|
-
} catch (error) {
|
|
209
|
-
throw new TopicError(`Failed to publish message to channel "${this.channelName}"`, this.channelName, {
|
|
210
|
-
cause: error
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
});
|
|
214
|
-
}
|
|
215
|
-
async publishBatch(entries) {
|
|
216
|
-
debug2("publishBatch %s (%d entries)", this.channelName, entries.length);
|
|
217
|
-
return this.traced("celerity.topic.publish_batch", {
|
|
218
|
-
"topic.channel": this.channelName,
|
|
219
|
-
"topic.message_count": entries.length
|
|
220
|
-
}, async () => {
|
|
221
|
-
const successful = [];
|
|
222
|
-
const failed = [];
|
|
223
|
-
const messageIds = [];
|
|
224
|
-
const pipeline = this.client.pipeline();
|
|
225
|
-
for (const entry of entries) {
|
|
226
|
-
const messageId = randomUUID();
|
|
227
|
-
messageIds.push(messageId);
|
|
228
|
-
const payload = buildEnvelope(entry.body, messageId, entry.options);
|
|
229
|
-
pipeline.publish(this.channelName, payload);
|
|
230
|
-
}
|
|
231
|
-
try {
|
|
232
|
-
const results = await pipeline.exec();
|
|
233
|
-
if (!results) {
|
|
234
|
-
throw new Error("Pipeline returned null");
|
|
235
|
-
}
|
|
236
|
-
for (let i = 0; i < entries.length; i++) {
|
|
237
|
-
const [err] = results[i];
|
|
238
|
-
if (err) {
|
|
239
|
-
failed.push({
|
|
240
|
-
id: entries[i].id,
|
|
241
|
-
code: err.name ?? "PipelineError",
|
|
242
|
-
message: err.message
|
|
243
|
-
});
|
|
244
|
-
} else {
|
|
245
|
-
successful.push({
|
|
246
|
-
id: entries[i].id,
|
|
247
|
-
messageId: messageIds[i]
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} catch (error) {
|
|
252
|
-
throw new TopicError(`Failed to publish message batch to channel "${this.channelName}"`, this.channelName, {
|
|
253
|
-
cause: error
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
successful,
|
|
258
|
-
failed
|
|
259
|
-
};
|
|
260
|
-
});
|
|
261
|
-
}
|
|
262
|
-
traced(name, attributes, fn) {
|
|
263
|
-
if (!this.tracer) return fn();
|
|
264
|
-
return this.tracer.withSpan(name, (span) => fn(span), attributes);
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
function buildEnvelope(body, messageId, options) {
|
|
268
|
-
const envelope = {
|
|
269
|
-
body: JSON.stringify(body),
|
|
270
|
-
messageId
|
|
271
|
-
};
|
|
272
|
-
if (options?.subject) {
|
|
273
|
-
envelope.subject = options.subject;
|
|
274
|
-
}
|
|
275
|
-
if (options?.attributes && Object.keys(options.attributes).length > 0) {
|
|
276
|
-
envelope.attributes = options.attributes;
|
|
277
|
-
}
|
|
278
|
-
return JSON.stringify(envelope);
|
|
279
|
-
}
|
|
280
|
-
__name(buildEnvelope, "buildEnvelope");
|
|
281
|
-
|
|
282
|
-
// src/providers/redis/config.ts
|
|
283
|
-
var DEFAULT_REDIS_URL = "redis://localhost:6379";
|
|
284
|
-
function captureRedisConfig() {
|
|
285
|
-
return {
|
|
286
|
-
url: process.env.CELERITY_LOCAL_REDIS_URL ?? DEFAULT_REDIS_URL
|
|
287
|
-
};
|
|
288
|
-
}
|
|
289
|
-
__name(captureRedisConfig, "captureRedisConfig");
|
|
290
|
-
|
|
291
|
-
// src/providers/redis/redis-topic-client.ts
|
|
292
|
-
var RedisTopicClient = class {
|
|
293
|
-
static {
|
|
294
|
-
__name(this, "RedisTopicClient");
|
|
295
|
-
}
|
|
296
|
-
tracer;
|
|
297
|
-
client = null;
|
|
298
|
-
config;
|
|
299
|
-
constructor(config, tracer) {
|
|
300
|
-
this.tracer = tracer;
|
|
301
|
-
this.config = config ?? captureRedisConfig();
|
|
302
|
-
}
|
|
303
|
-
topic(name) {
|
|
304
|
-
return new RedisTopic(name, this.getClient(), this.tracer);
|
|
305
|
-
}
|
|
306
|
-
async close() {
|
|
307
|
-
if (this.client) {
|
|
308
|
-
await this.client.quit();
|
|
309
|
-
this.client = null;
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
getClient() {
|
|
313
|
-
if (!this.client) {
|
|
314
|
-
const Redis = __require("ioredis").default ?? __require("ioredis");
|
|
315
|
-
this.client = new Redis(this.config.url);
|
|
316
|
-
}
|
|
317
|
-
return this.client;
|
|
318
|
-
}
|
|
319
|
-
};
|
|
320
|
-
|
|
321
9
|
// src/factory.ts
|
|
322
10
|
import { resolveConfig } from "@celerity-sdk/config";
|
|
323
|
-
function createTopicClient(options) {
|
|
11
|
+
async function createTopicClient(options) {
|
|
324
12
|
const resolved = resolveConfig("topic");
|
|
325
13
|
const provider = options?.provider ?? resolved.provider;
|
|
326
14
|
switch (provider) {
|
|
327
|
-
case "aws":
|
|
15
|
+
case "aws": {
|
|
16
|
+
const { SNSTopicClient } = await import("./sns-topic-client-643MNMBF.js");
|
|
328
17
|
return new SNSTopicClient(options?.aws, options?.tracer);
|
|
18
|
+
}
|
|
329
19
|
// Local environments always use Redis pub/sub regardless of deploy target.
|
|
330
20
|
// The Celerity CLI manages the Redis instance and local-events sidecar.
|
|
331
|
-
case "local":
|
|
332
|
-
|
|
21
|
+
case "local": {
|
|
22
|
+
const { RedisTopicClient } = await import("./redis-topic-client-SPF63T34.js");
|
|
23
|
+
const client = new RedisTopicClient(options?.local, options?.tracer);
|
|
24
|
+
await client.ensureIoRedis();
|
|
25
|
+
return client;
|
|
26
|
+
}
|
|
333
27
|
// case "gcp":
|
|
334
28
|
// v1: Google Cloud Pub/Sub
|
|
335
29
|
// case "azure":
|
|
@@ -375,10 +69,10 @@ function getTopic(container, resourceName) {
|
|
|
375
69
|
__name(getTopic, "getTopic");
|
|
376
70
|
|
|
377
71
|
// src/layer.ts
|
|
378
|
-
import
|
|
72
|
+
import createDebug from "debug";
|
|
379
73
|
import { TRACER_TOKEN, CONFIG_SERVICE_TOKEN } from "@celerity-sdk/common";
|
|
380
74
|
import { captureResourceLinks, getLinksOfType, RESOURCE_CONFIG_NAMESPACE } from "@celerity-sdk/config";
|
|
381
|
-
var
|
|
75
|
+
var debug = createDebug("celerity:topic");
|
|
382
76
|
var TopicLayer = class {
|
|
383
77
|
static {
|
|
384
78
|
__name(this, "TopicLayer");
|
|
@@ -387,10 +81,10 @@ var TopicLayer = class {
|
|
|
387
81
|
async handle(context, next) {
|
|
388
82
|
if (!this.initialized) {
|
|
389
83
|
const tracer = context.container.has(TRACER_TOKEN) ? await context.container.resolve(TRACER_TOKEN) : void 0;
|
|
390
|
-
const client = createTopicClient({
|
|
84
|
+
const client = await createTopicClient({
|
|
391
85
|
tracer
|
|
392
86
|
});
|
|
393
|
-
|
|
87
|
+
debug("registering TopicClient");
|
|
394
88
|
context.container.register("TopicClient", {
|
|
395
89
|
useValue: client
|
|
396
90
|
});
|
|
@@ -401,7 +95,7 @@ var TopicLayer = class {
|
|
|
401
95
|
const resourceConfig = configService.namespace(RESOURCE_CONFIG_NAMESPACE);
|
|
402
96
|
for (const [resourceName, configKey] of topicLinks) {
|
|
403
97
|
const actualName = await resourceConfig.getOrThrow(configKey);
|
|
404
|
-
|
|
98
|
+
debug("registered topic resource %s \u2192 %s", resourceName, actualName);
|
|
405
99
|
context.container.register(topicToken(resourceName), {
|
|
406
100
|
useValue: client.topic(actualName)
|
|
407
101
|
});
|
|
@@ -411,7 +105,7 @@ var TopicLayer = class {
|
|
|
411
105
|
...topicLinks.entries()
|
|
412
106
|
][0];
|
|
413
107
|
const actualName = await resourceConfig.getOrThrow(configKey);
|
|
414
|
-
|
|
108
|
+
debug("registered default topic \u2192 %s", actualName);
|
|
415
109
|
context.container.register(DEFAULT_TOPIC_TOKEN, {
|
|
416
110
|
useValue: client.topic(actualName)
|
|
417
111
|
});
|
|
@@ -424,8 +118,6 @@ var TopicLayer = class {
|
|
|
424
118
|
};
|
|
425
119
|
export {
|
|
426
120
|
DEFAULT_TOPIC_TOKEN,
|
|
427
|
-
RedisTopicClient,
|
|
428
|
-
SNSTopicClient,
|
|
429
121
|
Topic,
|
|
430
122
|
TopicClient,
|
|
431
123
|
TopicError,
|