@@ -138,7 +138,7 @@ export default function Topics(props) {
138138 Object . entries ( spec . channels ) . forEach ( ( [ key , channelObj ] ) => {
139139 const address = channelObj . address || `/${ key } ` ;
140140 channelIndex [ `#/channels/${ key } ` ] = address ;
141- channelIndex [ `#/channels/${ key . replaceAll ( / \/ / g, '~1' ) } ` ] = address ;
141+ channelIndex [ `#/channels/${ key . replace ( / \/ / g, '~1' ) } ` ] = address ;
142142 channelMap [ address ] = channelMap [ address ] || {
143143 parameters : channelObj . parameters || { } ,
144144 ...( channelObj . description && { description : channelObj . description } ) ,
@@ -174,7 +174,7 @@ export default function Topics(props) {
174174 payload : {
175175 type : 'object' ,
176176 properties : {
177- ...( existing ?. payload ?. properties || { } ) ,
177+ ...existing ?. payload ?. properties ,
178178 ...allProperties ,
179179 } ,
180180 } ,
@@ -219,43 +219,150 @@ export default function Topics(props) {
219219 } ;
220220 }
221221
222- /**
223- * Groups payload properties by message name and wires them into
224- * components, channels, and operations.
225- */
226- function wirePayloadProperties ( verbInfo , channelName , newComponents , newChannels , newOperations ) {
227- if ( ! verbInfo . message ?. payload ?. properties )
228- return { updatedComponents : newComponents , updatedChannels : newChannels } ;
229-
222+ function groupPayloadProperties ( properties ) {
230223 const byMessage = { } ;
231- Object . entries ( verbInfo . message . payload . properties ) . forEach ( ( [ compositeKey , propVal ] ) => {
224+
225+ Object . entries ( properties ) . forEach ( ( [ compositeKey , propVal ] ) => {
232226 const msgName = propVal [ 'x-message' ] || propVal [ 'x-operation' ] || '__default__' ;
233- byMessage [ msgName ] = byMessage [ msgName ] || { opName : propVal [ 'x-operation' ] , props : { } } ;
227+
228+ if ( ! byMessage [ msgName ] ) {
229+ byMessage [ msgName ] = { opName : propVal [ 'x-operation' ] , props : { } } ;
230+ }
231+
234232 const originalPropName = propVal [ 'x-prop-name' ] || compositeKey ;
235- const { 'x-operation' : _ , 'x-message' : __ , 'x-prop-name' : ___ , ...cleanProp } = propVal ;
233+ // const { 'x-operation': _, 'x-message': __, 'x-prop-name': ___, ...cleanProp } = propVal;
234+ const {
235+ 'x-operation' : xOperation ,
236+ 'x-message' : xMessage ,
237+ 'x-prop-name' : xPropName ,
238+ ...cleanProp
239+ } = propVal ;
240+
236241 byMessage [ msgName ] . props [ originalPropName ] = cleanProp ;
237242 } ) ;
238-
243+
244+ return byMessage ;
245+ }
246+
247+ function wireOperationMessage ( opName , channelName , msgName , newOperations ) {
248+ if ( ! opName || opName === '__default__' || ! newOperations [ opName ] ) return ;
249+
250+ const ref = { $ref : `#/channels/${ channelName } /messages/${ msgName } ` } ;
251+
252+ if ( ! newOperations [ opName ] . messages . some ( ( m ) => m . $ref === ref . $ref ) ) {
253+ newOperations [ opName ] . messages . push ( ref ) ;
254+ }
255+ }
256+
257+ function processMessageGroups ( byMessage , channelName , newComponents , newChannels , newOperations ) {
239258 let updatedComponents = { ...newComponents } ;
240259 let updatedChannels = { ...newChannels } ;
241-
260+
242261 Object . entries ( byMessage ) . forEach ( ( [ msgName , { opName, props : msgProps } ] ) => {
243262 updatedComponents = buildUpdatedComponents ( updatedComponents , msgName , msgProps ) ;
244263 updatedChannels = buildUpdatedChannels ( updatedChannels , channelName , msgName ) ;
264+
265+ wireOperationMessage ( opName , channelName , msgName , newOperations ) ;
266+ } ) ;
267+
268+ return { updatedComponents, updatedChannels } ;
269+ }
270+
271+ function wirePayloadProperties ( verbInfo , channelName , newComponents , newChannels , newOperations ) {
272+ if ( ! verbInfo . message ?. payload ?. properties ) {
273+ return { updatedComponents : newComponents , updatedChannels : newChannels } ;
274+ }
275+
276+ const byMessage = groupPayloadProperties ( verbInfo . message . payload . properties ) ;
277+
278+ return processMessageGroups (
279+ byMessage ,
280+ channelName ,
281+ newComponents ,
282+ newChannels ,
283+ newOperations ,
284+ ) ;
285+ }
286+
287+ function createOperationEntry ( opName , action , channelName , verbInfo , originalSpec ) {
288+ // const { messages: _, ...restOfOriginal } = originalSpec.operations?.[opName] || {};
289+ const { messages, ...restOfOriginal } = originalSpec . operations ?. [ opName ] || { } ;
290+
291+ return {
292+ ...restOfOriginal ,
293+ action,
294+ channel : { $ref : `#/channels/${ channelName } ` } ,
295+ 'x-auth-type' : verbInfo [ 'x-auth-type' ] ,
296+ ...( verbInfo [ 'x-uri-mapping' ] && { 'x-uri-mapping' : verbInfo [ 'x-uri-mapping' ] } ) ,
297+ ...( verbInfo [ 'x-scopes' ] && { 'x-scopes' : verbInfo [ 'x-scopes' ] } ) ,
298+ messages : [ ] ,
299+ } ;
300+ }
301+
302+ function resolveChannel ( channelAddress , channelObj , originalSpec , newChannels , addressToName ) {
303+ const existingChannelName = addressToName [ channelAddress ] ;
304+
305+ if ( existingChannelName ) {
306+ return {
307+ channelName : existingChannelName ,
308+ newChannels : {
309+ ...newChannels ,
310+ [ existingChannelName ] : {
311+ ...newChannels [ existingChannelName ] ,
312+ ...( channelObj . description && { description : channelObj . description } ) ,
313+ } ,
314+ } ,
315+ } ;
316+ }
317+
318+ const baseChannel = channelAddress . split ( '/' ) . find ( s => s . length > 0 ) || 'channel' ;
319+ const randomId = crypto . randomUUID ( ) . split ( '-' ) [ 0 ] ;
320+ const channelName = `${ baseChannel } _${ randomId } ` ;
321+ const parametersArray = channelObj . parameters || [ ] ;
322+
323+ return {
324+ channelName,
325+ newChannels : {
326+ ...newChannels ,
327+ [ channelName ] : {
328+ address : channelAddress ,
329+ ...( Object . keys ( parametersArray ) . length > 0 && { parameters : parametersArray } ) ,
330+ } ,
331+ } ,
332+ } ;
333+ }
334+
335+ /**
336+ * Helper to process specific verb actions (send/receive) for a channel.
337+ * This flattens the nesting from rebuildOperations.
338+ */
339+ function processVerbActions ( context ) {
340+ const {
341+ verbInfo, action, channelName,
342+ originalSpec, newOperations, newChannels, newComponents
343+ } = context ;
245344
246- // Wire into operations.{opName}.messages[]
247- if ( opName && opName !== '__default__' && newOperations [ opName ] ) {
248- const ref = { $ref : `#/channels/${ channelName } /messages/${ msgName } ` } ;
249- if ( ! newOperations [ opName ] . messages . some ( ( m ) => m . $ref === ref . $ref ) ) {
250- newOperations [ opName ] . messages . push ( ref ) ;
251- }
252- }
345+ // Level 4: Extracting the operations loop into a named helper or keeping it flat here
346+ const opList = verbInfo [ 'x-operations' ] || [ ] ;
347+ opList . forEach ( ( opName ) => {
348+ newOperations [ opName ] = createOperationEntry (
349+ opName ,
350+ action ,
351+ channelName ,
352+ verbInfo ,
353+ originalSpec ,
354+ ) ;
253355 } ) ;
254356
255- return { updatedComponents, updatedChannels } ;
357+ return wirePayloadProperties (
358+ verbInfo ,
359+ channelName ,
360+ newComponents ,
361+ newChannels ,
362+ newOperations ,
363+ ) ;
256364 }
257365
258- // Rebuild the AsyncAPI v3 `operations` object from the UI channel map.
259366 function rebuildOperations ( channelMap , originalSpec ) {
260367 const newOperations = { } ;
261368 let newChannels = { ...originalSpec . channels } ;
@@ -269,53 +376,32 @@ export default function Topics(props) {
269376 }
270377
271378 Object . entries ( channelMap ) . forEach ( ( [ channelAddress , channelObj ] ) => {
272- const existingChannelName = addressToName [ channelAddress ] ;
273- let channelName ;
274- if ( existingChannelName ) {
275- channelName = existingChannelName ;
276- newChannels = {
277- ...newChannels ,
278- [ existingChannelName ] : {
279- ...newChannels [ existingChannelName ] ,
280- ...( channelObj . description && { description : channelObj . description } ) ,
281- } ,
282- } ;
283- } else {
284- // Newly added channel
285- const baseChannel = channelAddress . replace ( / ^ \/ / , '' ) . split ( '/' ) [ 0 ] ;
286- const randomId = Math . random ( ) . toString ( 36 ) . substring ( 2 , 7 ) ;
287- channelName = `${ baseChannel } _${ randomId } ` ;
288- const parametersArray = channelObj . parameters || [ ] ;
289- newChannels = {
290- ...newChannels ,
291- [ channelName ] : {
292- address : channelAddress ,
293- ...( Object . keys ( parametersArray ) . length > 0 && { parameters : parametersArray } ) ,
294- } ,
295- } ;
296- }
379+ const resolved = resolveChannel (
380+ channelAddress ,
381+ channelObj ,
382+ originalSpec ,
383+ newChannels ,
384+ addressToName ,
385+ ) ;
386+
387+ const { channelName } = resolved ;
388+ newChannels = resolved . newChannels ;
297389
298390 [ 'send' , 'receive' ] . forEach ( ( action ) => {
299391 const verbInfo = channelObj [ action ] ;
300392 if ( ! verbInfo ) return ;
301- // Build operation entries FIRST so newOperations[opName] exists for message wiring
302- ( verbInfo [ 'x-operations' ] || [ ] ) . forEach ( ( opName ) => {
303- const { messages : _ , ...restOfOriginal } = originalSpec . operations ?. [ opName ] || { } ;
304- newOperations [ opName ] = {
305- ...restOfOriginal ,
306- action,
307- channel : { $ref : `#/channels/${ channelName } ` } ,
308- 'x-auth-type' : verbInfo [ 'x-auth-type' ] ,
309- ...( verbInfo [ 'x-uri-mapping' ] && { 'x-uri-mapping' : verbInfo [ 'x-uri-mapping' ] } ) ,
310- ...( verbInfo [ 'x-scopes' ] && { 'x-scopes' : verbInfo [ 'x-scopes' ] } ) ,
311- messages : [ ] ,
312- } ;
393+ const result = processVerbActions ( {
394+ verbInfo,
395+ action,
396+ channelName,
397+ originalSpec,
398+ newOperations,
399+ newChannels,
400+ newComponents,
313401 } ) ;
314402
315- // Wire payload properties and capture updated components/channels
316- ( { updatedComponents : newComponents , updatedChannels : newChannels } = wirePayloadProperties (
317- verbInfo , channelName , newComponents , newChannels , newOperations ,
318- ) ) ;
403+ newComponents = result . updatedComponents ;
404+ newChannels = result . updatedChannels ;
319405 } ) ;
320406 } ) ;
321407 return { newOperations, newChannels, newComponents } ;
0 commit comments