@crossdelta/cloudevents 0.5.7 → 0.6.1

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.
Files changed (70) hide show
  1. package/README.md +22 -2
  2. package/dist/index.cjs +1602 -0
  3. package/dist/index.d.mts +812 -0
  4. package/dist/index.d.ts +812 -9
  5. package/dist/index.js +1574 -6
  6. package/package.json +20 -18
  7. package/dist/adapters/cloudevents/cloudevents.d.ts +0 -14
  8. package/dist/adapters/cloudevents/cloudevents.js +0 -58
  9. package/dist/adapters/cloudevents/index.d.ts +0 -8
  10. package/dist/adapters/cloudevents/index.js +0 -7
  11. package/dist/adapters/cloudevents/parsers/binary-mode.d.ts +0 -5
  12. package/dist/adapters/cloudevents/parsers/binary-mode.js +0 -32
  13. package/dist/adapters/cloudevents/parsers/pubsub.d.ts +0 -5
  14. package/dist/adapters/cloudevents/parsers/pubsub.js +0 -54
  15. package/dist/adapters/cloudevents/parsers/raw-event.d.ts +0 -5
  16. package/dist/adapters/cloudevents/parsers/raw-event.js +0 -17
  17. package/dist/adapters/cloudevents/parsers/structured-mode.d.ts +0 -5
  18. package/dist/adapters/cloudevents/parsers/structured-mode.js +0 -18
  19. package/dist/adapters/cloudevents/types.d.ts +0 -29
  20. package/dist/adapters/cloudevents/types.js +0 -1
  21. package/dist/domain/contract-helper.d.ts +0 -63
  22. package/dist/domain/contract-helper.js +0 -61
  23. package/dist/domain/discovery.d.ts +0 -24
  24. package/dist/domain/discovery.js +0 -201
  25. package/dist/domain/handler-factory.d.ts +0 -49
  26. package/dist/domain/handler-factory.js +0 -169
  27. package/dist/domain/index.d.ts +0 -6
  28. package/dist/domain/index.js +0 -4
  29. package/dist/domain/types.d.ts +0 -108
  30. package/dist/domain/types.js +0 -6
  31. package/dist/domain/validation.d.ts +0 -37
  32. package/dist/domain/validation.js +0 -53
  33. package/dist/infrastructure/errors.d.ts +0 -53
  34. package/dist/infrastructure/errors.js +0 -54
  35. package/dist/infrastructure/index.d.ts +0 -4
  36. package/dist/infrastructure/index.js +0 -2
  37. package/dist/infrastructure/logging.d.ts +0 -18
  38. package/dist/infrastructure/logging.js +0 -27
  39. package/dist/middlewares/cloudevents-middleware.d.ts +0 -171
  40. package/dist/middlewares/cloudevents-middleware.js +0 -276
  41. package/dist/middlewares/index.d.ts +0 -1
  42. package/dist/middlewares/index.js +0 -1
  43. package/dist/processing/dlq-safe.d.ts +0 -82
  44. package/dist/processing/dlq-safe.js +0 -108
  45. package/dist/processing/handler-cache.d.ts +0 -36
  46. package/dist/processing/handler-cache.js +0 -94
  47. package/dist/processing/idempotency.d.ts +0 -51
  48. package/dist/processing/idempotency.js +0 -112
  49. package/dist/processing/index.d.ts +0 -4
  50. package/dist/processing/index.js +0 -4
  51. package/dist/processing/validation.d.ts +0 -41
  52. package/dist/processing/validation.js +0 -48
  53. package/dist/publishing/index.d.ts +0 -2
  54. package/dist/publishing/index.js +0 -2
  55. package/dist/publishing/nats.publisher.d.ts +0 -19
  56. package/dist/publishing/nats.publisher.js +0 -115
  57. package/dist/publishing/pubsub.publisher.d.ts +0 -39
  58. package/dist/publishing/pubsub.publisher.js +0 -84
  59. package/dist/transports/nats/base-message-processor.d.ts +0 -44
  60. package/dist/transports/nats/base-message-processor.js +0 -118
  61. package/dist/transports/nats/index.d.ts +0 -5
  62. package/dist/transports/nats/index.js +0 -5
  63. package/dist/transports/nats/jetstream-consumer.d.ts +0 -217
  64. package/dist/transports/nats/jetstream-consumer.js +0 -367
  65. package/dist/transports/nats/jetstream-message-processor.d.ts +0 -9
  66. package/dist/transports/nats/jetstream-message-processor.js +0 -32
  67. package/dist/transports/nats/nats-consumer.d.ts +0 -36
  68. package/dist/transports/nats/nats-consumer.js +0 -84
  69. package/dist/transports/nats/nats-message-processor.d.ts +0 -11
  70. package/dist/transports/nats/nats-message-processor.js +0 -32
