@rocketmq/core 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,11 +1,40 @@
1
- import { SchemaRegistry } from '@rocketmq/schema';
1
+ import { Constructor, SchemaRegistry } from '@rocketmq/schema';
2
2
  export { Field, FieldMeta, ProtoType, Schema, SchemaEntry } from '@rocketmq/schema';
3
+ import { ZodSchemaInput } from '@rocketmq/zod';
4
+ export { ZodSchemaInput, isRawZodObject, isZodSchemaInput, zodToFields, zodToProto } from '@rocketmq/zod';
5
+ import { z } from 'zod';
3
6
  import { Serializer } from '@rocketmq/serializer';
4
7
  export { Serializer } from '@rocketmq/serializer';
5
- import * as _rocketmq_amqp from '@rocketmq/amqp';
6
- import { AmqpChannel, PublishOptions, ConsumeMessage, ConsumeOptions, AmqpConnection, AssertQueueOptions, AssertQueueReply, AssertExchangeOptions, AssertExchangeReply, EmptyReply } from '@rocketmq/amqp';
8
+ import { PublishOptions, ConsumeOptions, AmqpConnection, AmqpChannel, AssertQueueOptions, AssertQueueReply, AssertExchangeOptions, AssertExchangeReply, EmptyReply, ConsumeMessage } from '@rocketmq/amqp';
7
9
  export { ConsumeMessage } from '@rocketmq/amqp';
8
10
 
11
+ /**
12
+ * Resolves proto3 definitions from decorator classes, Zod wrappers, or raw ZodObjects.
13
+ *
14
+ * Centralizes the tri-input logic so client.ts and queue-handle.ts stay
15
+ * clean — they just call `resolveProto(input, queueName)` without caring
16
+ * which path produced it.
17
+ *
18
+ * Usage:
19
+ * resolveProto(NotificationClass, 'q');
20
+ * resolveProto({ name: 'Notif', schema: zodSchema }, 'q');
21
+ * resolveProto(zodSchema, 'q'); // message name derived from queue name
22
+ */
23
+
24
+ /**
25
+ * Union type accepted by assertQueue/consume as the schema parameter.
26
+ *
27
+ * Three shapes:
28
+ * - `Constructor<T>` — decorator class
29
+ * - `ZodSchemaInput` — `{ name, schema }` wrapper with explicit message name
30
+ * - `z.ZodType<T>` — bare Zod schema, message name derived from queue name
31
+ *
32
+ * WHY z.ZodType<T> instead of z.ZodObject: ZodObject<ZodRawShape> erases T,
33
+ * so TS can't infer the message type in consume(). ZodType<T> preserves
34
+ * the output type. Runtime still validates it's a ZodObject via isRawZodObject.
35
+ */
36
+ type SchemaInput<T = unknown> = Constructor<T> | ZodSchemaInput | z.ZodType<T>;
37
+
9
38
  /**
10
39
  * Typed queue handle — the core DX innovation.
11
40
  *
@@ -21,10 +50,9 @@ export { ConsumeMessage } from '@rocketmq/amqp';
21
50
 
22
51
  declare class QueueHandle<T> {
23
52
  private readonly queueName;
24
- private readonly channel;
25
- private readonly registry;
26
- private readonly serializer;
27
- constructor(queueName: string, channel: AmqpChannel, registry: SchemaRegistry, serializer: Serializer);
53
+ private readonly client;
54
+ private readonly schema;
55
+ constructor(queueName: string, client: RocketMQ, schema: SchemaInput<T>);
28
56
  /**
29
57
  * Publishes a typed payload to this queue.
30
58
  *
@@ -38,24 +66,62 @@ declare class QueueHandle<T> {
38
66
  * arguments so the broker can verify schema subset compatibility.
39
67
  * Malformed messages are logged but not re-thrown to keep the loop alive.
40
68
  */
