@polagram/core 0.0.4 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/dist/index.d.ts +102 -113
  2. package/dist/polagram-core.js +1524 -1392
  3. package/dist/polagram-core.umd.cjs +23 -22
  4. package/package.json +3 -2
  5. package/dist/src/api.d.ts +0 -84
  6. package/dist/src/api.js +0 -183
  7. package/dist/src/ast/ast.test.d.ts +0 -1
  8. package/dist/src/ast/ast.test.js +0 -146
  9. package/dist/src/ast/index.d.ts +0 -119
  10. package/dist/src/ast/index.js +0 -2
  11. package/dist/src/config/index.d.ts +0 -1
  12. package/dist/src/config/index.js +0 -1
  13. package/dist/src/config/schema.d.ts +0 -198
  14. package/dist/src/config/schema.js +0 -82
  15. package/dist/src/config/schema.test.d.ts +0 -1
  16. package/dist/src/config/schema.test.js +0 -94
  17. package/dist/src/generator/base/walker.d.ts +0 -19
  18. package/dist/src/generator/base/walker.js +0 -56
  19. package/dist/src/generator/base/walker.test.d.ts +0 -1
  20. package/dist/src/generator/base/walker.test.js +0 -49
  21. package/dist/src/generator/generators/mermaid.d.ts +0 -24
  22. package/dist/src/generator/generators/mermaid.js +0 -140
  23. package/dist/src/generator/generators/mermaid.test.d.ts +0 -1
  24. package/dist/src/generator/generators/mermaid.test.js +0 -70
  25. package/dist/src/generator/generators/plantuml.d.ts +0 -17
  26. package/dist/src/generator/generators/plantuml.js +0 -131
  27. package/dist/src/generator/generators/plantuml.test.d.ts +0 -1
  28. package/dist/src/generator/generators/plantuml.test.js +0 -143
  29. package/dist/src/generator/interface.d.ts +0 -17
  30. package/dist/src/generator/interface.js +0 -1
  31. package/dist/src/index.d.ts +0 -13
  32. package/dist/src/index.js +0 -21
  33. package/dist/src/parser/base/lexer.d.ts +0 -18
  34. package/dist/src/parser/base/lexer.js +0 -95
  35. package/dist/src/parser/base/lexer.test.d.ts +0 -1
  36. package/dist/src/parser/base/lexer.test.js +0 -53
  37. package/dist/src/parser/base/parser.d.ts +0 -14
  38. package/dist/src/parser/base/parser.js +0 -43
  39. package/dist/src/parser/base/parser.test.d.ts +0 -1
  40. package/dist/src/parser/base/parser.test.js +0 -90
  41. package/dist/src/parser/base/token.d.ts +0 -18
  42. package/dist/src/parser/base/token.js +0 -1
  43. package/dist/src/parser/base/tokens.d.ts +0 -8
  44. package/dist/src/parser/base/tokens.js +0 -1
  45. package/dist/src/parser/format-detector.d.ts +0 -55
  46. package/dist/src/parser/format-detector.js +0 -98
  47. package/dist/src/parser/index.d.ts +0 -11
  48. package/dist/src/parser/index.js +0 -33
  49. package/dist/src/parser/index.test.d.ts +0 -1
  50. package/dist/src/parser/index.test.js +0 -23
  51. package/dist/src/parser/interface.d.ts +0 -8
  52. package/dist/src/parser/interface.js +0 -1
  53. package/dist/src/parser/languages/mermaid/constants.d.ts +0 -7
  54. package/dist/src/parser/languages/mermaid/constants.js +0 -20
  55. package/dist/src/parser/languages/mermaid/index.d.ts +0 -4
  56. package/dist/src/parser/languages/mermaid/index.js +0 -11
  57. package/dist/src/parser/languages/mermaid/lexer.d.ts +0 -14
  58. package/dist/src/parser/languages/mermaid/lexer.js +0 -152
  59. package/dist/src/parser/languages/mermaid/lexer.test.d.ts +0 -1
  60. package/dist/src/parser/languages/mermaid/lexer.test.js +0 -58
  61. package/dist/src/parser/languages/mermaid/parser.d.ts +0 -22
  62. package/dist/src/parser/languages/mermaid/parser.js +0 -340
  63. package/dist/src/parser/languages/mermaid/parser.test.d.ts +0 -1
  64. package/dist/src/parser/languages/mermaid/parser.test.js +0 -252
  65. package/dist/src/parser/languages/mermaid/tokens.d.ts +0 -9
  66. package/dist/src/parser/languages/mermaid/tokens.js +0 -1
  67. package/dist/src/parser/languages/plantuml/index.d.ts +0 -4
  68. package/dist/src/parser/languages/plantuml/index.js +0 -11
  69. package/dist/src/parser/languages/plantuml/lexer.d.ts +0 -15
  70. package/dist/src/parser/languages/plantuml/lexer.js +0 -143
  71. package/dist/src/parser/languages/plantuml/parser.d.ts +0 -23
  72. package/dist/src/parser/languages/plantuml/parser.js +0 -481
  73. package/dist/src/parser/languages/plantuml/parser.test.d.ts +0 -1
  74. package/dist/src/parser/languages/plantuml/parser.test.js +0 -236
  75. package/dist/src/parser/languages/plantuml/tokens.d.ts +0 -9
  76. package/dist/src/parser/languages/plantuml/tokens.js +0 -1
  77. package/dist/src/transformer/cleaners/prune-empty.d.ts +0 -9
  78. package/dist/src/transformer/cleaners/prune-empty.js +0 -27
  79. package/dist/src/transformer/cleaners/prune-empty.test.d.ts +0 -1
  80. package/dist/src/transformer/cleaners/prune-empty.test.js +0 -69
  81. package/dist/src/transformer/cleaners/prune-unused.d.ts +0 -5
  82. package/dist/src/transformer/cleaners/prune-unused.js +0 -48
  83. package/dist/src/transformer/cleaners/prune-unused.test.d.ts +0 -1
  84. package/dist/src/transformer/cleaners/prune-unused.test.js +0 -71
  85. package/dist/src/transformer/filters/focus.d.ts +0 -13
  86. package/dist/src/transformer/filters/focus.js +0 -71
  87. package/dist/src/transformer/filters/focus.test.d.ts +0 -1
  88. package/dist/src/transformer/filters/focus.test.js +0 -50
  89. package/dist/src/transformer/filters/remove.d.ts +0 -12
  90. package/dist/src/transformer/filters/remove.js +0 -82
  91. package/dist/src/transformer/filters/remove.test.d.ts +0 -1
  92. package/dist/src/transformer/filters/remove.test.js +0 -38
  93. package/dist/src/transformer/filters/resolve.d.ts +0 -9
  94. package/dist/src/transformer/filters/resolve.js +0 -32
  95. package/dist/src/transformer/filters/resolve.test.d.ts +0 -1
  96. package/dist/src/transformer/filters/resolve.test.js +0 -48
  97. package/dist/src/transformer/index.d.ts +0 -10
  98. package/dist/src/transformer/index.js +0 -10
  99. package/dist/src/transformer/lens.d.ts +0 -12
  100. package/dist/src/transformer/lens.js +0 -58
  101. package/dist/src/transformer/lens.test.d.ts +0 -1
  102. package/dist/src/transformer/lens.test.js +0 -60
  103. package/dist/src/transformer/orchestration/engine.d.ts +0 -5
  104. package/dist/src/transformer/orchestration/engine.js +0 -24
  105. package/dist/src/transformer/orchestration/engine.test.d.ts +0 -1
  106. package/dist/src/transformer/orchestration/engine.test.js +0 -52
  107. package/dist/src/transformer/orchestration/registry.d.ts +0 -10
  108. package/dist/src/transformer/orchestration/registry.js +0 -27
  109. package/dist/src/transformer/selector/matcher.d.ts +0 -9
  110. package/dist/src/transformer/selector/matcher.js +0 -62
  111. package/dist/src/transformer/selector/matcher.test.d.ts +0 -1
  112. package/dist/src/transformer/selector/matcher.test.js +0 -70
  113. package/dist/src/transformer/traverse/walker.d.ts +0 -14
  114. package/dist/src/transformer/traverse/walker.js +0 -67
  115. package/dist/src/transformer/traverse/walker.test.d.ts +0 -1
  116. package/dist/src/transformer/traverse/walker.test.js +0 -111
  117. package/dist/src/transformer/types.d.ts +0 -47
  118. package/dist/src/transformer/types.js +0 -1
  119. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,236 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { Lexer } from './lexer';
