@auto-engineer/server-generator-apollo-emmett 1.103.0 → 1.105.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/.turbo/turbo-build.log +1 -1
- package/.turbo/turbo-test.log +6 -6
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +119 -0
- package/dist/src/codegen/scaffoldFromSchema.d.ts +10 -0
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +55 -20
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.ts +16 -4
- package/dist/src/codegen/templates/command/decide.ts.ejs +4 -1
- package/dist/src/codegen/templates/command/evolve.specs.ts +16 -15
- package/dist/src/codegen/templates/command/evolve.ts.ejs +16 -15
- package/dist/src/codegen/templates/command/handle.specs.ts +146 -0
- package/dist/src/codegen/templates/command/handle.ts.ejs +21 -1
- package/dist/src/codegen/templates/query/projection.specs.ts +4 -1
- package/dist/src/codegen/templates/query/projection.ts.ejs +7 -1
- package/dist/src/codegen/templates/react/react.specs.specs.ts +0 -32
- package/dist/src/codegen/templates/react/react.ts.specs.ts +0 -49
- package/dist/src/commands/generate-server.d.ts +4 -0
- package/dist/src/commands/generate-server.d.ts.map +1 -1
- package/dist/src/commands/generate-server.js +37 -3
- package/dist/src/commands/generate-server.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +2 -0
- package/package.json +4 -4
- package/src/codegen/formatTsValue.specs.ts +12 -0
- package/src/codegen/formatTsValueSimple.specs.ts +1 -1
- package/src/codegen/scaffoldErrors.specs.ts +40 -0
- package/src/codegen/scaffoldFromSchema.ts +70 -31
- package/src/codegen/templates/command/decide.specs.ts +16 -4
- package/src/codegen/templates/command/decide.ts.ejs +4 -1
- package/src/codegen/templates/command/evolve.specs.ts +16 -15
- package/src/codegen/templates/command/evolve.ts.ejs +16 -15
- package/src/codegen/templates/command/handle.specs.ts +146 -0
- package/src/codegen/templates/command/handle.ts.ejs +21 -1
- package/src/codegen/templates/query/projection.specs.ts +4 -1
- package/src/codegen/templates/query/projection.ts.ejs +7 -1
- package/src/codegen/templates/react/react.specs.specs.ts +0 -32
- package/src/codegen/templates/react/react.ts.specs.ts +0 -49
- package/src/commands/generate-server.specs.ts +71 -0
- package/src/commands/generate-server.ts +45 -2
|
@@ -87,27 +87,28 @@ describe('evolve.ts.ejs', () => {
|
|
|
87
87
|
/**
|
|
88
88
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
89
89
|
*
|
|
90
|
-
*
|
|
90
|
+
* Evolve domain state in response to events.
|
|
91
|
+
* Only track fields needed for future decisions in decide.ts.
|
|
91
92
|
*
|
|
92
|
-
*
|
|
93
|
-
* -
|
|
94
|
-
* -
|
|
95
|
-
* - If
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* -
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* \`return
|
|
93
|
+
* RULES:
|
|
94
|
+
* - Return a new object literal for state transitions.
|
|
95
|
+
* For no-op (event doesn’t affect state), return the existing \`state\`.
|
|
96
|
+
* - If State is a discriminated union (check state.ts), EVERY return
|
|
97
|
+
* MUST include the discriminant field as a string literal.
|
|
98
|
+
* Example: \`return { status: ‘active’, field: event.data.field };\`
|
|
99
|
+
* - NEVER spread ...event.data or ...state — list each field explicitly.
|
|
100
|
+
*
|
|
101
|
+
* VERIFY before finalizing:
|
|
102
|
+
* [ ] Every return (except \`return state;\`) matches a variant from state.ts
|
|
103
|
+
* [ ] Every return includes the discriminant field
|
|
104
|
+
* [ ] No spread operators in return statements
|
|
102
105
|
*/
|
|
103
106
|
|
|
104
107
|
export const evolve = (state: State, event: ListingCreated): State => {
|
|
105
108
|
switch (event.type) {
|
|
106
109
|
case 'ListingCreated': {
|
|
107
|
-
// TODO:
|
|
108
|
-
return
|
|
109
|
-
...state,
|
|
110
|
-
};
|
|
110
|
+
// TODO: Return { status: 'variant', field1: ..., field2: ... } matching state.ts.
|
|
111
|
+
return state;
|
|
111
112
|
}
|
|
112
113
|
default:
|
|
113
114
|
return state;
|
|
@@ -7,18 +7,21 @@
|
|
|
7
7
|
/**
|
|
8
8
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
9
9
|
*
|
|
10
|
-
*
|
|
10
|
+
* Evolve domain state in response to events.
|
|
11
|
+
* Only track fields needed for future decisions in decide.ts.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
* -
|
|
14
|
-
* -
|
|
15
|
-
* - If
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `return
|
|
13
|
+
* RULES:
|
|
14
|
+
* - Return a new object literal for state transitions.
|
|
15
|
+
* For no-op (event doesn’t affect state), return the existing `state`.
|
|
16
|
+
* - If State is a discriminated union (check state.ts), EVERY return
|
|
17
|
+
* MUST include the discriminant field as a string literal.
|
|
18
|
+
* Example: `return { status: ‘active’, field: event.data.field };`
|
|
19
|
+
* - NEVER spread ...event.data or ...state — list each field explicitly.
|
|
20
|
+
*
|
|
21
|
+
* VERIFY before finalizing:
|
|
22
|
+
* [ ] Every return (except `return state;`) matches a variant from state.ts
|
|
23
|
+
* [ ] Every return includes the discriminant field
|
|
24
|
+
* [ ] No spread operators in return statements
|
|
22
25
|
*/
|
|
23
26
|
|
|
24
27
|
export const evolve = (
|
|
@@ -28,10 +31,8 @@
|
|
|
28
31
|
switch (event.type) {
|
|
29
32
|
<% events.forEach(event => { -%>
|
|
30
33
|
case '<%= event.type %>': {
|
|
31
|
-
// TODO:
|
|
32
|
-
return
|
|
33
|
-
...state
|
|
34
|
-
};
|
|
34
|
+
// TODO: Return { status: 'variant', field1: ..., field2: ... } matching state.ts.
|
|
35
|
+
return state;
|
|
35
36
|
}
|
|
36
37
|
<% }); -%>
|
|
37
38
|
default:
|
|
@@ -331,4 +331,150 @@ describe('generateScaffoldFilePlans', () => {
|
|
|
331
331
|
"
|
|
332
332
|
`);
|
|
333
333
|
});
|
|
334
|
+
it('should generate stream guard when not all commands share stream pattern fields', async () => {
|
|
335
|
+
const spec: SpecsSchema = {
|
|
336
|
+
variant: 'specs',
|
|
337
|
+
narratives: [
|
|
338
|
+
{
|
|
339
|
+
name: 'Member tracks workouts',
|
|
340
|
+
slices: [
|
|
341
|
+
{
|
|
342
|
+
type: 'command',
|
|
343
|
+
name: 'Track workouts',
|
|
344
|
+
stream: 'workouts-${memberId}',
|
|
345
|
+
client: {
|
|
346
|
+
specs: [],
|
|
347
|
+
},
|
|
348
|
+
server: {
|
|
349
|
+
description: 'test',
|
|
350
|
+
specs: [
|
|
351
|
+
{
|
|
352
|
+
type: 'gherkin',
|
|
353
|
+
feature: 'Track workouts',
|
|
354
|
+
rules: [
|
|
355
|
+
{
|
|
356
|
+
name: 'Should log workout',
|
|
357
|
+
examples: [
|
|
358
|
+
{
|
|
359
|
+
name: 'Member logs a workout',
|
|
360
|
+
steps: [
|
|
361
|
+
{
|
|
362
|
+
keyword: 'When',
|
|
363
|
+
text: 'LogWorkout',
|
|
364
|
+
docString: { memberId: 'm1', exercise: 'squat' },
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
keyword: 'Then',
|
|
368
|
+
text: 'WorkoutLogged',
|
|
369
|
+
docString: { memberId: 'm1', exercise: 'squat' },
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
},
|
|
373
|
+
],
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
name: 'Should calculate points',
|
|
377
|
+
examples: [
|
|
378
|
+
{
|
|
379
|
+
name: 'Points are calculated',
|
|
380
|
+
steps: [
|
|
381
|
+
{
|
|
382
|
+
keyword: 'When',
|
|
383
|
+
text: 'CalculatePoints',
|
|
384
|
+
docString: { season: 'winter' },
|
|
385
|
+
},
|
|
386
|
+
{
|
|
387
|
+
keyword: 'Then',
|
|
388
|
+
text: 'PointsCalculated',
|
|
389
|
+
docString: { season: 'winter', points: 10 },
|
|
390
|
+
},
|
|
391
|
+
],
|
|
392
|
+
},
|
|
393
|
+
],
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
data: {
|
|
399
|
+
items: [
|
|
400
|
+
{
|
|
401
|
+
target: { type: 'Event', name: 'WorkoutLogged' },
|
|
402
|
+
destination: {
|
|
403
|
+
type: 'stream',
|
|
404
|
+
pattern: 'workouts-${memberId}',
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
],
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
messages: [
|
|
415
|
+
{
|
|
416
|
+
type: 'command',
|
|
417
|
+
name: 'LogWorkout',
|
|
418
|
+
fields: [
|
|
419
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
420
|
+
{ name: 'exercise', type: 'string', required: true },
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
type: 'command',
|
|
425
|
+
name: 'CalculatePoints',
|
|
426
|
+
fields: [{ name: 'season', type: 'string', required: true }],
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
type: 'event',
|
|
430
|
+
name: 'WorkoutLogged',
|
|
431
|
+
source: 'internal',
|
|
432
|
+
fields: [
|
|
433
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
434
|
+
{ name: 'exercise', type: 'string', required: true },
|
|
435
|
+
],
|
|
436
|
+
},
|
|
437
|
+
{
|
|
438
|
+
type: 'event',
|
|
439
|
+
name: 'PointsCalculated',
|
|
440
|
+
source: 'internal',
|
|
441
|
+
fields: [
|
|
442
|
+
{ name: 'season', type: 'string', required: true },
|
|
443
|
+
{ name: 'points', type: 'number', required: true },
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
],
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
|
|
450
|
+
const handleFile = plans.find((p) => p.outputPath.endsWith('handle.ts'));
|
|
451
|
+
|
|
452
|
+
expect(handleFile?.contents).toMatchInlineSnapshot(`
|
|
453
|
+
"import { CommandHandler, type EventStore, type MessageHandlerResult } from '@event-driven-io/emmett';
|
|
454
|
+
import { evolve } from './evolve';
|
|
455
|
+
import { initialState } from './state';
|
|
456
|
+
import { decide } from './decide';
|
|
457
|
+
import type { LogWorkout, CalculatePoints } from './commands';
|
|
458
|
+
|
|
459
|
+
const handler = CommandHandler({
|
|
460
|
+
evolve,
|
|
461
|
+
initialState,
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
export const handle = async (
|
|
465
|
+
eventStore: EventStore,
|
|
466
|
+
command: LogWorkout | CalculatePoints,
|
|
467
|
+
): Promise<MessageHandlerResult> => {
|
|
468
|
+
const commandData = command.data;
|
|
469
|
+
if (!('memberId' in commandData)) {
|
|
470
|
+
throw new Error('Cannot determine stream: "memberId" not in command data');
|
|
471
|
+
}
|
|
472
|
+
const streamId = \`workouts-\${commandData.memberId}\`;
|
|
473
|
+
|
|
474
|
+
await handler(eventStore, streamId, (state) => decide(command, state));
|
|
475
|
+
return undefined;
|
|
476
|
+
};
|
|
477
|
+
"
|
|
478
|
+
`);
|
|
479
|
+
});
|
|
334
480
|
});
|
|
@@ -53,6 +53,16 @@ const integrationCalls = integrationData.map((d, i) => {
|
|
|
53
53
|
|
|
54
54
|
const resultVarName = integrationCalls.find(call => !!call.varName)?.varName;
|
|
55
55
|
const needsReturnValue = typeof resultVarName === 'string';
|
|
56
|
+
|
|
57
|
+
const streamVars = [];
|
|
58
|
+
const streamPatternStr = stream?.pattern ?? '';
|
|
59
|
+
const svRegex = /\$\{([^}]+)\}/g;
|
|
60
|
+
let svMatch;
|
|
61
|
+
while ((svMatch = svRegex.exec(streamPatternStr)) !== null) {
|
|
62
|
+
streamVars.push(svMatch[1]);
|
|
63
|
+
}
|
|
64
|
+
const allCmdFieldSets = commands.map(c => new Set((c.fields ?? []).map(f => f.name)));
|
|
65
|
+
const needsStreamGuard = streamVars.length > 0 && !allCmdFieldSets.every(fs => streamVars.every(v => fs.has(v)));
|
|
56
66
|
%>
|
|
57
67
|
|
|
58
68
|
<% integrationSideEffectImports.forEach((importSource) => { %>
|
|
@@ -86,7 +96,17 @@ eventStore: EventStore,
|
|
|
86
96
|
command: <%= commands.map(c => pascalCase(c.type)).join(' | ') %>
|
|
87
97
|
): Promise<MessageHandlerResult> => {
|
|
88
98
|
<% if (stream?.pattern?.includes('${')) { -%>
|
|
89
|
-
|
|
99
|
+
<% if (needsStreamGuard) { -%>
|
|
100
|
+
const commandData = command.data;
|
|
101
|
+
<% for (const v of streamVars) { -%>
|
|
102
|
+
if (!('<%= v %>' in commandData)) {
|
|
103
|
+
throw new Error('Cannot determine stream: "<%= v %>" not in command data');
|
|
104
|
+
}
|
|
105
|
+
<% } -%>
|
|
106
|
+
const streamId = `<%= stream.pattern.replace(/\$\{([^}]+)\}/g, (_, key) => `\${commandData.${key}}`) %>`;
|
|
107
|
+
<% } else { -%>
|
|
108
|
+
const streamId = `<%= stream.pattern.replace(/\$\{([^}]+)\}/g, (_, key) => `\${command.data.${key}}`) %>`;
|
|
109
|
+
<% } -%>
|
|
90
110
|
<% } else { -%>
|
|
91
111
|
const streamId = '<%= stream?.pattern ?? 'unknown-stream' %>';
|
|
92
112
|
<% } -%>
|
|
@@ -562,8 +562,11 @@ describe('projection.ts.ejs', () => {
|
|
|
562
562
|
// SINGLETON AGGREGATION PATTERN
|
|
563
563
|
// This projection maintains a single document that aggregates data from multiple entities.
|
|
564
564
|
// Use internal state to track individual entity information for accurate calculations.
|
|
565
|
+
|
|
566
|
+
type EntityData = TodoAdded['data'];
|
|
567
|
+
|
|
565
568
|
interface InternalTodoSummary extends TodoSummary {
|
|
566
|
-
_entities?: Record<string,
|
|
569
|
+
_entities?: Record<string, EntityData>;
|
|
567
570
|
}
|
|
568
571
|
|
|
569
572
|
type AllEvents = TodoAdded;
|
|
@@ -45,8 +45,14 @@ import { <%= stateEnums.join(', ') %> } from '../../../shared';
|
|
|
45
45
|
// SINGLETON AGGREGATION PATTERN
|
|
46
46
|
// This projection maintains a single document that aggregates data from multiple entities.
|
|
47
47
|
// Use internal state to track individual entity information for accurate calculations.
|
|
48
|
+
<% if (events.length === 1) { %>
|
|
49
|
+
type EntityData = <%= pascalCase(events[0].type) %>['data'];
|
|
50
|
+
<% } else { %>
|
|
51
|
+
type EntityData = Partial<<%= events.map(e => pascalCase(e.type) + "['data']").join(' & ') %>>;
|
|
52
|
+
<% } %>
|
|
53
|
+
|
|
48
54
|
interface Internal<%= pascalCase(targetName || 'State') %> extends <%= pascalCase(targetName || 'State') %> {
|
|
49
|
-
_entities?: Record<string,
|
|
55
|
+
_entities?: Record<string, EntityData>;
|
|
50
56
|
}
|
|
51
57
|
<% } %>
|
|
52
58
|
type AllEvents = <%= allEventTypes %>;
|
|
@@ -72,14 +72,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
72
72
|
name: 'Send notification to host',
|
|
73
73
|
server: {
|
|
74
74
|
description: 'Sends a host notification command in response to BookingRequested',
|
|
75
|
-
data: {
|
|
76
|
-
items: [
|
|
77
|
-
{
|
|
78
|
-
target: { type: 'Command', name: 'NotifyHost' },
|
|
79
|
-
destination: { type: 'stream', pattern: 'booking-${hostId}' },
|
|
80
|
-
},
|
|
81
|
-
],
|
|
82
|
-
},
|
|
83
75
|
specs: [
|
|
84
76
|
{
|
|
85
77
|
type: 'gherkin',
|
|
@@ -291,14 +283,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
291
283
|
name: 'Process tip after completion',
|
|
292
284
|
server: {
|
|
293
285
|
description: 'Processes tip command after appointment completion',
|
|
294
|
-
data: {
|
|
295
|
-
items: [
|
|
296
|
-
{
|
|
297
|
-
target: { type: 'Command', name: 'ProcessTip' },
|
|
298
|
-
destination: { type: 'stream', pattern: 'tips-${appointmentId}' },
|
|
299
|
-
},
|
|
300
|
-
],
|
|
301
|
-
},
|
|
302
286
|
specs: [
|
|
303
287
|
{
|
|
304
288
|
type: 'gherkin',
|
|
@@ -409,14 +393,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
409
393
|
name: 'notify milestone',
|
|
410
394
|
server: {
|
|
411
395
|
description: 'Notifies on milestone',
|
|
412
|
-
data: {
|
|
413
|
-
items: [
|
|
414
|
-
{
|
|
415
|
-
target: { type: 'Command', name: 'SendNotification' },
|
|
416
|
-
destination: { type: 'stream', pattern: 'notif-${memberId}' },
|
|
417
|
-
},
|
|
418
|
-
],
|
|
419
|
-
},
|
|
420
396
|
specs: [
|
|
421
397
|
{
|
|
422
398
|
type: 'gherkin',
|
|
@@ -532,14 +508,6 @@ describe('react.specs.ts.ejs (react slice)', () => {
|
|
|
532
508
|
name: 'notify barber of cancellation',
|
|
533
509
|
server: {
|
|
534
510
|
description: 'Notifies barber when appointment is cancelled',
|
|
535
|
-
data: {
|
|
536
|
-
items: [
|
|
537
|
-
{
|
|
538
|
-
target: { type: 'Command', name: 'NotifyBarber' },
|
|
539
|
-
destination: { type: 'stream', pattern: 'barber-${barberId}' },
|
|
540
|
-
},
|
|
541
|
-
],
|
|
542
|
-
},
|
|
543
511
|
specs: [
|
|
544
512
|
{
|
|
545
513
|
type: 'gherkin',
|
|
@@ -15,14 +15,6 @@ describe('react.ts.ejs', () => {
|
|
|
15
15
|
name: 'missing-when-slice',
|
|
16
16
|
server: {
|
|
17
17
|
description: 'A react slice with no When step',
|
|
18
|
-
data: {
|
|
19
|
-
items: [
|
|
20
|
-
{
|
|
21
|
-
target: { type: 'Command', name: 'DoSomething' },
|
|
22
|
-
destination: { type: 'stream', pattern: 'things-${id}' },
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
18
|
specs: [
|
|
27
19
|
{
|
|
28
20
|
type: 'gherkin',
|
|
@@ -113,14 +105,6 @@ describe('react.ts.ejs', () => {
|
|
|
113
105
|
name: 'Send order confirmation',
|
|
114
106
|
server: {
|
|
115
107
|
description: 'Sends confirmation after order placed',
|
|
116
|
-
data: {
|
|
117
|
-
items: [
|
|
118
|
-
{
|
|
119
|
-
target: { type: 'Command', name: 'SendConfirmation' },
|
|
120
|
-
destination: { type: 'stream', pattern: 'confirmations-${orderId}' },
|
|
121
|
-
},
|
|
122
|
-
],
|
|
123
|
-
},
|
|
124
108
|
specs: [
|
|
125
109
|
{
|
|
126
110
|
type: 'gherkin',
|
|
@@ -238,14 +222,6 @@ describe('react.ts.ejs', () => {
|
|
|
238
222
|
name: 'Notify Barber of Cancellation',
|
|
239
223
|
server: {
|
|
240
224
|
description: 'Notifies barber when appointment is cancelled',
|
|
241
|
-
data: {
|
|
242
|
-
items: [
|
|
243
|
-
{
|
|
244
|
-
target: { type: 'Command', name: 'NotifyBarber' },
|
|
245
|
-
destination: { type: 'stream', pattern: 'barber-${barberId}' },
|
|
246
|
-
},
|
|
247
|
-
],
|
|
248
|
-
},
|
|
249
225
|
specs: [
|
|
250
226
|
{
|
|
251
227
|
type: 'gherkin',
|
|
@@ -335,7 +311,6 @@ describe('react.ts.ejs', () => {
|
|
|
335
311
|
items: [
|
|
336
312
|
{
|
|
337
313
|
target: { type: 'Event', name: 'BarberNotified' },
|
|
338
|
-
destination: { type: 'stream', pattern: 'barber-${barberId}' },
|
|
339
314
|
},
|
|
340
315
|
],
|
|
341
316
|
},
|
|
@@ -466,14 +441,6 @@ export type BarberNotified = Event<
|
|
|
466
441
|
name: 'notify barber of cancellation',
|
|
467
442
|
server: {
|
|
468
443
|
description: 'Notifies barber when appointment is cancelled',
|
|
469
|
-
data: {
|
|
470
|
-
items: [
|
|
471
|
-
{
|
|
472
|
-
target: { type: 'Command', name: 'NotifyBarber' },
|
|
473
|
-
destination: { type: 'stream', pattern: 'barber-${barberId}' },
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
},
|
|
477
444
|
specs: [
|
|
478
445
|
{
|
|
479
446
|
type: 'gherkin',
|
|
@@ -607,14 +574,6 @@ export type BarberNotified = Event<
|
|
|
607
574
|
name: 'notify milestone',
|
|
608
575
|
server: {
|
|
609
576
|
description: 'Notifies on milestone',
|
|
610
|
-
data: {
|
|
611
|
-
items: [
|
|
612
|
-
{
|
|
613
|
-
target: { type: 'Command', name: 'SendNotification' },
|
|
614
|
-
destination: { type: 'stream', pattern: 'notif-${memberId}' },
|
|
615
|
-
},
|
|
616
|
-
],
|
|
617
|
-
},
|
|
618
577
|
specs: [
|
|
619
578
|
{
|
|
620
579
|
type: 'gherkin',
|
|
@@ -728,14 +687,6 @@ export type BarberNotified = Event<
|
|
|
728
687
|
name: 'check record updates',
|
|
729
688
|
server: {
|
|
730
689
|
description: 'Updates records after workout',
|
|
731
|
-
data: {
|
|
732
|
-
items: [
|
|
733
|
-
{
|
|
734
|
-
target: { type: 'Command', name: 'UpdateRecord' },
|
|
735
|
-
destination: { type: 'stream', pattern: 'records-${memberId}' },
|
|
736
|
-
},
|
|
737
|
-
],
|
|
738
|
-
},
|
|
739
690
|
specs: [
|
|
740
691
|
{
|
|
741
692
|
type: 'gherkin',
|
|
@@ -40,6 +40,9 @@ export type ServerGenerationFailedEvent = Event<'ServerGenerationFailed', {
|
|
|
40
40
|
destination: string;
|
|
41
41
|
error: string;
|
|
42
42
|
model: Model;
|
|
43
|
+
flowName?: string;
|
|
44
|
+
sliceName?: string;
|
|
45
|
+
sliceType?: string;
|
|
43
46
|
}>;
|
|
44
47
|
export type SliceGeneratedEvent = Event<'SliceGenerated', {
|
|
45
48
|
flowName: string;
|
|
@@ -67,6 +70,7 @@ export declare function deriveModelPath(destination: string): string;
|
|
|
67
70
|
export declare function writeModelToDisk(model: Model, destination: string): Promise<void>;
|
|
68
71
|
export declare function cleanServerDir(serverDir: string): Promise<void>;
|
|
69
72
|
export declare function writeHealthResolver(serverDir: string): Promise<void>;
|
|
73
|
+
export declare function createServerFailureEvent(command: GenerateServerCommand, error: unknown): ServerGenerationFailedEvent;
|
|
70
74
|
export declare function createSliceGeneratedEvent(flow: Narrative, slice: Slice, command: GenerateServerCommand): SliceGeneratedEvent;
|
|
71
75
|
export declare function emitSliceGenerationFailedForDuplicates(duplicateCommands: DuplicateCommandInfo[], spec: Model, command: GenerateServerCommand, events: GenerateServerEvents[]): void;
|
|
72
76
|
export declare function emitSliceGenerationFailedForFieldIssues(fieldIssues: FieldIssue[], spec: Model, command: GenerateServerCommand, events: GenerateServerEvents[]): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-server.d.ts","sourceRoot":"","sources":["../../../src/commands/generate-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,OAAO,EAAwB,KAAK,KAAK,EAAE,MAAM,4BAA4B,CAAC;AAC5F,OAAO,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAIxE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,UAAU,
|
|
1
|
+
{"version":3,"file":"generate-server.d.ts","sourceRoot":"","sources":["../../../src/commands/generate-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@ import { defineCommandHandler } from '@auto-engineer/message-bus';
|
|
|
6
6
|
import createDebug from 'debug';
|
|
7
7
|
import { execa } from 'execa';
|
|
8
8
|
import fs from 'fs-extra';
|
|
9
|
-
import { generateScaffoldFilePlans, writeScaffoldFilePlans, } from '../codegen/scaffoldFromSchema.js';
|
|
9
|
+
import { generateScaffoldFilePlans, ScaffoldError, TemplateRenderError, writeScaffoldFilePlans, } from '../codegen/scaffoldFromSchema.js';
|
|
10
10
|
import { ensureDirExists, ensureDirPath, toKebabCase } from '../codegen/utils/path.js';
|
|
11
11
|
const debug = createDebug('auto:server-generator-apollo-emmett');
|
|
12
12
|
const debugFiles = createDebug('auto:server-generator-apollo-emmett:files');
|
|
@@ -167,9 +167,33 @@ function stripAnsiCodes(text) {
|
|
|
167
167
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: intentionally matching ANSI escape sequences
|
|
168
168
|
return text.replace(/\x1B\[[0-9;]*m/g, '');
|
|
169
169
|
}
|
|
170
|
-
function createServerFailureEvent(command, error) {
|
|
170
|
+
export function createServerFailureEvent(command, error) {
|
|
171
171
|
debug('Server generation failed with error: %O', error);
|
|
172
|
-
|
|
172
|
+
let flowName;
|
|
173
|
+
let sliceName;
|
|
174
|
+
let sliceType;
|
|
175
|
+
let templateFile;
|
|
176
|
+
let errorMessage;
|
|
177
|
+
if (error instanceof Error) {
|
|
178
|
+
let current = error;
|
|
179
|
+
while (current instanceof Error) {
|
|
180
|
+
if (current instanceof ScaffoldError) {
|
|
181
|
+
flowName = current.flowName;
|
|
182
|
+
sliceName = current.sliceName;
|
|
183
|
+
sliceType = current.sliceType;
|
|
184
|
+
}
|
|
185
|
+
if (current instanceof TemplateRenderError) {
|
|
186
|
+
templateFile = current.templateFile;
|
|
187
|
+
}
|
|
188
|
+
current = current.cause;
|
|
189
|
+
}
|
|
190
|
+
const rootError = findRootCause(error);
|
|
191
|
+
const baseMessage = stripAnsiCodes(rootError.stack ?? rootError.message);
|
|
192
|
+
errorMessage = templateFile !== undefined ? `[template=${templateFile}] ${baseMessage}` : baseMessage;
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
errorMessage = 'Unknown error occurred';
|
|
196
|
+
}
|
|
173
197
|
return {
|
|
174
198
|
type: 'ServerGenerationFailed',
|
|
175
199
|
data: {
|
|
@@ -177,12 +201,22 @@ function createServerFailureEvent(command, error) {
|
|
|
177
201
|
destination: command.data.destination,
|
|
178
202
|
error: errorMessage,
|
|
179
203
|
model: command.data.model,
|
|
204
|
+
flowName,
|
|
205
|
+
sliceName,
|
|
206
|
+
sliceType,
|
|
180
207
|
},
|
|
181
208
|
timestamp: new Date(),
|
|
182
209
|
requestId: command.requestId,
|
|
183
210
|
correlationId: command.correlationId,
|
|
184
211
|
};
|
|
185
212
|
}
|
|
213
|
+
function findRootCause(error) {
|
|
214
|
+
let current = error;
|
|
215
|
+
while (current.cause instanceof Error) {
|
|
216
|
+
current = current.cause;
|
|
217
|
+
}
|
|
218
|
+
return current;
|
|
219
|
+
}
|
|
186
220
|
export function createSliceGeneratedEvent(flow, slice, command) {
|
|
187
221
|
return {
|
|
188
222
|
type: 'SliceGenerated',
|