41
- consume(handler: (msg: T, raw: ConsumeMessage) => void, opts?: ConsumeOptions): Promise<string>;
42
- /** Builds AMQP arguments for consumer schema subset checking. */
43
- private buildConsumerSchemaArgs;
69
+ consume(handler: TypedConsumeHandler<T>, opts?: ConsumeOptions): Promise<string>;
44
70
  }
45
71
 
72
+ /**
73
+ * RocketMQ TypeScript client — schema-aware AMQP wrapper.
74
+ *
75
+ * Composes schema, validation, serialization, and AMQP packages into
76
+ * a single user-facing API. Hides all internal wiring behind `connect()`
77
+ * and `mq.queue()`.
78
+ *
79
+ * Supports two schema input styles:
80
+ * - Decorator classes: `mq.assertQueue('q', MyClass)`
81
+ * - Zod schemas: `mq.assertQueue('q', { name: 'Msg', schema: zodObj })`
82
+ *
83
+ * Usage:
84
+ * const mq = await connect();
85
+ * const orders = mq.queue("orders", Order);
86
+ * orders.send({ id: "1", customerId: "c1", qty: 5 });
87
+ */
88
+
89
+ type TypedConsumeHandler<T> = (msg: T, raw: ConsumeMessage) => void | Promise<void>;
90
+ interface RocketAssertQueueOptions extends AssertQueueOptions {
91
+ /** Force update an existing queue's schema even if it conflicts. */
92
+ schemaOverride?: boolean;
93
+ /** Remove the schema binding from an existing queue. */
94
+ schemaDelete?: boolean;
95
+ }
96
+ interface RocketConsumeOptions extends ConsumeOptions {
97
+ /**
98
+ * Explicit schema class for consumer compatibility validation.
99
+ *
100
+ * TypeScript generics are erased at runtime, so `consume<T>` alone
101
+ * cannot send `T`'s proto to the broker. Pass the class here so the
102
+ * broker can verify the consumer's expected fields match the queue's
103
+ * declared schema.
104
+ *
105
+ * Example:
106
+ * await mq.consume<Order>('orders', handler, { consumerSchema: Order });
107
+ */
108
+ consumerSchema?: Constructor;
109
+ }
46
110
  interface RocketOptions {
47
- /** AMQP connection URL. Default: amqp://guest:guest@localhost:5672 */
48
- url?: string;
111
+ /** AMQP connection URL. */
112
+ url: string;
49
113
  /** Custom serializer. Default: JsonSerializer. */
50
114
  serializer?: Serializer;
51
115
  }
52
116
  /** Opens a connection + channel ready for schema-aware operations. */
