@auto-engineer/server-generator-apollo-emmett 1.88.0 → 1.90.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 +5 -5
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +90 -0
- package/dist/src/codegen/extract/slice-normalizer.d.ts.map +1 -1
- package/dist/src/codegen/extract/slice-normalizer.js +14 -0
- package/dist/src/codegen/extract/slice-normalizer.js.map +1 -1
- package/dist/src/codegen/extract/type-helpers.d.ts +10 -0
- package/dist/src/codegen/extract/type-helpers.d.ts.map +1 -1
- package/dist/src/codegen/extract/type-helpers.js +17 -0
- package/dist/src/codegen/extract/type-helpers.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +6 -4
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +293 -34
- package/dist/src/codegen/templates/command/decide.specs.ts +34 -14
- package/dist/src/codegen/templates/command/decide.specs.ts.ejs +47 -14
- package/dist/src/codegen/templates/command/decide.ts.ejs +32 -4
- package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +72 -1
- package/dist/src/codegen/templates/command/mutation.resolver.ts.ejs +1 -1
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +124 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +20 -0
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +5 -1
- package/dist/src/codegen/templates/query/projection.ts.ejs +5 -0
- package/dist/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
- package/dist/src/codegen/templates/react/react.specs.specs.ts +115 -0
- package/dist/src/codegen/templates/react/react.specs.ts +9 -2
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +1 -3
- package/dist/src/codegen/templates/react/react.ts.ejs +22 -9
- package/dist/src/codegen/templates/react/react.ts.specs.ts +253 -0
- package/dist/src/codegen/templates/react/register.specs.ts +27 -23
- package/dist/src/codegen/templates/react/register.ts.ejs +5 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/ketchup-plan.md +12 -3
- package/package.json +4 -4
- package/src/codegen/extract/slice-normalizer.specs.ts +83 -0
- package/src/codegen/extract/slice-normalizer.ts +15 -0
- package/src/codegen/extract/type-helpers.specs.ts +77 -1
- package/src/codegen/extract/type-helpers.ts +23 -0
- package/src/codegen/formatTsValueSimple.specs.ts +8 -0
- package/src/codegen/scaffoldFromSchema.ts +7 -3
- package/src/codegen/templates/command/decide.specs.specs.ts +293 -34
- package/src/codegen/templates/command/decide.specs.ts +34 -14
- package/src/codegen/templates/command/decide.specs.ts.ejs +47 -14
- package/src/codegen/templates/command/decide.ts.ejs +32 -4
- package/src/codegen/templates/command/mutation.resolver.specs.ts +72 -1
- package/src/codegen/templates/command/mutation.resolver.ts.ejs +1 -1
- package/src/codegen/templates/query/projection.specs.specs.ts +124 -0
- package/src/codegen/templates/query/projection.specs.ts +20 -0
- package/src/codegen/templates/query/projection.specs.ts.ejs +5 -1
- package/src/codegen/templates/query/projection.ts.ejs +5 -0
- package/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
- package/src/codegen/templates/react/react.specs.specs.ts +115 -0
- package/src/codegen/templates/react/react.specs.ts +9 -2
- package/src/codegen/templates/react/react.specs.ts.ejs +1 -3
- package/src/codegen/templates/react/react.ts.ejs +22 -9
- package/src/codegen/templates/react/react.ts.specs.ts +253 -0
- package/src/codegen/templates/react/register.specs.ts +27 -23
- package/src/codegen/templates/react/register.ts.ejs +5 -1
- package/dist/src/codegen/extract/graphql.d.ts +0 -14
- package/dist/src/codegen/extract/graphql.d.ts.map +0 -1
- package/dist/src/codegen/extract/graphql.js +0 -81
- package/dist/src/codegen/extract/graphql.js.map +0 -1
- package/src/codegen/extract/graphql.ts +0 -103
|
@@ -194,6 +194,8 @@ describe('react.ts.ejs', () => {
|
|
|
194
194
|
expect(reactFile?.contents).toContain('commandSender.send({');
|
|
195
195
|
expect(reactFile?.contents).toContain('event.data.orderId');
|
|
196
196
|
expect(reactFile?.contents).toContain('undefined, // TODO: source unknown');
|
|
197
|
+
expect(reactFile?.contents).toContain('NEVER hardcode values copied from test assertions');
|
|
198
|
+
expect(reactFile?.contents).toContain('Preserve all import paths');
|
|
197
199
|
expect(reactFile?.contents).not.toContain('throw new IllegalStateError');
|
|
198
200
|
});
|
|
199
201
|
|
|
@@ -551,6 +553,8 @@ export type BarberNotified = Event<
|
|
|
551
553
|
const reactFile = plans.find((p) => p.outputPath.endsWith('notify-barber-of-cancellation/react.ts'));
|
|
552
554
|
|
|
553
555
|
expect(reactFile?.contents).toContain('aggregateStream');
|
|
556
|
+
expect(reactFile?.contents).toContain('Record<string, unknown>');
|
|
557
|
+
expect(reactFile?.contents).toContain('Do NOT modify or remove aggregateStream');
|
|
554
558
|
expect(reactFile?.contents).toContain('event.data.barberId');
|
|
555
559
|
expect(reactFile?.contents).toContain('appointment');
|
|
556
560
|
expect(reactFile?.contents).not.toContain('readable from database');
|
|
@@ -562,4 +566,253 @@ export type BarberNotified = Event<
|
|
|
562
566
|
expect(reactFile?.contents).not.toContain('throw new IllegalStateError');
|
|
563
567
|
expect(reactFile?.contents).not.toContain('// Example:');
|
|
564
568
|
});
|
|
569
|
+
|
|
570
|
+
it('should ignore event And-steps in react Then, using only commands', async () => {
|
|
571
|
+
const spec: SpecsSchema = {
|
|
572
|
+
variant: 'specs',
|
|
573
|
+
narratives: [
|
|
574
|
+
{
|
|
575
|
+
name: 'notification flow',
|
|
576
|
+
slices: [
|
|
577
|
+
{
|
|
578
|
+
type: 'command',
|
|
579
|
+
name: 'earn points',
|
|
580
|
+
client: { specs: [] },
|
|
581
|
+
server: {
|
|
582
|
+
description: '',
|
|
583
|
+
specs: [
|
|
584
|
+
{
|
|
585
|
+
type: 'gherkin',
|
|
586
|
+
feature: 'Earn points',
|
|
587
|
+
rules: [
|
|
588
|
+
{
|
|
589
|
+
name: 'Should earn',
|
|
590
|
+
examples: [
|
|
591
|
+
{
|
|
592
|
+
name: 'Points earned',
|
|
593
|
+
steps: [
|
|
594
|
+
{ keyword: 'When', text: 'EarnPoints', docString: { memberId: 'm1', amount: 10 } },
|
|
595
|
+
{ keyword: 'Then', text: 'PointsEarned', docString: { memberId: 'm1', amount: 10 } },
|
|
596
|
+
],
|
|
597
|
+
},
|
|
598
|
+
],
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
},
|
|
602
|
+
],
|
|
603
|
+
},
|
|
604
|
+
},
|
|
605
|
+
{
|
|
606
|
+
type: 'react',
|
|
607
|
+
name: 'notify milestone',
|
|
608
|
+
server: {
|
|
609
|
+
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
|
+
specs: [
|
|
619
|
+
{
|
|
620
|
+
type: 'gherkin',
|
|
621
|
+
feature: 'Milestone notification',
|
|
622
|
+
rules: [
|
|
623
|
+
{
|
|
624
|
+
name: 'Should notify',
|
|
625
|
+
examples: [
|
|
626
|
+
{
|
|
627
|
+
name: 'Milestone reached',
|
|
628
|
+
steps: [
|
|
629
|
+
{ keyword: 'When', text: 'PointsEarned', docString: { memberId: 'm1', amount: 10 } },
|
|
630
|
+
{ keyword: 'Then', text: 'MilestoneNotified', docString: { memberId: 'm1' } },
|
|
631
|
+
{ keyword: 'And', text: 'SendNotification', docString: { memberId: 'm1' } },
|
|
632
|
+
],
|
|
633
|
+
},
|
|
634
|
+
],
|
|
635
|
+
},
|
|
636
|
+
],
|
|
637
|
+
},
|
|
638
|
+
],
|
|
639
|
+
},
|
|
640
|
+
},
|
|
641
|
+
],
|
|
642
|
+
},
|
|
643
|
+
],
|
|
644
|
+
messages: [
|
|
645
|
+
{
|
|
646
|
+
type: 'command',
|
|
647
|
+
name: 'EarnPoints',
|
|
648
|
+
fields: [
|
|
649
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
650
|
+
{ name: 'amount', type: 'number', required: true },
|
|
651
|
+
],
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
type: 'event',
|
|
655
|
+
name: 'PointsEarned',
|
|
656
|
+
source: 'internal',
|
|
657
|
+
fields: [
|
|
658
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
659
|
+
{ name: 'amount', type: 'number', required: true },
|
|
660
|
+
],
|
|
661
|
+
},
|
|
662
|
+
{
|
|
663
|
+
type: 'command',
|
|
664
|
+
name: 'SendNotification',
|
|
665
|
+
fields: [{ name: 'memberId', type: 'string', required: true }],
|
|
666
|
+
},
|
|
667
|
+
{
|
|
668
|
+
type: 'event',
|
|
669
|
+
name: 'MilestoneNotified',
|
|
670
|
+
source: 'internal',
|
|
671
|
+
fields: [{ name: 'memberId', type: 'string', required: true }],
|
|
672
|
+
},
|
|
673
|
+
],
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
|
|
677
|
+
const reactFile = plans.find((p) => p.outputPath.endsWith('notify-milestone/react.ts'));
|
|
678
|
+
|
|
679
|
+
expect(reactFile?.contents).toContain("type: 'SendNotification'");
|
|
680
|
+
expect(reactFile?.contents).not.toContain('MilestoneNotified');
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
it('should use primitive linking field, skipping array/object fields', async () => {
|
|
684
|
+
const spec: SpecsSchema = {
|
|
685
|
+
variant: 'specs',
|
|
686
|
+
narratives: [
|
|
687
|
+
{
|
|
688
|
+
name: 'fitness flow',
|
|
689
|
+
slices: [
|
|
690
|
+
{
|
|
691
|
+
type: 'command',
|
|
692
|
+
name: 'log workout',
|
|
693
|
+
client: { specs: [] },
|
|
694
|
+
server: {
|
|
695
|
+
description: '',
|
|
696
|
+
specs: [
|
|
697
|
+
{
|
|
698
|
+
type: 'gherkin',
|
|
699
|
+
feature: 'Log workout',
|
|
700
|
+
rules: [
|
|
701
|
+
{
|
|
702
|
+
name: 'Should log workout',
|
|
703
|
+
examples: [
|
|
704
|
+
{
|
|
705
|
+
name: 'Workout logged',
|
|
706
|
+
steps: [
|
|
707
|
+
{
|
|
708
|
+
keyword: 'When',
|
|
709
|
+
text: 'LogWorkout',
|
|
710
|
+
docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
keyword: 'Then',
|
|
714
|
+
text: 'WorkoutLogged',
|
|
715
|
+
docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
],
|
|
720
|
+
},
|
|
721
|
+
],
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
},
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
type: 'react',
|
|
728
|
+
name: 'check record updates',
|
|
729
|
+
server: {
|
|
730
|
+
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
|
+
specs: [
|
|
740
|
+
{
|
|
741
|
+
type: 'gherkin',
|
|
742
|
+
feature: 'Check record updates',
|
|
743
|
+
rules: [
|
|
744
|
+
{
|
|
745
|
+
name: 'Should update records',
|
|
746
|
+
examples: [
|
|
747
|
+
{
|
|
748
|
+
name: 'Record updated',
|
|
749
|
+
steps: [
|
|
750
|
+
{
|
|
751
|
+
keyword: 'Given',
|
|
752
|
+
text: 'WorkoutSummary',
|
|
753
|
+
docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
keyword: 'When',
|
|
757
|
+
text: 'WorkoutLogged',
|
|
758
|
+
docString: { memberId: 'm1', exercises: [{ exercise: 'squat', sets: 3 }] },
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
keyword: 'Then',
|
|
762
|
+
text: 'UpdateRecord',
|
|
763
|
+
docString: { memberId: 'm1' },
|
|
764
|
+
},
|
|
765
|
+
],
|
|
766
|
+
},
|
|
767
|
+
],
|
|
768
|
+
},
|
|
769
|
+
],
|
|
770
|
+
},
|
|
771
|
+
],
|
|
772
|
+
},
|
|
773
|
+
},
|
|
774
|
+
],
|
|
775
|
+
},
|
|
776
|
+
],
|
|
777
|
+
messages: [
|
|
778
|
+
{
|
|
779
|
+
type: 'command',
|
|
780
|
+
name: 'LogWorkout',
|
|
781
|
+
fields: [
|
|
782
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
783
|
+
{ name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
|
|
784
|
+
],
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
type: 'event',
|
|
788
|
+
name: 'WorkoutLogged',
|
|
789
|
+
source: 'internal',
|
|
790
|
+
fields: [
|
|
791
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
792
|
+
{ name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
|
|
793
|
+
],
|
|
794
|
+
},
|
|
795
|
+
{
|
|
796
|
+
type: 'command',
|
|
797
|
+
name: 'UpdateRecord',
|
|
798
|
+
fields: [{ name: 'memberId', type: 'string', required: true }],
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
type: 'state',
|
|
802
|
+
name: 'WorkoutSummary',
|
|
803
|
+
fields: [
|
|
804
|
+
{ name: 'memberId', type: 'string', required: true },
|
|
805
|
+
{ name: 'exercises', type: '{exercise:string,sets:number}[]', required: true },
|
|
806
|
+
],
|
|
807
|
+
},
|
|
808
|
+
],
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
|
|
812
|
+
const reactFile = plans.find((p) => p.outputPath.endsWith('check-record-updates/react.ts'));
|
|
813
|
+
|
|
814
|
+
expect(reactFile?.contents).toContain("'WorkoutSummary-' + event.data.memberId");
|
|
815
|
+
expect(reactFile?.contents).toContain('aggregateStream');
|
|
816
|
+
expect(reactFile?.contents).not.toContain('event.data.exercises');
|
|
817
|
+
});
|
|
565
818
|
});
|
|
@@ -232,31 +232,35 @@ describe('register.ts.ejs (react slice)', () => {
|
|
|
232
232
|
const registerFile = plans.find((p) => p.outputPath.endsWith('send-notification-to-host/register.ts'));
|
|
233
233
|
|
|
234
234
|
expect(registerFile?.contents).toMatchInlineSnapshot(`
|
|
235
|
-
|
|
236
|
-
|
|
235
|
+
"import { type CommandSender, type EventSubscription, type EventStore } from '@event-driven-io/emmett';
|
|
236
|
+
import type { BookingRequested } from '../guest-submits-booking-request/events';
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
238
|
+
export async function register(messageBus: CommandSender & EventSubscription, eventStore: EventStore) {
|
|
239
|
+
messageBus.subscribe(async (event: BookingRequested) => {
|
|
240
|
+
/**
|
|
241
|
+
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
242
|
+
*
|
|
243
|
+
* - Replace the placeholder logic with the real implementation.
|
|
244
|
+
* - Send one or more commands via: messageBus.send({...})
|
|
245
|
+
* - If calling eventStore.aggregateStream(), type the evolve callback:
|
|
246
|
+
* evolve: (s: Record<string, unknown>, e: { type: string; data: Record<string, unknown> }) => ...
|
|
247
|
+
* - NEVER hardcode values copied from test assertions.
|
|
248
|
+
* - Preserve all import paths above — they are generated from the model.
|
|
249
|
+
*/
|
|
246
250
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
251
|
+
// await messageBus.send({
|
|
252
|
+
// type: 'NotifyHost',
|
|
253
|
+
// kind: 'Command',
|
|
254
|
+
// data: {
|
|
255
|
+
// // Map event fields to command fields here
|
|
256
|
+
// // e.g., userId: event.data.userId,
|
|
257
|
+
// },
|
|
258
|
+
// });
|
|
255
259
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
260
|
+
return;
|
|
261
|
+
}, 'BookingRequested');
|
|
262
|
+
}
|
|
263
|
+
"
|
|
264
|
+
`);
|
|
261
265
|
});
|
|
262
266
|
});
|
|
@@ -32,8 +32,12 @@ async (event: <%= pascalCase(eventType) %>) => {
|
|
|
32
32
|
/**
|
|
33
33
|
* ## IMPLEMENTATION INSTRUCTIONS ##
|
|
34
34
|
*
|
|
35
|
-
* - Replace the placeholder logic with the
|
|
35
|
+
* - Replace the placeholder logic with the real implementation.
|
|
36
36
|
* - Send one or more commands via: messageBus.send({...})
|
|
37
|
+
* - If calling eventStore.aggregateStream(), type the evolve callback:
|
|
38
|
+
* evolve: (s: Record<string, unknown>, e: { type: string; data: Record<string, unknown> }) => ...
|
|
39
|
+
* - NEVER hardcode values copied from test assertions.
|
|
40
|
+
* - Preserve all import paths above — they are generated from the model.
|
|
37
41
|
*/
|
|
38
42
|
|
|
39
43
|
// await messageBus.send({
|