@message-queue-toolkit/core 24.2.0 → 25.1.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.
@@ -1,16 +1,62 @@
1
1
  import type { Either } from '@lokalise/node-core';
2
2
  import type { CommonEventDefinition } from '@message-queue-toolkit/schemas';
3
3
  import type { ZodSchema } from 'zod/v4';
4
+ import { type MessageTypeResolverConfig } from './MessageTypeResolver.ts';
5
+ export type SchemaEntry<MessagePayloadSchemas extends object> = {
6
+ schema: ZodSchema<MessagePayloadSchemas>;
7
+ /**
8
+ * Explicit message type for this schema.
9
+ * Required when using a custom resolver function.
10
+ */
11
+ messageType?: string;
12
+ };
13
+ export type DefinitionEntry = {
14
+ definition: CommonEventDefinition;
15
+ /**
16
+ * Explicit message type for this definition.
17
+ * Required when using a custom resolver function.
18
+ */
19
+ messageType?: string;
20
+ };
4
21
  export type MessageSchemaContainerOptions<MessagePayloadSchemas extends object> = {
5
- messageDefinitions: readonly CommonEventDefinition[];
6
- messageSchemas: readonly ZodSchema<MessagePayloadSchemas>[];
7
- messageTypeField?: string;
22
+ messageDefinitions: readonly DefinitionEntry[];
23
+ messageSchemas: readonly SchemaEntry<MessagePayloadSchemas>[];
24
+ /**
25
+ * Configuration for resolving message types.
26
+ */
27
+ messageTypeResolver?: MessageTypeResolverConfig;
8
28
  };
9
29
  export declare class MessageSchemaContainer<MessagePayloadSchemas extends object> {
10
30
  readonly messageDefinitions: Record<string | symbol, CommonEventDefinition>;
11
31
  private readonly messageSchemas;
12
- private readonly messageTypeField?;
32
+ private readonly messageTypeResolver?;
13
33
  constructor(options: MessageSchemaContainerOptions<MessagePayloadSchemas>);
14
- resolveSchema(message: Record<string, any>): Either<Error, ZodSchema<MessagePayloadSchemas>>;
15
- private resolveMap;
34
+ /**
35
+ * Resolves the schema for a message based on its type.
36
+ *
37
+ * @param message - The parsed message data
38
+ * @param attributes - Optional message-level attributes (e.g., PubSub attributes)
39
+ * @returns Either an error or the resolved schema
40
+ */
41
+ resolveSchema(message: Record<string, any>, attributes?: Record<string, unknown>): Either<Error, ZodSchema<MessagePayloadSchemas>>;
42
+ /**
43
+ * Resolves message type from message data and optional attributes.
44
+ * Only called when messageTypeResolver is configured.
45
+ */
46
+ private resolveMessageTypeFromData;
47
+ /**
48
+ * Gets the field path used for extracting message type from schemas during registration.
49
+ * Returns undefined for literal or custom resolver modes.
50
+ */
51
+ private getMessageTypePathForSchema;
52
+ /**
53
+ * Gets the literal message type if configured.
54
+ */
55
+ private getLiteralMessageType;
56
+ /**
57
+ * Validates that multiple schemas can be properly mapped at registration time.
58
+ */
59
+ private validateMultipleSchemas;
60
+ private resolveSchemaMap;
61
+ private resolveDefinitionMap;
16
62
  }
@@ -1,41 +1,149 @@
1
+ import { extractMessageTypeFromSchema, isMessageTypeLiteralConfig, isMessageTypePathConfig, isMessageTypeResolverFnConfig, resolveMessageType, } from "./MessageTypeResolver.js";
1
2
  const DEFAULT_SCHEMA_KEY = Symbol('NO_MESSAGE_TYPE');