@@ -1,108 +0,0 @@
1
- /**
2
- * DLQ-Safe mode utilities
3
- * Handles quarantine and error publishing for CloudEvents processing
4
- */
5
- import { logger } from '../infrastructure';
6
- import { publishRawEvent } from '../publishing';
7
- /**
8
- * Checks if DLQ-Safe mode is enabled
9
- */
10
- export const isDlqSafeMode = (options) => {
11
- return !!(options.quarantineTopic || options.errorTopic);
12
- };
13
- /**
14
- * Publishes a message to the quarantine topic for "poison messages" that can't be processed
15
- */
16
- export const quarantineMessage = async (processingContext, reason, options, error) => {
17
- if (!options.quarantineTopic) {
18
- logger.warn('No quarantine topic configured, skipping quarantine');
19
- return;
20
- }
21
- try {
22
- // Serialize error properly - handle ValidationError specially
23
- let serializedError;
24
- if (error) {
25
- if (typeof error === 'object' && error !== null && 'type' in error && error.type === 'ValidationError') {
26
- serializedError = JSON.stringify(error, null, 2);
27
- }
28
- else if (error instanceof Error) {
29
- serializedError = JSON.stringify({
30
- name: error.name,
31
- message: error.message,
32
- stack: error.stack,
33
- }, null, 2);
34
- }
35
- else {
36
- serializedError = JSON.stringify(error, null, 2);
37
- }
38
- }
39
- const quarantineData = {
40
- originalMessageId: processingContext.messageId,
41
- originalEventType: processingContext.eventType,
42
- originalEventData: processingContext.eventData,
43
- originalEventContext: processingContext.eventContext,
44
- originalCloudEvent: processingContext.originalCloudEvent,
45
- quarantinedAt: new Date().toISOString(),
46
- quarantineReason: reason,
47
- error: serializedError,
48
- };
49
- await publishRawEvent(options.quarantineTopic, 'hono.cloudevents.quarantined', quarantineData, {
50
- projectId: options.projectId,
51
- subject: `quarantine.${reason}`,
52
- source: options.source || 'hono-cloudevents',
53
- attributes: {
54
- 'quarantine-reason': reason,
55
- },
56
- });
57
- logger.info(`Message ${processingContext.messageId} quarantined: ${reason}`);
58
- }
59
- catch (publishError) {
60
- const errorMessage = publishError instanceof Error ? publishError.message : 'Unknown error';
61
- logger.error(`Failed to quarantine message ${processingContext.messageId}: ${errorMessage}`);
62
- }
63
- };
64
- /**
65
- * Publishes recoverable processing errors to the error topic
66
- */
67
- export const publishRecoverableError = async (processingContext, error, options) => {
68
- if (!options.errorTopic) {
69
- logger.warn('No error topic configured, skipping error publishing');
70
- return;
71
- }
72
- try {
73
- const errorData = {
74
- originalMessageId: processingContext.messageId,
75
- originalEventType: processingContext.eventType,
76
- originalEventData: processingContext.eventData,
77
- originalEventContext: processingContext.eventContext,
78
- originalCloudEvent: processingContext.originalCloudEvent,
79
- errorTimestamp: new Date().toISOString(),
80
- error: {
81
- message: error instanceof Error ? error.message : String(error),
82
- stack: error instanceof Error ? error.stack : undefined,
83
- type: error instanceof Error ? error.constructor.name : typeof error,
84
- },
85
- };
86
- await publishRawEvent(options.errorTopic, 'hono.cloudevents.processing-error', errorData, {
87
- projectId: options.projectId,
88
- subject: 'processing.error',
89
- source: options.source || 'hono-cloudevents',
90
- });
91
- logger.info(`Processing error published for message ${processingContext.messageId}`);
92
- }
93
- catch (publishError) {
94
- const errorMessage = publishError instanceof Error ? publishError.message : 'Unknown error';
95
- logger.error(`Failed to publish error for message ${processingContext.messageId}: ${errorMessage}`);
96
- }
97
- };
98
- /**
99
- * Creates processing context from event data
100
- */
101
- export const createProcessingContext = (eventType, eventData, context, originalCloudEvent) => ({
102
- messageId: context?.messageId || 'unknown',
103
- eventType,
104
- eventData,
105
- eventContext: context,
106
- timestamp: new Date().toISOString(),
107
- originalCloudEvent,
108
- });
@@ -1,36 +0,0 @@
1
- /**
2
- * Handler Cache Management
3
- * Immutable cache operations for CloudEvents handlers
4
- */
5
- import type { ZodTypeAny } from 'zod';
6
- import { type EnrichedEvent, type HandlerConstructor } from '../domain';
7
- export interface ProcessedHandler {
8
- type: string;
9
- name: string;
10
- schema: ZodTypeAny;
11
- handle: (payload: unknown, context?: unknown) => Promise<void>;
12
- match?: (event: EnrichedEvent<unknown>) => boolean;
13
- safeParse?: boolean;
14
- }
15
- /**
16
- * Clears the handler cache. Useful for testing.
17
- * @internal
18
- */
19
- export declare const clearHandlerCache: () => void;
20
- export declare const getCachedHandlers: (key: string) => ProcessedHandler[];
21
- export declare const setCachedHandlers: (key: string, handlers: ProcessedHandler[]) => void;
22
- export declare const hasCachedHandlers: (key: string) => boolean;
23
- export declare const isMessageProcessed: (messageId: string) => boolean;
24
- export declare const addProcessedMessage: (messageId: string) => void;
25
- /**
26
- * Converts HandlerConstructor to ProcessedHandler
27
- */
28
- export declare const processHandler: (HandlerClass: HandlerConstructor) => ProcessedHandler | null;
29
- /**
30
- * Creates a cache key from configuration with environment awareness
31
- */
32
- export declare const createCacheKey: (discover?: string, handlers?: HandlerConstructor[]) => string;
33
- /**
34
- * Setup handlers with caching
35
- */
36
- export declare const setupHandlers: (cacheKey: string, discover?: string, handlers?: HandlerConstructor[], log?: boolean | "pretty" | "structured") => Promise<void>;
@@ -1,94 +0,0 @@
1
- /**
2
- * Handler Cache Management
3
- * Immutable cache operations for CloudEvents handlers
4
- */
5
- import { discoverHandlers, extractTypeFromSchema } from '../domain';
6
- import { logger } from '../infrastructure/logging';
7
- const createEmptyCache = () => new Map();
8
- const createEmptyMessageIds = () => new Set();
9
- // Global state (necessary for middleware persistence)
10
- let handlerCache = createEmptyCache();
11
- let processedMessageIds = createEmptyMessageIds();
12
- /**
13
- * Clears the handler cache. Useful for testing.
14
- * @internal
15
- */
16
- export const clearHandlerCache = () => {
17
- handlerCache = createEmptyCache();
18
- processedMessageIds = createEmptyMessageIds();
19
- };
20
- // Pure cache operations
21
- export const getCachedHandlers = (key) => handlerCache.get(key) || [];
22
- export const setCachedHandlers = (key, handlers) => {
23
- handlerCache = new Map(handlerCache).set(key, handlers);
24
- };
25
- export const hasCachedHandlers = (key) => handlerCache.has(key);
26
- export const isMessageProcessed = (messageId) => processedMessageIds.has(messageId);
27
- export const addProcessedMessage = (messageId) => {
28
- processedMessageIds = new Set(processedMessageIds).add(messageId);
29
- };
30
- /**
31
- * Converts HandlerConstructor to ProcessedHandler
32
- */
33
- export const processHandler = (HandlerClass) => {
34
- const metadata = HandlerClass.__eventarcMetadata;
35
- if (!metadata)
36
- return null;
37
- const instance = new HandlerClass();
38
- const eventType = metadata.declaredType || extractTypeFromSchema(metadata.schema);
39
- if (!eventType)
40
- return null;
41
- return {
42
- type: eventType,
43
- name: HandlerClass.name,
44
- schema: metadata.schema,
45
- handle: async (payload, context) => {
46
- await Promise.resolve(instance.handle(payload, context));
47
- },
48
- match: metadata.match,
49
- safeParse: metadata.safeParse || false,
50
- };
51
- };
52
- /**
53
- * Creates a cache key from configuration with environment awareness
54
- */
55
- export const createCacheKey = (discover, handlers = []) => {
56
- const discoverKey = discover || 'no-discover';
57
- const handlersKey = handlers
58
- .map((h) => h.name)
59
- .sort()
60
- .join(',') || 'no-handlers';
61
- const envKey = process.env.NODE_ENV || 'development';
62
- return `${envKey}:${discoverKey}:${handlersKey}`;
63
- };
64
- /**
65
- * Setup handlers with caching
66
- */
67
- export const setupHandlers = async (cacheKey, discover, handlers = [], log = false) => {
68
- if (hasCachedHandlers(cacheKey))
69
- return;
70
- try {
71
- const allHandlers = discover ? await discoverHandlers(discover, { log: !!log }) : handlers;
72
- const processedHandlers = allHandlers.map(processHandler).filter(Boolean);
73
- setCachedHandlers(cacheKey, processedHandlers);
74
- if (log && processedHandlers.length > 0) {
75
- if (log === 'pretty') {
76
- logger.info(`Discovered ${processedHandlers.length} handler${processedHandlers.length === 1 ? '' : 's'}:`);
77
- for (const handler of processedHandlers) {
78
- logger.info(` • ${handler.name}`);
79
- }
80
- }
81
- else {
82
- // structured or true
83
- logger.info('Handler discovery completed', {
84
- handlerCount: processedHandlers.length,
85
- handlers: processedHandlers.map((h) => h.name),
86
- });
87
- }
88
- }
89
- }
90
- catch (error) {
91
- logger.error('Failed to setup handlers:', error);
92
- setCachedHandlers(cacheKey, []);
93
- }
94
- };
@@ -1,51 +0,0 @@
1
- /**
2
- * Idempotency utilities for CloudEvents processing
3
- *
4
- * Provides deduplication support to ensure handlers process each message exactly once.
5
- * Uses CloudEvent ID as the deduplication key.
6
- */
7
- import type { IdempotencyStore } from '../domain/types';
8
- /**
9
- * Options for the in-memory idempotency store
10
- */
11
- export interface InMemoryIdempotencyStoreOptions {
12
- /** Maximum number of message IDs to store (LRU eviction). @default 10000 */
13
- maxSize?: number;
14
- /** Default TTL for entries in milliseconds. @default 86400000 (24 hours) */
15
- defaultTtlMs?: number;
16
- }
17
- /**
18
- * Creates an in-memory idempotency store with LRU eviction and TTL support.
19
- *
20
- * Suitable for single-instance deployments or development. For production
21
- * multi-instance deployments, use a Redis-based store.
22
- *
23
- * @example
24
- * ```typescript
25
- * const store = createInMemoryIdempotencyStore({ maxSize: 5000, defaultTtlMs: 3600000 })
26
- *
27
- * await consumeJetStreamEvents({
28
- * stream: 'ORDERS',
29
- * subjects: ['orders.>'],
30
- * consumer: 'notifications',
31
- * discover: `./src/events/**\/*.handler.ts`,
32
- * idempotencyStore: store,
33
- * })
34
- * ```
35
- */
36
- export declare function createInMemoryIdempotencyStore(options?: InMemoryIdempotencyStoreOptions): IdempotencyStore;
37
- /**
38
- * Gets or creates the default idempotency store
39
- */
40
- export declare function getDefaultIdempotencyStore(): IdempotencyStore;
41
- /**
42
- * Resets the default idempotency store. Useful for testing.
43
- */
44
- export declare function resetDefaultIdempotencyStore(): void;
45
- /**
46
- * Checks if a message should be processed (not a duplicate)
47
- * and marks it as processed if so.
48
- *
49
- * @returns true if the message should be processed, false if it's a duplicate
50
- */
51
- export declare function checkAndMarkProcessed(store: IdempotencyStore, messageId: string, ttlMs?: number): Promise<boolean>;
@@ -1,112 +0,0 @@
1
- /**
2
- * Idempotency utilities for CloudEvents processing
3
- *
4
- * Provides deduplication support to ensure handlers process each message exactly once.
5
- * Uses CloudEvent ID as the deduplication key.
6
- */
7
- /**
8
- * Creates an in-memory idempotency store with LRU eviction and TTL support.
9
- *
10
- * Suitable for single-instance deployments or development. For production
11
- * multi-instance deployments, use a Redis-based store.
12
- *
13
- * @example
14
- * ```typescript
15
- * const store = createInMemoryIdempotencyStore({ maxSize: 5000, defaultTtlMs: 3600000 })
16
- *
17
- * await consumeJetStreamEvents({
18
- * stream: 'ORDERS',
19
- * subjects: ['orders.>'],
20
- * consumer: 'notifications',
21
- * discover: `./src/events/**\/*.handler.ts`,
22
- * idempotencyStore: store,
23
- * })
24
- * ```
25
- */
26
- export function createInMemoryIdempotencyStore(options = {}) {
27
- const { maxSize = 10_000, defaultTtlMs = 24 * 60 * 60 * 1000 } = options;
28
- // Use Map for insertion-order iteration (LRU approximation)
29
- const cache = new Map();
30
- const evictExpired = () => {
31
- const now = Date.now();
32
- for (const [key, entry] of cache) {
33
- if (entry.expiresAt <= now) {
34
- cache.delete(key);
35
- }
36
- }
37
- };
38
- const evictOldest = (count) => {
39
- const keysToDelete = [...cache.keys()].slice(0, count);
40
- for (const key of keysToDelete) {
41
- cache.delete(key);
42
- }
43
- };
44
- return {
45
- has(messageId) {
46
- const entry = cache.get(messageId);
47
- if (!entry)
48
- return false;
49
- // Check if expired
50
- if (entry.expiresAt <= Date.now()) {
51
- cache.delete(messageId);
52
- return false;
53
- }
54
- // Move to end for LRU (re-insert)
55
- cache.delete(messageId);
56
- cache.set(messageId, entry);
57
- return true;
58
- },
59
- add(messageId, ttlMs) {
60
- // Evict expired entries periodically
61
- if (cache.size >= maxSize) {
62
- evictExpired();
63
- }
64
- // If still at capacity, evict oldest entries
65
- if (cache.size >= maxSize) {
66
- const evictCount = Math.max(1, Math.floor(maxSize * 0.1)); // Evict 10%
67
- evictOldest(evictCount);
68
- }
69
- cache.set(messageId, {
70
- expiresAt: Date.now() + (ttlMs ?? defaultTtlMs),
71
- });
72
- },
73
- clear() {
74
- cache.clear();
75
- },
76
- };
77
- }
78
- /**
79
- * Default idempotency store instance.
80
- * Used when no custom store is provided to consumers.
81
- */
82
- let defaultStore = null;
83
- /**
84
- * Gets or creates the default idempotency store
85
- */
86
- export function getDefaultIdempotencyStore() {
87
- if (!defaultStore) {
88
- defaultStore = createInMemoryIdempotencyStore();
89
- }
90
- return defaultStore;
91
- }
92
- /**
93
- * Resets the default idempotency store. Useful for testing.
94
- */
95
- export function resetDefaultIdempotencyStore() {
96
- defaultStore?.clear?.();
97
- defaultStore = null;
98
- }
99
- /**
100
- * Checks if a message should be processed (not a duplicate)
101
- * and marks it as processed if so.
102
- *
103
- * @returns true if the message should be processed, false if it's a duplicate
104
- */
105
- export async function checkAndMarkProcessed(store, messageId, ttlMs) {
106
- const isDuplicate = await store.has(messageId);
107
- if (isDuplicate) {
108
- return false;
109
- }
110
- await store.add(messageId, ttlMs);
111
- return true;
112
- }
@@ -1,4 +0,0 @@
1
- export * from './dlq-safe';
2
- export * from './handler-cache';
3
- export * from './idempotency';
4
- export * from './validation';
@@ -1,4 +0,0 @@
1
- export * from './dlq-safe';
2
- export * from './handler-cache';
3
- export * from './idempotency';
4
- export * from './validation';
@@ -1,41 +0,0 @@
1
- /**
2
- * Validation utilities for CloudEvents processing
3
- */
4
- import type { HandlerValidationError } from '../domain';
5
- import type { ProcessedHandler } from './handler-cache';
6
- /**
7
- * Creates validation details from Zod error
8
- */
9
- export declare const createValidationDetails: (error: {
10
- issues: Array<{
11
- code: string;
12
- message: string;
13
- path: PropertyKey[];
14
- expected?: unknown;
15
- received?: unknown;
16
- }>;
17
- }) => {
18
- code: string;
19
- message: string;
20
- path: (string | number)[];
21
- expected: string;
22
- received: string;
23
- }[];
24
- /**
25
- * Validates event data against handler schema
26
- * Returns structured result for both normal and safeParse modes
27
- */
28
- export declare const validateEventData: (handler: ProcessedHandler, eventData: unknown) => {
29
- success: true;
30
- } | {
31
- error: HandlerValidationError;
32
- shouldSkip: boolean;
33
- };
34
- /**
35
- * Checks if handler matches event
36
- */
37
- export declare const isHandlerMatching: (eventType: string, eventData: unknown, context: unknown) => (handler: ProcessedHandler) => boolean;
38
- /**
39
- * Throws validation error in standard mode
40
- */
41
- export declare const throwValidationError: (handlerType: string, error: HandlerValidationError) => never;
@@ -1,48 +0,0 @@
1
- import { createValidationError } from '../infrastructure';
2
- /**
3
- * Creates validation details from Zod error
4
- */
5
- export const createValidationDetails = (error) => error.issues.map((issue) => ({
6
- code: issue.code,
7
- message: issue.message,
8
- path: issue.path.filter((p) => typeof p !== 'symbol'),
9
- expected: String(issue.expected),
10
- received: String(issue.received),
11
- }));
12
- /**
13
- * Validates event data against handler schema
14
- * Returns structured result for both normal and safeParse modes
15
- */
16
- export const validateEventData = (handler, eventData) => {
17
- const result = handler.schema.safeParse(eventData);
18
- if (!result.success) {
19
- const validationDetails = createValidationDetails(result.error);
20
- const handlerValidationError = {
21
- handlerName: handler.name,
22
- validationErrors: validationDetails,
23
- };
24
- return {
25
- error: handlerValidationError,
26
- shouldSkip: handler.safeParse || false,
27
- };
28
- }
29
- return { success: true };
30
- };
31
- /**
32
- * Checks if handler matches event
33
- */
34
- export const isHandlerMatching = (eventType, eventData, context) => (handler) => {
35
- if (handler.type !== eventType)
36
- return false;
37
- if (handler.match) {
38
- const enrichedEvent = { data: eventData, ...context };
39
- return handler.match(enrichedEvent);
40
- }
41
- return true;
42
- };
43
- /**
44
- * Throws validation error in standard mode
45
- */
46
- export const throwValidationError = (handlerType, error) => {
47
- throw createValidationError(handlerType, [error]);
48
- };
@@ -1,2 +0,0 @@
1
- export * from './nats.publisher';
2
- export * from './pubsub.publisher';
@@ -1,2 +0,0 @@
1
- export * from './nats.publisher';
2
- export * from './pubsub.publisher';
@@ -1,19 +0,0 @@
1
- import type { ZodTypeAny } from 'zod';
2
- import { type RoutingConfig } from '../domain';
3
- export interface PublishNatsEventOptions {
4
- servers?: string;
5
- source?: string;
6
- subject?: string;
7
- tenantId?: string;
8
- }
9
- export declare const deriveSubjectFromType: (eventType: string, config?: RoutingConfig) => string;
10
- export declare const deriveStreamFromType: (eventType: string, config?: RoutingConfig) => string | undefined;
11
- export declare const publishNatsRawEvent: (subjectName: string, eventType: string, eventData: unknown, options?: PublishNatsEventOptions) => Promise<string>;
12
- export declare const publishNatsEvent: <T extends ZodTypeAny>(subjectName: string, schema: T, eventData: unknown, options?: PublishNatsEventOptions) => Promise<string>;
13
- export declare const publish: (eventTypeOrContract: string | {
14
- type: string;
15
- channel?: {
16
- subject?: string;
17
- };
18
- }, eventData: unknown, options?: PublishNatsEventOptions) => Promise<string>;
19
- export declare const __resetNatsPublisher: () => void;
@@ -1,115 +0,0 @@
1
- import { connect, StringCodec } from 'nats';
2
- import { extractTypeFromSchema } from '../domain';
3
- import { createValidationError, logger } from '../infrastructure';
4
- const sc = StringCodec();
5
- let natsConnectionPromise = null;
6
- const pluralize = (word) => {
7
- if (word.endsWith('y') && !['a', 'e', 'i', 'o', 'u'].includes(word[word.length - 2])) {
8
- return `${word.slice(0, -1)}ies`;
9
- }
10
- if (word.endsWith('s') || word.endsWith('sh') || word.endsWith('ch') || word.endsWith('x')) {
11
- return `${word}es`;
12
- }
13
- return `${word}s`;
14
- };
15
- const deriveSubjectFromEventType = (eventType) => {
16
- const parts = eventType.split('.');
17
- if (parts.length < 2)
18
- return eventType;
19
- const domain = parts[0];
20
- const action = parts.slice(1).join('.');
21
- const pluralDomain = pluralize(domain);
22
- return `${pluralDomain}.${action}`;
23
- };
24
- const getNatsConnection = async (servers) => {
25
- if (!natsConnectionPromise) {
26
- const url = servers ?? process.env.NATS_URL ?? 'nats://localhost:4222';
27
- natsConnectionPromise = connect({ servers: url })
28
- .then((connection) => {
29
- logger.debug(`[NATS] connected to ${url}`);
30
- return connection;
31
- })
32
- .catch((error) => {
33
- logger.error('[NATS] connection error', error);
34
- natsConnectionPromise = null;
35
- throw error;
36
- });
37
- }
38
- return natsConnectionPromise;
39
- };
40
- export const deriveSubjectFromType = (eventType, config) => {
41
- if (!config?.typeToSubjectMap) {
42
- return config?.defaultSubjectPrefix ? `${config.defaultSubjectPrefix}.${eventType}` : eventType;
43
- }
44
- const sortedPrefixes = Object.keys(config.typeToSubjectMap).sort((a, b) => b.length - a.length);
45
- for (const prefix of sortedPrefixes) {
46
- if (eventType.startsWith(prefix)) {
47
- const suffix = eventType.slice(prefix.length);
48
- const mappedPrefix = config.typeToSubjectMap[prefix];
49
- const cleanSuffix = suffix.startsWith('.') ? suffix.slice(1) : suffix;
50
- return cleanSuffix ? `${mappedPrefix}.${cleanSuffix}` : mappedPrefix;
51
- }
52
- }
53
- return config.defaultSubjectPrefix ? `${config.defaultSubjectPrefix}.${eventType}` : eventType;
54
- };
55
- export const deriveStreamFromType = (eventType, config) => {
56
- if (!config?.typeToStreamMap)
57
- return undefined;
58
- const sortedPrefixes = Object.keys(config.typeToStreamMap).sort((a, b) => b.length - a.length);
59
- for (const prefix of sortedPrefixes) {
60
- if (eventType.startsWith(prefix)) {
61
- return config.typeToStreamMap[prefix];
62
- }
63
- }
64
- return undefined;
65
- };
66
- export const publishNatsRawEvent = async (subjectName, eventType, eventData, options) => {
67
- const cloudEvent = {
68
- specversion: '1.0',
69
- type: eventType,
70
- source: options?.source || 'hono-service',
71
- id: crypto.randomUUID(),
72
- time: new Date().toISOString(),
73
- datacontenttype: 'application/json',
74
- data: eventData,
75
- ...(options?.subject && { subject: options.subject }),
76
- ...(options?.tenantId && { tenantid: options.tenantId }),
77
- };
78
- const data = JSON.stringify(cloudEvent);
79
- const nc = await getNatsConnection(options?.servers);
80
- nc.publish(subjectName, sc.encode(data));
81
- logger.debug(`Published CloudEvent ${eventType} to NATS subject ${subjectName} (id=${cloudEvent.id})`);
82
- return cloudEvent.id;
83
- };
84
- export const publishNatsEvent = async (subjectName, schema, eventData, options) => {
85
- const eventType = extractTypeFromSchema(schema);
86
- if (!eventType) {
87
- throw new Error('Could not extract event type from schema. Make sure your schema has proper metadata.');
88
- }
89
- const validationResult = schema.safeParse(eventData);
90
- if (!validationResult.success) {
91
- const validationDetails = validationResult.error.issues.map((issue) => ({
92
- code: issue.code,
93
- message: issue.message,
94
- path: issue.path.filter((p) => typeof p !== 'symbol'),
95
- expected: 'expected' in issue ? String(issue.expected) : undefined,
96
- received: 'received' in issue ? String(issue.received) : undefined,
97
- }));
98
- const handlerValidationError = {
99
- handlerName: `NatsPublisher:${eventType}`,
100
- validationErrors: validationDetails,
101
- };
102
- throw createValidationError(eventType, [handlerValidationError]);
103
- }
104
- return publishNatsRawEvent(subjectName, eventType, validationResult.data, options);
105
- };
106
- export const publish = async (eventTypeOrContract, eventData, options) => {
107
- const eventType = typeof eventTypeOrContract === 'string' ? eventTypeOrContract : eventTypeOrContract.type;
108
- const natsSubject = typeof eventTypeOrContract === 'string'
109
- ? deriveSubjectFromEventType(eventTypeOrContract)
110
- : (eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type);
111
- return publishNatsRawEvent(natsSubject, eventType, eventData, options);
112
- };
113
- export const __resetNatsPublisher = () => {
114
- natsConnectionPromise = null;
115
- };