@auto-engineer/server-generator-apollo-emmett 0.10.4 → 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.
Files changed (70) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/src/codegen/extract/events.d.ts +2 -2
  3. package/dist/src/codegen/extract/events.d.ts.map +1 -1
  4. package/dist/src/codegen/extract/events.js +16 -6
  5. package/dist/src/codegen/extract/events.js.map +1 -1
  6. package/dist/src/codegen/extract/gwt.js +7 -22
  7. package/dist/src/codegen/extract/gwt.js.map +1 -1
  8. package/dist/src/codegen/extract/imports.d.ts +29 -0
  9. package/dist/src/codegen/extract/imports.d.ts.map +1 -0
  10. package/dist/src/codegen/extract/imports.js +55 -0
  11. package/dist/src/codegen/extract/imports.js.map +1 -0
  12. package/dist/src/codegen/extract/index.d.ts +1 -0
  13. package/dist/src/codegen/extract/index.d.ts.map +1 -1
  14. package/dist/src/codegen/extract/index.js +1 -0
  15. package/dist/src/codegen/extract/index.js.map +1 -1
  16. package/dist/src/codegen/extract/messages.d.ts.map +1 -1
  17. package/dist/src/codegen/extract/messages.js +33 -7
  18. package/dist/src/codegen/extract/messages.js.map +1 -1
  19. package/dist/src/codegen/extract/query.d.ts +3 -1
  20. package/dist/src/codegen/extract/query.d.ts.map +1 -1
  21. package/dist/src/codegen/extract/query.js +12 -12
  22. package/dist/src/codegen/extract/query.js.map +1 -1
  23. package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
  24. package/dist/src/codegen/scaffoldFromSchema.js +9 -1
  25. package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
  26. package/dist/src/codegen/templates/command/decide.specs.specs.ts +235 -8
  27. package/dist/src/codegen/templates/command/decide.specs.ts +8 -8
  28. package/dist/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
  29. package/dist/src/codegen/templates/command/decide.ts.ejs +2 -2
  30. package/dist/src/codegen/templates/command/events.ts.ejs +2 -2
  31. package/dist/src/codegen/templates/command/evolve.ts.ejs +3 -3
  32. package/dist/src/codegen/templates/command/handle.specs.ts +6 -6
  33. package/dist/src/codegen/templates/command/handle.ts.ejs +3 -3
  34. package/dist/src/codegen/templates/query/projection.specs.specs.ts +623 -0
  35. package/dist/src/codegen/templates/query/projection.specs.ts.ejs +174 -52
  36. package/dist/src/codegen/templates/query/projection.ts.ejs +30 -29
  37. package/dist/src/codegen/templates/react/react.specs.specs.ts +7 -4
  38. package/dist/src/codegen/templates/react/react.specs.ts.ejs +118 -67
  39. package/dist/src/codegen/types.d.ts +2 -0
  40. package/dist/src/codegen/types.d.ts.map +1 -1
  41. package/dist/tsconfig.tsbuildinfo +1 -1
  42. package/package.json +4 -4
  43. package/src/codegen/extract/events.ts +20 -3
  44. package/src/codegen/extract/gwt.ts +10 -26
  45. package/src/codegen/extract/imports.ts +71 -0
  46. package/src/codegen/extract/index.ts +1 -0
  47. package/src/codegen/extract/messages.ts +34 -7
  48. package/src/codegen/extract/query.ts +17 -19
  49. package/src/codegen/scaffoldFromSchema.ts +13 -0
  50. package/src/codegen/templates/command/decide.specs.specs.ts +235 -8
  51. package/src/codegen/templates/command/decide.specs.ts +8 -8
  52. package/src/codegen/templates/command/decide.specs.ts.ejs +95 -30
  53. package/src/codegen/templates/command/decide.ts.ejs +2 -2
  54. package/src/codegen/templates/command/events.ts.ejs +2 -2
  55. package/src/codegen/templates/command/evolve.ts.ejs +3 -3
  56. package/src/codegen/templates/command/handle.specs.ts +6 -6
  57. package/src/codegen/templates/command/handle.ts.ejs +3 -3
  58. package/src/codegen/templates/query/projection.specs.specs.ts +623 -0
  59. package/src/codegen/templates/query/projection.specs.ts.ejs +174 -52
  60. package/src/codegen/templates/query/projection.ts.ejs +30 -29
  61. package/src/codegen/templates/react/react.specs.specs.ts +7 -4
  62. package/src/codegen/templates/react/react.specs.ts.ejs +118 -67
  63. package/src/codegen/types.ts +2 -0
  64. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts +0 -2
  65. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.d.ts.map +0 -1
  66. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js +0 -168
  67. package/dist/src/codegen/scaffoldFromSchema.query-slice-register.specs.js.map +0 -1
  68. package/dist/src/codegen/templates/query/projection.specs.specs..ts +0 -296
  69. package/src/codegen/scaffoldFromSchema.query-slice-register.specs.ts +0 -179
  70. 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 projectionType = states[0]?.type ?? 'UnknownState';
