@polagram/core 0.0.3 → 0.1.0

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 (99) hide show
  1. package/dist/index.d.ts +172 -85
  2. package/dist/polagram-core.js +2721 -2061
  3. package/dist/polagram-core.umd.cjs +20 -14
  4. package/package.json +11 -9
  5. package/dist/src/api.d.ts +0 -75
  6. package/dist/src/api.js +0 -160
  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 -182
  14. package/dist/src/config/schema.js +0 -78
  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/interface.d.ts +0 -17
  26. package/dist/src/generator/interface.js +0 -1
  27. package/dist/src/index.d.ts +0 -9
  28. package/dist/src/index.js +0 -17
  29. package/dist/src/parser/base/lexer.d.ts +0 -18
  30. package/dist/src/parser/base/lexer.js +0 -95
  31. package/dist/src/parser/base/lexer.test.d.ts +0 -1
  32. package/dist/src/parser/base/lexer.test.js +0 -53
  33. package/dist/src/parser/base/parser.d.ts +0 -14
  34. package/dist/src/parser/base/parser.js +0 -43
  35. package/dist/src/parser/base/parser.test.d.ts +0 -1
  36. package/dist/src/parser/base/parser.test.js +0 -90
  37. package/dist/src/parser/index.d.ts +0 -10
  38. package/dist/src/parser/index.js +0 -29
  39. package/dist/src/parser/index.test.d.ts +0 -1
  40. package/dist/src/parser/index.test.js +0 -23
  41. package/dist/src/parser/interface.d.ts +0 -8
  42. package/dist/src/parser/interface.js +0 -1
  43. package/dist/src/parser/languages/mermaid/constants.d.ts +0 -7
  44. package/dist/src/parser/languages/mermaid/constants.js +0 -20
  45. package/dist/src/parser/languages/mermaid/index.d.ts +0 -4
  46. package/dist/src/parser/languages/mermaid/index.js +0 -11
  47. package/dist/src/parser/languages/mermaid/lexer.d.ts +0 -14
  48. package/dist/src/parser/languages/mermaid/lexer.js +0 -152
  49. package/dist/src/parser/languages/mermaid/lexer.test.d.ts +0 -1
  50. package/dist/src/parser/languages/mermaid/lexer.test.js +0 -58
  51. package/dist/src/parser/languages/mermaid/parser.d.ts +0 -21
  52. package/dist/src/parser/languages/mermaid/parser.js +0 -340
  53. package/dist/src/parser/languages/mermaid/parser.test.d.ts +0 -1
  54. package/dist/src/parser/languages/mermaid/parser.test.js +0 -252
  55. package/dist/src/parser/languages/mermaid/tokens.d.ts +0 -9
  56. package/dist/src/parser/languages/mermaid/tokens.js +0 -1
  57. package/dist/src/transformer/cleaners/prune-empty.d.ts +0 -9
  58. package/dist/src/transformer/cleaners/prune-empty.js +0 -27
  59. package/dist/src/transformer/cleaners/prune-empty.test.d.ts +0 -1
  60. package/dist/src/transformer/cleaners/prune-empty.test.js +0 -69
  61. package/dist/src/transformer/cleaners/prune-unused.d.ts +0 -5
  62. package/dist/src/transformer/cleaners/prune-unused.js +0 -48
  63. package/dist/src/transformer/cleaners/prune-unused.test.d.ts +0 -1
  64. package/dist/src/transformer/cleaners/prune-unused.test.js +0 -71
  65. package/dist/src/transformer/filters/focus.d.ts +0 -13
  66. package/dist/src/transformer/filters/focus.js +0 -71
  67. package/dist/src/transformer/filters/focus.test.d.ts +0 -1
  68. package/dist/src/transformer/filters/focus.test.js +0 -50
  69. package/dist/src/transformer/filters/remove.d.ts +0 -12
  70. package/dist/src/transformer/filters/remove.js +0 -82
  71. package/dist/src/transformer/filters/remove.test.d.ts +0 -1
  72. package/dist/src/transformer/filters/remove.test.js +0 -38
  73. package/dist/src/transformer/filters/resolve.d.ts +0 -9
  74. package/dist/src/transformer/filters/resolve.js +0 -32
  75. package/dist/src/transformer/filters/resolve.test.d.ts +0 -1
  76. package/dist/src/transformer/filters/resolve.test.js +0 -48
  77. package/dist/src/transformer/index.d.ts +0 -10
  78. package/dist/src/transformer/index.js +0 -10
  79. package/dist/src/transformer/lens.d.ts +0 -12
  80. package/dist/src/transformer/lens.js +0 -58
  81. package/dist/src/transformer/lens.test.d.ts +0 -1
  82. package/dist/src/transformer/lens.test.js +0 -60
  83. package/dist/src/transformer/orchestration/engine.d.ts +0 -5
  84. package/dist/src/transformer/orchestration/engine.js +0 -24
  85. package/dist/src/transformer/orchestration/engine.test.d.ts +0 -1
  86. package/dist/src/transformer/orchestration/engine.test.js +0 -41
  87. package/dist/src/transformer/orchestration/registry.d.ts +0 -10
  88. package/dist/src/transformer/orchestration/registry.js +0 -27
  89. package/dist/src/transformer/selector/matcher.d.ts +0 -9
  90. package/dist/src/transformer/selector/matcher.js +0 -62
  91. package/dist/src/transformer/selector/matcher.test.d.ts +0 -1
  92. package/dist/src/transformer/selector/matcher.test.js +0 -53
  93. package/dist/src/transformer/traverse/walker.d.ts +0 -14
  94. package/dist/src/transformer/traverse/walker.js +0 -67
  95. package/dist/src/transformer/traverse/walker.test.d.ts +0 -1
  96. package/dist/src/transformer/traverse/walker.test.js +0 -48
  97. package/dist/src/transformer/types.d.ts +0 -47
  98. package/dist/src/transformer/types.js +0 -1
  99. package/dist/tsconfig.tsbuildinfo +0 -1
