@crossdelta/cloudevents 0.6.8 → 0.7.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.
package/README.md CHANGED
@@ -192,9 +192,16 @@ consumeJetStreams({
192
192
 
193
193
  // Core NATS — fire-and-forget, simpler
194
194
  await consumeNatsEvents({
195
- subjects: ['notifications.*'],
195
+ subject: 'notifications.*',
196
+ consumerName: 'my-service',
196
197
  discover: './src/events/**/*.handler.ts',
197
198
  })
199
+
200
+ // NATS Core Request-Reply — synchronous validation feedback
201
+ // Consumer returns result, publisher awaits it
202
+ import { request } from '@crossdelta/cloudevents'
203
+ const result = await request('call.received', payload)
204
+ // result: { accepted: true, data: { ... } } | { accepted: false, error: '...' }
198
205
  ```
199
206
 
200
207
  ---
@@ -555,7 +562,8 @@ app.use('/events', cloudEvents({ discover: 'src/events/**/*.handler.ts' }))
555
562
  | `ensureJetStreams(options)` | Create/update JetStream streams |
556
563
  | `consumeJetStreams(options)` | Consume from multiple streams |
557
564
  | `consumeNatsEvents(options)` | Consume fire-and-forget |
558
- | `publish(type, data)` | Publish event |
565
+ | `publish(type, data)` | Publish event (fire-and-forget) |
566
+ | `request(type, data, opts?)` | Publish event and await reply (Request-Reply) |
559
567
  | `isNatsConnected()` | Check if NATS connection is live (for health checks) |
560
568
  | `closeConnection()` | Drain and close the NATS connection (for CLI tools) |
561
569
  ---
package/bin/cli.js CHANGED
@@ -740,9 +740,10 @@ __export(nats_publisher_exports, {
740
740
  isNatsConnected: () => isNatsConnected,
741
741
  publish: () => publish,
742
742
  publishNatsEvent: () => publishNatsEvent,
743
- publishNatsRawEvent: () => publishNatsRawEvent
743
+ publishNatsRawEvent: () => publishNatsRawEvent,
744
+ request: () => request
744
745
  });
745
- var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection, closeConnection, connectNats, isNatsConnected, __resetNatsPublisher, deriveSubjectFromType, deriveStreamFromType, publishNatsRawEvent, publishNatsEvent, publish;
746
+ var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection, closeConnection, connectNats, isNatsConnected, __resetNatsPublisher, deriveSubjectFromType, deriveStreamFromType, publishNatsRawEvent, publishNatsEvent, publish, DEFAULT_REQUEST_TIMEOUT, request;
746
747
  var init_nats_publisher = __esm({
747
748
  "src/publishing/nats.publisher.ts"() {
748
749
  init_domain();
@@ -874,6 +875,30 @@ var init_nats_publisher = __esm({
874
875
  const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
875
876
  return publishNatsRawEvent(natsSubject, eventType, eventData, options);
876
877
  };
878
+ DEFAULT_REQUEST_TIMEOUT = 1e4;
879
+ request = async (eventTypeOrContract, eventData, options) => {
880
+ const eventType = typeof eventTypeOrContract === "string" ? eventTypeOrContract : eventTypeOrContract.type;
881
+ const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
882
+ const cloudEvent = {
883
+ specversion: "1.0",
884
+ type: eventType,
885
+ source: options?.source || "api-gateway",
886
+ id: crypto.randomUUID(),
887
+ time: (/* @__PURE__ */ new Date()).toISOString(),
888
+ datacontenttype: "application/json",
889
+ data: eventData,
890
+ ...options?.subject && { subject: options.subject },
891
+ ...options?.tenantId && { tenantid: options.tenantId }
892
+ };
893
+ const data = JSON.stringify(cloudEvent);
894
+ const nc = await getNatsConnection(options?.servers);
895
+ const msg = await nc.request(natsSubject, sc.encode(data), {
896
+ timeout: options?.timeout ?? DEFAULT_REQUEST_TIMEOUT
897
+ });
898
+ logger.debug(`Request-Reply for ${eventType} on ${natsSubject} (id=${cloudEvent.id})`);
899
+ const responseJson = sc.decode(msg.data);
900
+ return JSON.parse(responseJson);
901
+ };
877
902
  }
878
903
  });
879
904
 
package/dist/index.cjs CHANGED
@@ -1350,7 +1350,7 @@ function handleEvent(schemaOrOptions, handler, eventType) {
1350
1350
  safeParse
1351
1351
  };
1352
1352
  async handle(payload, context) {
1353
- await Promise.resolve(handler(payload, context));
1353
+ return await Promise.resolve(handler(payload, context));
1354
1354
  }
1355
1355
  };
1356
1356
  Object.defineProperty(HandlerClass, "name", {
@@ -1389,9 +1389,10 @@ __export(nats_publisher_exports, {
1389
1389
  isNatsConnected: () => exports.isNatsConnected,
1390
1390
  publish: () => exports.publish,
1391
1391
  publishNatsEvent: () => exports.publishNatsEvent,
1392
- publishNatsRawEvent: () => exports.publishNatsRawEvent
1392
+ publishNatsRawEvent: () => exports.publishNatsRawEvent,
1393
+ request: () => exports.request
1393
1394
  });
1394
- var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection; exports.closeConnection = void 0; exports.connectNats = void 0; exports.isNatsConnected = void 0; exports.__resetNatsPublisher = void 0; exports.deriveSubjectFromType = void 0; exports.deriveStreamFromType = void 0; exports.publishNatsRawEvent = void 0; exports.publishNatsEvent = void 0; exports.publish = void 0;
1395
+ var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection; exports.closeConnection = void 0; exports.connectNats = void 0; exports.isNatsConnected = void 0; exports.__resetNatsPublisher = void 0; exports.deriveSubjectFromType = void 0; exports.deriveStreamFromType = void 0; exports.publishNatsRawEvent = void 0; exports.publishNatsEvent = void 0; exports.publish = void 0; var DEFAULT_REQUEST_TIMEOUT; exports.request = void 0;
1395
1396
  var init_nats_publisher = __esm({
1396
1397
  "src/publishing/nats.publisher.ts"() {
1397
1398
  init_domain();
@@ -1523,6 +1524,30 @@ var init_nats_publisher = __esm({
1523
1524
  const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
1524
1525
  return exports.publishNatsRawEvent(natsSubject, eventType, eventData, options);
1525
1526
  };
1527
+ DEFAULT_REQUEST_TIMEOUT = 1e4;
1528
+ exports.request = async (eventTypeOrContract, eventData, options) => {
1529
+ const eventType = typeof eventTypeOrContract === "string" ? eventTypeOrContract : eventTypeOrContract.type;
1530
+ const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
1531
+ const cloudEvent = {
1532
+ specversion: "1.0",
1533
+ type: eventType,
1534
+ source: options?.source || "api-gateway",
1535
+ id: crypto.randomUUID(),
1536
+ time: (/* @__PURE__ */ new Date()).toISOString(),
1537
+ datacontenttype: "application/json",
1538
+ data: eventData,
1539
+ ...options?.subject && { subject: options.subject },
1540
+ ...options?.tenantId && { tenantid: options.tenantId }
1541
+ };
1542
+ const data = JSON.stringify(cloudEvent);
1543
+ const nc = await getNatsConnection(options?.servers);
1544
+ const msg = await nc.request(natsSubject, sc.encode(data), {
1545
+ timeout: options?.timeout ?? DEFAULT_REQUEST_TIMEOUT
1546
+ });
1547
+ logger.debug(`Request-Reply for ${eventType} on ${natsSubject} (id=${cloudEvent.id})`);
1548
+ const responseJson = sc.decode(msg.data);
1549
+ return JSON.parse(responseJson);
1550
+ };
1526
1551
  }
1527
1552
  });
1528
1553
 
@@ -1870,7 +1895,7 @@ var processHandler = (HandlerClass) => {
1870
1895
  name: HandlerClass.name,
1871
1896
  schema: metadata.schema,
1872
1897
  handle: async (payload, context) => {
1873
- await Promise.resolve(instance.handle(payload, context));
1898
+ return await Promise.resolve(instance.handle(payload, context));
1874
1899
  },
1875
1900
  match: metadata.match,
1876
1901
  safeParse: metadata.safeParse || false
@@ -2168,8 +2193,8 @@ function cloudEvents(options = {}) {
2168
2193
  // package.json
2169
2194
  var package_default = {
2170
2195
  name: "@crossdelta/cloudevents",
2171
- version: "0.6.8",
2172
- description: "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream"};
2196
+ version: "0.7.1",
2197
+ description: "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream & Core"};
2173
2198
 
2174
2199
  // src/plugin.ts
2175
2200
  var createAddCommand = (config) => ({
@@ -2344,8 +2369,8 @@ function createBaseMessageProcessor(deps) {
2344
2369
  };
2345
2370
  const executeHandler2 = async (handler, enriched, context) => {
2346
2371
  try {
2347
- await handler.handle(enriched.data, enriched);
2348
- return { success: true };
2372
+ const result = await handler.handle(enriched.data, enriched);
2373
+ return { success: true, result };
2349
2374
  } catch (error) {
2350
2375
  if (dlqEnabled) {
2351
2376
  await publishRecoverableError(context, error, options);
@@ -2359,15 +2384,15 @@ function createBaseMessageProcessor(deps) {
2359
2384
  const handler = findHandler(enriched);
2360
2385
  if (!handler) {
2361
2386
  const result2 = await handleMissingHandler(context, enriched.eventType);
2362
- return result2.shouldAck;
2387
+ return { shouldAck: result2.shouldAck };
2363
2388
  }
2364
2389
  const validationResult = validateEventData(handler, enriched.data);
2365
2390
  if ("error" in validationResult) {
2366
2391
  const result2 = await handleValidationFailure(validationResult, handler, context);
2367
- return result2.shouldAck;
2392
+ return { shouldAck: result2.shouldAck };
2368
2393
  }
2369
2394
  const result = await executeHandler2(handler, enriched, context);
2370
- return result.success;
2395
+ return { shouldAck: result.success, result: result.result };
2371
2396
  };
2372
2397
  const handleParseError = async (error, context, redeliveryCount = 0) => {
2373
2398
  logger2.error(`[${name}] failed to parse CloudEvent (attempt ${redeliveryCount + 1})`, error);
@@ -2419,7 +2444,8 @@ var createJetStreamMessageProcessor = (deps) => {
2419
2444
  try {
2420
2445
  const cloudEvent = base.parseCloudEvent(msg.data);
2421
2446
  const enriched = base.toEnrichedEvent(cloudEvent);
2422
- return base.processEvent(cloudEvent, enriched);
2447
+ const { shouldAck } = await base.processEvent(cloudEvent, enriched);
2448
+ return shouldAck;
2423
2449
  } catch (error) {
2424
2450
  const unknownCtx = toUnknownContext(msg);
2425
2451
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
@@ -2686,8 +2712,14 @@ var consumeJetStreams = consumeJetStreamStreams;
2686
2712
  // src/transports/nats/nats-consumer.ts
2687
2713
  init_domain();
2688
2714
  init_logging();
2689
-
2690
- // src/transports/nats/nats-message-processor.ts
2715
+ var sc3 = nats.StringCodec();
2716
+ var isRequestMessage = (msg) => Boolean(msg.reply);
2717
+ var sendReply = (msg, payload) => {
2718
+ if (isRequestMessage(msg)) {
2719
+ msg.respond(sc3.encode(JSON.stringify(payload)));
2720
+ }
2721
+ };
2722
+ var isStructuredReply = (value) => value != null && typeof value === "object" && "accepted" in value;
2691
2723
  var createNatsMessageProcessor = (deps) => {
2692
2724
  const { subject, decode } = deps;
2693
2725
  const base = createBaseMessageProcessor(deps);
@@ -2703,14 +2735,18 @@ var createNatsMessageProcessor = (deps) => {
2703
2735
  try {
2704
2736
  const cloudEvent = base.parseCloudEvent(msg.data);
2705
2737
  const enriched = base.toEnrichedEvent(cloudEvent);
2706
- await base.processEvent(cloudEvent, enriched);
2738
+ const { result } = await base.processEvent(cloudEvent, enriched);
2739
+ const reply = isStructuredReply(result) ? result : { accepted: true, ...result != null ? { data: result } : {} };
2740
+ sendReply(msg, reply);
2707
2741
  } catch (error) {
2742
+ sendReply(msg, { accepted: false, error: error instanceof Error ? error.message : "Processing failed" });
2708
2743
  const unknownCtx = toUnknownContext(msg);
2709
2744
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
2710
2745
  await base.handleParseError(error, context);
2711
2746
  }
2712
2747
  };
2713
2748
  const handleUnhandledProcessingError = async (msg, error) => {
2749
+ sendReply(msg, { accepted: false, error: error instanceof Error ? error.message : "Unhandled error" });
2714
2750
  const unknownCtx = toUnknownContext(msg);
2715
2751
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
2716
2752
  await base.handleUnhandledError(error, context);
@@ -2719,7 +2755,7 @@ var createNatsMessageProcessor = (deps) => {
2719
2755
  };
2720
2756
 
2721
2757
  // src/transports/nats/nats-consumer.ts
2722
- var sc3 = nats.StringCodec();
2758
+ var sc4 = nats.StringCodec();
2723
2759
  var CONSUMER_REGISTRY_KEY = "__crossdelta_nats_consumers__";
2724
2760
  function getConsumerRegistry() {
2725
2761
  if (!globalThis[CONSUMER_REGISTRY_KEY]) {
@@ -2763,7 +2799,7 @@ async function consumeNatsEvents(options) {
2763
2799
  dlqEnabled,
2764
2800
  options,
2765
2801
  processedHandlers,
2766
- decode: (data) => sc3.decode(data),
2802
+ decode: (data) => sc4.decode(data),
2767
2803
  logger
2768
2804
  });
2769
2805
  const processSubscription = async () => {
package/dist/index.d.cts CHANGED
@@ -406,7 +406,7 @@ declare const publishEvent: (eventType: string, data: Record<string, unknown>, o
406
406
  * Event handler interface
407
407
  */
408
408
  interface EventHandler<T = unknown> {
409
- handle(payload: T, context?: unknown): Promise<void> | void;
409
+ handle(payload: T, context?: unknown): Promise<unknown> | unknown;
410
410
  }
411
411
  /**
412
412
  * Constructor type for event handler classes that implement the EventHandler interface
@@ -593,7 +593,7 @@ declare function createContract<TSchema extends ZodTypeAny>(options: {
593
593
  * })
594
594
  * ```
595
595
  */
596
- declare function handleEvent<TSchema extends ZodTypeAny>(schemaOrOptions: TSchema | HandleEventOptions<TSchema> | HandleEventOptions, handler: (payload: TSchema['_output'], context?: EventContext) => Promise<void> | void, eventType?: string): HandlerConstructor;
596
+ declare function handleEvent<TSchema extends ZodTypeAny>(schemaOrOptions: TSchema | HandleEventOptions<TSchema> | HandleEventOptions, handler: (payload: TSchema['_output'], context?: EventContext) => Promise<unknown> | unknown, eventType?: string): HandlerConstructor;
597
597
  /**
598
598
  * Creates an event schema with type inference
599
599
  * Automatically enforces the presence of a 'type' field
@@ -1297,6 +1297,14 @@ interface CloudEventsPfPluginOptions {
1297
1297
  }
1298
1298
  declare const createPfPlugin: (options?: CloudEventsPfPluginOptions) => PfPlugin;
1299
1299
 
1300
+ interface RequestNatsEventOptions {
1301
+ servers?: string;
1302
+ source?: string;
1303
+ subject?: string;
1304
+ tenantId?: string;
1305
+ /** Timeout in milliseconds for the request (default: 10000) */
1306
+ timeout?: number;
1307
+ }
1300
1308
  interface PublishNatsEventOptions {
1301
1309
  servers?: string;
1302
1310
  source?: string;
@@ -1333,6 +1341,25 @@ declare const publish: (eventTypeOrContract: string | {
1333
1341
  subject?: string;
1334
1342
  };
1335
1343
  }, eventData: unknown, options?: PublishNatsEventOptions) => Promise<string>;
1344
+ interface NatsRequestResponse<T = unknown> {
1345
+ accepted: boolean;
1346
+ data?: T;
1347
+ error?: string;
1348
+ [key: string]: unknown;
1349
+ }
1350
+ /**
1351
+ * Publish a CloudEvent via NATS Request-Reply and wait for the consumer's response.
1352
+ *
1353
+ * The subscriber is expected to call `msg.respond()` with a JSON-encoded
1354
+ * `NatsRequestResponse`. If no subscriber replies within the timeout the
1355
+ * returned promise rejects.
1356
+ */
1357
+ declare const request: <T = unknown>(eventTypeOrContract: string | {
1358
+ type: string;
1359
+ channel?: {
1360
+ subject?: string;
1361
+ };
1362
+ }, eventData: unknown, options?: RequestNatsEventOptions) => Promise<NatsRequestResponse<T>>;
1336
1363
 
1337
1364
  interface PublishEventOptions {
1338
1365
  projectId?: string;
@@ -1603,4 +1630,4 @@ interface NatsConsumerOptions extends Pick<CloudEventsOptions, 'quarantineTopic'
1603
1630
  */
1604
1631
  declare function consumeNatsEvents(options: NatsConsumerOptions): Promise<Subscription>;
1605
1632
 
1606
- export { type ChannelConfig, type ChannelMetadata, type CloudEventsPfPluginOptions, type ContractCreatedEffect, type ContractPaths, type CreateEventContext, type CreateEventOptions, type DomainEffect, type EnrichedEvent, type EventContext, type EventNames, type EventTypeValidation, type FileSystem, type HandleEventOptions, type HandlerCreatedEffect, type IdempotencyStore, type InferEventData, type JetStreamConsumerOptions, type JetStreamStreamOptions, type JetStreamStreamsConsumerOptions, type JetStreamStreamsOptions, type ListEventsContext, type PublishEventContext, type PublishEventOptions, type PublishNatsEventOptions, type RoutingConfig, type SchemaField, type StreamConfig, type StreamDefinition, type StreamWiredEffect, __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
1633
+ export { type ChannelConfig, type ChannelMetadata, type CloudEventsPfPluginOptions, type ContractCreatedEffect, type ContractPaths, type CreateEventContext, type CreateEventOptions, type DomainEffect, type EnrichedEvent, type EventContext, type EventNames, type EventTypeValidation, type FileSystem, type HandleEventOptions, type HandlerCreatedEffect, type IdempotencyStore, type InferEventData, type JetStreamConsumerOptions, type JetStreamStreamOptions, type JetStreamStreamsConsumerOptions, type JetStreamStreamsOptions, type ListEventsContext, type NatsRequestResponse, type PublishEventContext, type PublishEventOptions, type PublishNatsEventOptions, type RequestNatsEventOptions, type RoutingConfig, type SchemaField, type StreamConfig, type StreamDefinition, type StreamWiredEffect, __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, request, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
package/dist/index.d.ts CHANGED
@@ -406,7 +406,7 @@ declare const publishEvent: (eventType: string, data: Record<string, unknown>, o
406
406
  * Event handler interface
407
407
  */
408
408
  interface EventHandler<T = unknown> {
409
- handle(payload: T, context?: unknown): Promise<void> | void;
409
+ handle(payload: T, context?: unknown): Promise<unknown> | unknown;
410
410
  }
411
411
  /**
412
412
  * Constructor type for event handler classes that implement the EventHandler interface
@@ -593,7 +593,7 @@ declare function createContract<TSchema extends ZodTypeAny>(options: {
593
593
  * })
594
594
  * ```
595
595
  */
596
- declare function handleEvent<TSchema extends ZodTypeAny>(schemaOrOptions: TSchema | HandleEventOptions<TSchema> | HandleEventOptions, handler: (payload: TSchema['_output'], context?: EventContext) => Promise<void> | void, eventType?: string): HandlerConstructor;
596
+ declare function handleEvent<TSchema extends ZodTypeAny>(schemaOrOptions: TSchema | HandleEventOptions<TSchema> | HandleEventOptions, handler: (payload: TSchema['_output'], context?: EventContext) => Promise<unknown> | unknown, eventType?: string): HandlerConstructor;
597
597
  /**
598
598
  * Creates an event schema with type inference
599
599
  * Automatically enforces the presence of a 'type' field
@@ -1297,6 +1297,14 @@ interface CloudEventsPfPluginOptions {
1297
1297
  }
1298
1298
  declare const createPfPlugin: (options?: CloudEventsPfPluginOptions) => PfPlugin;
1299
1299
 
1300
+ interface RequestNatsEventOptions {
1301
+ servers?: string;
1302
+ source?: string;
1303
+ subject?: string;
1304
+ tenantId?: string;
1305
+ /** Timeout in milliseconds for the request (default: 10000) */
1306
+ timeout?: number;
1307
+ }
1300
1308
  interface PublishNatsEventOptions {
1301
1309
  servers?: string;
1302
1310
  source?: string;
@@ -1333,6 +1341,25 @@ declare const publish: (eventTypeOrContract: string | {
1333
1341
  subject?: string;
1334
1342
  };
1335
1343
  }, eventData: unknown, options?: PublishNatsEventOptions) => Promise<string>;
1344
+ interface NatsRequestResponse<T = unknown> {
1345
+ accepted: boolean;
1346
+ data?: T;
1347
+ error?: string;
1348
+ [key: string]: unknown;
1349
+ }
1350
+ /**
1351
+ * Publish a CloudEvent via NATS Request-Reply and wait for the consumer's response.
1352
+ *
1353
+ * The subscriber is expected to call `msg.respond()` with a JSON-encoded
1354
+ * `NatsRequestResponse`. If no subscriber replies within the timeout the
1355
+ * returned promise rejects.
1356
+ */
1357
+ declare const request: <T = unknown>(eventTypeOrContract: string | {
1358
+ type: string;
1359
+ channel?: {
1360
+ subject?: string;
1361
+ };
1362
+ }, eventData: unknown, options?: RequestNatsEventOptions) => Promise<NatsRequestResponse<T>>;
1336
1363
 
1337
1364
  interface PublishEventOptions {
1338
1365
  projectId?: string;
@@ -1603,4 +1630,4 @@ interface NatsConsumerOptions extends Pick<CloudEventsOptions, 'quarantineTopic'
1603
1630
  */
1604
1631
  declare function consumeNatsEvents(options: NatsConsumerOptions): Promise<Subscription>;
1605
1632
 
1606
- export { type ChannelConfig, type ChannelMetadata, type CloudEventsPfPluginOptions, type ContractCreatedEffect, type ContractPaths, type CreateEventContext, type CreateEventOptions, type DomainEffect, type EnrichedEvent, type EventContext, type EventNames, type EventTypeValidation, type FileSystem, type HandleEventOptions, type HandlerCreatedEffect, type IdempotencyStore, type InferEventData, type JetStreamConsumerOptions, type JetStreamStreamOptions, type JetStreamStreamsConsumerOptions, type JetStreamStreamsOptions, type ListEventsContext, type PublishEventContext, type PublishEventOptions, type PublishNatsEventOptions, type RoutingConfig, type SchemaField, type StreamConfig, type StreamDefinition, type StreamWiredEffect, __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
1633
+ export { type ChannelConfig, type ChannelMetadata, type CloudEventsPfPluginOptions, type ContractCreatedEffect, type ContractPaths, type CreateEventContext, type CreateEventOptions, type DomainEffect, type EnrichedEvent, type EventContext, type EventNames, type EventTypeValidation, type FileSystem, type HandleEventOptions, type HandlerCreatedEffect, type IdempotencyStore, type InferEventData, type JetStreamConsumerOptions, type JetStreamStreamOptions, type JetStreamStreamsConsumerOptions, type JetStreamStreamsOptions, type ListEventsContext, type NatsRequestResponse, type PublishEventContext, type PublishEventOptions, type PublishNatsEventOptions, type RequestNatsEventOptions, type RoutingConfig, type SchemaField, type StreamConfig, type StreamDefinition, type StreamWiredEffect, __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, request, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
package/dist/index.js CHANGED
@@ -1325,7 +1325,7 @@ function handleEvent(schemaOrOptions, handler, eventType) {
1325
1325
  safeParse
1326
1326
  };
1327
1327
  async handle(payload, context) {
1328
- await Promise.resolve(handler(payload, context));
1328
+ return await Promise.resolve(handler(payload, context));
1329
1329
  }
1330
1330
  };
1331
1331
  Object.defineProperty(HandlerClass, "name", {
@@ -1364,9 +1364,10 @@ __export(nats_publisher_exports, {
1364
1364
  isNatsConnected: () => isNatsConnected,
1365
1365
  publish: () => publish,
1366
1366
  publishNatsEvent: () => publishNatsEvent,
1367
- publishNatsRawEvent: () => publishNatsRawEvent
1367
+ publishNatsRawEvent: () => publishNatsRawEvent,
1368
+ request: () => request
1368
1369
  });
1369
- var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection, closeConnection, connectNats, isNatsConnected, __resetNatsPublisher, deriveSubjectFromType, deriveStreamFromType, publishNatsRawEvent, publishNatsEvent, publish;
1370
+ var sc, natsConnectionPromise, deriveSubjectFromEventType, getNatsConnection, closeConnection, connectNats, isNatsConnected, __resetNatsPublisher, deriveSubjectFromType, deriveStreamFromType, publishNatsRawEvent, publishNatsEvent, publish, DEFAULT_REQUEST_TIMEOUT, request;
1370
1371
  var init_nats_publisher = __esm({
1371
1372
  "src/publishing/nats.publisher.ts"() {
1372
1373
  init_domain();
@@ -1498,6 +1499,30 @@ var init_nats_publisher = __esm({
1498
1499
  const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
1499
1500
  return publishNatsRawEvent(natsSubject, eventType, eventData, options);
1500
1501
  };
1502
+ DEFAULT_REQUEST_TIMEOUT = 1e4;
1503
+ request = async (eventTypeOrContract, eventData, options) => {
1504
+ const eventType = typeof eventTypeOrContract === "string" ? eventTypeOrContract : eventTypeOrContract.type;
1505
+ const natsSubject = typeof eventTypeOrContract === "string" ? deriveSubjectFromEventType(eventTypeOrContract) : eventTypeOrContract.channel?.subject ?? eventTypeOrContract.type;
1506
+ const cloudEvent = {
1507
+ specversion: "1.0",
1508
+ type: eventType,
1509
+ source: options?.source || "api-gateway",
1510
+ id: crypto.randomUUID(),
1511
+ time: (/* @__PURE__ */ new Date()).toISOString(),
1512
+ datacontenttype: "application/json",
1513
+ data: eventData,
1514
+ ...options?.subject && { subject: options.subject },
1515
+ ...options?.tenantId && { tenantid: options.tenantId }
1516
+ };
1517
+ const data = JSON.stringify(cloudEvent);
1518
+ const nc = await getNatsConnection(options?.servers);
1519
+ const msg = await nc.request(natsSubject, sc.encode(data), {
1520
+ timeout: options?.timeout ?? DEFAULT_REQUEST_TIMEOUT
1521
+ });
1522
+ logger.debug(`Request-Reply for ${eventType} on ${natsSubject} (id=${cloudEvent.id})`);
1523
+ const responseJson = sc.decode(msg.data);
1524
+ return JSON.parse(responseJson);
1525
+ };
1501
1526
  }
1502
1527
  });
1503
1528
 
@@ -1845,7 +1870,7 @@ var processHandler = (HandlerClass) => {
1845
1870
  name: HandlerClass.name,
1846
1871
  schema: metadata.schema,
1847
1872
  handle: async (payload, context) => {
1848
- await Promise.resolve(instance.handle(payload, context));
1873
+ return await Promise.resolve(instance.handle(payload, context));
1849
1874
  },
1850
1875
  match: metadata.match,
1851
1876
  safeParse: metadata.safeParse || false
@@ -2143,8 +2168,8 @@ function cloudEvents(options = {}) {
2143
2168
  // package.json
2144
2169
  var package_default = {
2145
2170
  name: "@crossdelta/cloudevents",
2146
- version: "0.6.8",
2147
- description: "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream"};
2171
+ version: "0.7.1",
2172
+ description: "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream & Core"};
2148
2173
 
2149
2174
  // src/plugin.ts
2150
2175
  var createAddCommand = (config) => ({
@@ -2319,8 +2344,8 @@ function createBaseMessageProcessor(deps) {
2319
2344
  };
2320
2345
  const executeHandler2 = async (handler, enriched, context) => {
2321
2346
  try {
2322
- await handler.handle(enriched.data, enriched);
2323
- return { success: true };
2347
+ const result = await handler.handle(enriched.data, enriched);
2348
+ return { success: true, result };
2324
2349
  } catch (error) {
2325
2350
  if (dlqEnabled) {
2326
2351
  await publishRecoverableError(context, error, options);
@@ -2334,15 +2359,15 @@ function createBaseMessageProcessor(deps) {
2334
2359
  const handler = findHandler(enriched);
2335
2360
  if (!handler) {
2336
2361
  const result2 = await handleMissingHandler(context, enriched.eventType);
2337
- return result2.shouldAck;
2362
+ return { shouldAck: result2.shouldAck };
2338
2363
  }
2339
2364
  const validationResult = validateEventData(handler, enriched.data);
2340
2365
  if ("error" in validationResult) {
2341
2366
  const result2 = await handleValidationFailure(validationResult, handler, context);
2342
- return result2.shouldAck;
2367
+ return { shouldAck: result2.shouldAck };
2343
2368
  }
2344
2369
  const result = await executeHandler2(handler, enriched, context);
2345
- return result.success;
2370
+ return { shouldAck: result.success, result: result.result };
2346
2371
  };
2347
2372
  const handleParseError = async (error, context, redeliveryCount = 0) => {
2348
2373
  logger2.error(`[${name}] failed to parse CloudEvent (attempt ${redeliveryCount + 1})`, error);
@@ -2394,7 +2419,8 @@ var createJetStreamMessageProcessor = (deps) => {
2394
2419
  try {
2395
2420
  const cloudEvent = base.parseCloudEvent(msg.data);
2396
2421
  const enriched = base.toEnrichedEvent(cloudEvent);
2397
- return base.processEvent(cloudEvent, enriched);
2422
+ const { shouldAck } = await base.processEvent(cloudEvent, enriched);
2423
+ return shouldAck;
2398
2424
  } catch (error) {
2399
2425
  const unknownCtx = toUnknownContext(msg);
2400
2426
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
@@ -2661,8 +2687,14 @@ var consumeJetStreams = consumeJetStreamStreams;
2661
2687
  // src/transports/nats/nats-consumer.ts
2662
2688
  init_domain();
2663
2689
  init_logging();
2664
-
2665
- // src/transports/nats/nats-message-processor.ts
2690
+ var sc3 = StringCodec();
2691
+ var isRequestMessage = (msg) => Boolean(msg.reply);
2692
+ var sendReply = (msg, payload) => {
2693
+ if (isRequestMessage(msg)) {
2694
+ msg.respond(sc3.encode(JSON.stringify(payload)));
2695
+ }
2696
+ };
2697
+ var isStructuredReply = (value) => value != null && typeof value === "object" && "accepted" in value;
2666
2698
  var createNatsMessageProcessor = (deps) => {
2667
2699
  const { subject, decode } = deps;
2668
2700
  const base = createBaseMessageProcessor(deps);
@@ -2678,14 +2710,18 @@ var createNatsMessageProcessor = (deps) => {
2678
2710
  try {
2679
2711
  const cloudEvent = base.parseCloudEvent(msg.data);
2680
2712
  const enriched = base.toEnrichedEvent(cloudEvent);
2681
- await base.processEvent(cloudEvent, enriched);
2713
+ const { result } = await base.processEvent(cloudEvent, enriched);
2714
+ const reply = isStructuredReply(result) ? result : { accepted: true, ...result != null ? { data: result } : {} };
2715
+ sendReply(msg, reply);
2682
2716
  } catch (error) {
2717
+ sendReply(msg, { accepted: false, error: error instanceof Error ? error.message : "Processing failed" });
2683
2718
  const unknownCtx = toUnknownContext(msg);
2684
2719
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
2685
2720
  await base.handleParseError(error, context);
2686
2721
  }
2687
2722
  };
2688
2723
  const handleUnhandledProcessingError = async (msg, error) => {
2724
+ sendReply(msg, { accepted: false, error: error instanceof Error ? error.message : "Unhandled error" });
2689
2725
  const unknownCtx = toUnknownContext(msg);
2690
2726
  const context = createProcessingContext("unknown", decode(msg.data), unknownCtx, void 0);
2691
2727
  await base.handleUnhandledError(error, context);
@@ -2694,7 +2730,7 @@ var createNatsMessageProcessor = (deps) => {
2694
2730
  };
2695
2731
 
2696
2732
  // src/transports/nats/nats-consumer.ts
2697
- var sc3 = StringCodec();
2733
+ var sc4 = StringCodec();
2698
2734
  var CONSUMER_REGISTRY_KEY = "__crossdelta_nats_consumers__";
2699
2735
  function getConsumerRegistry() {
2700
2736
  if (!globalThis[CONSUMER_REGISTRY_KEY]) {
@@ -2738,7 +2774,7 @@ async function consumeNatsEvents(options) {
2738
2774
  dlqEnabled,
2739
2775
  options,
2740
2776
  processedHandlers,
2741
- decode: (data) => sc3.decode(data),
2777
+ decode: (data) => sc4.decode(data),
2742
2778
  logger
2743
2779
  });
2744
2780
  const processSubscription = async () => {
@@ -2755,4 +2791,4 @@ async function consumeNatsEvents(options) {
2755
2791
  // src/index.ts
2756
2792
  init_utils();
2757
2793
 
2758
- export { __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
2794
+ export { __resetNatsPublisher, checkAndMarkProcessed, clearHandlerCache, closeConnection, cloudEvents, connectNats, consumeJetStreamEvents, consumeJetStreamStreams, consumeJetStreams, consumeNatsEvents, contractCreated, createContract, createEvent, createEventFlowSteps, createInMemoryIdempotencyStore, createMemoryFileSystem, createPfPlugin, deriveEventNames, deriveStreamFromType, deriveSubjectFromType, discoverEventTypes, ensureJetStreamStream, ensureJetStreamStreams, ensureJetStreams, eventSchema, extractTypeFromSchema, generateContract, generateContractContent, generateEventHandler, generateEventHandlerContent, generateJsonMock, generateJsonMockFromContract, generateMock, generateMockContent, getContractFilePath, getContractPaths, getDefaultIdempotencyStore, getHandlerFilePath, getHandlerPath, getJsonMockPath, getMockFilePath, getStreamName, handleEvent, handlerCreated, initFaker, isNatsConnected, isValidEventType, jsonMockExists, listEvents, listEventsFlowSteps, loadJsonMock, normalizeSubject, parseDataInput, parseEventFromContext, parseEventTypeFromContract, parseEventTypeFromHandler, parseFieldsInput, pluralize, publish, publishEvent, publishEventFlowSteps, publishNatsEvent, publishNatsRawEvent, publishRawEvent, request, resetDefaultIdempotencyStore, singularize, streamWired, toKebabCase, toPascalCase, validateEventType };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@crossdelta/cloudevents",
3
- "version": "0.6.8",
4
- "description": "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream",
3
+ "version": "0.7.1",
4
+ "description": "CloudEvents toolkit for TypeScript - Zod validation, handler discovery, NATS JetStream & Core",
5
5
  "author": "crossdelta",
6
6
  "license": "MIT",
7
7
  "keywords": [
@@ -47,7 +47,7 @@
47
47
  "prepublishOnly": "bun run build"
48
48
  },
49
49
  "dependencies": {
50
- "@crossdelta/flowcore": "0.1.0",
50
+ "@crossdelta/flowcore": "0.1.1",
51
51
  "cloudevents": "^10.0.0",
52
52
  "glob": "^11.1.0",
53
53
  "nats": "^2.29.3",