mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-04 04:44:08 +01:00 
			
		
		
		
	Merge branch 'develop' into 6637-add-new-participant-types-to-sequence-diagrams
This commit is contained in:
		
							
								
								
									
										5
									
								
								.changeset/light-flowers-judge.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/light-flowers-judge.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': patch
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
fix: Make flowchart elk detector regex match less greedy
 | 
			
		||||
							
								
								
									
										5
									
								
								.changeset/rare-women-fly.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/rare-women-fly.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': patch
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
fix: Add escaped class literal name on namespace
 | 
			
		||||
							
								
								
									
										5
									
								
								.changeset/silver-eyes-build.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/silver-eyes-build.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': patch
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
chore: migrate to class-based ArchitectureDB implementation
 | 
			
		||||
							
								
								
									
										5
									
								
								.changeset/vast-buses-see.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.changeset/vast-buses-see.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
---
 | 
			
		||||
'mermaid': patch
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
chore: Update packet diagram to use new class-based database structure
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/e2e-timings.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/e2e-timings.yml
									
									
									
									
										vendored
									
									
								
							@@ -58,7 +58,7 @@ jobs:
 | 
			
		||||
          echo "EOF" >> $GITHUB_OUTPUT
 | 
			
		||||
 | 
			
		||||
      - name: Commit and create pull request
 | 
			
		||||
        uses: peter-evans/create-pull-request@2e50522bdf313efe32e5628afead9048374012ed
 | 
			
		||||
        uses: peter-evans/create-pull-request@07cbaebb4bfc9c5d7db426ea5a5f585df29dd0a0
 | 
			
		||||
        with:
 | 
			
		||||
          add-paths: |
 | 
			
		||||
            cypress/timings.json
 | 
			
		||||
 
 | 
			
		||||
