diff --git a/.changeset/hungry-baths-glow.md b/.changeset/hungry-baths-glow.md
new file mode 100644
index 000000000..b3084bcab
--- /dev/null
+++ b/.changeset/hungry-baths-glow.md
@@ -0,0 +1,5 @@
+---
+'mermaid': minor
+---
+
+feat: Added support for new participant types (`actor`, `boundary`, `control`, `entity`, `database`, `collections`, `queue`) in `sequenceDiagram`.
diff --git a/cypress/integration/rendering/sequencediagram-v2.spec.js b/cypress/integration/rendering/sequencediagram-v2.spec.js
new file mode 100644
index 000000000..f1c2aafbd
--- /dev/null
+++ b/cypress/integration/rendering/sequencediagram-v2.spec.js
@@ -0,0 +1,659 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
+
+const looks = ['classic'];
+const participantTypes = [
+ { type: 'participant', display: 'participant' },
+ { type: 'actor', display: 'actor' },
+ { type: 'boundary', display: 'boundary' },
+ { type: 'control', display: 'control' },
+ { type: 'entity', display: 'entity' },
+ { type: 'database', display: 'database' },
+ { type: 'collections', display: 'collections' },
+ { type: 'queue', display: 'queue' },
+];
+
+const restrictedTypes = ['boundary', 'control', 'entity', 'database', 'collections', 'queue'];
+
+const interactionTypes = ['->>', '-->>', '->', '-->', '-x', '--x', '->>+', '-->>+'];
+
+const notePositions = ['left of', 'right of', 'over'];
+
+function getParticipantLine(name, type, alias) {
+ if (restrictedTypes.includes(type)) {
+ return ` participant ${name}@{ "type" : "${type}" }\n`;
+ } else if (alias) {
+ return ` participant ${name}@{ "type" : "${type}" } \n`;
+ } else {
+ return ` participant ${name}@{ "type" : "${type}" }\n`;
+ }
+}
+
+looks.forEach((look) => {
+ describe(`Sequence Diagram Tests - ${look} look`, () => {
+ it('should render all participant types', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ participantTypes.forEach((pt, index) => {
+ const name = `${pt.display}${index}`;
+ diagramCode += getParticipantLine(name, pt.type);
+ });
+ for (let i = 0; i < participantTypes.length - 1; i++) {
+ diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
+ }
+ imgSnapshotTest(diagramCode, { look, sequence: { diagramMarginX: 50, diagramMarginY: 10 } });
+ });
+
+ it('should render all interaction types', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ diagramCode += getParticipantLine('A', 'actor');
+ diagramCode += getParticipantLine('B', 'boundary');
+ interactionTypes.forEach((interaction, index) => {
+ diagramCode += ` A ${interaction} B: ${interaction} message ${index}\n`;
+ });
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render participant creation and destruction', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ participantTypes.forEach((pt, index) => {
+ const name = `${pt.display}${index}`;
+ diagramCode += getParticipantLine('A', pt.type);
+ diagramCode += getParticipantLine('B', pt.type);
+ diagramCode += ` create participant ${name}@{ "type" : "${pt.type}" }\n`;
+ diagramCode += ` A ->> ${name}: Hello ${pt.display}\n`;
+ if (index % 2 === 0) {
+ diagramCode += ` destroy ${name}\n`;
+ }
+ });
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render notes in all positions', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ diagramCode += getParticipantLine('A', 'actor');
+ diagramCode += getParticipantLine('B', 'boundary');
+ notePositions.forEach((position, index) => {
+ diagramCode += ` Note ${position} A: Note ${position} ${index}\n`;
+ });
+ diagramCode += ` A ->> B: Message with notes\n`;
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render parallel interactions', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ participantTypes.slice(0, 4).forEach((pt, index) => {
+ diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
+ });
+ diagramCode += ` par Parallel actions\n`;
+ for (let i = 0; i < 3; i += 2) {
+ diagramCode += ` ${participantTypes[i].display}${i} ->> ${participantTypes[i + 1].display}${i + 1}: Message ${i}\n`;
+ if (i < participantTypes.length - 2) {
+ diagramCode += ` and\n`;
+ }
+ }
+ diagramCode += ` end\n`;
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render alternative flows', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ diagramCode += getParticipantLine('A', 'actor');
+ diagramCode += getParticipantLine('B', 'boundary');
+ diagramCode += ` alt Successful case\n`;
+ diagramCode += ` A ->> B: Request\n`;
+ diagramCode += ` B -->> A: Success\n`;
+ diagramCode += ` else Failure case\n`;
+ diagramCode += ` A ->> B: Request\n`;
+ diagramCode += ` B --x A: Failure\n`;
+ diagramCode += ` end\n`;
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render loops', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ participantTypes.slice(0, 3).forEach((pt, index) => {
+ diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
+ });
+ diagramCode += ` loop For each participant\n`;
+ for (let i = 0; i < 3; i++) {
+ diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Message ${i}\n`;
+ }
+ diagramCode += ` end\n`;
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render boxes around groups', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ diagramCode += ` box Group 1\n`;
+ participantTypes.slice(0, 3).forEach((pt, index) => {
+ diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
+ });
+ diagramCode += ` end\n`;
+ diagramCode += ` box rgb(200,220,255) Group 2\n`;
+ participantTypes.slice(3, 6).forEach((pt, index) => {
+ diagramCode += ` ${getParticipantLine(`${pt.display}${index}`, pt.type)}`;
+ });
+ diagramCode += ` end\n`;
+ diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[3].display}0: Cross-group message\n`;
+ imgSnapshotTest(diagramCode, { look });
+ });
+
+ it('should render with different font settings', () => {
+ let diagramCode = `sequenceDiagram\n`;
+ participantTypes.slice(0, 3).forEach((pt, index) => {
+ diagramCode += getParticipantLine(`${pt.display}${index}`, pt.type);
+ });
+ diagramCode += ` ${participantTypes[0].display}0 ->> ${participantTypes[1].display}1: Regular message\n`;
+ diagramCode += ` Note right of ${participantTypes[1].display}1: Regular note\n`;
+ imgSnapshotTest(diagramCode, {
+ look,
+ sequence: {
+ actorFontFamily: 'courier',
+ actorFontSize: 14,
+ messageFontFamily: 'Arial',
+ messageFontSize: 12,
+ noteFontFamily: 'times',
+ noteFontSize: 16,
+ noteAlign: 'left',
+ },
+ });
+ });
+ });
+});
+
+// Additional tests for specific combinations
+describe('Sequence Diagram Special Cases', () => {
+ it('should render complex sequence with all features', () => {
+ const diagramCode = `
+ sequenceDiagram
+ box rgb(200,220,255) Authentication
+ actor User
+ participant LoginUI@{ "type": "boundary" }
+ participant AuthService@{ "type": "control" }
+ participant UserDB@{ "type": "database" }
+ end
+
+ box rgb(200,255,220) Order Processing
+ participant Order@{ "type": "entity" }
+ participant OrderQueue@{ "type": "queue" }
+ participant AuditLogs@{ "type": "collections" }
+ end
+
+ User ->> LoginUI: Enter credentials
+ LoginUI ->> AuthService: Validate
+ AuthService ->> UserDB: Query user
+ UserDB -->> AuthService: User data
+ alt Valid credentials
+ AuthService -->> LoginUI: Success
+ LoginUI -->> User: Welcome
+
+ par Place order
+ User ->> Order: New order
+ Order ->> OrderQueue: Process
+ and
+ Order ->> AuditLogs: Record
+ end
+
+ loop Until confirmed
+ OrderQueue ->> Order: Update status
+ Order -->> User: Notification
+ end
+ else Invalid credentials
+ AuthService --x LoginUI: Failure
+ LoginUI --x User: Retry
+ end
+ `;
+ imgSnapshotTest(diagramCode, {});
+ });
+
+ it('should render with wrapped messages and notes', () => {
+ const diagramCode = `
+ sequenceDiagram
+ participant A
+ participant B
+
+ A ->> B: This is a very long message that should wrap properly in the diagram rendering
+ Note over A,B: This is a very long note that should also wrap properly when rendered in the diagram
+
+ par Wrapped parallel
+ A ->> B: Parallel message 1
with explicit line break
+ and
+ B ->> A: Parallel message 2
with explicit line break
+ end
+
+ loop Wrapped loop
+ Note right of B: This is a long note
in a loop
+ A ->> B: Message in loop
+ end
+ `;
+ imgSnapshotTest(diagramCode, { sequence: { wrap: true } });
+ });
+ describe('Sequence Diagram Rendering with Different Participant Types', () => {
+ it('should render a sequence diagram with various participant types', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant User@{ "type": "actor" }
+ participant AuthService@{ "type": "control" }
+ participant UI@{ "type": "boundary" }
+ participant OrderController@{ "type": "control" }
+ participant Product@{ "type": "entity" }
+ participant MongoDB@{ "type": "database" }
+ participant Products@{ "type": "collections" }
+ participant OrderQueue@{ "type": "queue" }
+ User ->> UI: Login request
+ UI ->> AuthService: Validate credentials
+ AuthService -->> UI: Authentication token
+ UI ->> OrderController: Place order
+ OrderController ->> Product: Check availability
+ Product -->> OrderController: Available
+ OrderController ->> MongoDB: Save order
+ MongoDB -->> OrderController: Order saved
+ OrderController ->> OrderQueue: Process payment
+ OrderQueue -->> User: Order confirmation
+ `
+ );
+ });
+
+ it('should render participant creation and destruction with different types', () => {
+ imgSnapshotTest(`
+ sequenceDiagram
+ participant Alice@{ "type" : "boundary" }
+ Alice->>Bob: Hello Bob, how are you ?
+ Bob->>Alice: Fine, thank you. And you?
+ create participant Carl@{ "type" : "control" }
+ Alice->>Carl: Hi Carl!
+ create actor D as Donald
+ Carl->>D: Hi!
+ destroy Carl
+ Alice-xCarl: We are too many
+ destroy Bob
+ Bob->>Alice: I agree
+ `);
+ });
+
+ it('should handle complex interactions between different participant types', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ box rgb(200,220,255) Authentication
+ participant User@{ "type": "actor" }
+ participant LoginUI@{ "type": "boundary" }
+ participant AuthService@{ "type": "control" }
+ participant UserDB@{ "type": "database" }
+ end
+
+ box rgb(200,255,220) Order Processing
+ participant Order@{ "type": "entity" }
+ participant OrderQueue@{ "type": "queue" }
+ participant AuditLogs@{ "type": "collections" }
+ end
+
+ User ->> LoginUI: Enter credentials
+ LoginUI ->> AuthService: Validate
+ AuthService ->> UserDB: Query user
+ UserDB -->> AuthService: User data
+
+ alt Valid credentials
+ AuthService -->> LoginUI: Success
+ LoginUI -->> User: Welcome
+
+ par Place order
+ User ->> Order: New order
+ Order ->> OrderQueue: Process
+ and
+ Order ->> AuditLogs: Record
+ end
+
+ loop Until confirmed
+ OrderQueue ->> Order: Update status
+ Order -->> User: Notification
+ end
+ else Invalid credentials
+ AuthService --x LoginUI: Failure
+ LoginUI --x User: Retry
+ end
+ `,
+ { sequence: { useMaxWidth: false } }
+ );
+ });
+
+ it('should render parallel processes with different participant types', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Customer@{ "type": "actor" }
+ participant Frontend@{ "type": "participant" }
+ participant PaymentService@{ "type": "boundary" }
+ participant InventoryManager@{ "type": "control" }
+ participant Order@{ "type": "entity" }
+ participant OrdersDB@{ "type": "database" }
+ participant NotificationQueue@{ "type": "queue" }
+
+ Customer ->> Frontend: Place order
+ Frontend ->> Order: Create order
+ par Parallel Processing
+ Order ->> PaymentService: Process payment
+ and
+ Order ->> InventoryManager: Reserve items
+ end
+ PaymentService -->> Order: Payment confirmed
+ InventoryManager -->> Order: Items reserved
+ Order ->> OrdersDB: Save finalized order
+ OrdersDB -->> Order: Order saved
+ Order ->> NotificationQueue: Send confirmation
+ NotificationQueue -->> Customer: Order confirmation
+ `
+ );
+ });
+ });
+ it('should render different participant types with notes and loops', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ actor Admin
+ participant Dashboard
+ participant AuthService@{ "type" : "boundary" }
+ participant UserManager@{ "type" : "control" }
+ participant UserProfile@{ "type" : "entity" }
+ participant UserDB@{ "type" : "database" }
+ participant Logs@{ "type" : "database" }
+
+ Admin ->> Dashboard: Open user management
+ loop Authentication check
+ Dashboard ->> AuthService: Verify admin rights
+ AuthService ->> Dashboard: Access granted
+ end
+ Dashboard ->> UserManager: List users
+ UserManager ->> UserDB: Query users
+ UserDB ->> UserManager: Return user data
+ Note right of UserDB: Encrypted data
requires decryption
+ UserManager ->> UserProfile: Format profiles
+ UserProfile ->> UserManager: Formatted data
+ UserManager ->> Dashboard: Display users
+ Dashboard ->> Logs: Record access
+ Logs ->> Admin: Audit trail
+ `
+ );
+ });
+
+ it('should render different participant types with alternative flows', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ actor Client
+ participant MobileApp
+ participant CloudService@{ "type" : "boundary" }
+ participant DataProcessor@{ "type" : "control" }
+ participant Transaction@{ "type" : "entity" }
+ participant TransactionsDB@{ "type" : "database" }
+ participant EventBus@{ "type" : "queue" }
+
+ Client ->> MobileApp: Initiate transaction
+ MobileApp ->> CloudService: Authenticate
+ alt Authentication successful
+ CloudService -->> MobileApp: Auth token
+ MobileApp ->> DataProcessor: Process data
+ DataProcessor ->> Transaction: Create transaction
+ Transaction ->> TransactionsDB: Save record
+ TransactionsDB -->> Transaction: Confirmation
+ Transaction ->> EventBus: Publish event
+ EventBus -->> Client: Notification
+ else Authentication failed
+ CloudService -->> MobileApp: Error
+ MobileApp -->> Client: Show error
+ end
+ `
+ );
+ });
+
+ it('should render different participant types with wrapping text', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant B@{ "type" : "boundary" }
+ participant C@{ "type" : "control" }
+ participant E@{ "type" : "entity" }
+ participant DB@{ "type" : "database" }
+ participant COL@{ "type" : "collections" }
+ participant Q@{ "type" : "queue" }
+
+ FE ->> B: Another long message
with explicit
line breaks
+ B -->> FE: Response message that is also quite long and needs to wrap
+ FE ->> C: Process data
+ C ->> E: Validate
+ E -->> C: Validation result
+ C ->> DB: Save
+ DB -->> C: Save result
+ C ->> COL: Log
+ COL -->> Q: Forward
+ Q -->> LongNameUser: Final response with confirmation of all actions taken
+ `,
+ { sequence: { wrap: true } }
+ );
+ });
+
+ describe('Sequence Diagram - New Participant Types with Long Notes and Messages', () => {
+ it('should render long notes left of boundary', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "boundary" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note left of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render wrapped long notes left of control', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "control" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note left of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render long notes right of entity', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "entity" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note right of Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render wrapped long notes right of database', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note right of Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render long notes over collections', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "collections" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note over Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render wrapped long notes over queue', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "queue" }
+ actor Bob
+ Alice->>Bob: Hola
+ Note over Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render notes over actor and boundary', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ actor Alice
+ participant Charlie@{ "type" : "boundary" }
+ note over Alice: Some note
+ note over Charlie: Other note
+ `,
+ {}
+ );
+ });
+
+ it('should render long messages from database to collections', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ participant Bob@{ "type" : "collections" }
+ Alice->>Bob: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render wrapped long messages from control to entity', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "control" }
+ participant Bob@{ "type" : "entity" }
+ Alice->>Bob:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ Bob->>Alice: I'm short though
+ `,
+ {}
+ );
+ });
+
+ it('should render long messages from queue to boundary', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ participant Alice@{ "type" : "queue" }
+ participant Bob@{ "type" : "boundary" }
+ Alice->>Bob: I'm short
+ Bob->>Alice: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ `,
+ {}
+ );
+ });
+
+ it('should render wrapped long messages from actor to database', () => {
+ imgSnapshotTest(
+ `
+ sequenceDiagram
+ actor Alice
+ participant Bob@{ "type" : "database" }
+ Alice->>Bob: I'm short
+ Bob->>Alice:wrap: Extremely utterly long line of longness which had previously overflown the actor box as it is much longer than what it should be
+ `,
+ {}
+ );
+ });
+ });
+
+ describe('svg size', () => {
+ it('should render a sequence diagram when useMaxWidth is true (default)', () => {
+ renderGraph(
+ `
+ sequenceDiagram
+ actor Alice
+ participant Bob@{ "type" : "boundary" }
+ participant John@{ "type" : "control" }
+ Alice ->> Bob: Hello Bob, how are you?
+ Bob-->>John: How about you John?
+ Bob--x Alice: I am good thanks!
+ Bob-x John: I am good thanks!
+ Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row.
+ Bob-->Alice: Checking with John...
+ alt either this
+ Alice->>John: Yes
+ else or this
+ Alice->>John: No
+ else or this will happen
+ Alice->John: Maybe
+ end
+ par this happens in parallel
+ Alice -->> Bob: Parallel message 1
+ and
+ Alice -->> John: Parallel message 2
+ end
+ `,
+ { sequence: { useMaxWidth: true } }
+ );
+ cy.get('svg').should((svg) => {
+ expect(svg).to.have.attr('width', '100%');
+ const style = svg.attr('style');
+ expect(style).to.match(/^max-width: [\d.]+px;$/);
+ const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
+ expect(maxWidthValue).to.be.within(820 * 0.95, 820 * 1.05);
+ });
+ });
+
+ it('should render a sequence diagram when useMaxWidth is false', () => {
+ renderGraph(
+ `
+ sequenceDiagram
+ actor Alice
+ participant Bob@{ "type" : "boundary" }
+ participant John@{ "type" : "control" }
+ Alice ->> Bob: Hello Bob, how are you?
+ Bob-->>John: How about you John?
+ Bob--x Alice: I am good thanks!
+ Bob-x John: I am good thanks!
+ Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row.
+ Bob-->Alice: Checking with John...
+ alt either this
+ Alice->>John: Yes
+ else or this
+ Alice->>John: No
+ else or this will happen
+ Alice->John: Maybe
+ end
+ par this happens in parallel
+ Alice -->> Bob: Parallel message 1
+ and
+ Alice -->> John: Parallel message 2
+ end
+ `,
+ { sequence: { useMaxWidth: false } }
+ );
+ cy.get('svg').should((svg) => {
+ const width = parseFloat(svg.attr('width'));
+ expect(width).to.be.within(820 * 0.95, 820 * 1.05);
+ expect(svg).to.not.have.attr('style');
+ });
+ });
+ });
+});
diff --git a/docs/config/setup/mermaid/interfaces/ParseOptions.md b/docs/config/setup/mermaid/interfaces/ParseOptions.md
index ea96f2706..e068a91fb 100644
--- a/docs/config/setup/mermaid/interfaces/ParseOptions.md
+++ b/docs/config/setup/mermaid/interfaces/ParseOptions.md
@@ -10,7 +10,7 @@
# Interface: ParseOptions
-Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L72)
+Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:72](https://github.com/mermaid-js/mer
> `optional` **suppressErrors**: `boolean`
-Defined in: [packages/mermaid/src/types.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L77)
+Defined in: [packages/mermaid/src/types.ts:89](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L89)
If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
The `parseError` function will not be called.
diff --git a/docs/config/setup/mermaid/interfaces/ParseResult.md b/docs/config/setup/mermaid/interfaces/ParseResult.md
index 7a5990610..1651a6fa9 100644
--- a/docs/config/setup/mermaid/interfaces/ParseResult.md
+++ b/docs/config/setup/mermaid/interfaces/ParseResult.md
@@ -10,7 +10,7 @@
# Interface: ParseResult
-Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80)
+Defined in: [packages/mermaid/src/types.ts:92](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L92)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mer
> **config**: [`MermaidConfig`](MermaidConfig.md)
-Defined in: [packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
+Defined in: [packages/mermaid/src/types.ts:100](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L100)
The config passed as YAML frontmatter or directives
@@ -28,6 +28,6 @@ The config passed as YAML frontmatter or directives
> **diagramType**: `string`
-Defined in: [packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)
+Defined in: [packages/mermaid/src/types.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L96)
The diagram type, e.g. 'flowchart', 'sequence', etc.
diff --git a/docs/config/setup/mermaid/interfaces/RenderResult.md b/docs/config/setup/mermaid/interfaces/RenderResult.md
index fc5fac4f5..c0e5496b8 100644
--- a/docs/config/setup/mermaid/interfaces/RenderResult.md
+++ b/docs/config/setup/mermaid/interfaces/RenderResult.md
@@ -10,7 +10,7 @@
# Interface: RenderResult
-Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
+Defined in: [packages/mermaid/src/types.ts:110](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L110)
## Properties
@@ -18,7 +18,7 @@ Defined in: [packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mer
> `optional` **bindFunctions**: (`element`) => `void`
-Defined in: [packages/mermaid/src/types.ts:116](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L116)
+Defined in: [packages/mermaid/src/types.ts:128](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L128)
Bind function to be called after the svg has been inserted into the DOM.
This is necessary for adding event listeners to the elements in the svg.
@@ -45,7 +45,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
> **diagramType**: `string`
-Defined in: [packages/mermaid/src/types.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L106)
+Defined in: [packages/mermaid/src/types.ts:118](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L118)
The diagram type, e.g. 'flowchart', 'sequence', etc.
@@ -55,6 +55,6 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
> **svg**: `string`
-Defined in: [packages/mermaid/src/types.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L102)
+Defined in: [packages/mermaid/src/types.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L114)
The svg code for the rendered graph.
diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md
index 0b5b038e2..e473cc00f 100644
--- a/docs/syntax/sequenceDiagram.md
+++ b/docs/syntax/sequenceDiagram.md
@@ -74,6 +74,126 @@ sequenceDiagram
Bob->>Alice: Hi Alice
```
+### Boundary
+
+If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "boundary" }
+ participant Bob
+ Alice->>Bob: Request from boundary
+ Bob->>Alice: Response to boundary
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "boundary" }
+ participant Bob
+ Alice->>Bob: Request from boundary
+ Bob->>Alice: Response to boundary
+```
+
+### Control
+
+If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "control" }
+ participant Bob
+ Alice->>Bob: Control request
+ Bob->>Alice: Control response
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "control" }
+ participant Bob
+ Alice->>Bob: Control request
+ Bob->>Alice: Control response
+```
+
+### Entity
+
+If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "entity" }
+ participant Bob
+ Alice->>Bob: Entity request
+ Bob->>Alice: Entity response
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "entity" }
+ participant Bob
+ Alice->>Bob: Entity request
+ Bob->>Alice: Entity response
+```
+
+### Database
+
+If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ participant Bob
+ Alice->>Bob: DB query
+ Bob->>Alice: DB result
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ participant Bob
+ Alice->>Bob: DB query
+ Bob->>Alice: DB result
+```
+
+### Collections
+
+If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "collections" }
+ participant Bob
+ Alice->>Bob: Collections request
+ Bob->>Alice: Collections response
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "collections" }
+ participant Bob
+ Alice->>Bob: Collections request
+ Bob->>Alice: Collections response
+```
+
+### Queue
+
+If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "queue" }
+ participant Bob
+ Alice->>Bob: Queue message
+ Bob->>Alice: Queue response
+```
+
+```mermaid
+sequenceDiagram
+ participant Alice@{ "type" : "queue" }
+ participant Bob
+ Alice->>Bob: Queue message
+ Bob->>Alice: Queue response
+```
+
### Aliases
The actor can have a convenient identifier and a descriptive label.
diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
index 9defa64e8..f1364895b 100644
--- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
+++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
@@ -14,7 +14,7 @@
// Special states for recognizing aliases
// A special state for grabbing text up to the first comment/newline
-%x ID ALIAS LINE
+%x ID ALIAS LINE CONFIG CONFIG_DATA
%x acc_title
%x acc_descr
@@ -28,6 +28,11 @@
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM';
+\@\{ { this.begin('CONFIG'); return 'CONFIG_START'; }
+[^\}]+ { return 'CONFIG_CONTENT'; }
+\} { this.popState(); this.popState(); return 'CONFIG_END'; }
+[^\<->\->:\n,;@\s]+(?=\@\{) { yytext = yytext.trim(); return 'ACTOR'; }
+[^\<->\->:\n,;@]+?([\-]*[^\<->\->:\n,;@]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; }
@@ -257,6 +262,8 @@ participant_statement
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.draw='actor'; $2.type='addParticipant'; $$=$2;}
| 'destroy' actor 'NEWLINE' {$2.type='destroyParticipant'; $$=$2;}
+ | 'participant' actor_with_config 'NEWLINE' {$2.draw='participant'; $2.type='addParticipant'; $$=$2;}
+
;
note_statement
@@ -341,6 +348,23 @@ signal
{ $$ = [$1,$3,{type: 'addMessage', from:$1.actor, to:$3.actor, signalType:$2, msg:$4}]}
;
+actor_with_config
+ : ACTOR config_object
+ {
+ $$ = {
+ type: 'addParticipant',
+ actor: $1,
+ config: $2
+ };
+ }
+ ;
+
+config_object
+ : CONFIG_START CONFIG_CONTENT CONFIG_END
+ {
+ $$ = $2.trim();
+ }
+ ;
// actor
// : actor_participant
// | actor_actor
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts
index 0d411a7f5..d4758f39e 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDb.ts
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.ts
@@ -1,4 +1,5 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
+import * as yaml from 'js-yaml';
import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { ImperativeState } from '../../utils/imperativeState.js';
@@ -13,6 +14,7 @@ import {
setDiagramTitle,
} from '../common/commonDb.js';
import type { Actor, AddMessageParams, Box, Message, Note } from './types.js';
+import type { ParticipantMetaData } from '../../types.js';
interface SequenceState {
prevActor?: string;
@@ -99,6 +101,17 @@ const PLACEMENT = {
OVER: 2,
} as const;
+export const PARTICIPANT_TYPE = {
+ ACTOR: 'actor',
+ BOUNDARY: 'boundary',
+ COLLECTIONS: 'collections',
+ CONTROL: 'control',
+ DATABASE: 'database',
+ ENTITY: 'entity',
+ PARTICIPANT: 'participant',
+ QUEUE: 'queue',
+} as const;
+
export class SequenceDB implements DiagramDB {
private readonly state = new ImperativeState(() => ({
prevActor: undefined,
@@ -143,9 +156,22 @@ export class SequenceDB implements DiagramDB {
id: string,
name: string,
description: { text: string; wrap?: boolean | null; type: string },
- type: string
+ type: string,
+ metadata?: any
) {
let assignedBox = this.state.records.currentBox;
+ let doc;
+ if (metadata !== undefined) {
+ let yamlData;
+ // detect if shapeData contains a newline character
+ if (!metadata.includes('\n')) {
+ yamlData = '{\n' + metadata + '\n}';
+ } else {
+ yamlData = metadata + '\n';
+ }
+ doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as ParticipantMetaData;
+ }
+ type = doc?.type ?? type;
const old = this.state.records.actors.get(id);
if (old) {
// If already set and trying to set to a new one throw error
@@ -544,7 +570,7 @@ export class SequenceDB implements DiagramDB {
});
break;
case 'addParticipant':
- this.addActor(param.actor, param.actor, param.description, param.draw);
+ this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
break;
case 'createParticipant':
if (this.state.records.actors.has(param.actor)) {
@@ -553,7 +579,7 @@ export class SequenceDB implements DiagramDB {
);
}
this.state.records.lastCreated = param.actor;
- this.addActor(param.actor, param.actor, param.description, param.draw);
+ this.addActor(param.actor, param.actor, param.description, param.draw, param.config);
this.state.records.createdActors.set(param.actor, this.state.records.messages.length);
break;
case 'destroyParticipant':
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
index 69c1b1d1d..68c1c7a29 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
@@ -2094,4 +2094,272 @@ Bob->>Alice:Got it!
const messages = diagram.db.getMessages();
});
+ describe('when parsing extended participant syntax', () => {
+ it('should parse participants with different quote styles and whitespace', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ participant Bob@{ "type" : "database" }
+ participant Carl@{ type: "database" }
+ participant David@{ "type" : 'database' }
+ participant Eve@{ type: 'database' }
+ participant Favela@{ "type" : "database" }
+ Bob->>+Alice: Hi Alice
+ Alice->>+Bob: Hi Bob
+ `);
+
+ const actors = diagram.db.getActors();
+
+ expect(actors.get('Alice').type).toBe('database');
+ expect(actors.get('Alice').description).toBe('Alice');
+
+ expect(actors.get('Bob').type).toBe('database');
+ expect(actors.get('Bob').description).toBe('Bob');
+
+ expect(actors.get('Carl').type).toBe('database');
+ expect(actors.get('Carl').description).toBe('Carl');
+
+ expect(actors.get('David').type).toBe('database');
+ expect(actors.get('David').description).toBe('David');
+
+ expect(actors.get('Eve').type).toBe('database');
+ expect(actors.get('Eve').description).toBe('Eve');
+
+ expect(actors.get('Favela').type).toBe('database');
+ expect(actors.get('Favela').description).toBe('Favela');
+
+ // Verify messages were parsed correctly
+ const messages = diagram.db.getMessages();
+ expect(messages.length).toBe(4); // 2 messages + 2 activation messages
+ expect(messages[0].from).toBe('Bob');
+ expect(messages[0].to).toBe('Alice');
+ expect(messages[0].message).toBe('Hi Alice');
+ expect(messages[2].from).toBe('Alice'); // Second message (index 2 due to activation)
+ expect(messages[2].to).toBe('Bob');
+ expect(messages[2].message).toBe('Hi Bob');
+ });
+
+ it('should parse mixed participant types with extended syntax', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant lead
+ participant dsa@{ "type" : "queue" }
+ API->>+Database: getUserb
+ Database-->>-API: userb
+ dsa --> Database: hello
+`);
+
+ // Verify actors were created
+ const actors = diagram.db.getActors();
+
+ expect(actors.get('lead').type).toBe('participant');
+ expect(actors.get('lead').description).toBe('lead');
+
+ // Participant with extended syntax
+ expect(actors.get('dsa').type).toBe('queue');
+ expect(actors.get('dsa').description).toBe('dsa');
+
+ // Implicitly created actors (from messages)
+ expect(actors.get('API').type).toBe('participant');
+ expect(actors.get('API').description).toBe('API');
+
+ expect(actors.get('Database').type).toBe('participant');
+ expect(actors.get('Database').description).toBe('Database');
+
+ // Verify messages were parsed correctly
+ const messages = diagram.db.getMessages();
+ expect(messages.length).toBe(5); // 3 messages + 2 activation messages
+
+ // First message with activation
+ expect(messages[0].from).toBe('API');
+ expect(messages[0].to).toBe('Database');
+ expect(messages[0].message).toBe('getUserb');
+ expect(messages[0].activate).toBe(true);
+
+ // Second message with deactivation
+ expect(messages[2].from).toBe('Database');
+ expect(messages[2].to).toBe('API');
+ expect(messages[2].message).toBe('userb');
+
+ // Third message
+ expect(messages[4].from).toBe('dsa');
+ expect(messages[4].to).toBe('Database');
+ expect(messages[4].message).toBe('hello');
+ });
+
+ it('should fail for malformed JSON in participant definition', async () => {
+ const invalidDiagram = `
+ sequenceDiagram
+ participant D@{ "type: "entity" }
+ participant E@{ "type": "dat
+ abase }
+ `;
+
+ let error = false;
+ try {
+ await mermaidAPI.parse(invalidDiagram);
+ } catch (e) {
+ error = true;
+ }
+ expect(error).toBe(true);
+ });
+
+ it('should fail for missing colon separator', async () => {
+ const invalidDiagram = `
+ sequenceDiagram
+ participant C@{ "type" "control" }
+ C ->> C: action
+ `;
+
+ let error = false;
+ try {
+ await mermaidAPI.parse(invalidDiagram);
+ } catch (e) {
+ error = true;
+ }
+ expect(error).toBe(true);
+ });
+
+ it('should fail for missing closing brace', async () => {
+ const invalidDiagram = `
+ sequenceDiagram
+ participant E@{ "type": "entity"
+ E ->> E: process
+ `;
+
+ let error = false;
+ try {
+ await mermaidAPI.parse(invalidDiagram);
+ } catch (e) {
+ error = true;
+ }
+ expect(error).toBe(true);
+ });
+ });
+ describe('participant type parsing', () => {
+ it('should parse boundary participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant boundary@{ "type" : "boundary" }
+ boundary->boundary: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('boundary').type).toBe('boundary');
+ expect(actors.get('boundary').description).toBe('boundary');
+ });
+
+ it('should parse control participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant C@{ "type" : "control" }
+ C->C: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('C').type).toBe('control');
+ expect(actors.get('C').description).toBe('C');
+ });
+
+ it('should parse entity participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant E@{ "type" : "entity" }
+ E->E: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('E').type).toBe('entity');
+ expect(actors.get('E').description).toBe('E');
+ });
+
+ it('should parse database participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant D@{ "type" : "database" }
+ D->D: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('D').type).toBe('database');
+ expect(actors.get('D').description).toBe('D');
+ });
+
+ it('should parse collections participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant L@{ "type" : "collections" }
+ L->L: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('L').type).toBe('collections');
+ expect(actors.get('L').description).toBe('L');
+ });
+
+ it('should parse queue participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant Q@{ "type" : "queue" }
+ Q->Q: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('Q').type).toBe('queue');
+ expect(actors.get('Q').description).toBe('Q');
+ });
+ });
+
+ describe('participant type parsing', () => {
+ it('should parse actor participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant A@{ "type" : "queue" }
+ A->A: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('A').type).toBe('queue');
+ expect(actors.get('A').description).toBe('A');
+ });
+
+ it('should parse participant participant', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant P@{ "type" : "database" }
+ P->P: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('P').type).toBe('database');
+ expect(actors.get('P').description).toBe('P');
+ });
+
+ it('should parse boundary using actor keyword', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant Alice@{ "type" : "collections" }
+ participant Bob@{ "type" : "control" }
+ Alice->>Bob: Hello Bob, how are you?
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('Alice').type).toBe('collections');
+ expect(actors.get('Bob').type).toBe('control');
+ expect(actors.get('Bob').description).toBe('Bob');
+ });
+
+ it('should parse control using participant keyword', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant C@{ "type" : "control" }
+ C->C: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('C').type).toBe('control');
+ expect(actors.get('C').description).toBe('C');
+ });
+
+ it('should parse entity using actor keyword', async () => {
+ const diagram = await Diagram.fromText(`
+ sequenceDiagram
+ participant E@{ "type" : "entity" }
+ E->E: test
+ `);
+ const actors = diagram.db.getActors();
+ expect(actors.get('E').type).toBe('entity');
+ expect(actors.get('E').description).toBe('E');
+ });
+ });
});
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
index c344968a1..86d57757a 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
+++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts
@@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth.js';
import utils from '../../utils.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { Diagram } from '../../Diagram.js';
+import { PARTICIPANT_TYPE } from './sequenceDb.js';
let conf = {};
@@ -854,11 +855,19 @@ function adjustCreatedDestroyedData(
msgModel.startx = msgModel.startx - adjustment;
}
}
+ const actorArray = [
+ PARTICIPANT_TYPE.ACTOR,
+ PARTICIPANT_TYPE.CONTROL,
+ PARTICIPANT_TYPE.ENTITY,
+ PARTICIPANT_TYPE.DATABASE,
+ ];
// if it is a create message
if (createdActors.get(msg.to) == index) {
const actor = actors.get(msg.to);
- const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
+ const adjustment = actorArray.includes(actor.type)
+ ? ACTOR_TYPE_WIDTH / 2 + 3
+ : actor.width / 2 + 3;
receiverAdjustment(actor, adjustment);
actor.starty = lineStartY - actor.height / 2;
bounds.bumpVerticalPos(actor.height / 2);
@@ -867,7 +876,7 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.from) == index) {
const actor = actors.get(msg.from);
if (conf.mirrorActors) {
- const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
+ const adjustment = actorArray.includes(actor.type) ? ACTOR_TYPE_WIDTH / 2 : actor.width / 2;
senderAdjustment(actor, adjustment);
}
actor.stopy = lineStartY - actor.height / 2;
@@ -877,7 +886,9 @@ function adjustCreatedDestroyedData(
else if (destroyedActors.get(msg.to) == index) {
const actor = actors.get(msg.to);
if (conf.mirrorActors) {
- const adjustment = actor.type == 'actor' ? ACTOR_TYPE_WIDTH / 2 + 3 : actor.width / 2 + 3;
+ const adjustment = actorArray.includes(actor.type)
+ ? ACTOR_TYPE_WIDTH / 2 + 3
+ : actor.width / 2 + 3;
receiverAdjustment(actor, adjustment);
}
actor.stopy = lineStartY - actor.height / 2;
@@ -1226,10 +1237,11 @@ export const draw = async function (_text: string, id: string, _version: string,
for (const box of bounds.models.boxes) {
box.height = bounds.getVerticalPos() - box.y;
bounds.insert(box.x, box.y, box.x + box.width, box.height);
- box.startx = box.x;
- box.starty = box.y;
- box.stopx = box.startx + box.width;
- box.stopy = box.starty + box.height;
+ const boxPadding = conf.boxMargin * 2;
+ box.startx = box.x - boxPadding;
+ box.starty = box.y - boxPadding * 0.25;
+ box.stopx = box.startx + box.width + 2 * boxPadding;
+ box.stopy = box.starty + box.height + boxPadding * 0.75;
box.stroke = 'rgb(0,0,0, 0.5)';
svgDraw.drawBox(diagram, box, conf);
}
@@ -1494,6 +1506,9 @@ async function calculateActorMargins(
return (total += actors.get(aKey).width + (actors.get(aKey).margin || 0));
}, 0);
+ const standardBoxPadding = conf.boxMargin * 8;
+ totalWidth += standardBoxPadding;
+
totalWidth -= 2 * conf.boxTextMargin;
if (box.wrap) {
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);
diff --git a/packages/mermaid/src/diagrams/sequence/styles.js b/packages/mermaid/src/diagrams/sequence/styles.js
index 5c36b4ed1..3cee9d3dc 100644
--- a/packages/mermaid/src/diagrams/sequence/styles.js
+++ b/packages/mermaid/src/diagrams/sequence/styles.js
@@ -12,6 +12,11 @@ const getStyles = (options) =>
.actor-line {
stroke: ${options.actorLineColor};
}
+
+ .innerArc {
+ stroke-width: 1.5;
+ stroke-dasharray: none;
+ }
.messageLine0 {
stroke-width: 1.5;
@@ -115,6 +120,7 @@ const getStyles = (options) =>
fill: ${options.actorBkg};
stroke-width: 2px;
}
+
`;
export default getStyles;
diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js
index 9a7c1bf54..7db661930 100644
--- a/packages/mermaid/src/diagrams/sequence/svgDraw.js
+++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js
@@ -415,6 +415,600 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
return height;
};
+/**
+ * Draws an actor in the diagram with the attached line
+ *
+ * @param {any} elem - The diagram we'll draw to.
+ * @param {any} actor - The actor to draw.
+ * @param {any} conf - DrawText implementation discriminator object
+ * @param {boolean} isFooter - If the actor is the footer one
+ */
+const drawActorTypeCollections = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + actor.height;
+
+ const boxplusLineGroup = elem.append('g').lower();
+ var g = boxplusLineGroup;
+
+ if (!isFooter) {
+ actorCnt++;
+ if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
+ g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
+ }
+ g.append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ g = boxplusLineGroup.append('g');
+ actor.actorCnt = actorCnt;
+
+ if (actor.links != null) {
+ g.attr('id', 'root-' + actorCnt);
+ }
+ }
+
+ const rect = svgDrawCommon.getNoteRect();
+ var cssclass = 'actor';
+ if (actor.properties?.class) {
+ cssclass = actor.properties.class;
+ } else {
+ rect.fill = '#eaeaea';
+ }
+ if (isFooter) {
+ cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssclass += ` ${TOP_ACTOR_CLASS}`;
+ }
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = cssclass;
+ rect.name = actor.name;
+
+ // DRAW STACKED RECTANGLES
+ const offset = 6;
+ const shadowRect = {
+ ...rect,
+ x: rect.x + (isFooter ? -offset : -offset),
+ y: rect.y + (isFooter ? +offset : +offset),
+ class: 'actor',
+ };
+ const rectElem = drawRect(g, rect); // draw main rectangle on top
+ drawRect(g, shadowRect);
+ actor.rectData = rect;
+
+ if (actor.properties?.icon) {
+ const iconSrc = actor.properties.icon.trim();
+ if (iconSrc.charAt(0) === '@') {
+ svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
+ } else {
+ svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
+ }
+ }
+
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ g,
+ rect.x - offset,
+ rect.y + offset,
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_BOX_CLASS}` },
+ conf
+ );
+
+ let height = actor.height;
+ if (rectElem.node) {
+ const bounds = rectElem.node().getBBox();
+ actor.height = bounds.height;
+ height = bounds.height;
+ }
+
+ return height;
+};
+
+const drawActorTypeQueue = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + actor.height;
+
+ const boxplusLineGroup = elem.append('g').lower();
+ let g = boxplusLineGroup;
+
+ if (!isFooter) {
+ actorCnt++;
+ if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
+ g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
+ }
+ g.append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ g = boxplusLineGroup.append('g');
+ actor.actorCnt = actorCnt;
+
+ if (actor.links != null) {
+ g.attr('id', 'root-' + actorCnt);
+ }
+ }
+
+ const rect = svgDrawCommon.getNoteRect();
+ let cssclass = 'actor';
+ if (actor.properties?.class) {
+ cssclass = actor.properties.class;
+ } else {
+ rect.fill = '#eaeaea';
+ }
+
+ if (isFooter) {
+ cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssclass += ` ${TOP_ACTOR_CLASS}`;
+ }
+
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = cssclass;
+ rect.name = actor.name;
+
+ // Cylinder dimensions
+ const ry = rect.height / 2;
+ const rx = ry / (2.5 + rect.height / 50);
+
+ // Cylinder base group
+ const cylinderGroup = g.append('g');
+ const cylinderArc = g.append('g');
+
+ // Main cylinder body
+ cylinderGroup
+ .append('path')
+ .attr(
+ 'd',
+ `M ${rect.x},${rect.y + ry}
+ a ${rx},${ry} 0 0 0 0,${rect.height}
+ h ${rect.width - 2 * rx}
+ a ${rx},${ry} 0 0 0 0,-${rect.height}
+ Z
+ `
+ )
+ .attr('class', cssclass);
+ cylinderArc
+ .append('path')
+ .attr(
+ 'd',
+ `M ${rect.x},${rect.y + ry}
+ a ${rx},${ry} 0 0 0 0,${rect.height}`
+ )
+ .attr('stroke', '#666')
+ .attr('stroke-width', '1px')
+ .attr('class', cssclass);
+
+ cylinderGroup.attr('transform', `translate(${rx}, ${-(rect.height / 2)})`);
+ cylinderArc.attr('transform', `translate(${rect.width - rx}, ${-rect.height / 2})`);
+
+ actor.rectData = rect;
+
+ if (actor.properties?.icon) {
+ const iconSrc = actor.properties.icon.trim();
+ const iconX = rect.x + rect.width - 20;
+ const iconY = rect.y + 10;
+ if (iconSrc.charAt(0) === '@') {
+ svgDrawCommon.drawEmbeddedImage(g, iconX, iconY, iconSrc.substr(1));
+ } else {
+ svgDrawCommon.drawImage(g, iconX, iconY, iconSrc);
+ }
+ }
+
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ g,
+ rect.x,
+ rect.y,
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_BOX_CLASS}` },
+ conf
+ );
+
+ let height = actor.height;
+ const lastPath = cylinderGroup.select('path:last-child');
+ if (lastPath.node()) {
+ const bounds = lastPath.node().getBBox();
+ actor.height = bounds.height;
+ height = bounds.height;
+ }
+
+ return height;
+};
+
+const drawActorTypeControl = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + 75;
+
+ const line = elem.append('g').lower();
+
+ if (!isFooter) {
+ actorCnt++;
+ line
+ .append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ actor.actorCnt = actorCnt;
+ }
+ const actElem = elem.append('g');
+ let cssClass = ACTOR_MAN_FIGURE_CLASS;
+ if (isFooter) {
+ cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssClass += ` ${TOP_ACTOR_CLASS}`;
+ }
+ actElem.attr('class', cssClass);
+ actElem.attr('name', actor.name);
+
+ const rect = svgDrawCommon.getNoteRect();
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.fill = '#eaeaea';
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = 'actor';
+
+ const cx = actor.x + actor.width / 2;
+ const cy = actorY + 30;
+ const r = 18;
+
+ actElem
+ .append('defs')
+ .append('marker')
+ .attr('id', 'filled-head-control')
+ .attr('refX', 11)
+ .attr('refY', 5.8)
+ .attr('markerWidth', 20)
+ .attr('markerHeight', 28)
+ .attr('orient', '172.5')
+ .append('path')
+ .attr('d', 'M 14.4 5.6 L 7.2 10.4 L 8.8 5.6 L 7.2 0.8 Z');
+
+ // Draw the base circle
+ actElem
+ .append('circle')
+ .attr('cx', cx)
+ .attr('cy', cy)
+ .attr('r', r)
+ .attr('fill', '#eaeaf7')
+ .attr('stroke', '#666')
+ .attr('stroke-width', 1.2);
+
+ // Draw looping arrow as arc path
+ actElem
+ .append('line')
+ .attr('marker-end', 'url(#filled-head-control)')
+ .attr('transform', `translate(${cx}, ${cy - r})`);
+
+ const bounds = actElem.node().getBBox();
+ actor.height = bounds.height + 2 * (conf?.sequence?.labelBoxHeight ?? 0);
+
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ actElem,
+ rect.x,
+ rect.y + r + (isFooter ? 5 : 10),
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
+ conf
+ );
+
+ return actor.height;
+};
+
+const drawActorTypeEntity = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + 75;
+
+ const line = elem.append('g').lower();
+
+ const actElem = elem.append('g');
+ let cssClass = ACTOR_MAN_FIGURE_CLASS;
+ if (isFooter) {
+ cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssClass += ` ${TOP_ACTOR_CLASS}`;
+ }
+ actElem.attr('class', cssClass);
+ actElem.attr('name', actor.name);
+
+ const rect = svgDrawCommon.getNoteRect();
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.fill = '#eaeaea';
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = 'actor';
+
+ const cx = actor.x + actor.width / 2;
+ const cy = actorY + (!isFooter ? 25 : 10);
+ const r = 18;
+
+ actElem
+ .append('circle')
+ .attr('cx', cx)
+ .attr('cy', cy)
+ .attr('r', r)
+ .attr('width', actor.width)
+ .attr('height', actor.height);
+
+ actElem
+ .append('line')
+ .attr('x1', cx - r)
+ .attr('x2', cx + r)
+ .attr('y1', cy + r)
+ .attr('y2', cy + r)
+ .attr('stroke', '#333')
+ .attr('stroke-width', 2);
+
+ const bounds = actElem.node().getBBox();
+ actor.height = bounds.height + (conf?.sequence?.labelBoxHeight ?? 0);
+
+ if (!isFooter) {
+ actorCnt++;
+ line
+ .append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ actor.actorCnt = actorCnt;
+ }
+
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ actElem,
+ rect.x,
+ rect.y + (!isFooter ? (cy + r - actorY) / 2 : (cy - actorY + r - 5) / 2),
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
+ conf
+ );
+
+ if (!isFooter) {
+ actElem.attr('transform', `translate(${0}, ${r / 2})`);
+ } else {
+ actElem.attr('transform', `translate(${0}, ${r / 2})`);
+ }
+
+ return actor.height;
+};
+
+const drawActorTypeDatabase = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + actor.height + 2 * conf.boxTextMargin;
+
+ const boxplusLineGroup = elem.append('g').lower();
+ let g = boxplusLineGroup;
+
+ if (!isFooter) {
+ actorCnt++;
+ if (Object.keys(actor.links || {}).length && !conf.forceMenus) {
+ g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer');
+ }
+ g.append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ g = boxplusLineGroup.append('g');
+ actor.actorCnt = actorCnt;
+
+ if (actor.links != null) {
+ g.attr('id', 'root-' + actorCnt);
+ }
+ }
+
+ const rect = svgDrawCommon.getNoteRect();
+
+ let cssclass = 'actor';
+ if (actor.properties?.class) {
+ cssclass = actor.properties.class;
+ } else {
+ rect.fill = '#eaeaea';
+ }
+
+ if (isFooter) {
+ cssclass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssclass += ` ${TOP_ACTOR_CLASS}`;
+ }
+
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = cssclass;
+ rect.name = actor.name;
+
+ // Cylinder dimensions
+ rect.x = actor.x;
+ rect.y = actorY;
+ const w = rect.width / 4;
+ const h = rect.width / 4;
+ const rx = w / 2;
+ const ry = rx / (2.5 + w / 50);
+
+ // Cylinder base group
+ const cylinderGroup = g.append('g');
+
+ const d = `
+ M ${rect.x},${rect.y + ry}
+ a ${rx},${ry} 0 0 0 ${w},0
+ a ${rx},${ry} 0 0 0 -${w},0
+ l 0,${h - 2 * ry}
+ a ${rx},${ry} 0 0 0 ${w},0
+ l 0,-${h - 2 * ry}
+`;
+ // Draw the main cylinder body
+ cylinderGroup
+ .append('path')
+ .attr('d', d)
+ .attr('fill', '#eaeaea')
+ .attr('stroke', '#000')
+ .attr('stroke-width', 1)
+ .attr('class', cssclass);
+
+ if (!isFooter) {
+ cylinderGroup.attr('transform', `translate(${w * 1.5}, ${(rect.height + ry) / 4})`);
+ } else {
+ cylinderGroup.attr('transform', `translate(${w * 1.5}, ${rect.height / 4 - 2 * ry})`);
+ }
+ actor.rectData = rect;
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ g,
+ rect.x,
+ rect.y + (!isFooter ? (rect.height + ry) / 2 : (rect.height + h) / 4),
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_BOX_CLASS}` },
+ conf
+ );
+
+ const lastPath = cylinderGroup.select('path:last-child');
+ if (lastPath.node()) {
+ const bounds = lastPath.node().getBBox();
+ actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
+ }
+
+ return actor.height;
+};
+
+const drawActorTypeBoundary = function (elem, actor, conf, isFooter) {
+ const actorY = isFooter ? actor.stopy : actor.starty;
+ const center = actor.x + actor.width / 2;
+ const centerY = actorY + 80;
+ const radius = 30;
+ const line = elem.append('g').lower();
+
+ if (!isFooter) {
+ actorCnt++;
+ line
+ .append('line')
+ .attr('id', 'actor' + actorCnt)
+ .attr('x1', center)
+ .attr('y1', centerY)
+ .attr('x2', center)
+ .attr('y2', 2000)
+ .attr('class', 'actor-line 200')
+ .attr('stroke-width', '0.5px')
+ .attr('stroke', '#999')
+ .attr('name', actor.name);
+
+ actor.actorCnt = actorCnt;
+ }
+ const actElem = elem.append('g');
+ let cssClass = ACTOR_MAN_FIGURE_CLASS;
+ if (isFooter) {
+ cssClass += ` ${BOTTOM_ACTOR_CLASS}`;
+ } else {
+ cssClass += ` ${TOP_ACTOR_CLASS}`;
+ }
+ actElem.attr('class', cssClass);
+ actElem.attr('name', actor.name);
+
+ const rect = svgDrawCommon.getNoteRect();
+ rect.x = actor.x;
+ rect.y = actorY;
+ rect.fill = '#eaeaea';
+ rect.width = actor.width;
+ rect.height = actor.height;
+ rect.class = 'actor';
+
+ actElem
+ .append('line')
+ .attr('id', 'actor-man-torso' + actorCnt)
+ .attr('x1', actor.x + actor.width / 2 - radius * 2.5)
+ .attr('y1', actorY + 10)
+ .attr('x2', actor.x + actor.width / 2 - 15)
+ .attr('y2', actorY + 10);
+
+ actElem
+ .append('line')
+ .attr('id', 'actor-man-arms' + actorCnt)
+ .attr('x1', actor.x + actor.width / 2 - radius * 2.5)
+ .attr('y1', actorY + 0) // starting Y
+ .attr('x2', actor.x + actor.width / 2 - radius * 2.5)
+ .attr('y2', actorY + 20); // ending Y (26px long, adjust as needed)
+
+ actElem
+ .append('circle')
+ .attr('cx', actor.x + actor.width / 2)
+ .attr('cy', actorY + 10)
+ .attr('r', radius);
+
+ const bounds = actElem.node().getBBox();
+ actor.height = bounds.height + (conf.sequence.labelBoxHeight ?? 0);
+
+ _drawTextCandidateFunc(conf, hasKatex(actor.description))(
+ actor.description,
+ actElem,
+ rect.x,
+ rect.y + (!isFooter ? radius / 2 + 3 : radius / 2 - 4),
+ rect.width,
+ rect.height,
+ { class: `actor ${ACTOR_MAN_FIGURE_CLASS}` },
+ conf
+ );
+
+ if (!isFooter) {
+ actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
+ } else {
+ actElem.attr('transform', `translate(0,${radius / 2 + 7})`);
+ }
+
+ return actor.height;
+};
+
const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const actorY = isFooter ? actor.stopy : actor.starty;
const center = actor.x + actor.width / 2;
@@ -516,6 +1110,18 @@ export const drawActor = async function (elem, actor, conf, isFooter) {
return await drawActorTypeActor(elem, actor, conf, isFooter);
case 'participant':
return await drawActorTypeParticipant(elem, actor, conf, isFooter);
+ case 'boundary':
+ return await drawActorTypeBoundary(elem, actor, conf, isFooter);
+ case 'control':
+ return await drawActorTypeControl(elem, actor, conf, isFooter);
+ case 'entity':
+ return await drawActorTypeEntity(elem, actor, conf, isFooter);
+ case 'database':
+ return await drawActorTypeDatabase(elem, actor, conf, isFooter);
+ case 'collections':
+ return await drawActorTypeCollections(elem, actor, conf, isFooter);
+ case 'queue':
+ return await drawActorTypeQueue(elem, actor, conf, isFooter);
}
};
diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md
index c0e927dae..6d10c6f97 100644
--- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md
+++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md
@@ -46,6 +46,78 @@ sequenceDiagram
Bob->>Alice: Hi Alice
```
+### Boundary
+
+If you want to use the boundary symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "boundary" }
+ participant Bob
+ Alice->>Bob: Request from boundary
+ Bob->>Alice: Response to boundary
+```
+
+### Control
+
+If you want to use the control symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "control" }
+ participant Bob
+ Alice->>Bob: Control request
+ Bob->>Alice: Control response
+```
+
+### Entity
+
+If you want to use the entity symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "entity" }
+ participant Bob
+ Alice->>Bob: Entity request
+ Bob->>Alice: Entity response
+```
+
+### Database
+
+If you want to use the database symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "database" }
+ participant Bob
+ Alice->>Bob: DB query
+ Bob->>Alice: DB result
+```
+
+### Collections
+
+If you want to use the collections symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "collections" }
+ participant Bob
+ Alice->>Bob: Collections request
+ Bob->>Alice: Collections response
+```
+
+### Queue
+
+If you want to use the queue symbol for a participant, use the JSON configuration syntax as shown below.
+
+```mermaid-example
+sequenceDiagram
+ participant Alice@{ "type" : "queue" }
+ participant Bob
+ Alice->>Bob: Queue message
+ Bob->>Alice: Queue response
+```
+
### Aliases
The actor can have a convenient identifier and a descriptive label.
diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts
index d1394e71b..477fb17b1 100644
--- a/packages/mermaid/src/types.ts
+++ b/packages/mermaid/src/types.ts
@@ -13,6 +13,18 @@ export interface NodeMetaData {
ticket?: string;
}
+export interface ParticipantMetaData {
+ type?:
+ | 'actor'
+ | 'participant'
+ | 'boundary'
+ | 'control'
+ | 'entity'
+ | 'database'
+ | 'collections'
+ | 'queue';
+}
+
export interface EdgeMetaData {
animation?: 'fast' | 'slow';
animate?: boolean;