@@ -1,252 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { Lexer } from './lexer';
3
- import { Parser } from './parser';
4
- describe('Mermaid 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 "sequenceDiagram" header', () => {
12
- const input = `sequenceDiagram`;
13
- const ast = parse(input);
14
- expect(ast.kind).toBe('root');
15
- expect(ast.meta.source).toBe('mermaid');
16
- expect(ast.participants).toEqual([]);
17
- expect(ast.events).toEqual([]);
18
- });
19
- it('should handle newlines around header', () => {
20
- const input = `
21
- sequenceDiagram
22
- `;
23
- const ast = parse(input);
24
- expect(ast.meta.source).toBe('mermaid');
25
- });
26
- });
27
- describe('Participant Definitions', () => {
28
- it('should parse simple participant', () => {
29
- const input = `
30
- sequenceDiagram
31
- participant Alice
32
- participant Bob
33
- `;
34
- const ast = parse(input);
35
- expect(ast.participants).toHaveLength(2);
36
- expect(ast.participants[0]).toMatchObject({
37
- id: 'Alice',
38
- name: 'Alice',
39
- type: 'participant'
40
- });
41
- expect(ast.participants[1]).toMatchObject({
42
- id: 'Bob',
43
- name: 'Bob',
44
- type: 'participant'
45
- });
46
- });
47
- it('should parse participant with multi-word alias', () => {
48
- const input = `
49
- sequenceDiagram
50
- participant API as API Server
51
- participant DB as Database System
52
- API->>DB: Query
53
- `;
54
- const ast = parse(input);
55
- const api = ast.participants.find(p => p.id === 'API');
56
- expect(api).toBeDefined();
57
- expect(api?.name).toBe('API Server');
58
- });
59
- });
60
- describe('Message Definitions', () => {
61
- it('should parse sync message and implicit participants', () => {
62
- const input = `
63
- sequenceDiagram
64
- A->B: Hello
65
- `;
66
- const ast = parse(input);
67
- expect(ast.participants).toHaveLength(2);
68
- expect(ast.participants).toMatchObject([
69
- { id: 'A', name: 'A' },
70
- { id: 'B', name: 'B' }
71
- ]);
72
- expect(ast.events).toHaveLength(1);
73
- expect(ast.events[0]).toMatchObject({
74
- kind: 'message',
75
- from: 'A',
76
- to: 'B',
77
- text: 'Hello',
78
- type: 'sync',
79
- style: { line: 'solid', head: 'open' }
80
- });
81
- });
82
- it('should parse various arrow types', () => {
83
- const input = `
84
- sequenceDiagram
85
- A->>B: Arrow
86
- A-->>B: Reply Arrow
87
- A-->B: Dotted Open
88
- A-xB: Cross
89
- `;
90
- const ast = parse(input);
91
- expect(ast.events).toHaveLength(4);
92
- const m1 = ast.events[0];
93
- const m2 = ast.events[1];
94
- const m3 = ast.events[2];
95
- const m4 = ast.events[3];
96
- // ->> : sync, solid, arrow
97
- expect(m1).toMatchObject({
98
- kind: 'message',
99
- type: 'sync',
100
- style: { line: 'solid', head: 'arrow' }
101
- });
102
- // -->> : reply, dotted, arrow
103
- expect(m2).toMatchObject({
104
- kind: 'message',
105
- type: 'reply',
106
- style: { line: 'dotted', head: 'arrow' }
107
- });
108
- // --> : reply, dotted, open
109
- expect(m3).toMatchObject({
110
- kind: 'message',
111
- type: 'reply',
112
- style: { line: 'dotted', head: 'open' }
113
- });
114
- // -x : destroy, solid, cross
115
- expect(m4).toMatchObject({
116
- kind: 'message',
117
- type: 'destroy',
118
- style: { line: 'solid', head: 'cross' }
119
- });
120
- });
121
- it('should parse lifecycle activations in messages', () => {
122
- const input = `
123
- sequenceDiagram
124
- A->>+B: Activate Target
125
- B-->>-A: Deactivate Source
126
- `;
127
- const ast = parse(input);
128
- const m1 = ast.events[0];
129
- const m2 = ast.events[1];
130
- if (m1.kind !== 'message' || m2.kind !== 'message') {
131
- throw new Error('Expected message events');
132
- }
133
- expect(m1.lifecycle).toMatchObject({ activateTarget: true, deactivateSource: false });
134
- expect(m2.lifecycle).toMatchObject({ activateTarget: false, deactivateSource: true });
135
- });
136
- });
137
- describe('Lifecycle Definitions', () => {
138
- it('should parse standalone activate/deactivate', () => {
139
- const input = `
140
- sequenceDiagram
141
- activate A
142
- deactivate A
143
- `;
144
- const ast = parse(input);
145
- expect(ast.events).toHaveLength(2);
146
- expect(ast.events[0]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'activate' });
147
- expect(ast.events[1]).toMatchObject({ kind: 'activation', participantId: 'A', action: 'deactivate' });
148
- });
149
- });
150
- describe('Note Definitions', () => {
151
- it('should parse note positions', () => {
152
- const input = `
153
- sequenceDiagram
154
- note left of A: Note Left
155
- note right of A: Note Right
156
- note over A: Note Over
157
- `;
158
- const ast = parse(input);
159
- expect(ast.events).toHaveLength(3);
160
- expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'left', participantIds: ['A'], text: 'Note Left' });
161
- expect(ast.events[1]).toMatchObject({ kind: 'note', position: 'right', participantIds: ['A'], text: 'Note Right' });
162
- expect(ast.events[2]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A'], text: 'Note Over' });
163
- });
164
- it('should parse note over multiple participants', () => {
165
- const input = `sequenceDiagram\nnote over A,B: Shared Note`;
166
- const ast = parse(input);
167
- expect(ast.events[0]).toMatchObject({ kind: 'note', position: 'over', participantIds: ['A', 'B'], text: 'Shared Note' });
168
- });
169
- });
170
- describe('Fragment Definitions', () => {
171
- it('should parse loop fragment', () => {
172
- const input = `
173
- sequenceDiagram
174
- loop Every minute
175
- A->B: Check
176
- end
177
- `;
178
- const ast = parse(input);
179
- expect(ast.events).toHaveLength(1);
180
- const fragment = ast.events[0];
181
- expect(fragment.kind).toBe('fragment');
182
- if (fragment.kind !== 'fragment')
183
- return; // type guard
184
- expect(fragment.operator).toBe('loop');
185
- expect(fragment.branches).toHaveLength(1);
186
- expect(fragment.branches[0].condition).toBe('Every minute');
187
- expect(fragment.branches[0].events).toHaveLength(1);
188
- expect(fragment.branches[0].events[0].kind).toBe('message');
189
- });
190
- it('should parse alt/else fragment', () => {
191
- const input = `
192
- sequenceDiagram
193
- alt Success
194
- A->B: OK
195
- else Failure
196
- A->B: Error
197
- end
198
- `;
199
- const ast = parse(input);
200
- const fragment = ast.events[0];
201
- expect(fragment.kind).toBe('fragment');
202
- expect(fragment.operator).toBe('alt');
203
- expect(fragment.branches).toHaveLength(2);
204
- expect(fragment.branches[0].condition).toBe('Success');
205
- expect(fragment.branches[0].events).toHaveLength(1);
206
- expect(fragment.branches[1].condition).toBe('Failure');
207
- expect(fragment.branches[1].events).toHaveLength(1);
208
- });
209
- });
210
- describe('Box/Group Definitions', () => {
211
- it('should parse box definition with color and name', () => {
212
- const code = `
213
- sequenceDiagram
214
- box "Frontend" #eef
215
- participant A
216
- participant B
217
- end
218
- box Backend
219
- participant C
220
- end
221
- A->>C: Request
222
- C-->>A: Response
223
- `;
224
- const ast = parse(code);
225
- // Verify participants
226
- expect(ast.participants).toHaveLength(3);
227
- expect(ast.participants.map(p => p.id)).toEqual(['A', 'B', 'C']);
228
- // Verify groups
229
- expect(ast.groups).toHaveLength(2);
230
- const group1 = ast.groups[0];
231
- expect(group1.name).toContain('Frontend');
232
- expect(group1.participantIds).toEqual(['A', 'B']);
233
- const group2 = ast.groups[1];
234
- expect(group2.name).toBe('Backend');
235
- expect(group2.participantIds).toEqual(['C']);
236
- });
237
- it('should handle messages inside box block', () => {
238
- const code = `
239
- sequenceDiagram
240
- participant U
241
- box App
242
- participant A
243
- U->>A: inside box
244
- end
245
- `;
246
- const ast = parse(code);
247
- expect(ast.events).toHaveLength(1);
248
- expect(ast.events[0].kind).toBe('message');
249
- expect(ast.groups[0].participantIds).toEqual(['A']);
250
- });
251
- });
252
- });
@@ -1,9 +0,0 @@
1
- export type TokenType = 'SEQUENCE_DIAGRAM' | 'NEWLINE' | 'EOF' | 'PARTICIPANT' | 'COLON' | 'IDENTIFIER' | 'STRING' | 'ARROW' | 'LOOP' | 'ALT' | 'OPT' | 'END' | 'ELSE' | 'UNKNOWN' | 'AS' | 'ACTOR' | 'TITLE' | 'NOTE' | 'LEFT' | 'RIGHT' | 'OVER' | 'OF' | 'ACTIVATE' | 'DEACTIVATE' | 'PLUS' | 'MINUS' | 'COMMA' | 'BOX';
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
- }