3
- import { Parser } from './parser';
4
- describe('PlantUML Parser', () => {
5
- const parse = (input) => {
6
- const lexer = new Lexer(input);
7
- const parser = new Parser(lexer);
8
- return parser.parse();
9
- };
10
- describe('Basic Document Structure', () => {
11
- it('should parse @startuml/@enduml block', () => {
12
- const input = `
13
- @startuml
14
- @enduml
15
- `;
16
- const ast = parse(input);
17
- expect(ast.kind).toBe('root');
18
- expect(ast.meta.source).toBe('plantuml');
19
- });
20
- it('should parse title', () => {
21
- const input = `
22
- @startuml
23
- title My Diagram
24
- @enduml
25
- `;
26
- const ast = parse(input);
27
- expect(ast.meta.title).toBe('My Diagram');
28
- });
29
- });
30
- describe('Participants & Types', () => {
31
- it('should parse various participant types', () => {
32
- const input = `
33
- @startuml
34
- actor User
35
- database DB
36
- participant "Service Wrapper" as Svc
37
- @enduml
38
- `;
39
- const ast = parse(input);
40
- expect(ast.participants).toHaveLength(3);
41
- expect(ast.participants[0]).toMatchObject({ id: 'User', name: 'User', type: 'actor' });
42
- expect(ast.participants[1]).toMatchObject({ id: 'DB', name: 'DB', type: 'database' });
43
- expect(ast.participants[2]).toMatchObject({ id: 'Svc', name: 'Service Wrapper', type: 'participant' });
44
- });
45
- });
46
- describe('Messages & Arrows', () => {
47
- it('should parse sync and reply messages', () => {
48
- const input = `
49
- @startuml
50
- A -> B : Sync Call
51
- B --> A : Reply
52
- A -> A : Internal
53
- @enduml
54
- `;
55
- const ast = parse(input);
56
- expect(ast.events).toHaveLength(3);
57
- expect(ast.events[0]).toMatchObject({
58
- kind: 'message',
59
- from: 'A',
60
- to: 'B',
61
- text: 'Sync Call',
62
- type: 'sync',
63
- style: { line: 'solid', head: 'arrow' }
64
- });
65
- expect(ast.events[1]).toMatchObject({
66
- kind: 'message',
67
- from: 'B',
68
- to: 'A',
69
- text: 'Reply',
70
- type: 'reply',
71
- style: { line: 'dotted', head: 'arrow' } // PlantUML --> is dotted arrow
72
- });
73
- expect(ast.events[2]).toMatchObject({
74
- kind: 'message',
75
- from: 'A',
76
- to: 'A',
77
- text: 'Internal'
78
- });
79
- });
80
- });
81
- describe('Lifecycle', () => {
82
- it('should parse activate and deactivate', () => {
83
- const input = `
84
- @startuml
85
- activate A
86
- A -> B
87
- deactivate A
88
- @enduml
89
- `;
90
- const ast = parse(input);
91
- expect(ast.events).toHaveLength(3);
92
- expect(ast.events[0]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'activate' });
93
- expect(ast.events[1]).toMatchObject({ kind: 'message', from: 'A', to: 'B' });
94
- expect(ast.events[2]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'deactivate' });
95
- });
96
- });
97
- describe('Notes', () => {
98
- it('should parse notes with position', () => {
99
- const input = `
100
- @startuml
101
- note left of A : Note Left
102
- note right of A : Note Right
103
- note over A : Note Over
104
- note over A, B : Shared Note
105
- @enduml
106
- `;
107
- const ast = parse(input);
108
- expect(ast.events).toHaveLength(4);
109
- expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'left', participantIds: ['A'], text: 'Note Left' });
110
- expect(ast.events[1]).toMatchObject({ kind: 'note', position: 'right', participantIds: ['A'], text: 'Note Right' });
111
- expect(ast.events[2]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A'], text: 'Note Over' });
112
- expect(ast.events[3]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A', 'B'], text: 'Shared Note' });
113
- });
114
- });
115
- describe('Fragments', () => {
116
- it('should parse alt/else/end', () => {
117
- const input = `
118
- @startuml
119
- alt success
120
- A -> B : OK
121
- else failure
122
- A -> B : Fail
123
- end
124
- @enduml
125
- `;
126
- const ast = parse(input);
127
- expect(ast.events).toHaveLength(1);
128
- const fragment = ast.events[0];
129
- expect(fragment.kind).toBe('fragment');
130
- expect(fragment.operator).toBe('alt');
131
- expect(fragment.branches).toHaveLength(2);
132
- expect(fragment.branches[0].condition).toBe('success');
133
- expect(fragment.branches[0].events).toHaveLength(1);
134
- expect(fragment.branches[1].condition).toBe('failure');
135
- expect(fragment.branches[1].events).toHaveLength(1);
136
- });
137
- it('should parse opt/loop', () => {
138
- const input = `
139
- @startuml
140
- opt maybe
141
- A -> B : Maybe
142
- end
143
- loop forever
144
- A -> B : Repeat
145
- end
146
- @enduml
147
- `;
148
- const ast = parse(input);
149
- expect(ast.events).toHaveLength(2);
150
- expect(ast.events[0].kind).toBe('fragment');
151
- expect(ast.events[0].operator).toBe('opt');
152
- expect(ast.events[1].kind).toBe('fragment');
153
- expect(ast.events[1].operator).toBe('loop');
154
- });
155
- });
156
- describe('Grouping', () => {
157
- it('should parse box with participants', () => {
158
- const input = `
159
- @startuml
160
- box "Internal Service" #LightBlue
161
- participant A
162
- participant B
163
- end box
164
- participant C
165
- @enduml
166
- `;
167
- const ast = parse(input);
168
- expect(ast.groups).toHaveLength(1);
169
- const box = ast.groups[0];
170
- expect(box.kind).toBe('group');
171
- expect(box.name).toBe('Internal Service');
172
- expect(box.style?.backgroundColor).toBe('#LightBlue');
173
- expect(box.participantIds).toEqual(['A', 'B']);
174
- expect(ast.participants).toHaveLength(3);
175
- });
176
- });
177
- describe('Edge Cases', () => {
178
- it('should ignore single line comments', () => {
179
- const input = `
180
- @startuml
181
- ' This is a comment
182
- A -> B : Hello ' Another comment? No, this is part of string usually?
183
- ' Comment line
184
- @enduml
185
- `;
186
- const ast = parse(input);
187
- expect(ast.events).toHaveLength(1);
188
- expect(ast.events[0]).toMatchObject({ text: "Hello ' Another comment? No, this is part of string usually?" });
189
- });
190
- it('should parse nested fragments', () => {
191
- const input = `
192
- @startuml
193
- alt outer
194
- A -> B : Outer
195
- loop inner
196
- A -> B : Inner
197
- end
198
- end
199
- @enduml
200
- `;
201
- const ast = parse(input);
202
- expect(ast.events).toHaveLength(1);
203
- const outer = ast.events[0];
204
- expect(outer.operator).toBe('alt');
205
- const outerEvents = outer.branches[0].events;
206
- expect(outerEvents).toHaveLength(2);
207
- expect(outerEvents[0].kind).toBe('message');
208
- const inner = outerEvents[1];
209
- expect(inner.kind).toBe('fragment');
210
- expect(inner.operator).toBe('loop');
211
- expect(inner.branches[0].events).toHaveLength(1);
212
- });
213
- it('should parse multi-line notes', () => {
214
- const input = `
215
- @startuml
216
- note left of A
217
- Line 1
218
- Line 2
219
- end note
220
- @enduml
221
- `;
222
- const ast = parse(input);
223
- expect(ast.events).toHaveLength(1);
224
- expect(ast.events[0]).toMatchObject({
225
- kind: 'note',
226
- position: 'left',
227
- participantIds: ['A'],
228
- // text should contain lines. trim() might remove leading/trailing.
229
- // We probably want to preserve internal newlines.
230
- });
231
- const text = ast.events[0].text;
232
- expect(text).toContain('Line 1');
233
- expect(text).toContain('Line 2');
234
- });
235
- });
236
- });
@@ -1,9 +0,0 @@
1
- export type TokenType = 'START_UML' | 'END_UML' | 'NEWLINE' | 'EOF' | 'UNKNOWN' | 'IDENTIFIER' | 'STRING' | 'TITLE' | 'PARTICIPANT' | 'ACTOR' | 'DATABASE' | 'ACTIVATE' | 'DEACTIVATE' | 'NOTE' | 'OF' | 'LEFT' | 'RIGHT' | 'OVER' | 'ALT' | 'OPT' | 'LOOP' | 'ELSE' | 'END' | 'BOX' | 'AS' | 'ARROW' | 'COLON' | 'COMMA';
2
- export interface Token {
3
- type: TokenType;
4
- literal: string;
5
- line: number;
6
- column: number;
7
- start: number;
8
- end: number;
9
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,9 +0,0 @@
1
- import { EventNode, FragmentNode } from '../../ast';
2
- import { Walker } from '../traverse/walker';
3
- /**
4
- * Cleaner that removes empty fragments and branches from the AST.
5
- * This runs after filters to ensure the AST structure remains valid/clean.
6
- */
7
- export declare class StructureCleaner extends Walker {
8
- protected visitFragment(node: FragmentNode): EventNode[];
9
- }
@@ -1,27 +0,0 @@
1
- import { Walker } from '../traverse/walker';
2
- /**
3
- * Cleaner that removes empty fragments and branches from the AST.
4
- * This runs after filters to ensure the AST structure remains valid/clean.
5
- */
6
- export class StructureCleaner extends Walker {
7
- visitFragment(node) {
8
- // 1. Let super map the branches (recurse)
9
- const result = super.visitFragment(node);
10
- // Should contain exactly one node (the fragment with mapped branches)
11
- if (result.length === 0)
12
- return [];
13
- const fragment = result[0];
14
- // 2. Filter out empty branches
15
- // A branch is "empty" if it has 0 events.
16
- const validBranches = fragment.branches.filter(b => b.events.length > 0);
17
- // 3. Evaluation
18
- if (validBranches.length === 0) {
19
- return []; // Remove fragment entirely
20
- }
21
- // Return updated fragment
22
- return [{
23
- ...fragment,
24
- branches: validBranches
25
- }];
26
- }
27
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,69 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { StructureCleaner } from './prune-empty';
3
- describe('StructureCleaner', () => {
4
- const createAst = (events) => ({
5
- kind: 'root',
6
- meta: { version: '1', source: 'unknown' },
7
- participants: [],
8
- groups: [],
9
- events
10
- });
11
- const msg = {
12
- kind: 'message',
13
- id: 'm1',
14
- text: 'M',
15
- from: 'A',
16
- to: 'B',
17
- type: 'sync',
18
- style: { line: 'solid', head: 'arrow' }
19
- };
20
- it('removes fragment if it has no branches', () => {
21
- const fragment = {
22
- kind: 'fragment', id: 'f1', operator: 'alt',
23
- branches: [] // Empty
24
- };
25
- const root = createAst([fragment, msg]);
26
- const result = new StructureCleaner().transform(root);
27
- expect(result.events).toHaveLength(1);
28
- expect(result.events[0].id).toBe('m1');
29
- });
30
- it('removes branch if it has no events', () => {
31
- const fragment = {
32
- kind: 'fragment', id: 'f1', operator: 'alt',
33
- branches: [
34
- { id: 'b1', condition: 'C1', events: [] }, // Empty
35
- { id: 'b2', condition: 'C2', events: [msg] } // Should keep
36
- ]
37
- };
38
- const root = createAst([fragment]);
39
- const result = new StructureCleaner().transform(root);
40
- const resFrag = result.events[0];
41
- expect(resFrag.branches).toHaveLength(1);
42
- expect(resFrag.branches[0].id).toBe('b2');
43
- });
44
- it('removes fragment if all branches are empty', () => {
45
- const fragment = {
46
- kind: 'fragment', id: 'f1', operator: 'alt',
47
- branches: [
48
- { id: 'b1', condition: 'C1', events: [] }
49
- ]
50
- };
51
- const root = createAst([fragment]);
52
- const result = new StructureCleaner().transform(root);
53
- expect(result.events).toHaveLength(0);
54
- });
55
- it('recursively cleans nested fragments', () => {
56
- const innerFrag = {
57
- kind: 'fragment', id: 'inner', operator: 'loop',
58
- branches: [{ id: 'ib', condition: 'loop', events: [] }]
59
- };
60
- const outerFrag = {
61
- kind: 'fragment', id: 'outer', operator: 'opt',
62
- branches: [{ id: 'ob', condition: 'opt', events: [innerFrag] }]
63
- };
64
- const root = createAst([outerFrag, msg]);
65
- const result = new StructureCleaner().transform(root);
66
- expect(result.events).toHaveLength(1);
67
- expect(result.events[0].id).toBe('m1');
68
- });
69
- });
@@ -1,5 +0,0 @@
1
- import { PolagramRoot } from '../../ast';
2
- export declare class UnusedCleaner {
3
- transform(root: PolagramRoot): PolagramRoot;
4
- private collectUsedParticipants;
5
- }
@@ -1,48 +0,0 @@
1
- export class UnusedCleaner {
2
- transform(root) {
3
- const usedIds = this.collectUsedParticipants(root.events);
4
- // Filter participants
5
- const activeParticipants = root.participants.filter(p => usedIds.has(p.id));
6
- // Filter groups (remove if empty, clean up member lists)
7
- const activeGroups = root.groups.map(g => ({
8
- ...g,
9
- participantIds: g.participantIds.filter(pid => usedIds.has(pid))
10
- })).filter(g => g.participantIds.length > 0);
11
- return {
12
- ...root,
13
- participants: activeParticipants,
14
- groups: activeGroups
15
- };
16
- }
17
- collectUsedParticipants(events) {
18
- const used = new Set();
19
- function scan(nodes) {
20
- for (const node of nodes) {
21
- switch (node.kind) {
22
- case 'message':
23
- if (node.from)
24
- used.add(node.from);
25
- if (node.to)
26
- used.add(node.to);
27
- break;
28
- case 'fragment':
29
- for (const branch of node.branches) {
30
- scan(branch.events);
31
- }
32
- break;
33
- case 'activation':
34
- used.add(node.participantId);
35
- break;
36
- case 'note':
37
- node.participantIds.forEach(id => used.add(id));
38
- break;
39
- case 'ref':
40
- node.participantIds.forEach(id => used.add(id));
41
- break;
42
- }
43
- }
44
- }
45
- scan(events);
46
- return used;
47
- }
48
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,71 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { UnusedCleaner } from './prune-unused';
3
- describe('UnusedCleaner', () => {
4
- const createAst = (participants, events) => ({
5
- kind: 'root',
6
- meta: { version: '1', source: 'unknown' },
7
- participants,
8
- groups: [],
9
- events
10
- });
11
- it('removes participants that are not referenced in messages', () => {
12
- const p1 = { id: 'A', name: 'Alice', type: 'participant' };
13
- const p2 = { id: 'B', name: 'Bob', type: 'participant' };
14
- const p3 = { id: 'C', name: 'Charlie', type: 'participant' }; // Unused
15
- const msg = {
16
- kind: 'message', id: 'm1', text: 'Hi', from: 'A', to: 'B',
17
- type: 'sync', style: { line: 'solid', head: 'arrow' }
18
- };
19
- const root = createAst([p1, p2, p3], [msg]);
20
- const result = new UnusedCleaner().transform(root);
21
- expect(result.participants).toHaveLength(2);
22
- expect(result.participants.map(p => p.id)).toEqual(['A', 'B']);
23
- });
24
- it('keeps participants referenced in activations', () => {
25
- const p1 = { id: 'A', name: 'Alice', type: 'participant' };
26
- const act = { kind: 'activation', participantId: 'A', action: 'activate' };
27
- const root = createAst([p1], [act]);
28
- const result = new UnusedCleaner().transform(root);
29
- expect(result.participants).toHaveLength(1);
30
- });
31
- it('keeps participants referenced in notes', () => {
32
- const p1 = { id: 'A', name: 'Alice', type: 'participant' };
33
- const note = { kind: 'note', id: 'n1', text: 'Note', position: 'over', participantIds: ['A'] };
34
- const root = createAst([p1], [note]);
35
- const result = new UnusedCleaner().transform(root);
36
- expect(result.participants).toHaveLength(1);
37
- });
38
- it('cleans up groups removing unused members', () => {
39
- const p1 = { id: 'A', name: 'Alice', type: 'participant' };
40
- const p2 = { id: 'B', name: 'Bob', type: 'participant' }; // Unused
41
- const group = { kind: 'group', id: 'g1', name: 'G', participantIds: ['A', 'B'] };
42
- const msg = {
43
- kind: 'message', id: 'm1', text: 'Hi', from: 'A', to: 'A',
44
- type: 'sync', style: { line: 'solid', head: 'arrow' }
45
- };
46
- const root = {
47
- kind: 'root',
48
- meta: { version: '1', source: 'unknown' },
49
- participants: [p1, p2],
50
- groups: [group],
51
- events: [msg]
52
- };
53
- const result = new UnusedCleaner().transform(root);
54
- expect(result.participants.map(p => p.id)).toEqual(['A']);
55
- expect(result.groups).toHaveLength(1);
56
- expect(result.groups[0].participantIds).toEqual(['A']);
57
- });
58
- it('removes group entirely if becomes empty', () => {
59
- const p2 = { id: 'B', name: 'Bob', type: 'participant' }; // Unused
60
- const group = { kind: 'group', id: 'g1', name: 'G', participantIds: ['B'] };
61
- const root = {
62
- kind: 'root',
63
- meta: { version: '1', source: 'unknown' },
64
- participants: [p2],
65
- groups: [group],
66
- events: []
67
- };
68
- const result = new UnusedCleaner().transform(root);
69
- expect(result.groups).toHaveLength(0);
70
- });
71
- });
@@ -1,13 +0,0 @@
1
- import { EventNode, PolagramRoot } from '../../ast';
2
- import { Walker } from '../traverse/walker';
3
- import { FocusLayer } from '../types';
4
- export declare class FocusFilter extends Walker {
5
- private layer;
6
- private matcher;
7
- private targetParticipantIds;
8
- constructor(layer: FocusLayer);
9
- transform(root: PolagramRoot): PolagramRoot;
10
- private resolveTargetParticipants;
11
- protected visitEvent(node: EventNode): EventNode[];
12
- private isRelatedToParticipant;
13
- }
@@ -1,71 +0,0 @@
1
- import { Matcher } from '../selector/matcher';
2
- import { Walker } from '../traverse/walker';
3
- export class FocusFilter extends Walker {
4
- constructor(layer) {
5
- super();
6
- Object.defineProperty(this, "layer", {
7
- enumerable: true,
8
- configurable: true,
9
- writable: true,
10
- value: layer
11
- });
12
- Object.defineProperty(this, "matcher", {
13
- enumerable: true,
14
- configurable: true,
15
- writable: true,
16
- value: new Matcher()
17
- });
18
- Object.defineProperty(this, "targetParticipantIds", {
19
- enumerable: true,
20
- configurable: true,
21
- writable: true,
22
- value: new Set()
23
- });
24
- }
25
- transform(root) {
26
- this.resolveTargetParticipants(root);
27
- return super.transform(root);
28
- }
29
- resolveTargetParticipants(root) {
30
- this.targetParticipantIds.clear();
31
- const selector = this.layer.selector;
32
- root.participants.forEach(p => {
33
- if (this.matcher.matchParticipant(p, selector)) {
34
- this.targetParticipantIds.add(p.id);
35
- }
36
- });
37
- }
38
- visitEvent(node) {
39
- // Only apply filtering if we have targets.
40
- // If targets is empty, strictly speaking we should hide everything?
41
- // Yes, "Focus on X" means "Show ONLY X". If X is not found, show nothing.
42
- if (node.kind === 'message') {
43
- const msg = node;
44
- if (!this.isRelatedToParticipant(msg)) {
45
- return []; // Drop irrelevant message
46
- }
47
- }
48
- if (node.kind === 'note') {
49
- const note = node;
50
- const isRelated = note.participantIds.some(pid => this.targetParticipantIds.has(pid));
51
- if (!isRelated)
52
- return [];
53
- }
54
- if (node.kind === 'activation') {
55
- const activation = node;
56
- if (!this.targetParticipantIds.has(activation.participantId)) {
57
- return [];
58
- }
59
- }
60
- return super.visitEvent(node);
61
- }
62
- // Helper to check if message involves the participant from selector
63
- isRelatedToParticipant(msg) {
64
- // Check if from or to matches any targeted participant ID
65
- if (msg.from && this.targetParticipantIds.has(msg.from))
66
- return true;
67
- if (msg.to && this.targetParticipantIds.has(msg.to))
68
- return true;
69
- return false;
70
- }
71
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,50 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { FocusFilter } from './focus';
3
- describe('FocusFilter', () => {
4
- const createAst = (events) => ({
5
- kind: 'root',
6
- meta: { version: '1', source: 'unknown' },
7
- participants: [
8
- { id: 'A', name: 'A', type: 'participant' },
9
- { id: 'B', name: 'B', type: 'participant' },
10
- { id: 'C', name: 'C', type: 'participant' },
11
- { id: 'D', name: 'D', type: 'participant' }
12
- ],
13
- groups: [],
14
- events
15
- });
16
- const msgAB = { kind: 'message', id: 'm1', text: 'A->B', from: 'A', to: 'B', type: 'sync', style: { line: 'solid', head: 'arrow' } };
17
- const msgBC = { kind: 'message', id: 'm2', text: 'B->C', from: 'B', to: 'C', type: 'sync', style: { line: 'solid', head: 'arrow' } };
18
- const msgCD = { kind: 'message', id: 'm3', text: 'C->D', from: 'C', to: 'D', type: 'sync', style: { line: 'solid', head: 'arrow' } };
19
- it('removes messages not related to focused participant', () => {
20
- const root = createAst([msgAB, msgBC, msgCD]);
21
- const layer = {
22
- action: 'focus',
23
- selector: { kind: 'participant', name: 'A' }
24
- };
25
- // Expect: msgAB (involves A), others removed
26
- const result = new FocusFilter(layer).transform(root);
27
- expect(result.events).toHaveLength(1);
28
- expect(result.events[0].id).toBe('m1');
29
- });
30
- it('removes messages even inside fragments, but keeps structure', () => {
31
- const fragment = {
32
- kind: 'fragment', id: 'f1', operator: 'alt',
33
- branches: [
34
- { id: 'b1', condition: 'related', events: [msgAB] },
35
- { id: 'b2', condition: 'irrelevant', events: [msgCD] }
36
- ]
37
- };
38
- const root = createAst([fragment]);
39
- const layer = {
40
- action: 'focus',
41
- selector: { kind: 'participant', name: 'A' }
42
- };
43
- const result = new FocusFilter(layer).transform(root);
44
- const resFrag = result.events[0];
45
- // b1 should keep event
46
- expect(resFrag.branches[0].events).toHaveLength(1);
47
- // b2 should have event removed, BUT branch itself remains (empty)
48
- expect(resFrag.branches[1].events).toHaveLength(0);
49
- });
50
- });
@@ -1,12 +0,0 @@
1
- import { EventNode, PolagramRoot } from '../../ast';
2
- import { Walker } from '../traverse/walker';
3
- import { RemoveLayer } from '../types';
4
- export declare class RemoveFilter extends Walker {
5
- private layer;
6
- private matcher;
7
- private removedParticipantIds;
8
- constructor(layer: RemoveLayer);
9
- transform(root: PolagramRoot): PolagramRoot;
10
- protected visitEvent(node: EventNode): EventNode[];
11
- private isRelatedToRemovedParticipant;
12
- }