@auto-engineer/server-generator-apollo-emmett 0.10.3 → 0.10.5
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/CHANGELOG.md +16 -0
- package/dist/src/codegen/extract/events.d.ts +2 -2
- package/dist/src/codegen/extract/events.d.ts.map +1 -1
- package/dist/src/codegen/extract/events.js +16 -6
- package/dist/src/codegen/extract/events.js.map +1 -1
- package/dist/src/codegen/extract/gwt.js +7 -22
- package/dist/src/codegen/extract/gwt.js.map +1 -1
- package/dist/src/codegen/extract/imports.d.ts +29 -0
- package/dist/src/codegen/extract/imports.d.ts.map +1 -0
- package/dist/src/codegen/extract/imports.js +55 -0
- package/dist/src/codegen/extract/imports.js.map +1 -0
- package/dist/src/codegen/extract/index.d.ts +1 -0
- package/dist/src/codegen/extract/index.d.ts.map +1 -1
- package/dist/src/codegen/extract/index.js +1 -0
- package/dist/src/codegen/extract/index.js.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js +33 -7
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/extract/query.d.ts +3 -1
- package/dist/src/codegen/extract/query.d.ts.map +1 -1
- package/dist/src/codegen/extract/query.js +12 -12
- package/dist/src/codegen/extract/query.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +9 -1
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +235 -8
- package/dist/src/codegen/templates/command/decide.specs.ts +8 -8
- package/dist/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
- package/dist/src/codegen/templates/command/decide.ts.ejs +2 -2
- package/dist/src/codegen/templates/command/events.ts.ejs +2 -2
- package/dist/src/codegen/templates/command/evolve.ts.ejs +3 -3
- package/dist/src/codegen/templates/command/handle.specs.ts +6 -6
- package/dist/src/codegen/templates/command/handle.ts.ejs +3 -3
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +623 -0
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +174 -52
- package/dist/src/codegen/templates/query/projection.ts.ejs +30 -29
- package/dist/src/codegen/templates/react/react.specs.specs.ts +7 -4
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +118 -67
- package/dist/src/codegen/types.d.ts +2 -0
- package/dist/src/codegen/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/codegen/extract/events.ts +20 -3
- package/src/codegen/extract/gwt.ts +10 -26
- package/src/codegen/extract/imports.ts +71 -0
- package/src/codegen/extract/index.ts +1 -0
- package/src/codegen/extract/messages.ts +34 -7
- package/src/codegen/extract/query.ts +17 -19
- package/src/codegen/scaffoldFromSchema.ts +13 -0
- package/src/codegen/templates/command/decide.specs.specs.ts +235 -8
- package/src/codegen/templates/command/decide.specs.ts +8 -8
- package/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
- package/src/codegen/templates/command/decide.ts.ejs +2 -2
- package/src/codegen/templates/command/events.ts.ejs +2 -2
- package/src/codegen/templates/command/evolve.ts.ejs +3 -3
- package/src/codegen/templates/command/handle.specs.ts +6 -6
- package/src/codegen/templates/command/handle.ts.ejs +3 -3
- package/src/codegen/templates/query/projection.specs.specs.ts +623 -0
- package/src/codegen/templates/query/projection.specs.ts.ejs +174 -52
- package/src/codegen/templates/query/projection.ts.ejs +30 -29
- package/src/codegen/templates/react/react.specs.specs.ts +7 -4
- package/src/codegen/templates/react/react.specs.ts.ejs +118 -67
- package/src/codegen/types.ts +2 -0
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts +0 -2
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts.map +0 -1
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js +0 -168
- package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js.map +0 -1
- package/dist/src/codegen/templates/query/projection.specs.specs..ts +0 -296
- package/src/codegen/scaffoldFromSchema.query-slice-register.specs.ts +0 -179
- package/src/codegen/templates/query/projection.specs.specs..ts +0 -296
|
@@ -1,71 +1,193 @@
|
|
|
1
1
|
<%_
|
|
2
|
+
const targetName = slice?.server?.data?.[0]?.target?.name || 'UnknownState';
|
|
3
|
+
const TargetType = pascalCase(targetName);
|
|
4
|
+
const projName = projectionName || "UnknownProjection";
|
|
2
5
|
const idField = projectionIdField ?? 'id';
|
|
3
|
-
const
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
6
|
+
const uniqueEventTypes = allEventTypesArray;
|
|
7
|
+
|
|
8
|
+
const ruleGroups = new Map();
|
|
9
|
+
const rules = slice?.server?.specs?.rules || [];
|
|
10
|
+
for (const rule of rules) {
|
|
11
|
+
const ruleDescription = rule.description || `${flowName} | ${sliceName}`;
|
|
12
|
+
if (!ruleGroups.has(ruleDescription)) {
|
|
13
|
+
ruleGroups.set(ruleDescription, []);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const example of rule.examples || []) {
|
|
17
|
+
ruleGroups.get(ruleDescription).push({
|
|
18
|
+
description: example.description || 'should handle events correctly',
|
|
19
|
+
given: example.given || [],
|
|
20
|
+
when: example.when || [],
|
|
21
|
+
then: example.then || []
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
_%>
|
|
8
26
|
|
|
9
27
|
import { describe, it, beforeEach, expect } from 'vitest';
|
|
10
|
-
import {
|
|
11
|
-
InMemoryProjectionSpec,
|
|
12
|
-
} from '@event-driven-io/emmett';
|
|
28
|
+
import { InMemoryProjectionSpec } from '@event-driven-io/emmett';
|
|
13
29
|
import { projection } from './projection';
|
|
14
|
-
|
|
15
|
-
import { <%=
|
|
16
|
-
|
|
17
|
-
<% if (eventTypes.length > 0) { -%>
|
|
18
|
-
type ProjectionEvent = <%= eventTypes.join(' | ') %>;
|
|
19
|
-
<% } else { -%>
|
|
20
|
-
type ProjectionEvent = never;
|
|
30
|
+
<% for (const group of eventImportGroups) { -%>
|
|
31
|
+
import type { <%= group.eventTypes.join(', ') %> } from '<%= group.importPath %>';
|
|
21
32
|
<% } -%>
|
|
33
|
+
import { <%= TargetType %> } from './state';
|
|
22
34
|
|
|
23
|
-
|
|
24
|
-
let given: InMemoryProjectionSpec<ProjectionEvent>;
|
|
35
|
+
type ProjectionEvent = <%= uniqueEventTypes.length ? uniqueEventTypes.join(' | ') : 'never' %>;
|
|
25
36
|
|
|
26
|
-
|
|
37
|
+
<% for (const [ruleDescription, ruleTests] of ruleGroups.entries()) { %>
|
|
38
|
+
describe('<%= ruleDescription %>', () => {
|
|
39
|
+
let given: InMemoryProjectionSpec<ProjectionEvent>;
|
|
40
|
+
|
|
41
|
+
beforeEach(() => {
|
|
27
42
|
given = InMemoryProjectionSpec.for({ projection });
|
|
28
|
-
|
|
43
|
+
});
|
|
29
44
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
<% for (const testCase of ruleTests) {
|
|
46
|
+
const givenEvents = Array.isArray(testCase.given) ? testCase.given : [];
|
|
47
|
+
const whenEvents = Array.isArray(testCase.when) ? testCase.when : (testCase.when ? [testCase.when] : []);
|
|
48
|
+
const thenStates = Array.isArray(testCase.then) ? testCase.then : [];
|
|
49
|
+
const allTestEvents = [...givenEvents, ...whenEvents].filter(e => e.eventRef && e.eventRef !== '');
|
|
50
|
+
|
|
51
|
+
if (thenStates.length > 0) {
|
|
52
|
+
const expectedState = thenStates.find(t => t.stateRef === targetName);
|
|
53
|
+
if (!expectedState) continue;
|
|
54
|
+
|
|
55
|
+
const description = testCase.description || 'should handle events correctly';
|
|
56
|
+
_%>
|
|
57
|
+
|
|
58
|
+
it('<%= description %>', () =>
|
|
59
|
+
given([<% if (givenEvents.length > 0) {
|
|
60
|
+
for (const evt of givenEvents) {
|
|
61
|
+
if (!evt.eventRef || evt.eventRef === '') continue;
|
|
62
|
+
const eventMessage = messages.find(m => m.name === evt.eventRef);
|
|
63
|
+
const streamNameValue = slice.stream ? `'${slice.stream}'` : "'test-stream'";
|
|
64
|
+
_%>
|
|
65
|
+
{
|
|
66
|
+
type: '<%= evt.eventRef %>',
|
|
39
67
|
data: {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
68
|
+
<% const dataKeys = Object.keys(evt.exampleData || {});
|
|
69
|
+
for (let i = 0; i < dataKeys.length; i++) {
|
|
70
|
+
const key = dataKeys[i];
|
|
71
|
+
const value = evt.exampleData[key];
|
|
72
|
+
const isLast = i === dataKeys.length - 1;
|
|
73
|
+
const field = eventMessage?.fields?.find(f => f.name === key);
|
|
74
|
+
const tsType = field?.tsType || field?.type || 'string';
|
|
75
|
+
|
|
76
|
+
let formattedValue;
|
|
77
|
+
if (value === null || value === undefined) {
|
|
78
|
+
formattedValue = 'null';
|
|
79
|
+
} else if (tsType === 'string' || tsType === 'ID') {
|
|
80
|
+
formattedValue = `'${value}'`;
|
|
81
|
+
} else if (tsType === 'number' || tsType === 'boolean') {
|
|
82
|
+
formattedValue = String(value);
|
|
83
|
+
} else if (tsType === 'Date') {
|
|
84
|
+
formattedValue = `new Date('${value}')`;
|
|
85
|
+
} else if (Array.isArray(value)) {
|
|
86
|
+
formattedValue = JSON.stringify(value);
|
|
87
|
+
} else {
|
|
88
|
+
formattedValue = `'${value}'`;
|
|
89
|
+
}
|
|
90
|
+
-%>
|
|
91
|
+
<%= key %>: <%= formattedValue %><%= isLast ? '' : ',' %>
|
|
92
|
+
<% } -%>
|
|
43
93
|
},
|
|
44
94
|
metadata: {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
},
|
|
95
|
+
streamName: <%= streamNameValue %>,
|
|
96
|
+
streamPosition: 1n,
|
|
97
|
+
globalPosition: 1n,
|
|
49
98
|
},
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
99
|
+
},<% } } -%>])
|
|
100
|
+
.when([<% if (whenEvents.length > 0) {
|
|
101
|
+
for (const evt of whenEvents) {
|
|
102
|
+
if (!evt.eventRef || evt.eventRef === '') continue;
|
|
103
|
+
const eventMessage = messages.find(m => m.name === evt.eventRef);
|
|
104
|
+
const streamNameValue = slice.stream ? `'${slice.stream}'` : "'test-stream'";
|
|
105
|
+
_%>
|
|
106
|
+
{
|
|
107
|
+
type: '<%= evt.eventRef %>',
|
|
108
|
+
data: {
|
|
109
|
+
<% const dataKeys = Object.keys(evt.exampleData || {});
|
|
110
|
+
for (let i = 0; i < dataKeys.length; i++) {
|
|
111
|
+
const key = dataKeys[i];
|
|
112
|
+
const value = evt.exampleData[key];
|
|
113
|
+
const isLast = i === dataKeys.length - 1;
|
|
114
|
+
const field = eventMessage?.fields?.find(f => f.name === key);
|
|
115
|
+
const tsType = field?.tsType || field?.type || 'string';
|
|
57
116
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
117
|
+
let formattedValue;
|
|
118
|
+
if (value === null || value === undefined) {
|
|
119
|
+
formattedValue = 'null';
|
|
120
|
+
} else if (tsType === 'string' || tsType === 'ID') {
|
|
121
|
+
formattedValue = `'${value}'`;
|
|
122
|
+
} else if (tsType === 'number' || tsType === 'boolean') {
|
|
123
|
+
formattedValue = String(value);
|
|
124
|
+
} else if (tsType === 'Date') {
|
|
125
|
+
formattedValue = `new Date('${value}')`;
|
|
126
|
+
} else if (Array.isArray(value)) {
|
|
127
|
+
formattedValue = JSON.stringify(value);
|
|
128
|
+
} else {
|
|
129
|
+
formattedValue = `'${value}'`;
|
|
130
|
+
}
|
|
131
|
+
-%>
|
|
132
|
+
<%= key %>: <%= formattedValue %><%= isLast ? '' : ',' %>
|
|
133
|
+
<% } -%>
|
|
134
|
+
},
|
|
135
|
+
metadata: {
|
|
136
|
+
streamName: <%= streamNameValue %>,
|
|
137
|
+
streamPosition: <%= givenEvents.length + whenEvents.indexOf(evt) + 1 %>n,
|
|
138
|
+
globalPosition: <%= givenEvents.length + whenEvents.indexOf(evt) + 1 %>n,
|
|
139
|
+
},
|
|
140
|
+
},<% } } -%>])
|
|
141
|
+
.then(async (state) => {
|
|
142
|
+
const document = await state.database
|
|
143
|
+
.collection<<%= TargetType %>>('<%= projName %>')
|
|
144
|
+
.findOne((doc) => <%
|
|
145
|
+
const idField = projectionIdField ?? 'id';
|
|
146
|
+
if (idField.includes('-')) {
|
|
147
|
+
// Handle composite keys
|
|
148
|
+
const parts = idField.split('-');
|
|
149
|
+
const conditions = parts.map(part => {
|
|
150
|
+
const value = expectedState.exampleData?.[part];
|
|
151
|
+
const valueStr = typeof value === 'string' ? `'${value}'` : value || "'test-value'";
|
|
152
|
+
return `doc.${part} === ${valueStr}`;
|
|
153
|
+
}).join(' && ');
|
|
154
|
+
%><%= conditions %><%
|
|
155
|
+
} else {
|
|
156
|
+
const value = expectedState.exampleData?.[idField];
|
|
157
|
+
const valueStr = typeof value === 'string' ? `'${value}'` : value || "'test-id'";
|
|
158
|
+
%>doc.<%= idField %> === <%= valueStr %><%
|
|
159
|
+
}
|
|
160
|
+
%>);
|
|
161
|
+
|
|
162
|
+
const expected: <%= TargetType %> = {
|
|
163
|
+
<% const stateKeys = Object.keys(expectedState.exampleData || {});
|
|
164
|
+
for (let i = 0; i < stateKeys.length; i++) {
|
|
165
|
+
const key = stateKeys[i];
|
|
166
|
+
const value = expectedState.exampleData[key];
|
|
167
|
+
const isLast = i === stateKeys.length - 1;
|
|
168
|
+
const stateMessage = messages.find(m => m.name === targetName);
|
|
169
|
+
const field = stateMessage?.fields?.find(f => f.name === key);
|
|
170
|
+
const tsType = field?.tsType || field?.type || 'string';
|
|
171
|
+
|
|
172
|
+
let formattedValue;
|
|
173
|
+
if (value === null || value === undefined) {
|
|
174
|
+
formattedValue = 'null';
|
|
175
|
+
} else if (tsType === 'string' || tsType === 'ID') {
|
|
176
|
+
formattedValue = `'${value}'`;
|
|
177
|
+
} else if (tsType === 'number' || tsType === 'boolean') {
|
|
178
|
+
formattedValue = String(value);
|
|
179
|
+
} else if (Array.isArray(value)) {
|
|
180
|
+
formattedValue = JSON.stringify(value);
|
|
181
|
+
} else {
|
|
182
|
+
formattedValue = `'${value}'`;
|
|
183
|
+
}
|
|
184
|
+
-%>
|
|
185
|
+
<%= key %>: <%= formattedValue %><%= isLast ? '' : ',' %>
|
|
186
|
+
<% } -%>
|
|
65
187
|
};
|
|
66
188
|
|
|
67
189
|
expect(document).toMatchObject(expected);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
190
|
+
}));
|
|
191
|
+
<% } } %>
|
|
192
|
+
});
|
|
193
|
+
<% } -%>
|
|
@@ -4,28 +4,15 @@ type ReadEvent,
|
|
|
4
4
|
type InMemoryReadEventMetadata,
|
|
5
5
|
} from '@event-driven-io/emmett';
|
|
6
6
|
import type { <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %> } from './state';<%
|
|
7
|
-
if (
|
|
8
|
-
const
|
|
9
|
-
for (const event of events) {
|
|
10
|
-
const fromSameFlow = event.sourceFlowName === flowName;
|
|
11
|
-
const basePath = fromSameFlow
|
|
12
|
-
? `../${toKebabCase(event.sourceSliceName ?? 'unknown')}/events`
|
|
13
|
-
: `../${toKebabCase(event.sourceFlowName ?? 'unknown')}/${toKebabCase(event.sourceSliceName ?? 'unknown')}/events`;
|
|
14
|
-
|
|
15
|
-
if (!importGroups.has(basePath)) {
|
|
16
|
-
importGroups.set(basePath, []);
|
|
17
|
-
}
|
|
18
|
-
importGroups.get(basePath).push(event.type);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
for (const [importPath, typeNames] of importGroups.entries()) {
|
|
7
|
+
if (eventImportGroups.length > 0) {
|
|
8
|
+
for (const group of eventImportGroups) {
|
|
22
9
|
%>
|
|
23
|
-
|
|
10
|
+
import type { <%= group.eventTypes.join(', ') %> } from '<%= group.importPath %>';
|
|
24
11
|
<%
|
|
25
|
-
}
|
|
12
|
+
}
|
|
26
13
|
} -%>
|
|
27
14
|
|
|
28
|
-
type AllEvents = <%=
|
|
15
|
+
type AllEvents = <%= allEventTypes %>;
|
|
29
16
|
|
|
30
17
|
export const projection = inMemorySingleStreamProjection<
|
|
31
18
|
<%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %>,
|
|
@@ -33,7 +20,19 @@ AllEvents
|
|
|
33
20
|
>({
|
|
34
21
|
collectionName: '<%= pascalCase(slice.server?.data?.[0]?.origin?.name || "unknown-collection") %>',
|
|
35
22
|
canHandle: [<%- events.map(e => `'${e.type}'`).join(', ') %>],
|
|
36
|
-
getDocumentId: (event) =>
|
|
23
|
+
getDocumentId: (event) => <%
|
|
24
|
+
const idField = slice.server?.data?.[0]?.origin?.idField ?? 'id';
|
|
25
|
+
// Check if idField contains hyphen-separated composite keys
|
|
26
|
+
if (idField.includes('-')) {
|
|
27
|
+
const parts = idField.split('-');
|
|
28
|
+
const template = parts.map((part, index) =>
|
|
29
|
+
index === 0 ? `\${event.data.${part}}` : `-\${event.data.${part}}`
|
|
30
|
+
).join('');
|
|
31
|
+
%>`<%= template %>`<%
|
|
32
|
+
} else {
|
|
33
|
+
%>event.data.<%= idField %><%
|
|
34
|
+
}
|
|
35
|
+
%>,
|
|
37
36
|
evolve: (
|
|
38
37
|
document: <%= pascalCase(slice.server?.data?.[0]?.target?.name || 'UnknownState') %> | null,
|
|
39
38
|
event: ReadEvent<AllEvents, InMemoryReadEventMetadata>
|
|
@@ -42,9 +41,11 @@ switch (event.type) {
|
|
|
42
41
|
<% for (const event of events) {
|
|
43
42
|
const targetName = slice.server?.data?.[0]?.target?.name;
|
|
44
43
|
const queryGwt = slice.type === 'query'
|
|
45
|
-
? queryGwtMapping.find(gwt =>
|
|
46
|
-
gwt.given.some(g => g.eventRef === event.type)
|
|
47
|
-
|
|
44
|
+
? queryGwtMapping.find(gwt => {
|
|
45
|
+
const inGiven = gwt.given && gwt.given.some(g => g.eventRef === event.type);
|
|
46
|
+
const inWhen = gwt.when.some(g => g.eventRef === event.type);
|
|
47
|
+
return inGiven || inWhen;
|
|
48
|
+
})
|
|
48
49
|
: undefined;
|
|
49
50
|
const example = slice.type === 'query'
|
|
50
51
|
? queryGwt?.then.find(t => t.stateRef === targetName)?.exampleData
|
|
@@ -94,19 +95,19 @@ case '<%= event.type %>': {
|
|
|
94
95
|
|
|
95
96
|
let placeholder = 'undefined';
|
|
96
97
|
if (type === 'string' || type === 'ID') {
|
|
97
|
-
placeholder = "''";
|
|
98
|
+
placeholder = "/* TODO: map from event.data */ ''";
|
|
98
99
|
} else if (type === 'number') {
|
|
99
|
-
placeholder = '0';
|
|
100
|
+
placeholder = '/* TODO: map from event.data */ 0';
|
|
100
101
|
} else if (type === 'boolean') {
|
|
101
|
-
placeholder = 'false';
|
|
102
|
+
placeholder = '/* TODO: map from event.data */ false';
|
|
102
103
|
} else if (type === 'Date') {
|
|
103
|
-
placeholder = 'new Date()';
|
|
104
|
+
placeholder = '/* TODO: map from event.data */ new Date()';
|
|
104
105
|
} else if (type.startsWith('Array<')) {
|
|
105
|
-
placeholder = '[]';
|
|
106
|
+
placeholder = '/* TODO: map from event.data */ []';
|
|
106
107
|
} else {
|
|
107
|
-
placeholder = '
|
|
108
|
+
placeholder = '/* TODO: map from event.data */ undefined as any';
|
|
108
109
|
}
|
|
109
|
-
%>
|
|
110
|
+
%> <%= field %>: <%- placeholder %><%= isLast ? '' : ',' %>
|
|
110
111
|
<% } -%>
|
|
111
112
|
};
|
|
112
113
|
<% } -%>
|
|
@@ -173,14 +173,17 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
173
173
|
import type { BookingRequested } from '../guest-submits-booking-request/events';
|
|
174
174
|
import type { NotifyHost } from '../send-notification-to-host/commands';
|
|
175
175
|
|
|
176
|
-
|
|
176
|
+
type ReactorEvent = BookingRequested;
|
|
177
|
+
type ReactorCommand = NotifyHost;
|
|
178
|
+
|
|
179
|
+
describe('Should send host notification on booking request', () => {
|
|
177
180
|
let eventStore: InMemoryEventStore;
|
|
178
|
-
let given: ReactorSpecification<
|
|
181
|
+
let given: ReactorSpecification<ReactorEvent, ReactorCommand, ReactorContext>;
|
|
179
182
|
let messageBus: CommandSender;
|
|
180
183
|
|
|
181
184
|
beforeEach(() => {
|
|
182
185
|
eventStore = getInMemoryEventStore({});
|
|
183
|
-
given = ReactorSpecification.for<
|
|
186
|
+
given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
|
|
184
187
|
() => react({ eventStore, commandSender: messageBus }),
|
|
185
188
|
(commandSender) => {
|
|
186
189
|
messageBus = commandSender;
|
|
@@ -193,7 +196,7 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
193
196
|
);
|
|
194
197
|
});
|
|
195
198
|
|
|
196
|
-
it('
|
|
199
|
+
it('Booking request triggers host notification', async () => {
|
|
197
200
|
await given([])
|
|
198
201
|
.when({
|
|
199
202
|
type: 'BookingRequested',
|
|
@@ -1,88 +1,139 @@
|
|
|
1
1
|
<%
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const gwtList = specs?.rules?.flatMap(rule =>
|
|
5
|
-
rule.examples.map(example => ({
|
|
6
|
-
given: example.given,
|
|
7
|
-
when: example.when,
|
|
8
|
-
then: example.then
|
|
9
|
-
}))
|
|
10
|
-
) ?? [];
|
|
11
|
-
const firstGwt = gwtList[0];
|
|
12
|
-
const firstWhen = Array.isArray(firstGwt?.when) ? firstGwt.when[0] : firstGwt?.when;
|
|
13
|
-
const firstThen = Array.isArray(firstGwt?.then) ? firstGwt.then[0] : firstGwt?.then;
|
|
2
|
+
const ruleGroups = new Map();
|
|
3
|
+
const rules = slice.server?.specs?.rules || [];
|
|
14
4
|
|
|
15
|
-
const
|
|
16
|
-
const
|
|
5
|
+
const allUsedEvents = new Set();
|
|
6
|
+
const allUsedCommands = new Set();
|
|
17
7
|
|
|
18
|
-
|
|
19
|
-
const
|
|
8
|
+
for (const rule of rules) {
|
|
9
|
+
const ruleDescription = rule.description || `${flowName} | ${sliceName}`;
|
|
10
|
+
if (!ruleGroups.has(ruleDescription)) {
|
|
11
|
+
ruleGroups.set(ruleDescription, []);
|
|
12
|
+
}
|
|
20
13
|
|
|
21
|
-
const
|
|
22
|
-
|
|
14
|
+
for (const example of rule.examples || []) {
|
|
15
|
+
ruleGroups.get(ruleDescription).push({
|
|
16
|
+
description: example.description || 'should react correctly',
|
|
17
|
+
given: example.given || [],
|
|
18
|
+
when: example.when || [],
|
|
19
|
+
then: example.then || []
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const whenEvents = Array.isArray(example.when) ? example.when : (example.when ? [example.when] : []);
|
|
23
|
+
for (const evt of whenEvents) {
|
|
24
|
+
if (evt.eventRef) allUsedEvents.add(evt.eventRef);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const thenCommands = Array.isArray(example.then) ? example.then : (example.then ? [example.then] : []);
|
|
28
|
+
for (const cmd of thenCommands) {
|
|
29
|
+
if (cmd.commandRef) allUsedCommands.add(cmd.commandRef);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const eventImportGroups = new Map();
|
|
35
|
+
const commandImportGroups = new Map();
|
|
36
|
+
|
|
37
|
+
for (const eventType of allUsedEvents) {
|
|
38
|
+
const event = events.find(e => e.type === eventType);
|
|
39
|
+
if (event) {
|
|
40
|
+
const importPath = event.sourceSliceName ? `../${toKebabCase(event.sourceSliceName)}/events` : './events';
|
|
41
|
+
if (!eventImportGroups.has(importPath)) {
|
|
42
|
+
eventImportGroups.set(importPath, []);
|
|
43
|
+
}
|
|
44
|
+
eventImportGroups.get(importPath).push(pascalCase(eventType));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (const commandType of allUsedCommands) {
|
|
49
|
+
const command = commands.find(c => c.type === commandType);
|
|
50
|
+
if (command) {
|
|
51
|
+
const importPath = command.sourceSliceName ? `../${toKebabCase(command.sourceSliceName)}/commands` : './commands';
|
|
52
|
+
if (!commandImportGroups.has(importPath)) {
|
|
53
|
+
commandImportGroups.set(importPath, []);
|
|
54
|
+
}
|
|
55
|
+
commandImportGroups.get(importPath).push(pascalCase(commandType));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const allEventTypes = Array.from(allUsedEvents).map(e => pascalCase(e)).sort();
|
|
60
|
+
const allCommandTypes = Array.from(allUsedCommands).map(c => pascalCase(c)).sort();
|
|
23
61
|
%>
|
|
24
62
|
import { describe, it, beforeEach } from 'vitest';
|
|
25
63
|
import 'reflect-metadata';
|
|
26
64
|
import {
|
|
27
|
-
getInMemoryEventStore,
|
|
28
|
-
type InMemoryEventStore,
|
|
29
|
-
type CommandSender,
|
|
65
|
+
getInMemoryEventStore,
|
|
66
|
+
type InMemoryEventStore,
|
|
67
|
+
type CommandSender,
|
|
30
68
|
} from '@event-driven-io/emmett';
|
|
31
69
|
import { type ReactorContext, ReactorSpecification } from '../../../shared';
|
|
32
70
|
import { react } from './react';
|
|
33
|
-
|
|
34
|
-
import type { <%=
|
|
71
|
+
<% for (const [importPath, typeNames] of eventImportGroups.entries()) { -%>
|
|
72
|
+
import type { <%= typeNames.sort().join(', ') %> } from '<%= importPath %>';
|
|
73
|
+
<% } -%>
|
|
74
|
+
<% for (const [importPath, typeNames] of commandImportGroups.entries()) { -%>
|
|
75
|
+
import type { <%= typeNames.sort().join(', ') %> } from '<%= importPath %>';
|
|
76
|
+
<% } -%>
|
|
35
77
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
let given: ReactorSpecification<<%= pascalCase(eventType) %>, <%= pascalCase(commandType) %>, ReactorContext>;
|
|
39
|
-
let messageBus: CommandSender;
|
|
78
|
+
type ReactorEvent = <%= allEventTypes.length ? allEventTypes.join(' | ') : 'never' %>;
|
|
79
|
+
type ReactorCommand = <%= allCommandTypes.length ? allCommandTypes.join(' | ') : 'never' %>;
|
|
40
80
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
eventStore
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
81
|
+
<% for (const [ruleDescription, ruleTests] of ruleGroups.entries()) { %>
|
|
82
|
+
describe('<%= ruleDescription %>', () => {
|
|
83
|
+
let eventStore: InMemoryEventStore;
|
|
84
|
+
let given: ReactorSpecification<ReactorEvent, ReactorCommand, ReactorContext>;
|
|
85
|
+
let messageBus: CommandSender;
|
|
86
|
+
|
|
87
|
+
beforeEach(() => {
|
|
88
|
+
eventStore = getInMemoryEventStore({});
|
|
89
|
+
given = ReactorSpecification.for<ReactorEvent, ReactorCommand, ReactorContext>(
|
|
90
|
+
() => react({ eventStore, commandSender: messageBus }),
|
|
91
|
+
(commandSender) => {
|
|
92
|
+
messageBus = commandSender;
|
|
93
|
+
return {
|
|
94
|
+
eventStore,
|
|
95
|
+
commandSender,
|
|
96
|
+
database: eventStore.database,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
);
|
|
100
|
+
});
|
|
55
101
|
|
|
56
|
-
<% for (const
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
102
|
+
<% for (const testCase of ruleTests) {
|
|
103
|
+
const whenEvents = Array.isArray(testCase.when) ? testCase.when : (testCase.when ? [testCase.when] : []);
|
|
104
|
+
const thenCommands = Array.isArray(testCase.then) ? testCase.then : (testCase.then ? [testCase.then] : []);
|
|
105
|
+
|
|
106
|
+
if (whenEvents.length > 0 && thenCommands.length > 0) {
|
|
107
|
+
const exampleEvent = whenEvents[0];
|
|
108
|
+
const description = testCase.description ||
|
|
109
|
+
`should send ${thenCommands.map(c => c.commandRef).join(', ')} when ${exampleEvent.eventRef} is received`;
|
|
60
110
|
%>
|
|
61
|
-
it('<%= description %>', async () => {
|
|
62
|
-
await given([])
|
|
63
|
-
.when({
|
|
64
|
-
type: '<%= exampleEvent.eventRef %>',
|
|
65
|
-
data: <%- formatDataObject(exampleEvent.exampleData, events.find(e => e.type === exampleEvent.eventRef)) %>
|
|
66
|
-
})
|
|
67
|
-
<% if (
|
|
68
|
-
const commandSchema =
|
|
111
|
+
it('<%= description %>', async () => {
|
|
112
|
+
await given([])
|
|
113
|
+
.when({
|
|
114
|
+
type: '<%= exampleEvent.eventRef %>',
|
|
115
|
+
data: <%- formatDataObject(exampleEvent.exampleData, events.find(e => e.type === exampleEvent.eventRef)) %>
|
|
116
|
+
})
|
|
117
|
+
<% if (thenCommands.length === 1) {
|
|
118
|
+
const commandSchema = thenCommands[0];
|
|
69
119
|
%>
|
|
70
|
-
.then({
|
|
71
|
-
type: '<%= commandSchema.commandRef %>',
|
|
72
|
-
kind: 'Command',
|
|
73
|
-
data: <%- formatDataObject(commandSchema.exampleData, messages.find(m => m.name === commandSchema.commandRef && m.type === 'command')) %>
|
|
74
|
-
});
|
|
120
|
+
.then({
|
|
121
|
+
type: '<%= commandSchema.commandRef %>',
|
|
122
|
+
kind: 'Command',
|
|
123
|
+
data: <%- formatDataObject(commandSchema.exampleData, messages.find(m => m.name === commandSchema.commandRef && m.type === 'command')) %>
|
|
124
|
+
});
|
|
75
125
|
<% } else { %>
|
|
76
|
-
|
|
77
|
-
|
|
126
|
+
.then([
|
|
127
|
+
<% for (const cmd of thenCommands) { %>
|
|
78
128
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
129
|
+
type: '<%= cmd.commandRef %>',
|
|
130
|
+
kind: 'Command',
|
|
131
|
+
data: <%- formatDataObject(cmd.exampleData, messages.find(m => m.name === cmd.commandRef && m.type === 'command')) %>
|
|
82
132
|
},
|
|
83
|
-
<% } %>
|
|
84
|
-
]);
|
|
85
133
|
<% } %>
|
|
86
|
-
|
|
134
|
+
]);
|
|
87
135
|
<% } %>
|
|
88
|
-
});
|
|
136
|
+
});
|
|
137
|
+
<% } } %>
|
|
138
|
+
});
|
|
139
|
+
<% } -%>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/codegen/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,YAAY,CAAC,CAAC;IAC3C,IAAI,EAAE,cAAc,GAAG,YAAY,EAAE,CAAC;IACtC,IAAI,EAAE,KAAK,CAAC,YAAY,GAAG,YAAY,GAAG,cAAc,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/codegen/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAEjF,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IACnC,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,KAAK;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,CAAC;IACpC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,YAAY,CAAC,EAAE,OAAO,CAAC;KACxB,CAAC,CAAC;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,KAAK,CAAC,YAAY,GAAG,YAAY,CAAC,CAAC;IAC3C,IAAI,EAAE,cAAc,GAAG,YAAY,EAAE,CAAC;IACtC,IAAI,EAAE,KAAK,CAAC,YAAY,GAAG,YAAY,GAAG,cAAc,GAAG;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B"}
|