@@ -495,4 +495,34 @@ describe('Class diagram', () => {
 | 
			
		||||
      cy.get('a').should('have.attr', 'target', '_blank').should('have.attr', 'rel', 'noopener');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Include char sequence "graph" in text (#6795)', () => {
 | 
			
		||||
    it('has a label with char sequence "graph"', () => {
 | 
			
		||||
      imgSnapshotTest(
 | 
			
		||||
        `
 | 
			
		||||
        classDiagram
 | 
			
		||||
          class Person {
 | 
			
		||||
            +String name
 | 
			
		||||
            -Int id
 | 
			
		||||
            #double age
 | 
			
		||||
            +Text demographicProfile
 | 
			
		||||
          }
 | 
			
		||||
        `,
 | 
			
		||||
        { flowchart: { defaultRenderer: 'elk' } }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should handle backticks for namespace and class names', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `
 | 
			
		||||
      classDiagram
 | 
			
		||||
          namespace \`A::B\` {
 | 
			
		||||
              class \`IPC::Sender\`
 | 
			
		||||
          }
 | 
			
		||||
          RenderProcessHost --|> \`IPC::Sender\`
 | 
			
		||||
      `,
 | 
			
		||||
      {}
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -354,4 +354,19 @@ ORDER ||--|{ LINE-ITEM : contains
 | 
			
		||||
      { logLevel: 1 }
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Include char sequence "graph" in text (#6795)', () => {
 | 
			
		||||
    it('has a label with char sequence "graph"', () => {
 | 
			
		||||
      imgSnapshotTest(
 | 
			
		||||
        `
 | 
			
		||||
        erDiagram
 | 
			
		||||
          p[Photograph] {
 | 
			
		||||
            varchar(12) jobId
 | 
			
		||||
            date dateCreated
 | 
			
		||||
          }
 | 
			
		||||
        `,
 | 
			
		||||
        { flowchart: { defaultRenderer: 'elk' } }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -246,5 +246,22 @@ Word!\`]
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  describe('Include char sequence "graph" in text (#6795)', () => {
 | 
			
		||||
    it('has a label with char sequence "graph"', () => {
 | 
			
		||||
      imgSnapshotTest(
 | 
			
		||||
        `
 | 
			
		||||
        mindmap
 | 
			
		||||
          root
 | 
			
		||||
            Photograph
 | 
			
		||||
              Waterfall
 | 
			
		||||
              Landscape
 | 
			
		||||
            Geography
 | 
			
		||||
              Mountains
 | 
			
		||||
              Rocks
 | 
			
		||||
        `,
 | 
			
		||||
        { flowchart: { defaultRenderer: 'elk' } }
 | 
			
		||||
      );
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  /* The end */
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -104,6 +104,7 @@ Blogging frameworks and platforms
 | 
			
		||||
  - [Mermaid](https://nextra.site/docs/guide/mermaid)
 | 
			
		||||
- [WordPress](https://wordpress.org)
 | 
			
		||||
  - [MerPRess](https://wordpress.org/plugins/merpress/)
 | 
			
		||||
  - [WP Documentation](https://wordpress.org/themes/wp-documentation/)
 | 
			
		||||
 | 
			
		||||
### CMS/ECM
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,5 +78,41 @@ describe('diagram-orchestration', () => {
 | 
			
		||||
      flowchart: 1 "pie" pie: 2 "pie"`)
 | 
			
		||||
      ).toBe('pie');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should detect proper diagram when defaultRenderer is elk for flowchart', () => {
 | 
			
		||||
      expect(
 | 
			
		||||
        detectType('mindmap\n  root\n    Photograph\n      Waterfall', {
 | 
			
		||||
          flowchart: { defaultRenderer: 'elk' },
 | 
			
		||||
        })
 | 
			
		||||
      ).toBe('mindmap');
 | 
			
		||||
      expect(
 | 
			
		||||
        detectType(
 | 
			
		||||
          `
 | 
			
		||||
          classDiagram
 | 
			
		||||
            class Person {
 | 
			
		||||
              +String name
 | 
			
		||||
              -Int id
 | 
			
		||||
              #double age
 | 
			
		||||
              +Text demographicProfile
 | 
			
		||||
            }
 | 
			
		||||
          `,
 | 
			
		||||
          { flowchart: { defaultRenderer: 'elk' } }
 | 
			
		||||
        )
 | 
			
		||||
      ).toBe('class');
 | 
			
		||||
      expect(
 | 
			
		||||
        detectType(
 | 
			
		||||
          `
 | 
			
		||||
          erDiagram
 | 
			
		||||
            p[Photograph] {
 | 
			
		||||
              varchar(12) jobId
 | 
			
		||||
              date dateCreated
 | 
			
		||||
            }
 | 
			
		||||
          `,
 | 
			
		||||
          {
 | 
			
		||||
            flowchart: { defaultRenderer: 'elk' },
 | 
			
		||||
          }
 | 
			
		||||
        )
 | 
			
		||||
      ).toBe('er');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,12 @@
 | 
			
		||||
import { it, describe, expect } from 'vitest';
 | 
			
		||||
import { db } from './architectureDb.js';
 | 
			
		||||
import { parser } from './architectureParser.js';
 | 
			
		||||
 | 
			
		||||
const {
 | 
			
		||||
  clear,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
  getAccTitle,
 | 
			
		||||
  getAccDescription,
 | 
			
		||||
  getServices,
 | 
			
		||||
  getGroups,
 | 
			
		||||
  getEdges,
 | 
			
		||||
  getJunctions,
 | 
			
		||||
} = db;
 | 
			
		||||
 | 
			
		||||
import { ArchitectureDB } from './architectureDb.js';
 | 
			
		||||
describe('architecture diagrams', () => {
 | 
			
		||||
  let db: ArchitectureDB;
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    clear();
 | 
			
		||||
    db = new ArchitectureDB();
 | 
			
		||||
    // @ts-expect-error since type is set to undefined we will have error
 | 
			
		||||
    parser.parser?.yy = db;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('architecture diagram definitions', () => {
 | 
			
		||||
@@ -36,7 +27,7 @@ describe('architecture diagrams', () => {
 | 
			
		||||
    it('should handle title on the first line', async () => {
 | 
			
		||||
      const str = `architecture-beta title Simple Architecture Diagram`;
 | 
			
		||||
      await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
      expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
 | 
			
		||||
      expect(db.getDiagramTitle()).toBe('Simple Architecture Diagram');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle title on another line', async () => {
 | 
			
		||||
@@ -44,7 +35,7 @@ describe('architecture diagrams', () => {
 | 
			
		||||
            title Simple Architecture Diagram
 | 
			
		||||
            `;
 | 
			
		||||
      await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
      expect(getDiagramTitle()).toBe('Simple Architecture Diagram');
 | 
			
		||||
      expect(db.getDiagramTitle()).toBe('Simple Architecture Diagram');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle accessibility title and description', async () => {
 | 
			
		||||
@@ -53,8 +44,8 @@ describe('architecture diagrams', () => {
 | 
			
		||||
            accDescr: Accessibility Description
 | 
			
		||||
            `;
 | 
			
		||||
      await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
      expect(getAccTitle()).toBe('Accessibility Title');
 | 
			
		||||
      expect(getAccDescription()).toBe('Accessibility Description');
 | 
			
		||||
      expect(db.getAccTitle()).toBe('Accessibility Title');
 | 
			
		||||
      expect(db.getAccDescription()).toBe('Accessibility Description');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle multiline accessibility description', async () => {
 | 
			
		||||
@@ -64,7 +55,7 @@ describe('architecture diagrams', () => {
 | 
			
		||||
            }
 | 
			
		||||
            `;
 | 
			
		||||
      await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
      expect(getAccDescription()).toBe('Accessibility Description');
 | 
			
		||||
      expect(db.getAccDescription()).toBe('Accessibility Description');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,9 @@
 | 
			
		||||
import { getConfig as commonGetConfig } from '../../config.js';
 | 
			
		||||
import type { ArchitectureDiagramConfig } from '../../config.type.js';
 | 
			
		||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
 | 
			
		||||
import { getConfig as commonGetConfig } from '../../config.js';
 | 
			
		||||
import type { DiagramDB } from '../../diagram-api/types.js';
 | 
			
		||||
import type { D3Element } from '../../types.js';
 | 
			
		||||
import { ImperativeState } from '../../utils/imperativeState.js';
 | 
			
		||||
import { cleanAndMerge } from '../../utils.js';
 | 
			
		||||
import {
 | 
			
		||||
  clear as commonClear,
 | 
			
		||||
  getAccDescription,
 | 
			
		||||
@@ -14,7 +15,6 @@ import {
 | 
			
		||||
} from '../common/commonDb.js';
 | 
			
		||||
import type {
 | 
			
		||||
  ArchitectureAlignment,
 | 
			
		||||
  ArchitectureDB,
 | 
			
		||||
  ArchitectureDirectionPair,
 | 
			
		||||
  ArchitectureDirectionPairMap,
 | 
			
		||||
  ArchitectureEdge,
 | 
			
		||||
@@ -33,330 +33,333 @@ import {
 | 
			
		||||
  isArchitectureService,
 | 
			
		||||
  shiftPositionByArchitectureDirectionPair,
 | 
			
		||||
} from './architectureTypes.js';
 | 
			
		||||
import { cleanAndMerge } from '../../utils.js';
 | 
			
		||||
 | 
			
		||||
const DEFAULT_ARCHITECTURE_CONFIG: Required<ArchitectureDiagramConfig> =
 | 
			
		||||
  DEFAULT_CONFIG.architecture;
 | 
			
		||||
export class ArchitectureDB implements DiagramDB {
 | 
			
		||||
  private nodes: Record<string, ArchitectureNode> = {};
 | 
			
		||||
  private groups: Record<string, ArchitectureGroup> = {};
 | 
			
		||||
  private edges: ArchitectureEdge[] = [];
 | 
			
		||||
  private registeredIds: Record<string, 'node' | 'group'> = {};
 | 
			
		||||
  private dataStructures?: ArchitectureState['dataStructures'];
 | 
			
		||||
  private elements: Record<string, D3Element> = {};
 | 
			
		||||
 | 
			
		||||
const state = new ImperativeState<ArchitectureState>(() => ({
 | 
			
		||||
  nodes: {},
 | 
			
		||||
  groups: {},
 | 
			
		||||
  edges: [],
 | 
			
		||||
  registeredIds: {},
 | 
			
		||||
  config: DEFAULT_ARCHITECTURE_CONFIG,
 | 
			
		||||
  dataStructures: undefined,
 | 
			
		||||
  elements: {},
 | 
			
		||||
}));
 | 
			
		||||
 | 
			
		||||
const clear = (): void => {
 | 
			
		||||
  state.reset();
 | 
			
		||||
  commonClear();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addService = function ({
 | 
			
		||||
  id,
 | 
			
		||||
  icon,
 | 
			
		||||
  in: parent,
 | 
			
		||||
  title,
 | 
			
		||||
  iconText,
 | 
			
		||||
}: Omit<ArchitectureService, 'edges'>) {
 | 
			
		||||
  if (state.records.registeredIds[id] !== undefined) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The service id [${id}] is already in use by another ${state.records.registeredIds[id]}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (parent !== undefined) {
 | 
			
		||||
    if (id === parent) {
 | 
			
		||||
      throw new Error(`The service [${id}] cannot be placed within itself`);
 | 
			
		||||
    }
 | 
			
		||||
    if (state.records.registeredIds[parent] === undefined) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The service [${id}]'s parent does not exist. Please make sure the parent is created before this service`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (state.records.registeredIds[parent] === 'node') {
 | 
			
		||||
      throw new Error(`The service [${id}]'s parent is not a group`);
 | 
			
		||||
    }
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  state.records.registeredIds[id] = 'node';
 | 
			
		||||
  public clear(): void {
 | 
			
		||||
    this.nodes = {};
 | 
			
		||||
    this.groups = {};
 | 
			
		||||
    this.edges = [];
 | 
			
		||||
    this.registeredIds = {};
 | 
			
		||||
    this.dataStructures = undefined;
 | 
			
		||||
    this.elements = {};
 | 
			
		||||
    commonClear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  state.records.nodes[id] = {
 | 
			
		||||
  public addService({
 | 
			
		||||
    id,
 | 
			
		||||
    type: 'service',
 | 
			
		||||
    icon,
 | 
			
		||||
    in: parent,
 | 
			
		||||
    title,
 | 
			
		||||
    iconText,
 | 
			
		||||
    title,
 | 
			
		||||
    edges: [],
 | 
			
		||||
    in: parent,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getServices = (): ArchitectureService[] =>
 | 
			
		||||
  Object.values(state.records.nodes).filter<ArchitectureService>(isArchitectureService);
 | 
			
		||||
 | 
			
		||||
const addJunction = function ({ id, in: parent }: Omit<ArchitectureJunction, 'edges'>) {
 | 
			
		||||
  state.records.registeredIds[id] = 'node';
 | 
			
		||||
 | 
			
		||||
  state.records.nodes[id] = {
 | 
			
		||||
    id,
 | 
			
		||||
    type: 'junction',
 | 
			
		||||
    edges: [],
 | 
			
		||||
    in: parent,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getJunctions = (): ArchitectureJunction[] =>
 | 
			
		||||
  Object.values(state.records.nodes).filter<ArchitectureJunction>(isArchitectureJunction);
 | 
			
		||||
 | 
			
		||||
const getNodes = (): ArchitectureNode[] => Object.values(state.records.nodes);
 | 
			
		||||
 | 
			
		||||
const getNode = (id: string): ArchitectureNode | null => state.records.nodes[id];
 | 
			
		||||
 | 
			
		||||
const addGroup = function ({ id, icon, in: parent, title }: ArchitectureGroup) {
 | 
			
		||||
  if (state.records.registeredIds[id] !== undefined) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The group id [${id}] is already in use by another ${state.records.registeredIds[id]}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (parent !== undefined) {
 | 
			
		||||
    if (id === parent) {
 | 
			
		||||
      throw new Error(`The group [${id}] cannot be placed within itself`);
 | 
			
		||||
    }
 | 
			
		||||
    if (state.records.registeredIds[parent] === undefined) {
 | 
			
		||||
  }: Omit<ArchitectureService, 'edges'>): void {
 | 
			
		||||
    if (this.registeredIds[id] !== undefined) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The group [${id}]'s parent does not exist. Please make sure the parent is created before this group`
 | 
			
		||||
        `The service id [${id}] is already in use by another ${this.registeredIds[id]}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (state.records.registeredIds[parent] === 'node') {
 | 
			
		||||
      throw new Error(`The group [${id}]'s parent is not a group`);
 | 
			
		||||
    if (parent !== undefined) {
 | 
			
		||||
      if (id === parent) {
 | 
			
		||||
        throw new Error(`The service [${id}] cannot be placed within itself`);
 | 
			
		||||
      }
 | 
			
		||||
      if (this.registeredIds[parent] === undefined) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          `The service [${id}]'s parent does not exist. Please make sure the parent is created before this service`
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      if (this.registeredIds[parent] === 'node') {
 | 
			
		||||
        throw new Error(`The service [${id}]'s parent is not a group`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.registeredIds[id] = 'node';
 | 
			
		||||
 | 
			
		||||
    this.nodes[id] = {
 | 
			
		||||
      id,
 | 
			
		||||
      type: 'service',
 | 
			
		||||
      icon,
 | 
			
		||||
      iconText,
 | 
			
		||||
      title,
 | 
			
		||||
      edges: [],
 | 
			
		||||
      in: parent,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  state.records.registeredIds[id] = 'group';
 | 
			
		||||
 | 
			
		||||
  state.records.groups[id] = {
 | 
			
		||||
    id,
 | 
			
		||||
    icon,
 | 
			
		||||
    title,
 | 
			
		||||
    in: parent,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
const getGroups = (): ArchitectureGroup[] => {
 | 
			
		||||
  return Object.values(state.records.groups);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const addEdge = function ({
 | 
			
		||||
  lhsId,
 | 
			
		||||
  rhsId,
 | 
			
		||||
  lhsDir,
 | 
			
		||||
  rhsDir,
 | 
			
		||||
  lhsInto,
 | 
			
		||||
  rhsInto,
 | 
			
		||||
  lhsGroup,
 | 
			
		||||
  rhsGroup,
 | 
			
		||||
  title,
 | 
			
		||||
}: ArchitectureEdge<string>) {
 | 
			
		||||
  if (!isArchitectureDirection(lhsDir)) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${lhsDir}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (!isArchitectureDirection(rhsDir)) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `Invalid direction given for right hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${rhsDir}`
 | 
			
		||||
    );
 | 
			
		||||
  public getServices(): ArchitectureService[] {
 | 
			
		||||
    return Object.values(this.nodes).filter(isArchitectureService);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (state.records.nodes[lhsId] === undefined && state.records.groups[lhsId] === undefined) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The left-hand id [${lhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (state.records.nodes[rhsId] === undefined && state.records.groups[lhsId] === undefined) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The right-hand id [${rhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
 | 
			
		||||
    );
 | 
			
		||||
  public addJunction({ id, in: parent }: Omit<ArchitectureJunction, 'edges'>): void {
 | 
			
		||||
    this.registeredIds[id] = 'node';
 | 
			
		||||
 | 
			
		||||
    this.nodes[id] = {
 | 
			
		||||
      id,
 | 
			
		||||
      type: 'junction',
 | 
			
		||||
      edges: [],
 | 
			
		||||
      in: parent,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const lhsGroupId = state.records.nodes[lhsId].in;
 | 
			
		||||
  const rhsGroupId = state.records.nodes[rhsId].in;
 | 
			
		||||
  if (lhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The left-hand id [${lhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
  if (rhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `The right-hand id [${rhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
 | 
			
		||||
    );
 | 
			
		||||
  public getJunctions(): ArchitectureJunction[] {
 | 
			
		||||
    return Object.values(this.nodes).filter(isArchitectureJunction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const edge = {
 | 
			
		||||
  public getNodes(): ArchitectureNode[] {
 | 
			
		||||
    return Object.values(this.nodes);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getNode(id: string): ArchitectureNode | null {
 | 
			
		||||
    return this.nodes[id] ?? null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public addGroup({ id, icon, in: parent, title }: ArchitectureGroup): void {
 | 
			
		||||
    if (this.registeredIds?.[id] !== undefined) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The group id [${id}] is already in use by another ${this.registeredIds[id]}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (parent !== undefined) {
 | 
			
		||||
      if (id === parent) {
 | 
			
		||||
        throw new Error(`The group [${id}] cannot be placed within itself`);
 | 
			
		||||
      }
 | 
			
		||||
      if (this.registeredIds?.[parent] === undefined) {
 | 
			
		||||
        throw new Error(
 | 
			
		||||
          `The group [${id}]'s parent does not exist. Please make sure the parent is created before this group`
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
      if (this.registeredIds?.[parent] === 'node') {
 | 
			
		||||
        throw new Error(`The group [${id}]'s parent is not a group`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    this.registeredIds[id] = 'group';
 | 
			
		||||
 | 
			
		||||
    this.groups[id] = {
 | 
			
		||||
      id,
 | 
			
		||||
      icon,
 | 
			
		||||
      title,
 | 
			
		||||
      in: parent,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
  public getGroups(): ArchitectureGroup[] {
 | 
			
		||||
    return Object.values(this.groups);
 | 
			
		||||
  }
 | 
			
		||||
  public addEdge({
 | 
			
		||||
    lhsId,
 | 
			
		||||
    lhsDir,
 | 
			
		||||
    lhsInto,
 | 
			
		||||
    lhsGroup,
 | 
			
		||||
    rhsId,
 | 
			
		||||
    lhsDir,
 | 
			
		||||
    rhsDir,
 | 
			
		||||
    lhsInto,
 | 
			
		||||
    rhsInto,
 | 
			
		||||
    lhsGroup,
 | 
			
		||||
    rhsGroup,
 | 
			
		||||
    title,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  state.records.edges.push(edge);
 | 
			
		||||
  if (state.records.nodes[lhsId] && state.records.nodes[rhsId]) {
 | 
			
		||||
    state.records.nodes[lhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
 | 
			
		||||
    state.records.nodes[rhsId].edges.push(state.records.edges[state.records.edges.length - 1]);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getEdges = (): ArchitectureEdge[] => state.records.edges;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Returns the current diagram's adjacency list, spatial map, & group alignments.
 | 
			
		||||
 * If they have not been created, run the algorithms to generate them.
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
const getDataStructures = () => {
 | 
			
		||||
  if (state.records.dataStructures === undefined) {
 | 
			
		||||
    // Tracks how groups are aligned with one another. Generated while creating the adj list
 | 
			
		||||
    const groupAlignments: Record<
 | 
			
		||||
      string,
 | 
			
		||||
      Record<string, Exclude<ArchitectureAlignment, 'bend'>>
 | 
			
		||||
    > = {};
 | 
			
		||||
 | 
			
		||||
    // Create an adjacency list of the diagram to perform BFS on
 | 
			
		||||
    // Outer reduce applied on all services
 | 
			
		||||
    // Inner reduce applied on the edges for a service
 | 
			
		||||
    const adjList = Object.entries(state.records.nodes).reduce<
 | 
			
		||||
      Record<string, ArchitectureDirectionPairMap>
 | 
			
		||||
    >((prevOuter, [id, service]) => {
 | 
			
		||||
      prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
 | 
			
		||||
        // track the direction groups connect to one another
 | 
			
		||||
        const lhsGroupId = getNode(edge.lhsId)?.in;
 | 
			
		||||
        const rhsGroupId = getNode(edge.rhsId)?.in;
 | 
			
		||||
        if (lhsGroupId && rhsGroupId && lhsGroupId !== rhsGroupId) {
 | 
			
		||||
          const alignment = getArchitectureDirectionAlignment(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
          if (alignment !== 'bend') {
 | 
			
		||||
            groupAlignments[lhsGroupId] ??= {};
 | 
			
		||||
            groupAlignments[lhsGroupId][rhsGroupId] = alignment;
 | 
			
		||||
            groupAlignments[rhsGroupId] ??= {};
 | 
			
		||||
            groupAlignments[rhsGroupId][lhsGroupId] = alignment;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (edge.lhsId === id) {
 | 
			
		||||
          // source is LHS
 | 
			
		||||
          const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
          if (pair) {
 | 
			
		||||
            prevInner[pair] = edge.rhsId;
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          // source is RHS
 | 
			
		||||
          const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
 | 
			
		||||
          if (pair) {
 | 
			
		||||
            prevInner[pair] = edge.lhsId;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return prevInner;
 | 
			
		||||
      }, {});
 | 
			
		||||
      return prevOuter;
 | 
			
		||||
    }, {});
 | 
			
		||||
 | 
			
		||||
    // Configuration for the initial pass of BFS
 | 
			
		||||
    const firstId = Object.keys(adjList)[0];
 | 
			
		||||
    const visited = { [firstId]: 1 };
 | 
			
		||||
    // If a key is present in this object, it has not been visited
 | 
			
		||||
    const notVisited = Object.keys(adjList).reduce(
 | 
			
		||||
      (prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
 | 
			
		||||
      {} as Record<string, number>
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // Perform BFS on the adjacency list
 | 
			
		||||
    const BFS = (startingId: string): ArchitectureSpatialMap => {
 | 
			
		||||
      const spatialMap = { [startingId]: [0, 0] };
 | 
			
		||||
      const queue = [startingId];
 | 
			
		||||
      while (queue.length > 0) {
 | 
			
		||||
        const id = queue.shift();
 | 
			
		||||
        if (id) {
 | 
			
		||||
          visited[id] = 1;
 | 
			
		||||
          delete notVisited[id];
 | 
			
		||||
          const adj = adjList[id];
 | 
			
		||||
          const [posX, posY] = spatialMap[id];
 | 
			
		||||
          Object.entries(adj).forEach(([dir, rhsId]) => {
 | 
			
		||||
            if (!visited[rhsId]) {
 | 
			
		||||
              spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
 | 
			
		||||
                [posX, posY],
 | 
			
		||||
                dir as ArchitectureDirectionPair
 | 
			
		||||
              );
 | 
			
		||||
              queue.push(rhsId);
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return spatialMap;
 | 
			
		||||
    };
 | 
			
		||||
    const spatialMaps = [BFS(firstId)];
 | 
			
		||||
 | 
			
		||||
    // If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
 | 
			
		||||
    while (Object.keys(notVisited).length > 0) {
 | 
			
		||||
      spatialMaps.push(BFS(Object.keys(notVisited)[0]));
 | 
			
		||||
  }: ArchitectureEdge): void {
 | 
			
		||||
    if (!isArchitectureDirection(lhsDir)) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `Invalid direction given for left hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${String(lhsDir)}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    state.records.dataStructures = {
 | 
			
		||||
      adjList,
 | 
			
		||||
      spatialMaps,
 | 
			
		||||
      groupAlignments,
 | 
			
		||||
    if (!isArchitectureDirection(rhsDir)) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `Invalid direction given for right hand side of edge ${lhsId}--${rhsId}. Expected (L,R,T,B) got ${String(rhsDir)}`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (this.nodes[lhsId] === undefined && this.groups[lhsId] === undefined) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The left-hand id [${lhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (this.nodes[rhsId] === undefined && this.groups[rhsId] === undefined) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The right-hand id [${rhsId}] does not yet exist. Please create the service/group before declaring an edge to it.`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const lhsGroupId = this.nodes[lhsId].in;
 | 
			
		||||
    const rhsGroupId = this.nodes[rhsId].in;
 | 
			
		||||
    if (lhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The left-hand id [${lhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    if (rhsGroup && lhsGroupId && rhsGroupId && lhsGroupId == rhsGroupId) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        `The right-hand id [${rhsId}] is modified to traverse the group boundary, but the edge does not pass through two groups.`
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const edge = {
 | 
			
		||||
      lhsId,
 | 
			
		||||
      lhsDir,
 | 
			
		||||
      lhsInto,
 | 
			
		||||
      lhsGroup,
 | 
			
		||||
      rhsId,
 | 
			
		||||
      rhsDir,
 | 
			
		||||
      rhsInto,
 | 
			
		||||
      rhsGroup,
 | 
			
		||||
      title,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    this.edges.push(edge);
 | 
			
		||||
    if (this.nodes[lhsId] && this.nodes[rhsId]) {
 | 
			
		||||
      this.nodes[lhsId].edges.push(this.edges[this.edges.length - 1]);
 | 
			
		||||
      this.nodes[rhsId].edges.push(this.edges[this.edges.length - 1]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return state.records.dataStructures;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const setElementForId = (id: string, element: D3Element) => {
 | 
			
		||||
  state.records.elements[id] = element;
 | 
			
		||||
};
 | 
			
		||||
const getElementById = (id: string) => state.records.elements[id];
 | 
			
		||||
  public getEdges(): ArchitectureEdge[] {
 | 
			
		||||
    return this.edges;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
const getConfig = (): Required<ArchitectureDiagramConfig> => {
 | 
			
		||||
  const config = cleanAndMerge({
 | 
			
		||||
    ...DEFAULT_ARCHITECTURE_CONFIG,
 | 
			
		||||
    ...commonGetConfig().architecture,
 | 
			
		||||
  });
 | 
			
		||||
  return config;
 | 
			
		||||
};
 | 
			
		||||
  /**
 | 
			
		||||
   * Returns the current diagram's adjacency list, spatial map, & group alignments.
 | 
			
		||||
   * If they have not been created, run the algorithms to generate them.
 | 
			
		||||
   * @returns
 | 
			
		||||
   */
 | 
			
		||||
  public getDataStructures() {
 | 
			
		||||
    if (this.dataStructures === undefined) {
 | 
			
		||||
      // Tracks how groups are aligned with one another. Generated while creating the adj list
 | 
			
		||||
      const groupAlignments: Record<
 | 
			
		||||
        string,
 | 
			
		||||
        Record<string, Exclude<ArchitectureAlignment, 'bend'>>
 | 
			
		||||
      > = {};
 | 
			
		||||
 | 
			
		||||
export const db: ArchitectureDB = {
 | 
			
		||||
  clear,
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
  setAccTitle,
 | 
			
		||||
  getAccTitle,
 | 
			
		||||
  setAccDescription,
 | 
			
		||||
  getAccDescription,
 | 
			
		||||
  getConfig,
 | 
			
		||||
      // Create an adjacency list of the diagram to perform BFS on
 | 
			
		||||
      // Outer reduce applied on all services
 | 
			
		||||
      // Inner reduce applied on the edges for a service
 | 
			
		||||
      const adjList = Object.entries(this.nodes).reduce<
 | 
			
		||||
        Record<string, ArchitectureDirectionPairMap>
 | 
			
		||||
      >((prevOuter, [id, service]) => {
 | 
			
		||||
        prevOuter[id] = service.edges.reduce<ArchitectureDirectionPairMap>((prevInner, edge) => {
 | 
			
		||||
          // track the direction groups connect to one another
 | 
			
		||||
          const lhsGroupId = this.getNode(edge.lhsId)?.in;
 | 
			
		||||
          const rhsGroupId = this.getNode(edge.rhsId)?.in;
 | 
			
		||||
          if (lhsGroupId && rhsGroupId && lhsGroupId !== rhsGroupId) {
 | 
			
		||||
            const alignment = getArchitectureDirectionAlignment(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
            if (alignment !== 'bend') {
 | 
			
		||||
              groupAlignments[lhsGroupId] ??= {};
 | 
			
		||||
              groupAlignments[lhsGroupId][rhsGroupId] = alignment;
 | 
			
		||||
              groupAlignments[rhsGroupId] ??= {};
 | 
			
		||||
              groupAlignments[rhsGroupId][lhsGroupId] = alignment;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
  addService,
 | 
			
		||||
  getServices,
 | 
			
		||||
  addJunction,
 | 
			
		||||
  getJunctions,
 | 
			
		||||
  getNodes,
 | 
			
		||||
  getNode,
 | 
			
		||||
  addGroup,
 | 
			
		||||
  getGroups,
 | 
			
		||||
  addEdge,
 | 
			
		||||
  getEdges,
 | 
			
		||||
  setElementForId,
 | 
			
		||||
  getElementById,
 | 
			
		||||
  getDataStructures,
 | 
			
		||||
};
 | 
			
		||||
          if (edge.lhsId === id) {
 | 
			
		||||
            // source is LHS
 | 
			
		||||
            const pair = getArchitectureDirectionPair(edge.lhsDir, edge.rhsDir);
 | 
			
		||||
            if (pair) {
 | 
			
		||||
              prevInner[pair] = edge.rhsId;
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            // source is RHS
 | 
			
		||||
            const pair = getArchitectureDirectionPair(edge.rhsDir, edge.lhsDir);
 | 
			
		||||
            if (pair) {
 | 
			
		||||
              prevInner[pair] = edge.lhsId;
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          return prevInner;
 | 
			
		||||
        }, {});
 | 
			
		||||
        return prevOuter;
 | 
			
		||||
      }, {});
 | 
			
		||||
 | 
			
		||||
      // Configuration for the initial pass of BFS
 | 
			
		||||
      const firstId = Object.keys(adjList)[0];
 | 
			
		||||
      const visited = { [firstId]: 1 };
 | 
			
		||||
      // If a key is present in this object, it has not been visited
 | 
			
		||||
      const notVisited = Object.keys(adjList).reduce(
 | 
			
		||||
        (prev, id) => (id === firstId ? prev : { ...prev, [id]: 1 }),
 | 
			
		||||
        {} as Record<string, number>
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      // Perform BFS on the adjacency list
 | 
			
		||||
      const BFS = (startingId: string): ArchitectureSpatialMap => {
 | 
			
		||||
        const spatialMap = { [startingId]: [0, 0] };
 | 
			
		||||
        const queue = [startingId];
 | 
			
		||||
        while (queue.length > 0) {
 | 
			
		||||
          const id = queue.shift();
 | 
			
		||||
          if (id) {
 | 
			
		||||
            visited[id] = 1;
 | 
			
		||||
            delete notVisited[id];
 | 
			
		||||
            const adj = adjList[id];
 | 
			
		||||
            const [posX, posY] = spatialMap[id];
 | 
			
		||||
            Object.entries(adj).forEach(([dir, rhsId]) => {
 | 
			
		||||
              if (!visited[rhsId]) {
 | 
			
		||||
                spatialMap[rhsId] = shiftPositionByArchitectureDirectionPair(
 | 
			
		||||
                  [posX, posY],
 | 
			
		||||
                  dir as ArchitectureDirectionPair
 | 
			
		||||
                );
 | 
			
		||||
                queue.push(rhsId);
 | 
			
		||||
              }
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return spatialMap;
 | 
			
		||||
      };
 | 
			
		||||
      const spatialMaps = [BFS(firstId)];
 | 
			
		||||
 | 
			
		||||
      // If our diagram is disconnected, keep adding additional spatial maps until all disconnected graphs have been found
 | 
			
		||||
      while (Object.keys(notVisited).length > 0) {
 | 
			
		||||
        spatialMaps.push(BFS(Object.keys(notVisited)[0]));
 | 
			
		||||
      }
 | 
			
		||||
      this.dataStructures = {
 | 
			
		||||
        adjList,
 | 
			
		||||
        spatialMaps,
 | 
			
		||||
        groupAlignments,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
    return this.dataStructures;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setElementForId(id: string, element: D3Element): void {
 | 
			
		||||
    this.elements[id] = element;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getElementById(id: string): D3Element {
 | 
			
		||||
    return this.elements[id];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getConfig(): Required<ArchitectureDiagramConfig> {
 | 
			
		||||
    return cleanAndMerge({
 | 
			
		||||
      ...DEFAULT_ARCHITECTURE_CONFIG,
 | 
			
		||||
      ...commonGetConfig().architecture,
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getConfigField<T extends keyof ArchitectureDiagramConfig>(
 | 
			
		||||
    field: T
 | 
			
		||||
  ): Required<ArchitectureDiagramConfig>[T] {
 | 
			
		||||
    return this.getConfig()[field];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setAccTitle = setAccTitle;
 | 
			
		||||
  public getAccTitle = getAccTitle;
 | 
			
		||||
  public setDiagramTitle = setDiagramTitle;
 | 
			
		||||
  public getDiagramTitle = getDiagramTitle;
 | 
			
		||||
  public getAccDescription = getAccDescription;
 | 
			
		||||
  public setAccDescription = setAccDescription;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Typed wrapper for resolving an architecture diagram's config fields. Returns the default value if undefined
 | 
			
		||||
 * @param field - the config field to access
 | 
			
		||||
 * @returns
 | 
			
		||||
 */
 | 
			
		||||
export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
 | 
			
		||||
  field: T
 | 
			
		||||
): Required<ArchitectureDiagramConfig>[T] {
 | 
			
		||||
  return getConfig()[field];
 | 
			
		||||
}
 | 
			
		||||
// export function getConfigField<T extends keyof ArchitectureDiagramConfig>(
 | 
			
		||||
//   field: T
 | 
			
		||||
// ): Required<ArchitectureDiagramConfig>[T] {
 | 
			
		||||
//   return db.getConfig()[field];
 | 
			
		||||
// }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
 | 
			
		||||
import { parser } from './architectureParser.js';
 | 
			
		||||
import { db } from './architectureDb.js';
 | 
			
		||||
import { ArchitectureDB } from './architectureDb.js';
 | 
			
		||||
import styles from './architectureStyles.js';
 | 
			
		||||
import { renderer } from './architectureRenderer.js';
 | 
			
		||||
 | 
			
		||||
export const diagram: DiagramDefinition = {
 | 
			
		||||
  parser,
 | 
			
		||||
  db,
 | 
			
		||||
  get db() {
 | 
			
		||||
    return new ArchitectureDB();
 | 
			
		||||
  },
 | 
			
		||||
  renderer,
 | 
			
		||||
  styles,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,33 @@
 | 
			
		||||
import type { Architecture } from '@mermaid-js/parser';
 | 
			
		||||
import { parse } from '@mermaid-js/parser';
 | 
			
		||||
import { log } from '../../logger.js';
 | 
			
		||||
import type { ParserDefinition } from '../../diagram-api/types.js';
 | 
			
		||||
import { log } from '../../logger.js';
 | 
			
		||||
import { populateCommonDb } from '../common/populateCommonDb.js';
 | 
			
		||||
import type { ArchitectureDB } from './architectureTypes.js';
 | 
			
		||||
import { db } from './architectureDb.js';
 | 
			
		||||
import { ArchitectureDB } from './architectureDb.js';
 | 
			
		||||
 | 
			
		||||
const populateDb = (ast: Architecture, db: ArchitectureDB) => {
 | 
			
		||||
  populateCommonDb(ast, db);
 | 
			
		||||
  ast.groups.map(db.addGroup);
 | 
			
		||||
  ast.groups.map((group) => db.addGroup(group));
 | 
			
		||||
  ast.services.map((service) => db.addService({ ...service, type: 'service' }));
 | 
			
		||||
  ast.junctions.map((service) => db.addJunction({ ...service, type: 'junction' }));
 | 
			
		||||
  // @ts-ignore TODO our parser guarantees the type is L/R/T/B and not string. How to change to union type?
 | 
			
		||||
  ast.edges.map(db.addEdge);
 | 
			
		||||
  ast.edges.map((edge) => db.addEdge(edge));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parser: ParserDefinition = {
 | 
			
		||||
  parser: {
 | 
			
		||||
    // @ts-expect-error - ArchitectureDB is not assignable to DiagramDB
 | 
			
		||||
    yy: undefined,
 | 
			
		||||
  },
 | 
			
		||||
  parse: async (input: string): Promise<void> => {
 | 
			
		||||
    const ast: Architecture = await parse('architecture', input);
 | 
			
		||||
    log.debug(ast);
 | 
			
		||||
    const db = parser.parser?.yy;
 | 
			
		||||
    if (!(db instanceof ArchitectureDB)) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        'parser.parser?.yy was not a ArchitectureDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.'
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    populateDb(ast, db);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,3 @@
 | 
			
		||||
import { registerIconPacks } from '../../rendering-util/icons.js';
 | 
			
		||||
import type { Position } from 'cytoscape';
 | 
			
		||||
import cytoscape from 'cytoscape';
 | 
			
		||||
import type { FcoseLayoutOptions } from 'cytoscape-fcose';
 | 
			
		||||
@@ -7,9 +6,10 @@ import { select } from 'd3';
 | 
			
		||||
import type { DrawDefinition, SVG } from '../../diagram-api/types.js';
 | 
			
		||||
import type { Diagram } from '../../Diagram.js';
 | 
			
		||||
import { log } from '../../logger.js';
 | 
			
		||||
import { registerIconPacks } from '../../rendering-util/icons.js';
 | 
			
		||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
 | 
			
		||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
 | 
			
		||||
import { getConfigField } from './architectureDb.js';
 | 
			
		||||
import type { ArchitectureDB } from './architectureDb.js';
 | 
			
		||||
import { architectureIcons } from './architectureIcons.js';
 | 
			
		||||
import type {
 | 
			
		||||
  ArchitectureAlignment,
 | 
			
		||||
@@ -22,7 +22,6 @@ import type {
 | 
			
		||||
  NodeSingularData,
 | 
			
		||||
} from './architectureTypes.js';
 | 
			
		||||
import {
 | 
			
		||||
  type ArchitectureDB,
 | 
			
		||||
  type ArchitectureDirection,
 | 
			
		||||
  type ArchitectureEdge,
 | 
			
		||||
  type ArchitectureGroup,
 | 
			
		||||
@@ -44,7 +43,7 @@ registerIconPacks([
 | 
			
		||||
]);
 | 
			
		||||
cytoscape.use(fcose);
 | 
			
		||||
 | 
			
		||||
function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
 | 
			
		||||
function addServices(services: ArchitectureService[], cy: cytoscape.Core, db: ArchitectureDB) {
 | 
			
		||||
  services.forEach((service) => {
 | 
			
		||||
    cy.add({
 | 
			
		||||
      group: 'nodes',
 | 
			
		||||
@@ -54,15 +53,15 @@ function addServices(services: ArchitectureService[], cy: cytoscape.Core) {
 | 
			
		||||
        icon: service.icon,
 | 
			
		||||
        label: service.title,
 | 
			
		||||
        parent: service.in,
 | 
			
		||||
        width: getConfigField('iconSize'),
 | 
			
		||||
        height: getConfigField('iconSize'),
 | 
			
		||||
        width: db.getConfigField('iconSize'),
 | 
			
		||||
        height: db.getConfigField('iconSize'),
 | 
			
		||||
      } as NodeSingularData,
 | 
			
		||||
      classes: 'node-service',
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function addJunctions(junctions: ArchitectureJunction[], cy: cytoscape.Core) {
 | 
			
		||||
function addJunctions(junctions: ArchitectureJunction[], cy: cytoscape.Core, db: ArchitectureDB) {
 | 
			
		||||
  junctions.forEach((junction) => {
 | 
			
		||||
    cy.add({
 | 
			
		||||
      group: 'nodes',
 | 
			
		||||
@@ -70,8 +69,8 @@ function addJunctions(junctions: ArchitectureJunction[], cy: cytoscape.Core) {
 | 
			
		||||
        type: 'junction',
 | 
			
		||||
        id: junction.id,
 | 
			
		||||
        parent: junction.in,
 | 
			
		||||
        width: getConfigField('iconSize'),
 | 
			
		||||
        height: getConfigField('iconSize'),
 | 
			
		||||
        width: db.getConfigField('iconSize'),
 | 
			
		||||
        height: db.getConfigField('iconSize'),
 | 
			
		||||
      } as NodeSingularData,
 | 
			
		||||
      classes: 'node-junction',
 | 
			
		||||
    });
 | 
			
		||||
@@ -257,7 +256,8 @@ function getAlignments(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getRelativeConstraints(
 | 
			
		||||
  spatialMaps: ArchitectureSpatialMap[]
 | 
			
		||||
  spatialMaps: ArchitectureSpatialMap[],
 | 
			
		||||
  db: ArchitectureDB
 | 
			
		||||
): fcose.FcoseRelativePlacementConstraint[] {
 | 
			
		||||
  const relativeConstraints: fcose.FcoseRelativePlacementConstraint[] = [];
 | 
			
		||||
  const posToStr = (pos: number[]) => `${pos[0]},${pos[1]}`;
 | 
			
		||||
@@ -296,7 +296,7 @@ function getRelativeConstraints(
 | 
			
		||||
                [ArchitectureDirectionName[
 | 
			
		||||
                  getOppositeArchitectureDirection(dir as ArchitectureDirection)
 | 
			
		||||
                ]]: currId,
 | 
			
		||||
                gap: 1.5 * getConfigField('iconSize'),
 | 
			
		||||
                gap: 1.5 * db.getConfigField('iconSize'),
 | 
			
		||||
              });
 | 
			
		||||
            }
 | 
			
		||||
          });
 | 
			
		||||
@@ -353,7 +353,7 @@ function layoutArchitecture(
 | 
			
		||||
          style: {
 | 
			
		||||
            'text-valign': 'bottom',
 | 
			
		||||
            'text-halign': 'center',
 | 
			
		||||
            'font-size': `${getConfigField('fontSize')}px`,
 | 
			
		||||
            'font-size': `${db.getConfigField('fontSize')}px`,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
@@ -375,7 +375,7 @@ function layoutArchitecture(
 | 
			
		||||
          selector: '.node-group',
 | 
			
		||||
          style: {
 | 
			
		||||
            // @ts-ignore Incorrect library types
 | 
			
		||||
            padding: `${getConfigField('padding')}px`,
 | 
			
		||||
            padding: `${db.getConfigField('padding')}px`,
 | 
			
		||||
          },
 | 
			
		||||
        },
 | 
			
		||||
      ],
 | 
			
		||||
@@ -393,14 +393,14 @@ function layoutArchitecture(
 | 
			
		||||
    renderEl.remove();
 | 
			
		||||
 | 
			
		||||
    addGroups(groups, cy);
 | 
			
		||||
    addServices(services, cy);
 | 
			
		||||
    addJunctions(junctions, cy);
 | 
			
		||||
    addServices(services, cy, db);
 | 
			
		||||
    addJunctions(junctions, cy, db);
 | 
			
		||||
    addEdges(edges, cy);
 | 
			
		||||
    // Use the spatial map to create alignment arrays for fcose
 | 
			
		||||
    const alignmentConstraint = getAlignments(db, spatialMaps, groupAlignments);
 | 
			
		||||
 | 
			
		||||
    // Create the relative constraints for fcose by using an inverse of the spatial map and performing BFS on it
 | 
			
		||||
    const relativePlacementConstraint = getRelativeConstraints(spatialMaps);
 | 
			
		||||
    const relativePlacementConstraint = getRelativeConstraints(spatialMaps, db);
 | 
			
		||||
 | 
			
		||||
    const layout = cy.layout({
 | 
			
		||||
      name: 'fcose',
 | 
			
		||||
@@ -415,7 +415,9 @@ function layoutArchitecture(
 | 
			
		||||
        const { parent: parentA } = nodeData(nodeA);
 | 
			
		||||
        const { parent: parentB } = nodeData(nodeB);
 | 
			
		||||
        const elasticity =
 | 
			
		||||
          parentA === parentB ? 1.5 * getConfigField('iconSize') : 0.5 * getConfigField('iconSize');
 | 
			
		||||
          parentA === parentB
 | 
			
		||||
            ? 1.5 * db.getConfigField('iconSize')
 | 
			
		||||
            : 0.5 * db.getConfigField('iconSize');
 | 
			
		||||
        return elasticity;
 | 
			
		||||
      },
 | 
			
		||||
      edgeElasticity(edge: EdgeSingular) {
 | 
			
		||||
@@ -535,11 +537,11 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj: Diagram)
 | 
			
		||||
 | 
			
		||||
  const cy = await layoutArchitecture(services, junctions, groups, edges, db, ds);
 | 
			
		||||
 | 
			
		||||
  await drawEdges(edgesElem, cy);
 | 
			
		||||
  await drawGroups(groupElem, cy);
 | 
			
		||||
  await drawEdges(edgesElem, cy, db);
 | 
			
		||||
  await drawGroups(groupElem, cy, db);
 | 
			
		||||
  positionNodes(db, cy);
 | 
			
		||||
 | 
			
		||||
  setupGraphViewbox(undefined, svg, getConfigField('padding'), getConfigField('useMaxWidth'));
 | 
			
		||||
  setupGraphViewbox(undefined, svg, db.getConfigField('padding'), db.getConfigField('useMaxWidth'));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const renderer = { draw };
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import { getIconSVG } from '../../rendering-util/icons.js';
 | 
			
		||||
import type cytoscape from 'cytoscape';
 | 
			
		||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
 | 
			
		||||
import { createText } from '../../rendering-util/createText.js';
 | 
			
		||||
import { getIconSVG } from '../../rendering-util/icons.js';
 | 
			
		||||
import type { D3Element } from '../../types.js';
 | 
			
		||||
import { db, getConfigField } from './architectureDb.js';
 | 
			
		||||
import type { ArchitectureDB } from './architectureDb.js';
 | 
			
		||||
import { architectureIcons } from './architectureIcons.js';
 | 
			
		||||
import {
 | 
			
		||||
  ArchitectureDirectionArrow,
 | 
			
		||||
@@ -16,14 +16,17 @@ import {
 | 
			
		||||
  isArchitectureDirectionY,
 | 
			
		||||
  isArchitecturePairXY,
 | 
			
		||||
  nodeData,
 | 
			
		||||
  type ArchitectureDB,
 | 
			
		||||
  type ArchitectureJunction,
 | 
			
		||||
  type ArchitectureService,
 | 
			
		||||
} from './architectureTypes.js';
 | 
			
		||||
 | 
			
		||||
export const drawEdges = async function (edgesEl: D3Element, cy: cytoscape.Core) {
 | 
			
		||||
  const padding = getConfigField('padding');
 | 
			
		||||
  const iconSize = getConfigField('iconSize');
 | 
			
		||||
export const drawEdges = async function (
 | 
			
		||||
  edgesEl: D3Element,
 | 
			
		||||
  cy: cytoscape.Core,
 | 
			
		||||
  db: ArchitectureDB
 | 
			
		||||
) {
 | 
			
		||||
  const padding = db.getConfigField('padding');
 | 
			
		||||
  const iconSize = db.getConfigField('iconSize');
 | 
			
		||||
  const halfIconSize = iconSize / 2;
 | 
			
		||||
  const arrowSize = iconSize / 6;
 | 
			
		||||
  const halfArrowSize = arrowSize / 2;
 | 
			
		||||
@@ -183,13 +186,17 @@ export const drawEdges = async function (edgesEl: D3Element, cy: cytoscape.Core)
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawGroups = async function (groupsEl: D3Element, cy: cytoscape.Core) {
 | 
			
		||||
  const padding = getConfigField('padding');
 | 
			
		||||
export const drawGroups = async function (
 | 
			
		||||
  groupsEl: D3Element,
 | 
			
		||||
  cy: cytoscape.Core,
 | 
			
		||||
  db: ArchitectureDB
 | 
			
		||||
) {
 | 
			
		||||
  const padding = db.getConfigField('padding');
 | 
			
		||||
  const groupIconSize = padding * 0.75;
 | 
			
		||||
 | 
			
		||||
  const fontSize = getConfigField('fontSize');
 | 
			
		||||
  const fontSize = db.getConfigField('fontSize');
 | 
			
		||||
 | 
			
		||||
  const iconSize = getConfigField('iconSize');
 | 
			
		||||
  const iconSize = db.getConfigField('iconSize');
 | 
			
		||||
  const halfIconSize = iconSize / 2;
 | 
			
		||||
 | 
			
		||||
  await Promise.all(
 | 
			
		||||
@@ -266,7 +273,7 @@ export const drawServices = async function (
 | 
			
		||||
): Promise<number> {
 | 
			
		||||
  for (const service of services) {
 | 
			
		||||
    const serviceElem = elem.append('g');
 | 
			
		||||
    const iconSize = getConfigField('iconSize');
 | 
			
		||||
    const iconSize = db.getConfigField('iconSize');
 | 
			
		||||
 | 
			
		||||
    if (service.title) {
 | 
			
		||||
      const textElem = serviceElem.append('g');
 | 
			
		||||
@@ -350,7 +357,7 @@ export const drawJunctions = function (
 | 
			
		||||
) {
 | 
			
		||||
  junctions.forEach((junction) => {
 | 
			
		||||
    const junctionElem = elem.append('g');
 | 
			
		||||
    const iconSize = getConfigField('iconSize');
 | 
			
		||||
    const iconSize = db.getConfigField('iconSize');
 | 
			
		||||
 | 
			
		||||
    const bkgElem = junctionElem.append('g');
 | 
			
		||||
    bkgElem
 | 
			
		||||
 
 | 
			
		||||
@@ -15,4 +15,12 @@ describe('class diagram', function () {
 | 
			
		||||
      expect(() => parser.parse(`classDiagram\nnamespace ${prop} {\n\tclass A\n}`)).not.toThrow();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('backtick escaping', function () {
 | 
			
		||||
    it('should handle backtick-quoted namespace names', function () {
 | 
			
		||||
      expect(() =>
 | 
			
		||||
        parser.parse(`classDiagram\nnamespace \`A::B\` {\n\tclass \`IPC::Sender\`\n}`)
 | 
			
		||||
      ).not.toThrow();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -242,6 +242,7 @@ classLabel
 | 
			
		||||
 | 
			
		||||
namespaceName
 | 
			
		||||
    : alphaNumToken { $$=$1; }
 | 
			
		||||
    | classLiteralName { $$=$1; }
 | 
			
		||||
    | alphaNumToken DOT namespaceName { $$=$1+'.'+$3; }
 | 
			
		||||
    | alphaNumToken namespaceName { $$=$1+$2; }
 | 
			
		||||
    ;
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ const detector: DiagramDetector = (txt, config = {}): boolean => {
 | 
			
		||||
    // If diagram explicitly states flowchart-elk
 | 
			
		||||
    /^\s*flowchart-elk/.test(txt) ||
 | 
			
		||||
    // If a flowchart/graph diagram has their default renderer set to elk
 | 
			
		||||
    (/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
 | 
			
		||||
    (/^\s*(flowchart|graph)/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
 | 
			
		||||
  ) {
 | 
			
		||||
    config.layout = 'elk';
 | 
			
		||||
    return true;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import { getConfig as commonGetConfig } from '../../config.js';
 | 
			
		||||
import type { PacketDiagramConfig } from '../../config.type.js';
 | 
			
		||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
 | 
			
		||||
import type { DiagramDB } from '../../diagram-api/types.js';
 | 
			
		||||
import { cleanAndMerge } from '../../utils.js';
 | 
			
		||||
import {
 | 
			
		||||
  clear as commonClear,
 | 
			
		||||
@@ -11,49 +12,42 @@ import {
 | 
			
		||||
  setAccTitle,
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
} from '../common/commonDb.js';
 | 
			
		||||
import type { PacketDB, PacketData, PacketWord } from './types.js';
 | 
			
		||||
 | 
			
		||||
const defaultPacketData: PacketData = {
 | 
			
		||||
  packet: [],
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
let data: PacketData = structuredClone(defaultPacketData);
 | 
			
		||||
 | 
			
		||||
import type { PacketWord } from './types.js';
 | 
			
		||||
const DEFAULT_PACKET_CONFIG: Required<PacketDiagramConfig> = DEFAULT_CONFIG.packet;
 | 
			
		||||
 | 
			
		||||
const getConfig = (): Required<PacketDiagramConfig> => {
 | 
			
		||||
  const config = cleanAndMerge({
 | 
			
		||||
    ...DEFAULT_PACKET_CONFIG,
 | 
			
		||||
    ...commonGetConfig().packet,
 | 
			
		||||
  });
 | 
			
		||||
  if (config.showBits) {
 | 
			
		||||
    config.paddingY += 10;
 | 
			
		||||
export class PacketDB implements DiagramDB {
 | 
			
		||||
  private packet: PacketWord[] = [];
 | 
			
		||||
 | 
			
		||||
  public getConfig() {
 | 
			
		||||
    const config = cleanAndMerge({
 | 
			
		||||
      ...DEFAULT_PACKET_CONFIG,
 | 
			
		||||
      ...commonGetConfig().packet,
 | 
			
		||||
    });
 | 
			
		||||
    if (config.showBits) {
 | 
			
		||||
      config.paddingY += 10;
 | 
			
		||||
    }
 | 
			
		||||
    return config;
 | 
			
		||||
  }
 | 
			
		||||
  return config;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getPacket = (): PacketWord[] => data.packet;
 | 
			
		||||
 | 
			
		||||
const pushWord = (word: PacketWord) => {
 | 
			
		||||
  if (word.length > 0) {
 | 
			
		||||
    data.packet.push(word);
 | 
			
		||||
  public getPacket() {
 | 
			
		||||
    return this.packet;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const clear = () => {
 | 
			
		||||
  commonClear();
 | 
			
		||||
  data = structuredClone(defaultPacketData);
 | 
			
		||||
};
 | 
			
		||||
  public pushWord(word: PacketWord) {
 | 
			
		||||
    if (word.length > 0) {
 | 
			
		||||
      this.packet.push(word);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
export const db: PacketDB = {
 | 
			
		||||
  pushWord,
 | 
			
		||||
  getPacket,
 | 
			
		||||
  getConfig,
 | 
			
		||||
  clear,
 | 
			
		||||
  setAccTitle,
 | 
			
		||||
  getAccTitle,
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
  getAccDescription,
 | 
			
		||||
  setAccDescription,
 | 
			
		||||
};
 | 
			
		||||
  public clear() {
 | 
			
		||||
    commonClear();
 | 
			
		||||
    this.packet = [];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public setAccTitle = setAccTitle;
 | 
			
		||||
  public getAccTitle = getAccTitle;
 | 
			
		||||
  public setDiagramTitle = setDiagramTitle;
 | 
			
		||||
  public getDiagramTitle = getDiagramTitle;
 | 
			
		||||
  public getAccDescription = getAccDescription;
 | 
			
		||||
  public setAccDescription = setAccDescription;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,14 @@
 | 
			
		||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
 | 
			
		||||
import { db } from './db.js';
 | 
			
		||||
import { PacketDB } from './db.js';
 | 
			
		||||
import { parser } from './parser.js';
 | 
			
		||||
import { renderer } from './renderer.js';
 | 
			
		||||
import { styles } from './styles.js';
 | 
			
		||||
 | 
			
		||||
export const diagram: DiagramDefinition = {
 | 
			
		||||
  parser,
 | 
			
		||||
  db,
 | 
			
		||||
  get db() {
 | 
			
		||||
    return new PacketDB();
 | 
			
		||||
  },
 | 
			
		||||
  renderer,
 | 
			
		||||
  styles,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,24 +1,26 @@
 | 
			
		||||
import { it, describe, expect } from 'vitest';
 | 
			
		||||
import { db } from './db.js';
 | 
			
		||||
import { PacketDB } from './db.js';
 | 
			
		||||
import { parser } from './parser.js';
 | 
			
		||||
 | 
			
		||||
const { clear, getPacket, getDiagramTitle, getAccTitle, getAccDescription } = db;
 | 
			
		||||
 | 
			
		||||
describe('packet diagrams', () => {
 | 
			
		||||
  let db: PacketDB;
 | 
			
		||||
  beforeEach(() => {
 | 
			
		||||
    clear();
 | 
			
		||||
    db = new PacketDB();
 | 
			
		||||
    if (parser.parser) {
 | 
			
		||||
      parser.parser.yy = db;
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should handle a packet-beta definition', async () => {
 | 
			
		||||
    const str = `packet-beta`;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot('[]');
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot('[]');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should handle a packet definition', async () => {
 | 
			
		||||
    const str = `packet`;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot('[]');
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot('[]');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should handle diagram with data and title', async () => {
 | 
			
		||||
@@ -29,10 +31,10 @@ describe('packet diagrams', () => {
 | 
			
		||||
    0-10: "test"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getDiagramTitle()).toMatchInlineSnapshot('"Packet diagram"');
 | 
			
		||||
    expect(getAccTitle()).toMatchInlineSnapshot('"Packet accTitle"');
 | 
			
		||||
    expect(getAccDescription()).toMatchInlineSnapshot('"Packet accDescription"');
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getDiagramTitle()).toMatchInlineSnapshot('"Packet diagram"');
 | 
			
		||||
    expect(db.getAccTitle()).toMatchInlineSnapshot('"Packet accTitle"');
 | 
			
		||||
    expect(db.getAccDescription()).toMatchInlineSnapshot('"Packet accDescription"');
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
@@ -52,7 +54,7 @@ describe('packet diagrams', () => {
 | 
			
		||||
    11: "single"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
@@ -78,7 +80,7 @@ describe('packet diagrams', () => {
 | 
			
		||||
    +16: "word"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
@@ -104,7 +106,7 @@ describe('packet diagrams', () => {
 | 
			
		||||
    +16: "word"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
@@ -130,7 +132,7 @@ describe('packet diagrams', () => {
 | 
			
		||||
    11-90: "multiple"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
@@ -172,7 +174,7 @@ describe('packet diagrams', () => {
 | 
			
		||||
    17-63: "multiple"
 | 
			
		||||
    `;
 | 
			
		||||
    await expect(parser.parse(str)).resolves.not.toThrow();
 | 
			
		||||
    expect(getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
    expect(db.getPacket()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        [
 | 
			
		||||
          {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,12 +3,12 @@ import { parse } from '@mermaid-js/parser';
 | 
			
		||||
import type { ParserDefinition } from '../../diagram-api/types.js';
 | 
			
		||||
import { log } from '../../logger.js';
 | 
			
		||||
import { populateCommonDb } from '../common/populateCommonDb.js';
 | 
			
		||||
import { db } from './db.js';
 | 
			
		||||
import { PacketDB } from './db.js';
 | 
			
		||||
import type { PacketBlock, PacketWord } from './types.js';
 | 
			
		||||
 | 
			
		||||
const maxPacketSize = 10_000;
 | 
			
		||||
 | 
			
		||||
const populate = (ast: Packet) => {
 | 
			
		||||
const populate = (ast: Packet, db: PacketDB) => {
 | 
			
		||||
  populateCommonDb(ast, db);
 | 
			
		||||
  let lastBit = -1;
 | 
			
		||||
  let word: PacketWord = [];
 | 
			
		||||
@@ -91,9 +91,17 @@ const getNextFittingBlock = (
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parser: ParserDefinition = {
 | 
			
		||||
  // @ts-expect-error - PacketDB is not assignable to DiagramDB
 | 
			
		||||
  parser: { yy: undefined },
 | 
			
		||||
  parse: async (input: string): Promise<void> => {
 | 
			
		||||
    const ast: Packet = await parse('packet', input);
 | 
			
		||||
    const db = parser.parser?.yy;
 | 
			
		||||
    if (!(db instanceof PacketDB)) {
 | 
			
		||||
      throw new Error(
 | 
			
		||||
        'parser.parser?.yy was not a PacketDB. This is due to a bug within Mermaid, please report this issue at https://github.com/mermaid-js/mermaid/issues.'
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
    log.debug(ast);
 | 
			
		||||
    populate(ast);
 | 
			
		||||
    populate(ast, db);
 | 
			
		||||
  },
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -99,6 +99,7 @@ Blogging frameworks and platforms
 | 
			
		||||
  - [Mermaid](https://nextra.site/docs/guide/mermaid)
 | 
			
		||||
- [WordPress](https://wordpress.org)
 | 
			
		||||
  - [MerPRess](https://wordpress.org/plugins/merpress/)
 | 
			
		||||
  - [WP Documentation](https://wordpress.org/themes/wp-documentation/)
 | 
			
		||||
 | 
			
		||||
### CMS/ECM
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user