From 8671a0e0af26baa35273a624f98347472bab9d9a Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Thu, 28 Aug 2025 15:28:59 +0300 Subject: [PATCH 1/8] feat: add openapi support for chat.getMessage API --- apps/meteor/app/api/server/v1/chat.ts | 89 ++++++++++++++++++--------- 1 file changed, 60 insertions(+), 29 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index f289960f4f411..31bcd1eee93e2 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -10,7 +10,6 @@ import { isChatGetThreadsListProps, isChatDeleteProps, isChatSyncMessagesProps, - isChatGetMessageProps, isChatPostMessageProps, isChatSearchProps, isChatSendMessageProps, @@ -144,33 +143,6 @@ API.v1.addRoute( }, ); -API.v1.addRoute( - 'chat.getMessage', - { - authRequired: true, - validateParams: isChatGetMessageProps, - }, - { - async get() { - if (!this.queryParams.msgId) { - return API.v1.failure('The "msgId" query parameter must be provided.'); - } - - const msg = await getSingleMessage(this.userId, this.queryParams.msgId); - - if (!msg) { - return API.v1.failure(); - } - - const [message] = await normalizeMessagesForUser([msg], this.userId); - - return API.v1.success({ - message, - }); - }, - }, -); - type ChatPinMessage = { messageId: IMessage['_id']; }; @@ -179,6 +151,10 @@ type ChatUnpinMessage = { messageId: IMessage['_id']; }; +type ChatGetMessage = { + msgId: IMessage['_id']; +}; + const ChatPinMessageSchema = { type: 'object', properties: { @@ -203,10 +179,24 @@ const ChatUnpinMessageSchema = { additionalProperties: false, }; +const ChatGetMessageSchema = { + type: 'object', + properties: { + msgId: { + type: 'string', + minLength: 1, + }, + }, + required: ['msgId'], + additionalProperties: false, +}; + const isChatPinMessageProps = ajv.compile(ChatPinMessageSchema); const isChatUnpinMessageProps = ajv.compile(ChatUnpinMessageSchema); +const isChatGetMessageProps = ajv.compile(ChatGetMessageSchema); + const chatEndpoints = API.v1 .post( 'chat.pinMessage', @@ -350,8 +340,49 @@ const chatEndpoints = API.v1 message, }); }, - ); + ) + .get( + 'chat.getMessage', + { + authRequired: true, + query: isChatGetMessageProps, + response: { + 400: validateBadRequestErrorResponse, + 401: validateUnauthorizedErrorResponse, + 200: ajv.compile<{ message: IMessage }>({ + type: 'object', + properties: { + message: { $ref: '#/components/schemas/IMessage' }, + success: { + type: 'boolean', + enum: [true], + }, + }, + required: ['message', 'success'], + additionalProperties: false, + }), + }, + }, + async function action() { + if (!this.queryParams.msgId) { + return API.v1.failure('The "msgId" query parameter must be provided.'); + } + + const msg = await getSingleMessage(this.userId, this.queryParams.msgId); + + if (!msg) { + return API.v1.failure(); + } + + const [message] = await normalizeMessagesForUser([msg], this.userId); + + return API.v1.success({ + message, + }); + }, + ); + API.v1.addRoute( 'chat.postMessage', { authRequired: true, validateParams: isChatPostMessageProps }, From 4cc0fcbdbcf5b90c4f9e608b9701ddf5b57ffa01 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Thu, 28 Aug 2025 15:29:15 +0300 Subject: [PATCH 2/8] refactor: remove unused chat.getMessage from rest-typings --- packages/rest-typings/src/v1/chat.ts | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/packages/rest-typings/src/v1/chat.ts b/packages/rest-typings/src/v1/chat.ts index 71fd745d7f95c..1819a8745370a 100644 --- a/packages/rest-typings/src/v1/chat.ts +++ b/packages/rest-typings/src/v1/chat.ts @@ -115,24 +115,6 @@ const chatUnfollowMessageSchema = { export const isChatUnfollowMessageProps = ajv.compile(chatUnfollowMessageSchema); -type ChatGetMessage = { - msgId: IMessage['_id']; -}; - -const ChatGetMessageSchema = { - type: 'object', - properties: { - msgId: { - type: 'string', - minLength: 1, - }, - }, - required: ['msgId'], - additionalProperties: false, -}; - -export const isChatGetMessageProps = ajv.compile(ChatGetMessageSchema); - type ChatStarMessage = { messageId: IMessage['_id']; }; @@ -962,11 +944,6 @@ export type ChatEndpoints = { message: IMessage; }; }; - '/v1/chat.getMessage': { - GET: (params: ChatGetMessage) => { - message: IMessage; - }; - }; '/v1/chat.followMessage': { POST: (params: ChatFollowMessage) => void; }; From 7abd6a5575a770d3048b617fd747cba6dc517764 Mon Sep 17 00:00:00 2001 From: Ahmed Nasser Date: Thu, 28 Aug 2025 15:31:05 +0300 Subject: [PATCH 3/8] Create unlucky-sloths-cough.md --- .changeset/unlucky-sloths-cough.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/unlucky-sloths-cough.md diff --git a/.changeset/unlucky-sloths-cough.md b/.changeset/unlucky-sloths-cough.md new file mode 100644 index 0000000000000..ce42ebea33969 --- /dev/null +++ b/.changeset/unlucky-sloths-cough.md @@ -0,0 +1,6 @@ +--- +"@rocket.chat/meteor": patch +"@rocket.chat/rest-typings": patch +--- + +Add OpenAPI support for the Rocket.Chat chat.getMessage API endpoints by migrating to a modern chained route definition syntax and utilizing shared AJV schemas for validation to enhance API documentation and ensure type safety through response validation. From 959d68505bfde70836aebc3e5657985ace7a7565 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Thu, 28 Aug 2025 17:22:37 +0300 Subject: [PATCH 4/8] fix: add type annotations for message retrieval in chat.postMessage route --- apps/meteor/app/api/server/v1/chat.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 31bcd1eee93e2..84344d6be0379 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -369,20 +369,20 @@ const chatEndpoints = API.v1 return API.v1.failure('The "msgId" query parameter must be provided.'); } - const msg = await getSingleMessage(this.userId, this.queryParams.msgId); + const msg: IMessage | null = await getSingleMessage(this.userId, this.queryParams.msgId); if (!msg) { return API.v1.failure(); } - const [message] = await normalizeMessagesForUser([msg], this.userId); + const [message]: IMessage[] = await normalizeMessagesForUser([msg], this.userId); return API.v1.success({ message, }); }, ); - + API.v1.addRoute( 'chat.postMessage', { authRequired: true, validateParams: isChatPostMessageProps }, From c65437ce21011c029a4065d0dfdb57ce6bb3cdce Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Thu, 28 Aug 2025 20:12:54 +0300 Subject: [PATCH 5/8] feat: enhance message schema with detailed properties for chat messages --- apps/meteor/app/api/server/v1/chat.ts | 361 +++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 84344d6be0379..357f2ac678ca5 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -352,7 +352,366 @@ const chatEndpoints = API.v1 200: ajv.compile<{ message: IMessage }>({ type: 'object', properties: { - message: { $ref: '#/components/schemas/IMessage' }, + message: { + type: 'object', + properties: { + rid: { type: 'string' }, + msg: { type: 'string' }, + tmid: { type: 'string' }, + tshow: { type: 'boolean' }, + ts: { + type: 'string', + format: 'date-time', + }, + mentions: { + type: 'array', + items: { + $ref: '#/components/schemas/MessageMention', + }, + }, + groupable: { + type: 'boolean', + }, + channels: { + type: 'array', + items: { + $ref: '#/components/schemas/PickIRoom_idname', + }, + }, + u: { + $ref: '#/components/schemas/RequiredPickIUser_idusernamePickIUsername', + }, + blocks: { + $ref: '#/components/schemas/MessageSurfaceLayout', + }, + alias: { + type: 'string', + }, + md: { + anyOf: [ + { + type: 'array', + items: { + anyOf: [ + { + $ref: '#/components/schemas/Paragraph', + }, + { + $ref: '#/components/schemas/Code', + }, + { + $ref: '#/components/schemas/Heading', + }, + { + $ref: '#/components/schemas/Quote', + }, + { + $ref: '#/components/schemas/ListItem', + }, + { + $ref: '#/components/schemas/Tasks', + }, + { + $ref: '#/components/schemas/OrderedList', + }, + { + $ref: '#/components/schemas/UnorderedList', + }, + { + $ref: '#/components/schemas/LineBreak', + }, + { + $ref: '#/components/schemas/KaTeX', + }, + ], + }, + }, + { + type: 'array', + items: { + anyOf: [ + { + $ref: '#/components/schemas/BigEmoji.o1', + }, + ], + }, + minItems: 1, + maxItems: 1, + }, + ], + }, + _hidden: { + type: 'boolean', + }, + imported: { + type: 'boolean', + }, + replies: { + type: 'array', + items: { + type: 'string', + }, + }, + location: { + type: 'object', + properties: { + type: { + type: 'string', + enum: ['Point'], + }, + coordinates: { + type: 'array', + items: { + type: 'number', + }, + minItems: 2, + maxItems: 2, + }, + }, + required: ['type', 'coordinates'], + }, + starred: { + type: 'array', + items: { + type: 'object', + properties: { + _id: { + type: 'string', + }, + }, + required: ['_id'], + }, + }, + pinned: { + type: 'boolean', + }, + pinnedAt: { + type: 'string', + format: 'date-time', + }, + pinnedBy: { + $ref: '#/components/schemas/PickIUser_idusername', + }, + unread: { + type: 'boolean', + }, + temp: { + type: 'boolean', + }, + drid: { + type: 'string', + }, + tlm: { + type: 'string', + format: 'date-time', + }, + dcount: { + type: 'number', + }, + tcount: { + type: 'number', + }, + t: { + type: 'string', + }, + e2e: { + type: 'string', + enum: ['pending', 'done'], + }, + e2eMentions: { + type: 'object', + properties: { + e2eUserMentions: { + type: 'array', + items: { + type: 'string', + }, + }, + e2eChannelMentions: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + required: [], + }, + otrAck: { + type: 'string', + }, + urls: { + type: 'array', + items: { + $ref: '#/components/schemas/MessageUrl', + }, + }, + actionLinks: { + type: 'array', + items: { + type: 'object', + properties: { + icon: { + type: 'string', + }, + i18nLabel: {}, + label: { + type: 'string', + }, + method_id: { + type: 'string', + }, + params: { + type: 'string', + }, + }, + required: ['icon', 'i18nLabel', 'label', 'method_id', 'params'], + }, + deprecated: true, + }, + file: { + $ref: '#/components/schemas/FileProp', + deprecated: true, + }, + fileUpload: { + type: 'object', + properties: { + publicFilePath: { + type: 'string', + }, + type: { + type: 'string', + }, + size: { + type: 'number', + }, + }, + required: ['publicFilePath'], + }, + files: { + type: 'array', + items: { + $ref: '#/components/schemas/FileProp', + }, + }, + attachments: { type: 'array' }, + reactions: { + type: 'object', + properties: {}, + required: [], + additionalProperties: { + type: 'object', + properties: { + names: { + type: 'array', + items: { + type: 'string', + }, + }, + usernames: { + type: 'array', + items: { + type: 'string', + }, + }, + federationReactionEventIds: { + $ref: '#/components/schemas/Recordstringstring', + }, + }, + required: ['usernames'], + }, + }, + private: { + type: 'boolean', + }, + bot: { + $ref: '#/components/schemas/Recordstringany', + }, + sentByEmail: { + type: 'boolean', + }, + webRtcCallEndTs: { + type: 'string', + format: 'date-time', + }, + role: { + type: 'string', + }, + avatar: { + type: 'string', + }, + emoji: { + type: 'string', + }, + tokens: { + type: 'array', + items: { + $ref: '#/components/schemas/Token', + }, + }, + html: { + type: 'string', + }, + token: { + type: 'string', + }, + federation: { + type: 'object', + properties: { + eventId: { + type: 'string', + }, + }, + required: ['eventId'], + }, + slaData: { + type: 'object', + properties: { + definedBy: { + $ref: '#/components/schemas/PickIUser_idusername', + }, + sla: { + $ref: '#/components/schemas/PickIOmnichannelServiceLevelAgreementsname', + }, + }, + required: ['definedBy'], + }, + priorityData: { + type: 'object', + properties: { + definedBy: { + $ref: '#/components/schemas/PickIUser_idusername', + }, + priority: { + $ref: '#/components/schemas/PickILivechatPrioritynamei18n', + }, + }, + required: ['definedBy'], + }, + customFields: { + $ref: '#/components/schemas/IMessageCustomFields', + }, + content: { + type: 'object', + properties: { + algorithm: { + type: 'string', + }, + ciphertext: { + type: 'string', + }, + }, + required: ['algorithm', 'ciphertext'], + }, + _id: { + type: 'string', + }, + _updatedAt: { + type: 'string', + format: 'date-time', + }, + }, + required: ['rid', 'msg', 'ts', 'u', '_id', '_updatedAt'], + }, success: { type: 'boolean', enum: [true], From 83634f343b5aae7c4e5251b052509fad8edf3fd0 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Sun, 25 Jan 2026 16:10:26 +0000 Subject: [PATCH 6/8] chore: use typia shortcut ref --- apps/meteor/app/api/server/v1/chat.ts | 361 +------------------------- 1 file changed, 1 insertion(+), 360 deletions(-) diff --git a/apps/meteor/app/api/server/v1/chat.ts b/apps/meteor/app/api/server/v1/chat.ts index 357f2ac678ca5..84344d6be0379 100644 --- a/apps/meteor/app/api/server/v1/chat.ts +++ b/apps/meteor/app/api/server/v1/chat.ts @@ -352,366 +352,7 @@ const chatEndpoints = API.v1 200: ajv.compile<{ message: IMessage }>({ type: 'object', properties: { - message: { - type: 'object', - properties: { - rid: { type: 'string' }, - msg: { type: 'string' }, - tmid: { type: 'string' }, - tshow: { type: 'boolean' }, - ts: { - type: 'string', - format: 'date-time', - }, - mentions: { - type: 'array', - items: { - $ref: '#/components/schemas/MessageMention', - }, - }, - groupable: { - type: 'boolean', - }, - channels: { - type: 'array', - items: { - $ref: '#/components/schemas/PickIRoom_idname', - }, - }, - u: { - $ref: '#/components/schemas/RequiredPickIUser_idusernamePickIUsername', - }, - blocks: { - $ref: '#/components/schemas/MessageSurfaceLayout', - }, - alias: { - type: 'string', - }, - md: { - anyOf: [ - { - type: 'array', - items: { - anyOf: [ - { - $ref: '#/components/schemas/Paragraph', - }, - { - $ref: '#/components/schemas/Code', - }, - { - $ref: '#/components/schemas/Heading', - }, - { - $ref: '#/components/schemas/Quote', - }, - { - $ref: '#/components/schemas/ListItem', - }, - { - $ref: '#/components/schemas/Tasks', - }, - { - $ref: '#/components/schemas/OrderedList', - }, - { - $ref: '#/components/schemas/UnorderedList', - }, - { - $ref: '#/components/schemas/LineBreak', - }, - { - $ref: '#/components/schemas/KaTeX', - }, - ], - }, - }, - { - type: 'array', - items: { - anyOf: [ - { - $ref: '#/components/schemas/BigEmoji.o1', - }, - ], - }, - minItems: 1, - maxItems: 1, - }, - ], - }, - _hidden: { - type: 'boolean', - }, - imported: { - type: 'boolean', - }, - replies: { - type: 'array', - items: { - type: 'string', - }, - }, - location: { - type: 'object', - properties: { - type: { - type: 'string', - enum: ['Point'], - }, - coordinates: { - type: 'array', - items: { - type: 'number', - }, - minItems: 2, - maxItems: 2, - }, - }, - required: ['type', 'coordinates'], - }, - starred: { - type: 'array', - items: { - type: 'object', - properties: { - _id: { - type: 'string', - }, - }, - required: ['_id'], - }, - }, - pinned: { - type: 'boolean', - }, - pinnedAt: { - type: 'string', - format: 'date-time', - }, - pinnedBy: { - $ref: '#/components/schemas/PickIUser_idusername', - }, - unread: { - type: 'boolean', - }, - temp: { - type: 'boolean', - }, - drid: { - type: 'string', - }, - tlm: { - type: 'string', - format: 'date-time', - }, - dcount: { - type: 'number', - }, - tcount: { - type: 'number', - }, - t: { - type: 'string', - }, - e2e: { - type: 'string', - enum: ['pending', 'done'], - }, - e2eMentions: { - type: 'object', - properties: { - e2eUserMentions: { - type: 'array', - items: { - type: 'string', - }, - }, - e2eChannelMentions: { - type: 'array', - items: { - type: 'string', - }, - }, - }, - required: [], - }, - otrAck: { - type: 'string', - }, - urls: { - type: 'array', - items: { - $ref: '#/components/schemas/MessageUrl', - }, - }, - actionLinks: { - type: 'array', - items: { - type: 'object', - properties: { - icon: { - type: 'string', - }, - i18nLabel: {}, - label: { - type: 'string', - }, - method_id: { - type: 'string', - }, - params: { - type: 'string', - }, - }, - required: ['icon', 'i18nLabel', 'label', 'method_id', 'params'], - }, - deprecated: true, - }, - file: { - $ref: '#/components/schemas/FileProp', - deprecated: true, - }, - fileUpload: { - type: 'object', - properties: { - publicFilePath: { - type: 'string', - }, - type: { - type: 'string', - }, - size: { - type: 'number', - }, - }, - required: ['publicFilePath'], - }, - files: { - type: 'array', - items: { - $ref: '#/components/schemas/FileProp', - }, - }, - attachments: { type: 'array' }, - reactions: { - type: 'object', - properties: {}, - required: [], - additionalProperties: { - type: 'object', - properties: { - names: { - type: 'array', - items: { - type: 'string', - }, - }, - usernames: { - type: 'array', - items: { - type: 'string', - }, - }, - federationReactionEventIds: { - $ref: '#/components/schemas/Recordstringstring', - }, - }, - required: ['usernames'], - }, - }, - private: { - type: 'boolean', - }, - bot: { - $ref: '#/components/schemas/Recordstringany', - }, - sentByEmail: { - type: 'boolean', - }, - webRtcCallEndTs: { - type: 'string', - format: 'date-time', - }, - role: { - type: 'string', - }, - avatar: { - type: 'string', - }, - emoji: { - type: 'string', - }, - tokens: { - type: 'array', - items: { - $ref: '#/components/schemas/Token', - }, - }, - html: { - type: 'string', - }, - token: { - type: 'string', - }, - federation: { - type: 'object', - properties: { - eventId: { - type: 'string', - }, - }, - required: ['eventId'], - }, - slaData: { - type: 'object', - properties: { - definedBy: { - $ref: '#/components/schemas/PickIUser_idusername', - }, - sla: { - $ref: '#/components/schemas/PickIOmnichannelServiceLevelAgreementsname', - }, - }, - required: ['definedBy'], - }, - priorityData: { - type: 'object', - properties: { - definedBy: { - $ref: '#/components/schemas/PickIUser_idusername', - }, - priority: { - $ref: '#/components/schemas/PickILivechatPrioritynamei18n', - }, - }, - required: ['definedBy'], - }, - customFields: { - $ref: '#/components/schemas/IMessageCustomFields', - }, - content: { - type: 'object', - properties: { - algorithm: { - type: 'string', - }, - ciphertext: { - type: 'string', - }, - }, - required: ['algorithm', 'ciphertext'], - }, - _id: { - type: 'string', - }, - _updatedAt: { - type: 'string', - format: 'date-time', - }, - }, - required: ['rid', 'msg', 'ts', 'u', '_id', '_updatedAt'], - }, + message: { $ref: '#/components/schemas/IMessage' }, success: { type: 'boolean', enum: [true], From 1c083d18f1626776860c5ee2cd35ed3d6690ea74 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Mon, 26 Jan 2026 12:25:09 +0200 Subject: [PATCH 7/8] fix(updateMessage): ensure attachments have proper md arrays before saving --- apps/meteor/app/lib/server/functions/updateMessage.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index baf2628e73394..e7cc5c0189ab2 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -1,6 +1,6 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; -import type { IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; +import type { IMessage, IUser, AtLeast, MessageAttachment } from '@rocket.chat/core-typings'; import { Messages, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -74,6 +74,14 @@ export const updateMessage = async function ( delete editedMessage.md; } + // Ensure attachments have proper md arrays before saving + if (editedMessage.attachments) { + editedMessage.attachments = editedMessage.attachments.map((attachment: MessageAttachment) => ({ + ...attachment, + md: Array.isArray(attachment.md) ? attachment.md : [], + })); + } + // do not send $unset if not defined. Can cause exceptions in certain mongo versions. await Messages.updateOne( { _id }, From 48494420db6b429673ce59cd1bd6b8b9a8569722 Mon Sep 17 00:00:00 2001 From: ahmed-n-abdeltwab Date: Mon, 26 Jan 2026 16:33:49 +0200 Subject: [PATCH 8/8] fix: improve attachment md normalization and type safety --- .../app/lib/server/functions/updateMessage.ts | 28 ++++++++++++++----- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/apps/meteor/app/lib/server/functions/updateMessage.ts b/apps/meteor/app/lib/server/functions/updateMessage.ts index e7cc5c0189ab2..f5787cbce41b1 100644 --- a/apps/meteor/app/lib/server/functions/updateMessage.ts +++ b/apps/meteor/app/lib/server/functions/updateMessage.ts @@ -1,6 +1,7 @@ import { AppEvents, Apps } from '@rocket.chat/apps'; import { Message } from '@rocket.chat/core-services'; -import type { IMessage, IUser, AtLeast, MessageAttachment } from '@rocket.chat/core-typings'; +import type { IMessage, IUser, AtLeast } from '@rocket.chat/core-typings'; +import type { Root } from '@rocket.chat/message-parser'; import { Messages, Rooms } from '@rocket.chat/models'; import { Meteor } from 'meteor/meteor'; @@ -74,12 +75,25 @@ export const updateMessage = async function ( delete editedMessage.md; } - // Ensure attachments have proper md arrays before saving - if (editedMessage.attachments) { - editedMessage.attachments = editedMessage.attachments.map((attachment: MessageAttachment) => ({ - ...attachment, - md: Array.isArray(attachment.md) ? attachment.md : [], - })); + if (editedMessage.attachments != null) { + const attachments = Array.isArray(editedMessage.attachments) ? editedMessage.attachments : [editedMessage.attachments]; + + editedMessage.attachments = attachments.map((attachment) => { + let normalizedMd: Root; + + if (Array.isArray(attachment.md)) { + normalizedMd = attachment.md; + } else if (attachment.md != null) { + normalizedMd = [attachment.md]; + } else { + normalizedMd = []; + } + + return { + ...attachment, + md: normalizedMd, + }; + }); } // do not send $unset if not defined. Can cause exceptions in certain mongo versions.