@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.
- package/README.md +375 -25
- package/dist/events/DomainEventEmitter.js +1 -1
- package/dist/events/DomainEventEmitter.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/payload-store/JsonStreamStringifySerializer.d.ts +1 -1
- package/dist/queues/AbstractQueueService.d.ts +15 -3
- package/dist/queues/AbstractQueueService.js +51 -17
- package/dist/queues/AbstractQueueService.js.map +1 -1
- package/dist/queues/HandlerContainer.d.ts +36 -11
- package/dist/queues/HandlerContainer.js +69 -21
- package/dist/queues/HandlerContainer.js.map +1 -1
- package/dist/queues/HandlerSpy.d.ts +26 -3
- package/dist/queues/HandlerSpy.js +40 -8
- package/dist/queues/HandlerSpy.js.map +1 -1
- package/dist/queues/MessageSchemaContainer.d.ts +52 -6
- package/dist/queues/MessageSchemaContainer.js +126 -18
- package/dist/queues/MessageSchemaContainer.js.map +1 -1
- package/dist/queues/MessageTypeResolver.d.ts +154 -0
- package/dist/queues/MessageTypeResolver.js +82 -0
- package/dist/queues/MessageTypeResolver.js.map +1 -0
- package/dist/types/MessageQueueTypes.d.ts +2 -1
- package/dist/types/queueOptionsTypes.d.ts +148 -3
- package/dist/types/queueOptionsTypes.js +16 -1
- package/dist/types/queueOptionsTypes.js.map +1 -1
- package/dist/utils/startupResourcePollingUtils.d.ts +59 -0
- package/dist/utils/startupResourcePollingUtils.js +199 -0
- package/dist/utils/startupResourcePollingUtils.js.map +1 -0
- package/package.json +5 -4
|
@@ -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
|
|
6
|
-
messageSchemas: readonly
|
|
7
|
-
|
|
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
|
|
32
|
+
private readonly messageTypeResolver?;
|
|
13
33
|
constructor(options: MessageSchemaContainerOptions<MessagePayloadSchemas>);
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
|
|
6
|
+
messageTypeResolver;
|
|
6
7
|
constructor(options) {
|
|
7
|
-
this.
|
|
8
|
-
this.messageSchemas = this.
|
|
9
|
-
this.messageDefinitions = this.
|
|
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
|
-
|
|
15
|
-
|
|
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
|
|
42
|
+
error: new Error(`Unsupported message type: ${messageType}`),
|
|
19
43
|
};
|
|
20
44
|
}
|
|
21
45
|
return { result: schema };
|
|
22
46
|
}
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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] =
|
|
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":"
|
|
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>>;
|