2
3
  export class MessageSchemaContainer {
3
4
  messageDefinitions;
4
5
  messageSchemas;
5
- messageTypeField;
6
+ messageTypeResolver;
6
7
  constructor(options) {
7
- this.messageTypeField = options.messageTypeField;
8
- this.messageSchemas = this.resolveMap(options.messageSchemas);
9
- this.messageDefinitions = this.resolveMap(options.messageDefinitions ?? []);
8
+ this.messageTypeResolver = options.messageTypeResolver;
9
+ this.messageSchemas = this.resolveSchemaMap(options.messageSchemas);
10
+ this.messageDefinitions = this.resolveDefinitionMap(options.messageDefinitions ?? []);
10
11
  }
12
+ /**
13
+ * Resolves the schema for a message based on its type.
14
+ *
15
+ * @param message - The parsed message data
16
+ * @param attributes - Optional message-level attributes (e.g., PubSub attributes)
17
+ * @returns Either an error or the resolved schema
18
+ */
11
19
  resolveSchema(
12
20
  // biome-ignore lint/suspicious/noExplicitAny: This is expected
13
- message) {
14
- const messageType = this.messageTypeField ? message[this.messageTypeField] : undefined;
15
- const schema = this.messageSchemas[messageType ?? DEFAULT_SCHEMA_KEY];
21
+ message, attributes) {
22
+ // If no resolver configured, use the single default schema
23
+ if (!this.messageTypeResolver) {
24
+ const schema = this.messageSchemas[DEFAULT_SCHEMA_KEY];
25
+ if (!schema) {
26
+ return {
27
+ error: new Error('No messageTypeResolver configured and no default schema available'),
28
+ };
29
+ }
30
+ return { result: schema };
31
+ }
32
+ let messageType;
33
+ try {
34
+ messageType = this.resolveMessageTypeFromData(message, attributes);
35
+ }
36
+ catch (e) {
37
+ return { error: e instanceof Error ? e : new Error(String(e)) };
38
+ }
39
+ const schema = this.messageSchemas[messageType];
16
40
  if (!schema) {
17
41
  return {
18
- error: new Error(`Unsupported message type: ${messageType ?? DEFAULT_SCHEMA_KEY.toString()}`),
42
+ error: new Error(`Unsupported message type: ${messageType}`),
19
43
  };
20
44
  }
21
45
  return { result: schema };
22
46
  }
23
- resolveMap(array) {
47
+ /**
48
+ * Resolves message type from message data and optional attributes.
49
+ * Only called when messageTypeResolver is configured.
50
+ */
51
+ resolveMessageTypeFromData(messageData, messageAttributes) {
52
+ // This method is only called after checking messageTypeResolver exists in resolveSchema
53
+ const resolver = this.messageTypeResolver;
54
+ const context = { messageData, messageAttributes };
55
+ return resolveMessageType(resolver, context);
56
+ }
57
+ /**
58
+ * Gets the field path used for extracting message type from schemas during registration.
59
+ * Returns undefined for literal or custom resolver modes.
60
+ */
61
+ getMessageTypePathForSchema() {
62
+ if (this.messageTypeResolver && isMessageTypePathConfig(this.messageTypeResolver)) {
63
+ return this.messageTypeResolver.messageTypePath;
64
+ }
65
+ // For literal or custom resolver, we don't extract type from schema
66
+ return undefined;
67
+ }
68
+ /**
69
+ * Gets the literal message type if configured.
70
+ */
71
+ getLiteralMessageType() {
72
+ if (this.messageTypeResolver && isMessageTypeLiteralConfig(this.messageTypeResolver)) {
73
+ return this.messageTypeResolver.literal;
74
+ }
75
+ return undefined;
76
+ }
77
+ /**
78
+ * Validates that multiple schemas can be properly mapped at registration time.
79
+ */
80
+ validateMultipleSchemas(schemaCount) {
81
+ if (schemaCount <= 1)
82
+ return;
83
+ if (!this.messageTypeResolver) {
84
+ throw new Error('Multiple schemas require messageTypeResolver to be configured. ' +
85
+ 'Use messageTypePath config (to extract types from schema literals) or literal config.');
86
+ }
87
+ // Custom resolver function cannot be used with multiple schemas because
88
+ // we can't know what types it will return until runtime.
89
+ if (isMessageTypeResolverFnConfig(this.messageTypeResolver)) {
90
+ throw new Error('Custom resolver function cannot be used with multiple schemas. ' +
91
+ 'The resolver works for runtime type resolution, but at registration time ' +
92
+ 'we cannot determine which schema corresponds to which type. ' +
93
+ 'Use messageTypePath config (to extract types from schema literals) or register only a single schema.');
94
+ }
95
+ }
96
+ resolveSchemaMap(entries) {
24
97
  const result = {};
25
- for (const item of array) {
98
+ this.validateMultipleSchemas(entries.length);
99
+ const literalType = this.getLiteralMessageType();
100
+ const messageTypePath = this.getMessageTypePathForSchema();
101
+ for (const entry of entries) {
26
102
  let type;
27
- if (this.messageTypeField) {
28
- type =
29
- 'publisherSchema' in item
30
- ? // @ts-expect-error
31
- item.publisherSchema?.shape[this.messageTypeField]?.value
32
- : // @ts-expect-error
33
- item.shape?.[this.messageTypeField]?.value;
103
+ // Priority 1: Explicit messageType on the entry
104
+ if (entry.messageType) {
105
+ type = entry.messageType;
34
106
  }
107
+ // Priority 2: Literal type from resolver config (same for all schemas)
108
+ else if (literalType) {
109
+ type = literalType;
110
+ }
111
+ // Priority 3: Extract type from schema shape using the field path
112
+ else if (messageTypePath) {
113
+ // @ts-expect-error - ZodSchema has shape property at runtime
114
+ type = extractMessageTypeFromSchema(entry.schema, messageTypePath);
115
+ }
116
+ // If no type extracted, use DEFAULT_SCHEMA_KEY (single schema fallback)
35
117
  const key = type ?? DEFAULT_SCHEMA_KEY;
36
118
  if (result[key])
37
119
  throw new Error(`Duplicate schema for type: ${key.toString()}`);
38
- result[key] = item;
120
+ result[key] = entry.schema;
121
+ }
122
+ return result;
123
+ }
124
+ resolveDefinitionMap(entries) {
125
+ const result = {};
126
+ const literalType = this.getLiteralMessageType();
127
+ const messageTypePath = this.getMessageTypePathForSchema();
128
+ for (const entry of entries) {
129
+ let type;
130
+ // Priority 1: Explicit messageType on the entry
131
+ if (entry.messageType) {
132
+ type = entry.messageType;
133
+ }
134
+ // Priority 2: Literal type from resolver config (same for all definitions)
135
+ else if (literalType) {
136
+ type = literalType;
137
+ }
138
+ // Priority 3: Extract type from definition's publisherSchema using the field path
139
+ else if (messageTypePath) {
140
+ type = extractMessageTypeFromSchema(entry.definition.publisherSchema, messageTypePath);
141
+ }
142
+ // If no type extracted, use DEFAULT_SCHEMA_KEY (single definition fallback)
143
+ const key = type ?? DEFAULT_SCHEMA_KEY;
144
+ if (result[key])
145
+ throw new Error(`Duplicate definition for type: ${key.toString()}`);
146
+ result[key] = entry.definition;
39
147
  }
40
148
  return result;
41
149
  }
@@ -1 +1 @@
1
- {"version":3,"file":"MessageSchemaContainer.js","sourceRoot":"","sources":["../../lib/queues/MessageSchemaContainer.ts"],"names":[],"mappings":"AAUA,MAAM,kBAAkB,GAAG,MAAM,CAAC,iBAAiB,CAAC,CAAA;AAEpD,MAAM,OAAO,sBAAsB;IACjB,kBAAkB,CAAgD;IAEjE,cAAc,CAA2D;IACzE,gBAAgB,CAAS;IAE1C,YAAY,OAA6D;QACvE,IAAI,CAAC,gBAAgB,GAAG,OAAO,CAAC,gBAAgB,CAAA;QAChD,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;QAC7D,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAA;IAC7E,CAAC;IAEM,aAAa;IAClB,+DAA+D;IAC/D,OAA4B;QAE5B,MAAM,WAAW,GAAG,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;QAEtF,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,CAAC,WAAW,IAAI,kBAAkB,CAAC,CAAA;QACrE,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO;gBACL,KAAK,EAAE,IAAI,KAAK,CACd,6BAA6B,WAAW,IAAI,kBAAkB,CAAC,QAAQ,EAAE,EAAE,CAC5E;aACF,CAAA;QACH,CAAC;QACD,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAA;IAC3B,CAAC;IAEO,UAAU,CAChB,KAAmB;QAEnB,MAAM,MAAM,GAA+B,EAAE,CAAA;QAE7C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,IAAwB,CAAA;YAE5B,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;gBAC1B,IAAI;oBACF,iBAAiB,IAAI,IAAI;wBACvB,CAAC,CAAC,mBAAmB;4BACnB,IAAI,CAAC,eAAe,EAAE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,KAAK;wBAC3D,CAAC,CAAC,mBAAmB;4BACnB,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,CAAC,gBAAgB,CAAC,EAAE,KAAK,CAAA;YAClD,CAAC;YAED,MAAM,GAAG,GAAG,IAAI,IAAI,kBAAkB,CAAA;YACtC,IAAI,MAAM,CAAC,GAAG,CAAC;gBAAE,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAA;YAEhF,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAA;QACpB,CAAC;QAED,OAAO,MAAM,CAAA;IACf,CAAC;CACF"}
1
+ {"version":3,"file":"MessageSchemaContainer.js","sourceRoot":"","sources":["../../lib/queues/MessageSchemaContainer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,154 @@
1
+ /**
2
+ * Context passed to a custom message type resolver function.
3
+ * Contains both the parsed message data and any message-level attributes/metadata.
4
+ */
5
+ export type MessageTypeResolverContext = {
6
+ /**
7
+ * The parsed/decoded message body (e.g., JSON-parsed data field in PubSub)
8
+ */
9
+ messageData: unknown;
10
+ /**
11
+ * Message-level attributes/metadata (e.g., PubSub message attributes, SQS message attributes)
12
+ * This is where Cloud Storage notifications put eventType, for example.
13
+ */
14
+ messageAttributes?: Record<string, unknown>;
15
+ };
16
+ /**
17
+ * Function that extracts the message type from a message.
18
+ * Used for routing messages to appropriate handlers and schemas.
19
+ *
20
+ * The function MUST return a valid message type string. If the type cannot be
21
+ * determined, the function should either:
22
+ * - Return a default type (e.g., 'unknown' or a fallback handler type)
23
+ * - Throw an error with a descriptive message
24
+ *
25
+ * @example
26
+ * // Extract from attributes (e.g., Cloud Storage notifications)
27
+ * const resolver: MessageTypeResolverFn = ({ messageAttributes }) => {
28
+ * const eventType = messageAttributes?.eventType as string | undefined
29
+ * if (!eventType) {
30
+ * throw new Error('eventType attribute is required')
31
+ * }
32
+ * return eventType
33
+ * }
34
+ *
35
+ * @example
36
+ * // Extract from nested data with fallback
37
+ * const resolver: MessageTypeResolverFn = ({ messageData }) => {
38
+ * const data = messageData as { metadata?: { eventName?: string } }
39
+ * return data.metadata?.eventName ?? 'default.event'
40
+ * }
41
+ */
42
+ export type MessageTypeResolverFn = (context: MessageTypeResolverContext) => string;
43
+ /**
44
+ * Configuration for resolving message types.
45
+ *
46
+ * Three modes are supported:
47
+ *
48
+ * 1. **Field path** (string): Extract type from a field in the message using dot notation.
49
+ * Supports nested paths like 'metadata.type' or 'detail.eventType'.
50
+ * @example { messageTypePath: 'type' } // extracts from message.type
51
+ * @example { messageTypePath: 'detail-type' } // for EventBridge events
52
+ * @example { messageTypePath: 'metadata.eventType' } // nested path
53
+ *
54
+ * 2. **Constant type** (object with `literal`): All messages are treated as the same type.
55
+ * Useful when a subscription/queue only receives one type of message.
56
+ * @example { literal: 'order.created' }
57
+ *
58
+ * 3. **Custom resolver** (object with `resolver`): Full flexibility via callback function.
59
+ * Use when type needs to be extracted from attributes or requires transformation.
60
+ * @example
61
+ * {
62
+ * resolver: ({ messageAttributes }) => messageAttributes?.eventType as string
63
+ * }
64
+ *
65
+ * @example
66
+ * // Cloud Storage notifications via PubSub - type is in attributes
67
+ * const config: MessageTypeResolverConfig = {
68
+ * resolver: ({ messageAttributes }) => {
69
+ * const eventType = messageAttributes?.eventType as string | undefined
70
+ * if (!eventType) {
71
+ * throw new Error('eventType attribute is required for Cloud Storage notifications')
72
+ * }
73
+ * // Optionally map to your internal event types
74
+ * if (eventType === 'OBJECT_FINALIZE') return 'storage.object.created'
75
+ * if (eventType === 'OBJECT_DELETE') return 'storage.object.deleted'
76
+ * return eventType
77
+ * }
78
+ * }
79
+ *
80
+ * @example
81
+ * // CloudEvents format - type might be in envelope or data
82
+ * const config: MessageTypeResolverConfig = {
83
+ * resolver: ({ messageData, messageAttributes }) => {
84
+ * // Check for CloudEvents binary mode (type in attributes)
85
+ * if (messageAttributes?.['ce-type']) {
86
+ * return messageAttributes['ce-type'] as string
87
+ * }
88
+ * // Fall back to type in message data
89
+ * const data = messageData as { type?: string }
90
+ * if (!data.type) {
91
+ * throw new Error('Message type not found in CloudEvents envelope or message data')
92
+ * }
93
+ * return data.type
94
+ * }
95
+ * }
96
+ */
97
+ export type MessageTypeResolverConfig = {
98
+ /**
99
+ * Path to the field containing the message type.
100
+ * Supports dot notation for nested paths (e.g., 'metadata.type', 'detail.eventType').
101
+ */
102
+ messageTypePath: string;
103
+ } | {
104
+ /**
105
+ * Constant message type for all messages.
106
+ * Use when all messages in a queue/subscription are of the same type.
107
+ */
108
+ literal: string;
109
+ } | {
110
+ /**
111
+ * Custom function to extract message type from message data and/or attributes.
112
+ * Provides full flexibility for complex routing scenarios.
113
+ */
114
+ resolver: MessageTypeResolverFn;
115
+ };
116
+ /**
117
+ * Type guard to check if config uses field path mode
118
+ */
119
+ export declare function isMessageTypePathConfig(config: MessageTypeResolverConfig): config is {
120
+ messageTypePath: string;
121
+ };
122
+ /**
123
+ * Type guard to check if config uses literal/constant mode
124
+ */
125
+ export declare function isMessageTypeLiteralConfig(config: MessageTypeResolverConfig): config is {
126
+ literal: string;
127
+ };
128
+ /**
129
+ * Type guard to check if config uses custom resolver mode
130
+ */
131
+ export declare function isMessageTypeResolverFnConfig(config: MessageTypeResolverConfig): config is {
132
+ resolver: MessageTypeResolverFn;
133
+ };
134
+ /**
135
+ * Resolves message type using the provided configuration.
136
+ *
137
+ * @param config - The resolver configuration
138
+ * @param context - Context containing message data and attributes
139
+ * @returns The resolved message type
140
+ * @throws Error if message type cannot be resolved (for messageTypePath mode)
141
+ */
142
+ export declare function resolveMessageType(config: MessageTypeResolverConfig, context: MessageTypeResolverContext): string;
143
+ /**
144
+ * Extracts message type from schema definition using the field path.
145
+ * Used during handler/schema registration to build the routing map.
146
+ * Supports dot notation for nested paths (e.g., 'metadata.type').
147
+ *
148
+ * @param schema - Zod schema with shape property
149
+ * @param messageTypePath - Path to the field containing the type literal (supports dot notation)
150
+ * @returns The literal type value from the schema, or undefined if field doesn't exist or isn't a literal
151
+ */
152
+ export declare function extractMessageTypeFromSchema(schema: {
153
+ shape?: Record<string, any>;
154
+ }, messageTypePath: string | undefined): string | undefined;
@@ -0,0 +1,82 @@
1
+ import { getProperty } from 'dot-prop';
2
+ /**
3
+ * Type guard to check if config uses field path mode
4
+ */
5
+ export function isMessageTypePathConfig(config) {
6
+ return 'messageTypePath' in config;
7
+ }
8
+ /**
9
+ * Type guard to check if config uses literal/constant mode
10
+ */
11
+ export function isMessageTypeLiteralConfig(config) {
12
+ return 'literal' in config;
13
+ }
14
+ /**
15
+ * Type guard to check if config uses custom resolver mode
16
+ */
17
+ export function isMessageTypeResolverFnConfig(config) {
18
+ return 'resolver' in config;
19
+ }
20
+ /**
21
+ * Resolves message type using the provided configuration.
22
+ *
23
+ * @param config - The resolver configuration
24
+ * @param context - Context containing message data and attributes
25
+ * @returns The resolved message type
26
+ * @throws Error if message type cannot be resolved (for messageTypePath mode)
27
+ */
28
+ export function resolveMessageType(config, context) {
29
+ if (isMessageTypeLiteralConfig(config)) {
30
+ return config.literal;
31
+ }
32
+ if (isMessageTypePathConfig(config)) {
33
+ const rawMessageType = getProperty(context.messageData, config.messageTypePath);
34
+ if (rawMessageType === undefined || rawMessageType === null) {
35
+ throw new Error(`Unable to resolve message type: path '${config.messageTypePath}' not found in message data`);
36
+ }
37
+ if (typeof rawMessageType !== 'string') {
38
+ throw new Error(`Unable to resolve message type: path '${config.messageTypePath}' contains a non-string value (got ${typeof rawMessageType})`);
39
+ }
40
+ return rawMessageType;
41
+ }
42
+ // Custom resolver function - must return a string (user handles errors/defaults)
43
+ return config.resolver(context);
44
+ }
45
+ /**
46
+ * Extracts message type from schema definition using the field path.
47
+ * Used during handler/schema registration to build the routing map.
48
+ * Supports dot notation for nested paths (e.g., 'metadata.type').
49
+ *
50
+ * @param schema - Zod schema with shape property
51
+ * @param messageTypePath - Path to the field containing the type literal (supports dot notation)
52
+ * @returns The literal type value from the schema, or undefined if field doesn't exist or isn't a literal
53
+ */
54
+ export function extractMessageTypeFromSchema(
55
+ // biome-ignore lint/suspicious/noExplicitAny: Schema shape can be any
56
+ schema, messageTypePath) {
57
+ if (!messageTypePath) {
58
+ return undefined;
59
+ }
60
+ const pathParts = messageTypePath.split('.');
61
+ // biome-ignore lint/suspicious/noExplicitAny: Schema shape can be any
62
+ let current = schema;
63
+ for (const part of pathParts) {
64
+ if (!current?.shape) {
65
+ return undefined;
66
+ }
67
+ current = current.shape[part];
68
+ if (!current) {
69
+ return undefined;
70
+ }
71
+ }
72
+ // Check if the final field has a literal value (z.literal() creates a field with .value)
73
+ if (!('value' in current)) {
74
+ return undefined;
75
+ }
76
+ const value = current.value;
77
+ if (typeof value !== 'string') {
78
+ return undefined;
79
+ }
80
+ return value;
81
+ }
82
+ //# sourceMappingURL=MessageTypeResolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessageTypeResolver.js","sourceRoot":"","sources":["../../lib/queues/MessageTypeResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAA;AA2HtC;;GAEG;AACH,MAAM,UAAU,uBAAuB,CACrC,MAAiC;IAEjC,OAAO,iBAAiB,IAAI,MAAM,CAAA;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,0BAA0B,CACxC,MAAiC;IAEjC,OAAO,SAAS,IAAI,MAAM,CAAA;AAC5B,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,6BAA6B,CAC3C,MAAiC;IAEjC,OAAO,UAAU,IAAI,MAAM,CAAA;AAC7B,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAAiC,EACjC,OAAmC;IAEnC,IAAI,0BAA0B,CAAC,MAAM,CAAC,EAAE,CAAC;QACvC,OAAO,MAAM,CAAC,OAAO,CAAA;IACvB,CAAC;IAED,IAAI,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC;QACpC,MAAM,cAAc,GAAG,WAAW,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,CAAC,eAAe,CAAC,CAAA;QAC/E,IAAI,cAAc,KAAK,SAAS,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5D,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,CAAC,eAAe,6BAA6B,CAC7F,CAAA;QACH,CAAC;QACD,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;YACvC,MAAM,IAAI,KAAK,CACb,yCAAyC,MAAM,CAAC,eAAe,sCAAsC,OAAO,cAAc,GAAG,CAC9H,CAAA;QACH,CAAC;QACD,OAAO,cAAc,CAAA;IACvB,CAAC;IAED,iFAAiF;IACjF,OAAO,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;AACjC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,4BAA4B;AAC1C,sEAAsE;AACtE,MAAuC,EACvC,eAAmC;IAEnC,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC5C,sEAAsE;IACtE,IAAI,OAAO,GAAQ,MAAM,CAAA;IAEzB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC;YACpB,OAAO,SAAS,CAAA;QAClB,CAAC;QACD,OAAO,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED,yFAAyF;IACzF,IAAI,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,EAAE,CAAC;QAC1B,OAAO,SAAS,CAAA;IAClB,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;IAC3B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { CommonLogger, TransactionObservabilityManager } from '@lokalise/node-core';
1
+ import type { CommonLogger, ErrorReporter, TransactionObservabilityManager } from '@lokalise/node-core';
2
2
  import type { ZodSchema } from 'zod/v4';
3
3
  import type { PublicHandlerSpy } from '../queues/HandlerSpy.ts';
4
4
  export interface QueueConsumer {
@@ -28,5 +28,6 @@ export interface AsyncPublisher<MessagePayloadType extends object, MessageOption
28
28
  export type { TransactionObservabilityManager };
29
29
  export type ExtraParams = {
30
30
  logger?: CommonLogger;
31
+ errorReporter?: ErrorReporter;
31
32
  };
32
33
  export type SchemaMap<SupportedMessageTypes extends string> = Record<SupportedMessageTypes, ZodSchema<any>>;