4
- const projectionCollection = projectionName ?? 'unknown-collection';
5
- const testCases = queryGwtMapping ?? [];
6
- const eventTypes = Array.from(new Set(events.map(e => e.type)));
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
- import type { <%= eventTypes.join(', ') %> } from '../<%= toKebabCase(events[0]?.sourceSliceName || 'unknown') %>/events';
15
- import { <%= projectionType %> } from './state';
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
- describe('<%= projectionName %> Projection', () => {
24
- let given: InMemoryProjectionSpec<ProjectionEvent>;
35
+ type ProjectionEvent = <%= uniqueEventTypes.length ? uniqueEventTypes.join(' | ') : 'never' %>;
25
36
 
26
- beforeEach(() => {
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
- <%_ for (const [index, gwt] of testCases.entries()) {
31
- const isRemoval = gwt.then[0]?.exampleData && Object.keys(gwt.then[0].exampleData).length === 0;
32
- const expectedIdValue = JSON.stringify(gwt.then[0]?.exampleData?.[idField] ?? 'unknown-id');
33
- _%>
34
- it('<%= isRemoval ? 'removes' : 'creates or updates' %> <%= projectionType %> document - case <%= index + 1 %>', () =>
35
- given([
36
- <%_ for (const event of gwt.given) { _%>
37
- {
38
- type: '<%= event.eventRef %>',
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
- <%_ for (const [key, val] of Object.entries(event.exampleData || {})) { _%>
41
- <%= key %>: <%- formatTsValue(val, (events.find(e => e.type === event.eventRef)?.fields.find(f => f.name === key)?.tsType || 'string')) %>,
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
- streamName: 'ignored-stream',
46
- streamPosition: 1n,
47
- globalPosition: 1n,
48
- },
95
+ streamName: <%= streamNameValue %>,
96
+ streamPosition: 1n,
97
+ globalPosition: 1n,
49
98
  },
50
- <%_ } _%>
51
- ])
52
- .when([])
53
- .then(async (state) => {
54
- const document = await state.database
55
- .collection<<%= projectionType %>>('<%= projectionCollection %>')
56
- .findOne((doc) => doc.<%= idField %> === <%- expectedIdValue %>);
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
- <%_ if (isRemoval) { _%>
59
- expect(document).toBeNull();
60
- <%_ } else { _%>
61
- const expected: <%= projectionType %> = {
62
- <%_ for (const [key, val] of Object.entries(gwt.then[0].exampleData || {})) { _%>
63
- <%= key %>: <%- formatTsValue(val, (states[0]?.fields.find(f => f.name === key)?.tsType || 'string')) %>,
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 (events.length > 0) {
8
- const importGroups = new Map();
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
- import type { <%= typeNames.join(', ') %> } from '<%= importPath %>';
10
+ import type { <%= group.eventTypes.join(', ') %> } from '<%= group.importPath %>';
24
11
  <%
25
- }
12
+ }
26
13
  } -%>
27
14
 
28
- type AllEvents = <%= events.length > 0 ? events.map(e => e.type).join(' | ') : 'never' %>;
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) => event.data.<%= slice.server?.data?.[0]?.origin?.idField ?? 'id' %>,
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 = '{} as any';
108
+ placeholder = '/* TODO: map from event.data */ undefined as any';
108
109
  }
109
- %> <%= field %>: /* TODO: map from event.data */ <%- placeholder %><%= isLast ? '' : ',' %>
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
- describe('ManageBookings | SendNotificationToHost', () => {
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<BookingRequested, NotifyHost, ReactorContext>;
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<BookingRequested, NotifyHost, ReactorContext>(
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('should send NotifyHost when BookingRequested is received', async () => {
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
- // Extract all examples from specs/rules structure
3
- const specs = slice.server?.specs;
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 eventType = firstWhen?.eventRef;
16
- const commandType = firstThen?.commandRef;
5
+ const allUsedEvents = new Set();
6
+ const allUsedCommands = new Set();
17
7
 
18
- const event = events.find(e => e.type === eventType);
19
- const command = commands.find(c => c.type === commandType);
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 eventImportPath = toKebabCase(event?.sourceSliceName ?? 'unknown');
22
- const commandImportPath = toKebabCase(command?.sourceSliceName ?? 'unknown');
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
- import type { <%= pascalCase(eventType) %> } from '../<%= eventImportPath %>/events';
34
- import type { <%= pascalCase(commandType) %> } from '../<%= commandImportPath %>/commands';
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
- describe('<%= pascalCase(flowName) %> | <%= pascalCase(slice.name) %>', () => {
37
- let eventStore: InMemoryEventStore;
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
- beforeEach(() => {
42
- eventStore = getInMemoryEventStore({});
43
- given = ReactorSpecification.for<<%= pascalCase(eventType) %>, <%= pascalCase(commandType) %>, ReactorContext>(
44
- () => react({ eventStore, commandSender: messageBus }),
45
- (commandSender) => {
46
- messageBus = commandSender;
47
- return {
48
- eventStore,
49
- commandSender,
50
- database: eventStore.database,
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 example of gwtList) {
57
- const exampleEvent = Array.isArray(example.when) ? example.when[0] : example.when;
58
- const commands = example.then;
59
- const description = `should send ${commands.map(c => c.commandRef).join(', ')} when ${exampleEvent.eventRef} is received`;
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 (commands.length === 1) {
68
- const commandSchema = commands[0];
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
- .then([
77
- <% for (const cmd of commands) { %>
126
+ .then([
127
+ <% for (const cmd of thenCommands) { %>
78
128
  {
79
- type: '<%= cmd.commandRef %>',
80
- kind: 'Command',
81
- data: <%- formatDataObject(cmd.exampleData, messages.find(m => m.name === cmd.commandRef && m.type === 'command')) %>
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
+ <% } -%>
@@ -32,4 +32,6 @@ export interface GwtCondition {
32
32
  given?: Array<EventExample | StateExample>;
33
33
  when: CommandExample | EventExample[];
34
34
  then: Array<EventExample | StateExample | CommandExample | { errorType: string; message?: string }>;
35
+ description?: string;
36
+ ruleDescription?: string;
35
37
  }
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=scaffoldFromSchema.query-slice-register.specs.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"scaffoldFromSchema.query-slice-register.specs.d.ts","sourceRoot":"","sources":["../../../src/codegen/scaffoldFromSchema.query-slice-register.specs.ts"],"names":[],"mappings":""}