53
- declare function connect(opts?: RocketOptions): Promise<RocketMQ>;
117
+ declare function connect(opts: RocketOptions): Promise<RocketMQ>;
54
118
  declare class RocketMQ {
55
119
  private readonly conn;
56
120
  private readonly ch;
57
121
  private readonly registry;
58
122
  private readonly serializer;
123
+ private lastChannelError;
124
+ private readonly errorListener;
59
125
  constructor(conn: AmqpConnection, ch: AmqpChannel, registry: SchemaRegistry, serializer: Serializer);
60
126
  /** Exposes the raw AmqpChannel for event listeners (e.g. broker errors). */
61
127
  get channel(): AmqpChannel;
@@ -65,16 +131,28 @@ declare class RocketMQ {
65
131
  * Declares the queue with schema metadata in AMQP arguments so the
66
132
  * broker compiles and validates messages. Returns a QueueHandle<T>
67
133
  * for type-safe send/consume.
134
+ *
135
+ * Usage:
136
+ * const orders = await mq.queue('orders', OrderClass);
137
+ * const orders = await mq.queue('orders', zodSchema);
68
138
  */
69
- queue<T>(name: string, schema: new (...args: unknown[]) => T, opts?: AssertQueueOptions): Promise<QueueHandle<T>>;
139
+ queue<T>(name: string, schema: SchemaInput<T>, opts?: RocketAssertQueueOptions): Promise<QueueHandle<T>>;
70
140
  /**
71
- * Declares a queue with an optional schema class.
141
+ * Declares a queue with an optional schema (decorator class or Zod).
72
142
  *
73
143
  * When a schema is provided the proto3 definition is sent as AMQP
74
144
  * queue arguments (`x-schema`, `x-schema-type`, `x-schema-message`)
75
145
  * so the broker compiles and validates messages inline.
146
+ *
147
+ * Usage:
148
+ * await mq.assertQueue('orders', OrderClass);
149
+ * await mq.assertQueue('orders', { name: 'Order', schema: zodSchema });
150
+ * await mq.assertQueue('orders', zodSchema); // message name derived from queue name
76
151
  */
77
- assertQueue(name: string, schema?: Function, opts?: AssertQueueOptions): Promise<AssertQueueReply>;
152
+ assertQueue(name: string, schema?: SchemaInput, opts?: RocketAssertQueueOptions): Promise<AssertQueueReply>;
153
+ private buildSchemaQueueArgs;
154
+ /** Stores decorator class metadata in the registry for consumer lookups. */
155
+ private registerConstructorSchema;
78
156
  /** Declares an exchange (passthrough to AMQP layer). */
79
157
  assertExchange(name: string, type: string, opts?: AssertExchangeOptions): Promise<AssertExchangeReply>;
80
158
  /** Binds a queue to an exchange (passthrough to AMQP layer). */
@@ -91,25 +169,27 @@ declare class RocketMQ {
91
169
  */
92
170
  publish(exchange: string, routingKey: string, payload: Record<string, unknown>, opts?: PublishOptions): boolean;
93
171
  /**
94
- * Subscribes to a queue with JSON deserialization.
172
+ * Subscribes to a queue with JSON deserialization and broker-side
173
+ * consumer schema validation.
95
174
  *
96
- * When a schema was registered via `assertQueue(name, Schema)`,
97
- * the consumer's proto definition is sent to the broker for subset
98
- * checking the broker rejects consumers expecting fields the
99
- * queue's schema doesn't define.
175
+ * Accepts either a decorator class or a ZodSchemaInput for the schema
176
+ * parameter. The schema serves two purposes:
177
+ * - **Compile-time**: TypeScript infers `T`, so `msg` is fully typed.
178
+ * - **Runtime**: Its proto definition is sent to the broker as AMQP
179
+ * arguments so the broker can verify subset compatibility.
100
180
  *
101
181
  * Usage:
102
- * await mq.assertQueue("orders", Order);
103
- * await mq.consume("orders", (msg) => console.log(msg));
104
- */
105
- consume<T = Record<string, unknown>>(queue: string, handler: (msg: T, raw: ConsumeMessage) => void, opts?: _rocketmq_amqp.ConsumeOptions): Promise<string>;
106
- /**
107
- * Builds AMQP arguments for consumer schema compatibility checking.
108
- *
109
- * Returns `x-consumer-schema` + `x-consumer-schema-message` if the
110
- * queue has a registered schema, otherwise an empty object.
182
+ * await mq.consume("orders", Order, (msg) => console.log(msg.id));
183
+ * await mq.consume("orders", { name: "Order", schema: zodSchema }, handler);
184
+ * await mq.consume("orders", zodSchema, handler);
111
185
  */
186
+ consume<T>(queue: string, schema: SchemaInput<T>, handler: TypedConsumeHandler<T>, opts?: RocketConsumeOptions): Promise<string>;
187
+ private createConsumeCallback;
112
188
  private buildConsumerSchemaArgs;
189
+ /** Resolves consumer schema args from an explicit SchemaInput. */
190
+ private resolveConsumerArgs;
191
+ /** Falls back to registry lookup when no explicit schema is provided. */
192
+ private fallbackConsumerArgs;
113
193
  /** Acknowledges a message. */
114
194
  ack(msg: ConsumeMessage): void;
115
195
  /** Negative-acknowledges a message. */
@@ -142,19 +222,161 @@ declare class QueueError extends RocketMQError {
142
222
  }
143
223
  declare class PublishError extends RocketMQError {
144
224
  readonly queue: string;
145
- constructor(queue: string, cause?: unknown);
225
+ readonly payload: unknown;
226
+ constructor(queue: string, payload: unknown, cause?: unknown);
146
227
  }
147
228
  declare class ConsumeError extends RocketMQError {
148
229
  constructor(message: string, cause?: unknown);
149
230
  }
150
231
  declare class SerializationError extends RocketMQError {
151
- constructor(message: string, cause?: unknown);
232
+ readonly payload: unknown;
233
+ constructor(payload: unknown, cause?: unknown);
152
234
  }
153
235
  declare class SchemaError extends RocketMQError {
154
236
  constructor(message: string, cause?: unknown);
155
237
  }
238
+ /**
239
+ * Schema-specific error with structured details from the broker.
240
+ *
241
+ * Contains the error code, queue name, and per-field details
242
+ * so callers can programmatically inspect what went wrong.
243
+ * Field types use TypeScript names (number, string, boolean).
244
+ *
245
+ * Usage:
246
+ * catch (err) {
247
+ * if (err instanceof SchemaValidationError) {
248
+ * console.log(err.code); // 'SchemaTypeMismatch'
249
+ * console.log(err.queue); // 'zod-notifications'
250
+ * console.log(err.fields); // [{ name: 'id', expected: 'number', got: 'string' }]
251
+ * }
252
+ * }
253
+ */
254
+ declare class SchemaValidationError extends SchemaError {
255
+ readonly code: string;
256
+ readonly queue: string;
257
+ readonly fields: ReadonlyArray<{
258
+ readonly name: string;
259
+ readonly expected: string;
260
+ readonly got: string;
261
+ }>;
262
+ constructor(code: string, queue: string, fields: ReadonlyArray<{
263
+ readonly name: string;
264
+ readonly expected: string;
265
+ readonly got: string;
266
+ }>, message: string, cause?: unknown);
267
+ }
156
268
  declare class TimeoutError extends RocketMQError {
157
269
  constructor(message: string, cause?: unknown);
158
270
  }
159
271
 
160
- export { ConnectionError, ConsumeError, PublishError, QueueError, QueueHandle, RocketMQ, RocketMQError, type RocketOptions, SchemaError, SerializationError, TimeoutError, connect };
272
+ /**
273
+ * Machine-readable error codes returned by the broker.
274
+ *
275
+ * These mirror the Rust `ErrorCode` enum 1:1. The client uses them
276
+ * to construct specific exception subclasses and provide programmatic
277
+ * access to the error category.
278
+ *
279
+ * Usage:
280
+ * if (parsed.code === BrokerErrorCode.SchemaTypeMismatch) { ... }
281
+ */
282
+ declare enum BrokerErrorCode {
283
+ /** Consumer/publisher field type doesn't match queue schema. */
284
+ SchemaTypeMismatch = "SchemaTypeMismatch",
285
+ /** Consumer has fields not present in queue schema. */
286
+ SchemaExtraFields = "SchemaExtraFields",
287
+ /** JSON payload is missing required schema fields. */
288
+ SchemaMissingFields = "SchemaMissingFields",
289
+ /** Re-declaration schema conflicts with existing queue schema. */
290
+ SchemaConflict = "SchemaConflict",
291
+ /** Proto compilation failed (syntax error, etc.). */
292
+ SchemaCompileFailed = "SchemaCompileFailed",
293
+ /** Unsupported schema type (not protobuf). */
294
+ SchemaUnsupportedType = "SchemaUnsupportedType",
295
+ /** Schema validation on publish: wrong JSON value types. */
296
+ ValidationTypeMismatch = "ValidationTypeMismatch",
297
+ /** Payload is not valid JSON. */
298
+ ValidationInvalidJson = "ValidationInvalidJson",
299
+ /** Required AMQP argument missing (x-schema-type, x-schema-message). */
300
+ MissingArgument = "MissingArgument"
301
+ }
302
+
303
+ /**
304
+ * Parses structured JSON errors from AMQP Channel.Close reply_text.
305
+ *
306
+ * The broker encodes a `BrokerError` JSON object into the reply_text
307
+ * field. This module detects JSON (starts with `{`), validates the shape,
308
+ * and returns a typed `BrokerErrorPayload`.
309
+ *
310
+ * It also handles proto → TypeScript type name mapping, since the broker
311
+ * returns raw proto names (e.g. "double") and the client translates them
312
+ * to language-specific names (e.g. "number").
313
+ *
314
+ * Usage:
315
+ * const parsed = parseBrokerError(replyText);
316
+ * if (parsed) throw SchemaValidationError.fromBrokerError(parsed);
317
+ */
318
+
319
+ /** Structured error payload deserialized from broker JSON. */
320
+ interface BrokerErrorPayload {
321
+ code: BrokerErrorCode;
322
+ queue: string;
323
+ fields: FieldErrorDetail[];
324
+ truncated: boolean;
325
+ }
326
+ /** Per-field error detail from the broker. */
327
+ interface FieldErrorDetail {
328
+ /** Field name in the schema. */
329
+ name: string;
330
+ /** Proto type name that the queue schema expects (e.g. "double"). */
331
+ expected: string;
332
+ /** Proto type name that was actually received (e.g. "string"). */
333
+ got: string;
334
+ }
335
+ /**
336
+ * Maps proto3 type names to TypeScript-friendly equivalents.
337
+ *
338
+ * WHY here and not in the broker: the broker is language-agnostic.
339
+ * It returns raw proto kind names. Each client SDK maps them to the
340
+ * target language's type system.
341
+ *
342
+ * Usage:
343
+ * protoToTsType('double') // => 'number'
344
+ * protoToTsType('int32') // => 'number'
345
+ * protoToTsType('bool') // => 'boolean'
346
+ */
347
+ declare function protoToTsType(protoType: string): string;
348
+ /**
349
+ * Attempts to parse a structured broker error from AMQP reply_text.
350
+ *
351
+ * Returns `null` if the reply_text is a plain string (not JSON)
352
+ * or doesn't match the expected `BrokerErrorPayload` shape.
353
+ *
354
+ * Usage:
355
+ * const payload = parseBrokerError('{"code":"SchemaTypeMismatch",...}');
356
+ */
357
+ declare function parseBrokerError(replyText: string): BrokerErrorPayload | null;
358
+ /**
359
+ * Extracts the reply_text from an amqplib channel error message.
360
+ *
361
+ * amqplib formats errors as:
362
+ * 'Channel closed by server: 406 (PRECONDITION-FAILED) with message "..."'
363
+ *
364
+ * We extract the content between the last pair of double quotes.
365
+ *
366
+ * Usage:
367
+ * extractReplyText(new Error('...with message "{\"code\":...}"'))
368
+ */
369
+ declare function extractReplyText(err: unknown): string | null;
370
+ /**
371
+ * Formats a `BrokerErrorPayload` into a developer-friendly message.
372
+ *
373
+ * Uses TypeScript type names (via `protoToTsType`) so the error
374
+ * reads naturally to TS developers.
375
+ *
376
+ * Usage:
377
+ * formatBrokerError(payload)
378
+ * // => "Queue 'orders': field 'id' is number in queue but got string"
379
+ */
380
+ declare function formatBrokerError(payload: BrokerErrorPayload): string;
381
+
382
+ export { BrokerErrorCode, type BrokerErrorPayload, ConnectionError, ConsumeError, type FieldErrorDetail, PublishError, QueueError, QueueHandle, type RocketAssertQueueOptions, type RocketConsumeOptions, RocketMQ, RocketMQError, type RocketOptions, SchemaError, type SchemaInput, SchemaValidationError, SerializationError, TimeoutError, connect, extractReplyText, formatBrokerError, parseBrokerError, protoToTsType };