@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.
- package/dist/index.d.ts +172 -85
- package/dist/polagram-core.js +2721 -2061
- package/dist/polagram-core.umd.cjs +20 -14
- package/package.json +11 -9
- package/dist/src/api.d.ts +0 -75
- package/dist/src/api.js +0 -160
- package/dist/src/ast/ast.test.d.ts +0 -1
- package/dist/src/ast/ast.test.js +0 -146
- package/dist/src/ast/index.d.ts +0 -119
- package/dist/src/ast/index.js +0 -2
- package/dist/src/config/index.d.ts +0 -1
- package/dist/src/config/index.js +0 -1
- package/dist/src/config/schema.d.ts +0 -182
- package/dist/src/config/schema.js +0 -78
- package/dist/src/config/schema.test.d.ts +0 -1
- package/dist/src/config/schema.test.js +0 -94
- package/dist/src/generator/base/walker.d.ts +0 -19
- package/dist/src/generator/base/walker.js +0 -56
- package/dist/src/generator/base/walker.test.d.ts +0 -1
- package/dist/src/generator/base/walker.test.js +0 -49
- package/dist/src/generator/generators/mermaid.d.ts +0 -24
- package/dist/src/generator/generators/mermaid.js +0 -140
- package/dist/src/generator/generators/mermaid.test.d.ts +0 -1
- package/dist/src/generator/generators/mermaid.test.js +0 -70
- package/dist/src/generator/interface.d.ts +0 -17
- package/dist/src/generator/interface.js +0 -1
- package/dist/src/index.d.ts +0 -9
- package/dist/src/index.js +0 -17
- package/dist/src/parser/base/lexer.d.ts +0 -18
- package/dist/src/parser/base/lexer.js +0 -95
- package/dist/src/parser/base/lexer.test.d.ts +0 -1
- package/dist/src/parser/base/lexer.test.js +0 -53
- package/dist/src/parser/base/parser.d.ts +0 -14
- package/dist/src/parser/base/parser.js +0 -43
- package/dist/src/parser/base/parser.test.d.ts +0 -1
- package/dist/src/parser/base/parser.test.js +0 -90
- package/dist/src/parser/index.d.ts +0 -10
- package/dist/src/parser/index.js +0 -29
- package/dist/src/parser/index.test.d.ts +0 -1
- package/dist/src/parser/index.test.js +0 -23
- package/dist/src/parser/interface.d.ts +0 -8
- package/dist/src/parser/interface.js +0 -1
- package/dist/src/parser/languages/mermaid/constants.d.ts +0 -7
- package/dist/src/parser/languages/mermaid/constants.js +0 -20
- package/dist/src/parser/languages/mermaid/index.d.ts +0 -4
- package/dist/src/parser/languages/mermaid/index.js +0 -11
- package/dist/src/parser/languages/mermaid/lexer.d.ts +0 -14
- package/dist/src/parser/languages/mermaid/lexer.js +0 -152
- package/dist/src/parser/languages/mermaid/lexer.test.d.ts +0 -1
- package/dist/src/parser/languages/mermaid/lexer.test.js +0 -58
- package/dist/src/parser/languages/mermaid/parser.d.ts +0 -21
- package/dist/src/parser/languages/mermaid/parser.js +0 -340
- package/dist/src/parser/languages/mermaid/parser.test.d.ts +0 -1
- package/dist/src/parser/languages/mermaid/parser.test.js +0 -252
- package/dist/src/parser/languages/mermaid/tokens.d.ts +0 -9
- package/dist/src/parser/languages/mermaid/tokens.js +0 -1
- package/dist/src/transformer/cleaners/prune-empty.d.ts +0 -9
- package/dist/src/transformer/cleaners/prune-empty.js +0 -27
- package/dist/src/transformer/cleaners/prune-empty.test.d.ts +0 -1
- package/dist/src/transformer/cleaners/prune-empty.test.js +0 -69
- package/dist/src/transformer/cleaners/prune-unused.d.ts +0 -5
- package/dist/src/transformer/cleaners/prune-unused.js +0 -48
- package/dist/src/transformer/cleaners/prune-unused.test.d.ts +0 -1
- package/dist/src/transformer/cleaners/prune-unused.test.js +0 -71
- package/dist/src/transformer/filters/focus.d.ts +0 -13
- package/dist/src/transformer/filters/focus.js +0 -71
- package/dist/src/transformer/filters/focus.test.d.ts +0 -1
- package/dist/src/transformer/filters/focus.test.js +0 -50
- package/dist/src/transformer/filters/remove.d.ts +0 -12
- package/dist/src/transformer/filters/remove.js +0 -82
- package/dist/src/transformer/filters/remove.test.d.ts +0 -1
- package/dist/src/transformer/filters/remove.test.js +0 -38
- package/dist/src/transformer/filters/resolve.d.ts +0 -9
- package/dist/src/transformer/filters/resolve.js +0 -32
- package/dist/src/transformer/filters/resolve.test.d.ts +0 -1
- package/dist/src/transformer/filters/resolve.test.js +0 -48
- package/dist/src/transformer/index.d.ts +0 -10
- package/dist/src/transformer/index.js +0 -10
- package/dist/src/transformer/lens.d.ts +0 -12
- package/dist/src/transformer/lens.js +0 -58
- package/dist/src/transformer/lens.test.d.ts +0 -1
- package/dist/src/transformer/lens.test.js +0 -60
- package/dist/src/transformer/orchestration/engine.d.ts +0 -5
- package/dist/src/transformer/orchestration/engine.js +0 -24
- package/dist/src/transformer/orchestration/engine.test.d.ts +0 -1
- package/dist/src/transformer/orchestration/engine.test.js +0 -41
- package/dist/src/transformer/orchestration/registry.d.ts +0 -10
- package/dist/src/transformer/orchestration/registry.js +0 -27
- package/dist/src/transformer/selector/matcher.d.ts +0 -9
- package/dist/src/transformer/selector/matcher.js +0 -62
- package/dist/src/transformer/selector/matcher.test.d.ts +0 -1
- package/dist/src/transformer/selector/matcher.test.js +0 -53
- package/dist/src/transformer/traverse/walker.d.ts +0 -14
- package/dist/src/transformer/traverse/walker.js +0 -67
- package/dist/src/transformer/traverse/walker.test.d.ts +0 -1
- package/dist/src/transformer/traverse/walker.test.js +0 -48
- package/dist/src/transformer/types.d.ts +0 -47
- package/dist/src/transformer/types.js +0 -1
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import { Matcher } from '../selector/matcher';
|
|
2
|
-
import { Walker } from '../traverse/walker';
|
|
3
|
-
export class RemoveFilter 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, "removedParticipantIds", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
configurable: true,
|
|
21
|
-
writable: true,
|
|
22
|
-
value: new Set()
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
transform(root) {
|
|
26
|
-
const selector = this.layer.selector;
|
|
27
|
-
// 1. Pre-calculate removed participants
|
|
28
|
-
if (selector.kind === 'participant') {
|
|
29
|
-
root.participants.forEach(p => {
|
|
30
|
-
if (this.matcher.matchParticipant(p, selector)) {
|
|
31
|
-
this.removedParticipantIds.add(p.id);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
// Also remove the participants from definitions
|
|
35
|
-
root.participants = root.participants.filter(p => !this.removedParticipantIds.has(p.id));
|
|
36
|
-
}
|
|
37
|
-
// 2. Remove Groups
|
|
38
|
-
if (selector.kind === 'group') {
|
|
39
|
-
root.groups = root.groups.filter(g => !this.matcher.matchGroup(g, selector));
|
|
40
|
-
}
|
|
41
|
-
return super.transform(root);
|
|
42
|
-
}
|
|
43
|
-
visitEvent(node) {
|
|
44
|
-
const selector = this.layer.selector;
|
|
45
|
-
// A. Remove by Message Selector
|
|
46
|
-
if (selector.kind === 'message') {
|
|
47
|
-
if (node.kind === 'message') {
|
|
48
|
-
if (this.matcher.matchMessage(node, selector)) {
|
|
49
|
-
return [];
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
// Note: Message selector doesn't apply to other event types
|
|
53
|
-
}
|
|
54
|
-
// B. Remove by Participant (Cascade to events)
|
|
55
|
-
if (selector.kind === 'participant') {
|
|
56
|
-
if (node.kind === 'message') {
|
|
57
|
-
const msg = node;
|
|
58
|
-
if (this.isRelatedToRemovedParticipant(msg))
|
|
59
|
-
return [];
|
|
60
|
-
}
|
|
61
|
-
if (node.kind === 'note') {
|
|
62
|
-
const note = node;
|
|
63
|
-
const isRelated = note.participantIds.some(pid => this.removedParticipantIds.has(pid));
|
|
64
|
-
if (isRelated)
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
if (node.kind === 'activation') {
|
|
68
|
-
const activation = node;
|
|
69
|
-
if (this.removedParticipantIds.has(activation.participantId))
|
|
70
|
-
return [];
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return super.visitEvent(node);
|
|
74
|
-
}
|
|
75
|
-
isRelatedToRemovedParticipant(msg) {
|
|
76
|
-
if (msg.from && this.removedParticipantIds.has(msg.from))
|
|
77
|
-
return true;
|
|
78
|
-
if (msg.to && this.removedParticipantIds.has(msg.to))
|
|
79
|
-
return true;
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { RemoveFilter } from './remove';
|
|
3
|
-
describe('RemoveFilter', () => {
|
|
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
|
-
],
|
|
12
|
-
groups: [],
|
|
13
|
-
events
|
|
14
|
-
});
|
|
15
|
-
const msgA = { kind: 'message', id: 'm1', text: 'Hello', from: 'A', to: 'B', type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
16
|
-
const msgB = { kind: 'message', id: 'm2', text: 'World', from: 'B', to: 'C', type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
17
|
-
it('removes message by direct selector', () => {
|
|
18
|
-
const root = createAst([msgA, msgB]);
|
|
19
|
-
const layer = {
|
|
20
|
-
action: 'remove',
|
|
21
|
-
selector: { kind: 'message', text: 'Hello' }
|
|
22
|
-
};
|
|
23
|
-
const result = new RemoveFilter(layer).transform(root);
|
|
24
|
-
expect(result.events).toHaveLength(1);
|
|
25
|
-
expect(result.events[0].text).toBe('World');
|
|
26
|
-
});
|
|
27
|
-
it('removes messages related to removed participant', () => {
|
|
28
|
-
const root = createAst([msgA, msgB]);
|
|
29
|
-
// Remove 'C'. msgB involves C (to: C).
|
|
30
|
-
const layer = {
|
|
31
|
-
action: 'remove',
|
|
32
|
-
selector: { kind: 'participant', name: 'C' }
|
|
33
|
-
};
|
|
34
|
-
const result = new RemoveFilter(layer).transform(root);
|
|
35
|
-
expect(result.events).toHaveLength(1);
|
|
36
|
-
expect(result.events[0].id).toBe('m1');
|
|
37
|
-
});
|
|
38
|
-
});
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { EventNode, FragmentNode } from '../../ast';
|
|
2
|
-
import { Walker } from '../traverse/walker';
|
|
3
|
-
import { ResolveLayer } from '../types';
|
|
4
|
-
export declare class ResolveFilter extends Walker {
|
|
5
|
-
private layer;
|
|
6
|
-
private matcher;
|
|
7
|
-
constructor(layer: ResolveLayer);
|
|
8
|
-
protected visitFragment(node: FragmentNode): EventNode[];
|
|
9
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { Matcher } from '../selector/matcher';
|
|
2
|
-
import { Walker } from '../traverse/walker';
|
|
3
|
-
export class ResolveFilter 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
|
-
}
|
|
19
|
-
visitFragment(node) {
|
|
20
|
-
// 1. Check if ANY branch matches the selector
|
|
21
|
-
// The layer.selector is a FragmentSelector (which matches branch properties)
|
|
22
|
-
const matchedBranch = node.branches.find(branch => this.matcher.matchBranch(branch, this.layer.selector, node.operator));
|
|
23
|
-
if (matchedBranch) {
|
|
24
|
-
// RESOLVE ACTION:
|
|
25
|
-
// "Promote" the selected branch by returning its content.
|
|
26
|
-
// The FragmentNode itself is replaced by these events.
|
|
27
|
-
return this.mapEvents(matchedBranch.events);
|
|
28
|
-
}
|
|
29
|
-
// 2. If no match, behave like default (Keep fragment, recurse into children)
|
|
30
|
-
return super.visitFragment(node);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { ResolveFilter } from './resolve';
|
|
3
|
-
describe('ResolveFilter', () => {
|
|
4
|
-
const createAst = (events) => ({
|
|
5
|
-
kind: 'root',
|
|
6
|
-
meta: { version: '1', source: 'unknown' },
|
|
7
|
-
participants: [],
|
|
8
|
-
groups: [],
|
|
9
|
-
events
|
|
10
|
-
});
|
|
11
|
-
const msg = { kind: 'message', id: 'm1', text: 'Inside', from: 'A', to: 'B', type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
12
|
-
it('unwraps fragment branch matching selector', () => {
|
|
13
|
-
const fragment = {
|
|
14
|
-
kind: 'fragment', id: 'f1', operator: 'alt',
|
|
15
|
-
branches: [
|
|
16
|
-
{ id: 'b1', condition: 'target', events: [msg] },
|
|
17
|
-
{ id: 'b2', condition: 'other', events: [] }
|
|
18
|
-
]
|
|
19
|
-
};
|
|
20
|
-
const root = createAst([fragment]);
|
|
21
|
-
// Unwrap branches with condition 'target'
|
|
22
|
-
const layer = {
|
|
23
|
-
action: 'resolve',
|
|
24
|
-
selector: { kind: 'fragment', condition: 'target' }
|
|
25
|
-
};
|
|
26
|
-
const result = new ResolveFilter(layer).transform(root);
|
|
27
|
-
// Should trigger unwrap: return content of b1
|
|
28
|
-
expect(result.events).toHaveLength(1);
|
|
29
|
-
expect(result.events[0].id).toBe('m1');
|
|
30
|
-
});
|
|
31
|
-
it('does nothing if no branch matches', () => {
|
|
32
|
-
const fragment = {
|
|
33
|
-
kind: 'fragment', id: 'f1', operator: 'alt',
|
|
34
|
-
branches: [
|
|
35
|
-
{ id: 'b1', condition: 'other', events: [msg] }
|
|
36
|
-
]
|
|
37
|
-
};
|
|
38
|
-
const root = createAst([fragment]);
|
|
39
|
-
const layer = {
|
|
40
|
-
action: 'resolve',
|
|
41
|
-
selector: { kind: 'fragment', condition: 'nomatch' }
|
|
42
|
-
};
|
|
43
|
-
const result = new ResolveFilter(layer).transform(root);
|
|
44
|
-
// Should keep fragment
|
|
45
|
-
expect(result.events).toHaveLength(1);
|
|
46
|
-
expect(result.events[0].kind).toBe('fragment');
|
|
47
|
-
});
|
|
48
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from './cleaners/prune-empty';
|
|
2
|
-
export * from './cleaners/prune-unused';
|
|
3
|
-
export * from './filters/focus';
|
|
4
|
-
export * from './filters/remove';
|
|
5
|
-
export * from './filters/resolve';
|
|
6
|
-
export * from './lens';
|
|
7
|
-
export * from './orchestration/engine';
|
|
8
|
-
export * from './orchestration/registry';
|
|
9
|
-
export * from './selector/matcher';
|
|
10
|
-
export * from './types';
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
export * from './cleaners/prune-empty';
|
|
2
|
-
export * from './cleaners/prune-unused';
|
|
3
|
-
export * from './filters/focus';
|
|
4
|
-
export * from './filters/remove';
|
|
5
|
-
export * from './filters/resolve';
|
|
6
|
-
export * from './lens';
|
|
7
|
-
export * from './orchestration/engine';
|
|
8
|
-
export * from './orchestration/registry';
|
|
9
|
-
export * from './selector/matcher';
|
|
10
|
-
export * from './types';
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import { PolagramRoot } from '../ast';
|
|
2
|
-
import { Lens } from './types';
|
|
3
|
-
/**
|
|
4
|
-
* Type Guard to validate if an object is a valid Lens.
|
|
5
|
-
* Acts as an Anti-Corruption Layer.
|
|
6
|
-
*/
|
|
7
|
-
export declare function validateLens(lens: unknown): lens is Lens;
|
|
8
|
-
/**
|
|
9
|
-
* Apply a lens object to the AST.
|
|
10
|
-
* Facade for the TransformationEngine.
|
|
11
|
-
*/
|
|
12
|
-
export declare function applyLens(root: PolagramRoot, lens: Lens): PolagramRoot;
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
import { TransformationEngine } from './orchestration/engine';
|
|
2
|
-
/**
|
|
3
|
-
* Type Guard to validate if an object is a valid Lens.
|
|
4
|
-
* Acts as an Anti-Corruption Layer.
|
|
5
|
-
*/
|
|
6
|
-
export function validateLens(lens) {
|
|
7
|
-
if (typeof lens !== 'object' || lens === null) {
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
const l = lens;
|
|
11
|
-
// Check name (optional string)
|
|
12
|
-
if (Reflect.has(l, 'name') && typeof l.name !== 'string') {
|
|
13
|
-
return false;
|
|
14
|
-
}
|
|
15
|
-
// Check layers (required array)
|
|
16
|
-
if (!Array.isArray(l.layers)) {
|
|
17
|
-
return false;
|
|
18
|
-
}
|
|
19
|
-
// Validate each layer
|
|
20
|
-
for (const layer of l.layers) {
|
|
21
|
-
if (!validateLayer(layer)) {
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return true;
|
|
26
|
-
}
|
|
27
|
-
function validateLayer(layer) {
|
|
28
|
-
if (typeof layer !== 'object' || layer === null) {
|
|
29
|
-
return false;
|
|
30
|
-
}
|
|
31
|
-
const l = layer;
|
|
32
|
-
// Check action
|
|
33
|
-
if (typeof l.action !== 'string') {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
const validActions = ['focus', 'remove', 'resolve'];
|
|
37
|
-
if (!validActions.includes(l.action)) {
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
// Check selector
|
|
41
|
-
if (typeof l.selector !== 'object' || l.selector === null) {
|
|
42
|
-
return false;
|
|
43
|
-
}
|
|
44
|
-
// Simple check for selector structure
|
|
45
|
-
const s = l.selector;
|
|
46
|
-
if (typeof s.kind !== 'string') {
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Apply a lens object to the AST.
|
|
53
|
-
* Facade for the TransformationEngine.
|
|
54
|
-
*/
|
|
55
|
-
export function applyLens(root, lens) {
|
|
56
|
-
const engine = new TransformationEngine();
|
|
57
|
-
return engine.transform(root, lens.layers);
|
|
58
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import yaml from 'js-yaml';
|
|
2
|
-
import { describe, expect, it } from 'vitest';
|
|
3
|
-
import { applyLens, validateLens } from './lens';
|
|
4
|
-
describe('Lens API', () => {
|
|
5
|
-
describe('validateLens', () => {
|
|
6
|
-
it('should return true for valid lens object', () => {
|
|
7
|
-
const valid = {
|
|
8
|
-
name: 'Test Lens',
|
|
9
|
-
layers: [
|
|
10
|
-
{ action: 'focus', selector: { kind: 'participant', name: 'Bob' } }
|
|
11
|
-
]
|
|
12
|
-
};
|
|
13
|
-
expect(validateLens(valid)).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
it('should return false for invalid structure', () => {
|
|
16
|
-
expect(validateLens(null)).toBe(false);
|
|
17
|
-
expect(validateLens({ name: 123 })).toBe(false); // Invalid name
|
|
18
|
-
expect(validateLens({ layers: 'not-array' })).toBe(false);
|
|
19
|
-
});
|
|
20
|
-
it('should return false for invalid rule action', () => {
|
|
21
|
-
const invalid = {
|
|
22
|
-
layers: [{ action: 'destroyWorld', selector: { kind: 'participant' } }]
|
|
23
|
-
};
|
|
24
|
-
expect(validateLens(invalid)).toBe(false);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
27
|
-
describe('Integration with js-yaml (Usage Layer)', () => {
|
|
28
|
-
it('should parse YAML and apply lens correctly', () => {
|
|
29
|
-
const yamlStr = `
|
|
30
|
-
name: My Transformation
|
|
31
|
-
layers:
|
|
32
|
-
- action: focus
|
|
33
|
-
selector:
|
|
34
|
-
kind: participant
|
|
35
|
-
name: Bob
|
|
36
|
-
`;
|
|
37
|
-
// 1. Adapter Layer: Parse YAML
|
|
38
|
-
const parsed = yaml.load(yamlStr);
|
|
39
|
-
// 2. Anti-Corruption Layer: Validate
|
|
40
|
-
if (!validateLens(parsed)) {
|
|
41
|
-
throw new Error('Validation failed');
|
|
42
|
-
}
|
|
43
|
-
// 3. Facade: Apply
|
|
44
|
-
// Mocking a simple AST
|
|
45
|
-
const mockRoot = {
|
|
46
|
-
kind: 'root',
|
|
47
|
-
meta: { version: '1.0', source: 'mermaid', title: 'test' },
|
|
48
|
-
participants: [
|
|
49
|
-
{ id: 'Bob', name: 'Bob', type: 'participant' }
|
|
50
|
-
],
|
|
51
|
-
groups: [],
|
|
52
|
-
events: [],
|
|
53
|
-
};
|
|
54
|
-
const result = applyLens(mockRoot, parsed);
|
|
55
|
-
// Just verifying it runs without error and returns an AST (Transformation logic is tested elsewhere)
|
|
56
|
-
expect(result).toBeDefined();
|
|
57
|
-
expect(result.kind).toBe('root');
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
});
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { StructureCleaner } from '../cleaners/prune-empty';
|
|
2
|
-
import { UnusedCleaner } from '../cleaners/prune-unused';
|
|
3
|
-
import { transformerRegistry } from './registry';
|
|
4
|
-
export class TransformationEngine {
|
|
5
|
-
transform(root, layers) {
|
|
6
|
-
let currentAst = root;
|
|
7
|
-
// Phase 1: Layers (User Intent)
|
|
8
|
-
for (const layer of layers) {
|
|
9
|
-
const transformer = transformerRegistry.get(layer);
|
|
10
|
-
if (transformer) {
|
|
11
|
-
currentAst = transformer.transform(currentAst);
|
|
12
|
-
}
|
|
13
|
-
else {
|
|
14
|
-
console.warn(`Unknown action: ${layer.action}`);
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
// Phase 2: Cleaners (Integrity Assurance)
|
|
18
|
-
// 2-1. Structure Cleaner (Prune empty branches)
|
|
19
|
-
currentAst = new StructureCleaner().transform(currentAst);
|
|
20
|
-
// 2-2. Unused Cleaner (Prune unused definitions)
|
|
21
|
-
currentAst = new UnusedCleaner().transform(currentAst);
|
|
22
|
-
return currentAst;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { TransformationEngine } from './engine';
|
|
3
|
-
describe('TransformationEngine (Pipeline Integration)', () => {
|
|
4
|
-
const createAst = (participants, events) => ({
|
|
5
|
-
kind: 'root',
|
|
6
|
-
meta: { version: '1', source: 'unknown' },
|
|
7
|
-
participants,
|
|
8
|
-
groups: [],
|
|
9
|
-
events
|
|
10
|
-
});
|
|
11
|
-
const msgAB = { kind: 'message', id: 'm1', text: 'A->B', from: 'A0', to: 'B0', type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
12
|
-
const msgCD = { kind: 'message', id: 'm2', text: 'C->D', from: 'C0', to: 'D0', type: 'sync', style: { line: 'solid', head: 'arrow' } };
|
|
13
|
-
// Participants
|
|
14
|
-
const pA = { id: 'A0', name: 'A', type: 'participant' };
|
|
15
|
-
const pB = { id: 'B0', name: 'B', type: 'participant' };
|
|
16
|
-
const pC = { id: 'C0', name: 'C', type: 'participant' };
|
|
17
|
-
const pD = { id: 'D0', name: 'D', type: 'participant' };
|
|
18
|
-
it('runs FocusParticipant -> StructureCleaner pipeline correctly', () => {
|
|
19
|
-
const fragment = {
|
|
20
|
-
kind: 'fragment', id: 'f1', operator: 'alt',
|
|
21
|
-
branches: [
|
|
22
|
-
{ id: 'b1', condition: 'keep', events: [msgAB] },
|
|
23
|
-
{ id: 'b2', condition: 'drop', events: [msgCD] }
|
|
24
|
-
]
|
|
25
|
-
};
|
|
26
|
-
const root = createAst([pA, pB, pC, pD], [fragment]);
|
|
27
|
-
// Rule: Focus A. (Should keep msgAB, remove msgCD)
|
|
28
|
-
const layer = {
|
|
29
|
-
action: 'focus',
|
|
30
|
-
selector: { kind: 'participant', name: 'A' }
|
|
31
|
-
};
|
|
32
|
-
const result = new TransformationEngine().transform(root, [layer]);
|
|
33
|
-
// 1. Filter (Focus) should have cleared events in b2 but left empty branch
|
|
34
|
-
// 2. Cleaner (Structure) should have removed b2
|
|
35
|
-
const resFrag = result.events[0];
|
|
36
|
-
expect(resFrag.branches).toHaveLength(1);
|
|
37
|
-
expect(resFrag.branches[0].id).toBe('b1'); // b2 is gone
|
|
38
|
-
// 3. Cleaner (Unused) should have removed C and D
|
|
39
|
-
expect(result.participants.map(p => p.id).sort()).toEqual(['A0', 'B0']);
|
|
40
|
-
});
|
|
41
|
-
});
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { Layer, Transformer } from '../types';
|
|
2
|
-
type TransformerFactory = (layer: Layer) => Transformer;
|
|
3
|
-
declare class TransformerRegistry {
|
|
4
|
-
private factories;
|
|
5
|
-
constructor();
|
|
6
|
-
register(action: string, factory: TransformerFactory): void;
|
|
7
|
-
get(layer: Layer): Transformer | null;
|
|
8
|
-
}
|
|
9
|
-
export declare const transformerRegistry: TransformerRegistry;
|
|
10
|
-
export {};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { FocusFilter } from '../filters/focus';
|
|
2
|
-
import { RemoveFilter } from '../filters/remove';
|
|
3
|
-
import { ResolveFilter } from '../filters/resolve';
|
|
4
|
-
class TransformerRegistry {
|
|
5
|
-
constructor() {
|
|
6
|
-
Object.defineProperty(this, "factories", {
|
|
7
|
-
enumerable: true,
|
|
8
|
-
configurable: true,
|
|
9
|
-
writable: true,
|
|
10
|
-
value: new Map()
|
|
11
|
-
});
|
|
12
|
-
// Register defaults
|
|
13
|
-
this.register('resolve', (layer) => new ResolveFilter(layer));
|
|
14
|
-
this.register('focus', (layer) => new FocusFilter(layer));
|
|
15
|
-
this.register('remove', (layer) => new RemoveFilter(layer));
|
|
16
|
-
}
|
|
17
|
-
register(action, factory) {
|
|
18
|
-
this.factories.set(action, factory);
|
|
19
|
-
}
|
|
20
|
-
get(layer) {
|
|
21
|
-
const factory = this.factories.get(layer.action);
|
|
22
|
-
if (!factory)
|
|
23
|
-
return null;
|
|
24
|
-
return factory(layer);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
export const transformerRegistry = new TransformerRegistry();
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { FragmentBranch, FragmentOperator, MessageNode, Participant, ParticipantGroup } from '../../ast';
|
|
2
|
-
import { FragmentSelector, GroupSelector, MessageSelector, ParticipantSelector } from '../types';
|
|
3
|
-
export declare class Matcher {
|
|
4
|
-
matchBranch(branch: FragmentBranch, selector: FragmentSelector, parentOperator: FragmentOperator): boolean;
|
|
5
|
-
matchParticipant(node: Participant, selector: ParticipantSelector): boolean;
|
|
6
|
-
matchMessage(node: MessageNode, selector: MessageSelector): boolean;
|
|
7
|
-
matchGroup(node: ParticipantGroup, selector: GroupSelector): boolean;
|
|
8
|
-
private matchText;
|
|
9
|
-
}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
export class Matcher {
|
|
2
|
-
// -- Branch / Fragment --
|
|
3
|
-
matchBranch(branch, selector, parentOperator) {
|
|
4
|
-
// 1. Match Operator (e.g., "loop", "alt")
|
|
5
|
-
if (selector.operator) {
|
|
6
|
-
const ops = Array.isArray(selector.operator) ? selector.operator : [selector.operator];
|
|
7
|
-
if (!ops.includes(parentOperator))
|
|
8
|
-
return false;
|
|
9
|
-
}
|
|
10
|
-
// 2. Match Condition Text (e.g., "Success")
|
|
11
|
-
if (selector.condition) {
|
|
12
|
-
if (!branch.condition)
|
|
13
|
-
return false;
|
|
14
|
-
if (!this.matchText(branch.condition, selector.condition))
|
|
15
|
-
return false;
|
|
16
|
-
}
|
|
17
|
-
return true;
|
|
18
|
-
}
|
|
19
|
-
// -- Participant --
|
|
20
|
-
matchParticipant(node, selector) {
|
|
21
|
-
if (selector.id && !this.matchText(node.id, selector.id))
|
|
22
|
-
return false;
|
|
23
|
-
if (selector.name && !this.matchText(node.name, selector.name))
|
|
24
|
-
return false;
|
|
25
|
-
if (selector.stereotype && node.stereotype && !this.matchText(node.stereotype, selector.stereotype))
|
|
26
|
-
return false;
|
|
27
|
-
return true;
|
|
28
|
-
}
|
|
29
|
-
// -- Message --
|
|
30
|
-
matchMessage(node, selector) {
|
|
31
|
-
if (selector.text && !this.matchText(node.text, selector.text))
|
|
32
|
-
return false;
|
|
33
|
-
// Note: from/to in selector are TextMatchers, so we match against node.from/to (IDs)
|
|
34
|
-
// Ideally this should resolve IDs from Names if possible, but for now we look at raw fields.
|
|
35
|
-
if (selector.from && node.from && !this.matchText(node.from, selector.from))
|
|
36
|
-
return false;
|
|
37
|
-
if (selector.to && node.to && !this.matchText(node.to, selector.to))
|
|
38
|
-
return false;
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
// -- Group --
|
|
42
|
-
matchGroup(node, selector) {
|
|
43
|
-
if (selector.name && node.name && !this.matchText(node.name, selector.name))
|
|
44
|
-
return false;
|
|
45
|
-
return true;
|
|
46
|
-
}
|
|
47
|
-
// -- Helpers --
|
|
48
|
-
matchText(actual, matcher) {
|
|
49
|
-
if (typeof matcher === 'string') {
|
|
50
|
-
return actual === matcher; // Default: Exact match
|
|
51
|
-
}
|
|
52
|
-
if (matcher instanceof RegExp) {
|
|
53
|
-
return matcher.test(actual);
|
|
54
|
-
}
|
|
55
|
-
if (typeof matcher === 'object' && matcher.pattern) {
|
|
56
|
-
const flags = matcher.flags || '';
|
|
57
|
-
const regex = new RegExp(matcher.pattern, flags);
|
|
58
|
-
return regex.test(actual);
|
|
59
|
-
}
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { Matcher } from './matcher';
|
|
3
|
-
describe('Matcher', () => {
|
|
4
|
-
const matcher = new Matcher();
|
|
5
|
-
describe('Text Matching', () => {
|
|
6
|
-
// We test via matchParticipant since matchText is private
|
|
7
|
-
it('matches string as exact match', () => {
|
|
8
|
-
const p = { type: 'participant', name: 'UserLogService', id: 'p1' };
|
|
9
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: 'UserLogService' })).toBe(true);
|
|
10
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: 'Log' })).toBe(false);
|
|
11
|
-
});
|
|
12
|
-
it('matches RegExp', () => {
|
|
13
|
-
const p = { type: 'participant', name: 'UserLogService', id: 'p1' };
|
|
14
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: /Log/ })).toBe(true);
|
|
15
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: /^User/ })).toBe(true);
|
|
16
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: /^Service/ })).toBe(false);
|
|
17
|
-
});
|
|
18
|
-
it('matches Serializable Object', () => {
|
|
19
|
-
const p = { type: 'participant', name: 'UserLogService', id: 'p1' };
|
|
20
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: { pattern: 'Log' } })).toBe(true);
|
|
21
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', name: { pattern: '^user', flags: 'i' } })).toBe(true);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
describe('Participant Matching', () => {
|
|
25
|
-
it('matches by ID', () => {
|
|
26
|
-
const p = { type: 'participant', name: 'User', id: 'user_01' };
|
|
27
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', id: 'user_01' })).toBe(true);
|
|
28
|
-
expect(matcher.matchParticipant(p, { kind: 'participant', id: 'other' })).toBe(false);
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
describe('Message Matching', () => {
|
|
32
|
-
const msg = {
|
|
33
|
-
kind: 'message',
|
|
34
|
-
text: 'Login Request',
|
|
35
|
-
from: 'user',
|
|
36
|
-
to: 'api',
|
|
37
|
-
id: 'm1',
|
|
38
|
-
type: 'sync',
|
|
39
|
-
style: {
|
|
40
|
-
line: 'solid',
|
|
41
|
-
head: 'arrow'
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
it('matches by text', () => {
|
|
45
|
-
expect(matcher.matchMessage(msg, { kind: 'message', text: 'Login Request' })).toBe(true);
|
|
46
|
-
expect(matcher.matchMessage(msg, { kind: 'message', text: 'Login' })).toBe(false);
|
|
47
|
-
});
|
|
48
|
-
it('matches by from/to', () => {
|
|
49
|
-
expect(matcher.matchMessage(msg, { kind: 'message', from: 'user' })).toBe(true);
|
|
50
|
-
expect(matcher.matchMessage(msg, { kind: 'message', to: 'db' })).toBe(false);
|
|
51
|
-
});
|
|
52
|
-
});
|
|
53
|
-
});
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { EventNode, FragmentBranch, FragmentNode, PolagramRoot } from '../../ast';
|
|
2
|
-
/**
|
|
3
|
-
* Abstract base class for tree transformations.
|
|
4
|
-
* Implements the "Updating Visitor" pattern with Copy-on-Write immutability.
|
|
5
|
-
*
|
|
6
|
-
* Subclasses should override `visitEvent` to apply specific logic.
|
|
7
|
-
*/
|
|
8
|
-
export declare abstract class Walker {
|
|
9
|
-
transform(root: PolagramRoot): PolagramRoot;
|
|
10
|
-
protected mapEvents(events: EventNode[]): EventNode[];
|
|
11
|
-
protected visitEvent(node: EventNode): EventNode[];
|
|
12
|
-
protected visitFragment(node: FragmentNode): EventNode[];
|
|
13
|
-
protected visitBranch(branch: FragmentBranch): FragmentBranch;
|
|
14
|
-
}
|