@auto-engineer/server-generator-apollo-emmett 0.11.12 → 0.11.14
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/CHANGELOG.md +16 -0
- package/dist/src/codegen/extract/commands.d.ts +2 -2
- package/dist/src/codegen/extract/commands.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts +2 -11
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +4 -1
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +368 -0
- package/dist/src/codegen/templates/query/projection.specs.ts +404 -0
- package/dist/src/codegen/templates/query/projection.ts.ejs +64 -11
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/codegen/extract/commands.ts +2 -2
- package/src/codegen/extract/messages.ts +2 -13
- package/src/codegen/scaffoldFromSchema.ts +9 -1
- package/src/codegen/templates/query/projection.specs.specs.ts +368 -0
- package/src/codegen/templates/query/projection.specs.ts +404 -0
- package/src/codegen/templates/query/projection.ts.ejs +64 -11
package/package.json
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"graphql-type-json": "^0.3.2",
|
|
32
32
|
"uuid": "^11.0.0",
|
|
33
33
|
"web-streams-polyfill": "^4.1.0",
|
|
34
|
-
"@auto-engineer/
|
|
35
|
-
"@auto-engineer/
|
|
34
|
+
"@auto-engineer/message-bus": "0.11.14",
|
|
35
|
+
"@auto-engineer/narrative": "0.11.14"
|
|
36
36
|
},
|
|
37
37
|
"publishConfig": {
|
|
38
38
|
"access": "public"
|
|
@@ -43,9 +43,9 @@
|
|
|
43
43
|
"typescript": "^5.8.3",
|
|
44
44
|
"vitest": "^3.2.4",
|
|
45
45
|
"tsx": "^4.19.2",
|
|
46
|
-
"@auto-engineer/cli": "0.11.
|
|
46
|
+
"@auto-engineer/cli": "0.11.14"
|
|
47
47
|
},
|
|
48
|
-
"version": "0.11.
|
|
48
|
+
"version": "0.11.14",
|
|
49
49
|
"scripts": {
|
|
50
50
|
"generate:server": "tsx src/cli/index.ts",
|
|
51
51
|
"build": "tsc && tsx ../../scripts/fix-esm-imports.ts && rm -rf dist/src/codegen/templates && mkdir -p dist/src/codegen && cp -r src/codegen/templates dist/src/codegen/templates && cp src/server.ts dist/src && cp -r src/utils dist/src && cp -r src/domain dist/src",
|
|
@@ -23,7 +23,7 @@ function createCommandMessage(
|
|
|
23
23
|
export function extractCommandsFromGwt(
|
|
24
24
|
gwtSpecs: Array<{
|
|
25
25
|
given?: Array<EventExample | unknown>;
|
|
26
|
-
when
|
|
26
|
+
when?: CommandExample | EventExample | unknown[];
|
|
27
27
|
then: Array<EventExample | unknown | { errorType: string; message?: string }>;
|
|
28
28
|
}>,
|
|
29
29
|
allMessages: MessageDefinition[],
|
|
@@ -86,7 +86,7 @@ function processCommandExample(
|
|
|
86
86
|
export function extractCommandsFromThen(
|
|
87
87
|
gwtSpecs: Array<{
|
|
88
88
|
given?: Array<EventExample | unknown>;
|
|
89
|
-
when
|
|
89
|
+
when?: CommandExample | EventExample | unknown[];
|
|
90
90
|
then: Array<EventExample | unknown | { errorType: string; message?: string }>;
|
|
91
91
|
}>,
|
|
92
92
|
allMessages: MessageDefinition[],
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { extractCommandsFromGwt, extractCommandsFromThen } from './commands';
|
|
2
|
-
import { CommandExample,
|
|
2
|
+
import { CommandExample, EventExample, Slice } from '@auto-engineer/narrative';
|
|
3
3
|
import { Message, MessageDefinition } from '../types';
|
|
4
4
|
import { extractEventsFromGiven, extractEventsFromThen, extractEventsFromWhen } from './events';
|
|
5
5
|
import { extractFieldsFromMessage } from './fields';
|
|
@@ -22,21 +22,10 @@ export interface ExtractedMessages {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
export interface ReactGwtSpec {
|
|
25
|
-
when
|
|
25
|
+
when?: EventExample[];
|
|
26
26
|
then: CommandExample[];
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export interface CommandGwtSpec {
|
|
30
|
-
given?: EventExample[];
|
|
31
|
-
when: CommandExample;
|
|
32
|
-
then: Array<EventExample | ErrorExample>;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export interface QueryGwtSpec {
|
|
36
|
-
given: EventExample[];
|
|
37
|
-
then: StateExample[];
|
|
38
|
-
}
|
|
39
|
-
|
|
40
29
|
const EMPTY_EXTRACTED_MESSAGES: ExtractedMessages = {
|
|
41
30
|
commands: [],
|
|
42
31
|
events: [],
|
|
@@ -634,7 +634,15 @@ function findCommandSource(flows: Narrative[], commandType: string): { flowName:
|
|
|
634
634
|
})),
|
|
635
635
|
)
|
|
636
636
|
: [];
|
|
637
|
-
if (
|
|
637
|
+
if (
|
|
638
|
+
gwtSpecs.some(
|
|
639
|
+
(g) =>
|
|
640
|
+
g.when !== undefined &&
|
|
641
|
+
!Array.isArray(g.when) &&
|
|
642
|
+
'commandRef' in g.when &&
|
|
643
|
+
g.when.commandRef === commandType,
|
|
644
|
+
)
|
|
645
|
+
) {
|
|
638
646
|
debugSlice(' Found command source in flow: %s, slice: %s', flow.name, slice.name);
|
|
639
647
|
return { flowName: flow.name, sliceName: slice.name };
|
|
640
648
|
}
|
|
@@ -620,4 +620,372 @@ describe('projection.specs.ts.ejs', () => {
|
|
|
620
620
|
// canHandle must include BOTH events
|
|
621
621
|
expect(projectionFile?.contents).toContain("canHandle: ['QuestionnaireLinkSent', 'QuestionAnswered']");
|
|
622
622
|
});
|
|
623
|
+
|
|
624
|
+
it('should generate a valid test spec for singleton projection', async () => {
|
|
625
|
+
const spec: SpecsSchema = {
|
|
626
|
+
variant: 'specs',
|
|
627
|
+
narratives: [
|
|
628
|
+
{
|
|
629
|
+
name: 'todo-flow',
|
|
630
|
+
slices: [
|
|
631
|
+
{
|
|
632
|
+
type: 'command',
|
|
633
|
+
name: 'manage-todo',
|
|
634
|
+
stream: 'todo-${todoId}',
|
|
635
|
+
client: { description: '' },
|
|
636
|
+
server: {
|
|
637
|
+
description: '',
|
|
638
|
+
specs: {
|
|
639
|
+
name: 'Manage todo command',
|
|
640
|
+
rules: [
|
|
641
|
+
{
|
|
642
|
+
description: 'Should handle todo operations',
|
|
643
|
+
examples: [
|
|
644
|
+
{
|
|
645
|
+
description: 'User adds todo',
|
|
646
|
+
when: {
|
|
647
|
+
commandRef: 'AddTodo',
|
|
648
|
+
exampleData: {
|
|
649
|
+
todoId: 'todo_123',
|
|
650
|
+
title: 'Buy milk',
|
|
651
|
+
},
|
|
652
|
+
},
|
|
653
|
+
then: [
|
|
654
|
+
{
|
|
655
|
+
eventRef: 'TodoAdded',
|
|
656
|
+
exampleData: {
|
|
657
|
+
todoId: 'todo_123',
|
|
658
|
+
title: 'Buy milk',
|
|
659
|
+
},
|
|
660
|
+
},
|
|
661
|
+
],
|
|
662
|
+
},
|
|
663
|
+
],
|
|
664
|
+
},
|
|
665
|
+
],
|
|
666
|
+
},
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
type: 'query',
|
|
671
|
+
name: 'view-summary',
|
|
672
|
+
stream: 'todos',
|
|
673
|
+
client: { description: '' },
|
|
674
|
+
server: {
|
|
675
|
+
description: '',
|
|
676
|
+
data: [
|
|
677
|
+
{
|
|
678
|
+
target: {
|
|
679
|
+
type: 'State',
|
|
680
|
+
name: 'TodoSummary',
|
|
681
|
+
},
|
|
682
|
+
origin: {
|
|
683
|
+
type: 'projection',
|
|
684
|
+
name: 'TodoSummaryProjection',
|
|
685
|
+
singleton: true,
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
],
|
|
689
|
+
specs: {
|
|
690
|
+
name: 'View summary query',
|
|
691
|
+
rules: [
|
|
692
|
+
{
|
|
693
|
+
description: 'Should aggregate todo counts',
|
|
694
|
+
examples: [
|
|
695
|
+
{
|
|
696
|
+
description: 'Todo added updates count',
|
|
697
|
+
when: [
|
|
698
|
+
{
|
|
699
|
+
eventRef: 'TodoAdded',
|
|
700
|
+
exampleData: {
|
|
701
|
+
todoId: 'todo_123',
|
|
702
|
+
title: 'Buy milk',
|
|
703
|
+
},
|
|
704
|
+
},
|
|
705
|
+
],
|
|
706
|
+
then: [
|
|
707
|
+
{
|
|
708
|
+
stateRef: 'TodoSummary',
|
|
709
|
+
exampleData: {
|
|
710
|
+
totalCount: 1,
|
|
711
|
+
},
|
|
712
|
+
},
|
|
713
|
+
],
|
|
714
|
+
},
|
|
715
|
+
],
|
|
716
|
+
},
|
|
717
|
+
],
|
|
718
|
+
},
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
],
|
|
722
|
+
},
|
|
723
|
+
],
|
|
724
|
+
messages: [
|
|
725
|
+
{
|
|
726
|
+
type: 'command',
|
|
727
|
+
name: 'AddTodo',
|
|
728
|
+
fields: [
|
|
729
|
+
{ name: 'todoId', type: 'string', required: true },
|
|
730
|
+
{ name: 'title', type: 'string', required: true },
|
|
731
|
+
],
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
type: 'event',
|
|
735
|
+
name: 'TodoAdded',
|
|
736
|
+
source: 'internal',
|
|
737
|
+
fields: [
|
|
738
|
+
{ name: 'todoId', type: 'string', required: true },
|
|
739
|
+
{ name: 'title', type: 'string', required: true },
|
|
740
|
+
],
|
|
741
|
+
},
|
|
742
|
+
{
|
|
743
|
+
type: 'state',
|
|
744
|
+
name: 'TodoSummary',
|
|
745
|
+
fields: [{ name: 'totalCount', type: 'number', required: true }],
|
|
746
|
+
},
|
|
747
|
+
],
|
|
748
|
+
} as SpecsSchema;
|
|
749
|
+
|
|
750
|
+
const plans = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
|
|
751
|
+
const specFile = plans.find((p) => p.outputPath.endsWith('view-summary/projection.specs.ts'));
|
|
752
|
+
|
|
753
|
+
expect(specFile?.contents).toMatchInlineSnapshot(`
|
|
754
|
+
"import { describe, it, beforeEach, expect } from 'vitest';
|
|
755
|
+
import { InMemoryProjectionSpec } from '@event-driven-io/emmett';
|
|
756
|
+
import { projection } from './projection';
|
|
757
|
+
import type { TodoAdded } from '../manage-todo/events';
|
|
758
|
+
import { TodoSummary } from './state';
|
|
759
|
+
|
|
760
|
+
type ProjectionEvent = TodoAdded;
|
|
761
|
+
|
|
762
|
+
describe('Should aggregate todo counts', () => {
|
|
763
|
+
let given: InMemoryProjectionSpec<ProjectionEvent>;
|
|
764
|
+
|
|
765
|
+
beforeEach(() => {
|
|
766
|
+
given = InMemoryProjectionSpec.for({ projection });
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it('Todo added updates count', () =>
|
|
770
|
+
given([])
|
|
771
|
+
.when([
|
|
772
|
+
{
|
|
773
|
+
type: 'TodoAdded',
|
|
774
|
+
data: {
|
|
775
|
+
todoId: 'todo_123',
|
|
776
|
+
title: 'Buy milk',
|
|
777
|
+
},
|
|
778
|
+
metadata: {
|
|
779
|
+
streamName: 'todos',
|
|
780
|
+
streamPosition: 1n,
|
|
781
|
+
globalPosition: 1n,
|
|
782
|
+
},
|
|
783
|
+
},
|
|
784
|
+
])
|
|
785
|
+
.then(async (state) => {
|
|
786
|
+
const document = await state.database
|
|
787
|
+
.collection<TodoSummary>('TodoSummaryProjection')
|
|
788
|
+
.findOne((doc) => doc.id === 'test-id');
|
|
789
|
+
|
|
790
|
+
const expected: TodoSummary = {
|
|
791
|
+
totalCount: 1,
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
expect(document).toMatchObject(expected);
|
|
795
|
+
}));
|
|
796
|
+
});
|
|
797
|
+
"
|
|
798
|
+
`);
|
|
799
|
+
});
|
|
800
|
+
|
|
801
|
+
it('should generate a valid test spec for composite key projection', async () => {
|
|
802
|
+
const spec: SpecsSchema = {
|
|
803
|
+
variant: 'specs',
|
|
804
|
+
narratives: [
|
|
805
|
+
{
|
|
806
|
+
name: 'user-project-flow',
|
|
807
|
+
slices: [
|
|
808
|
+
{
|
|
809
|
+
type: 'command',
|
|
810
|
+
name: 'manage-user-project',
|
|
811
|
+
stream: 'user-project-${userId}-${projectId}',
|
|
812
|
+
client: { description: '' },
|
|
813
|
+
server: {
|
|
814
|
+
description: '',
|
|
815
|
+
specs: {
|
|
816
|
+
name: 'Manage user project command',
|
|
817
|
+
rules: [
|
|
818
|
+
{
|
|
819
|
+
description: 'Should handle user project operations',
|
|
820
|
+
examples: [
|
|
821
|
+
{
|
|
822
|
+
description: 'User joins project',
|
|
823
|
+
when: {
|
|
824
|
+
commandRef: 'JoinProject',
|
|
825
|
+
exampleData: {
|
|
826
|
+
userId: 'user_123',
|
|
827
|
+
projectId: 'proj_456',
|
|
828
|
+
role: 'developer',
|
|
829
|
+
},
|
|
830
|
+
},
|
|
831
|
+
then: [
|
|
832
|
+
{
|
|
833
|
+
eventRef: 'UserJoinedProject',
|
|
834
|
+
exampleData: {
|
|
835
|
+
userId: 'user_123',
|
|
836
|
+
projectId: 'proj_456',
|
|
837
|
+
role: 'developer',
|
|
838
|
+
},
|
|
839
|
+
},
|
|
840
|
+
],
|
|
841
|
+
},
|
|
842
|
+
],
|
|
843
|
+
},
|
|
844
|
+
],
|
|
845
|
+
},
|
|
846
|
+
},
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
type: 'query',
|
|
850
|
+
name: 'view-user-projects',
|
|
851
|
+
stream: 'user-projects',
|
|
852
|
+
client: { description: '' },
|
|
853
|
+
server: {
|
|
854
|
+
description: '',
|
|
855
|
+
data: [
|
|
856
|
+
{
|
|
857
|
+
target: {
|
|
858
|
+
type: 'State',
|
|
859
|
+
name: 'UserProject',
|
|
860
|
+
},
|
|
861
|
+
origin: {
|
|
862
|
+
type: 'projection',
|
|
863
|
+
name: 'UserProjectsProjection',
|
|
864
|
+
idField: ['userId', 'projectId'],
|
|
865
|
+
},
|
|
866
|
+
},
|
|
867
|
+
],
|
|
868
|
+
specs: {
|
|
869
|
+
name: 'View user projects query',
|
|
870
|
+
rules: [
|
|
871
|
+
{
|
|
872
|
+
description: 'Should track user project memberships',
|
|
873
|
+
examples: [
|
|
874
|
+
{
|
|
875
|
+
description: 'User joins project',
|
|
876
|
+
when: [
|
|
877
|
+
{
|
|
878
|
+
eventRef: 'UserJoinedProject',
|
|
879
|
+
exampleData: {
|
|
880
|
+
userId: 'user_123',
|
|
881
|
+
projectId: 'proj_456',
|
|
882
|
+
role: 'developer',
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
],
|
|
886
|
+
then: [
|
|
887
|
+
{
|
|
888
|
+
stateRef: 'UserProject',
|
|
889
|
+
exampleData: {
|
|
890
|
+
userId: 'user_123',
|
|
891
|
+
projectId: 'proj_456',
|
|
892
|
+
role: 'developer',
|
|
893
|
+
},
|
|
894
|
+
},
|
|
895
|
+
],
|
|
896
|
+
},
|
|
897
|
+
],
|
|
898
|
+
},
|
|
899
|
+
],
|
|
900
|
+
},
|
|
901
|
+
},
|
|
902
|
+
},
|
|
903
|
+
],
|
|
904
|
+
},
|
|
905
|
+
],
|
|
906
|
+
messages: [
|
|
907
|
+
{
|
|
908
|
+
type: 'command',
|
|
909
|
+
name: 'JoinProject',
|
|
910
|
+
fields: [
|
|
911
|
+
{ name: 'userId', type: 'string', required: true },
|
|
912
|
+
{ name: 'projectId', type: 'string', required: true },
|
|
913
|
+
{ name: 'role', type: 'string', required: true },
|
|
914
|
+
],
|
|
915
|
+
},
|
|
916
|
+
{
|
|
917
|
+
type: 'event',
|
|
918
|
+
name: 'UserJoinedProject',
|
|
919
|
+
source: 'internal',
|
|
920
|
+
fields: [
|
|
921
|
+
{ name: 'userId', type: 'string', required: true },
|
|
922
|
+
{ name: 'projectId', type: 'string', required: true },
|
|
923
|
+
{ name: 'role', type: 'string', required: true },
|
|
924
|
+
],
|
|
925
|
+
},
|
|
926
|
+
{
|
|
927
|
+
type: 'state',
|
|
928
|
+
name: 'UserProject',
|
|
929
|
+
fields: [
|
|
930
|
+
{ name: 'userId', type: 'string', required: true },
|
|
931
|
+
{ name: 'projectId', type: 'string', required: true },
|
|
932
|
+
{ name: 'role', type: 'string', required: true },
|
|
933
|
+
],
|
|
934
|
+
},
|
|
935
|
+
],
|
|
936
|
+
} as SpecsSchema;
|
|
937
|
+
|
|
938
|
+
const plans = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
|
|
939
|
+
const specFile = plans.find((p) => p.outputPath.endsWith('view-user-projects/projection.specs.ts'));
|
|
940
|
+
|
|
941
|
+
expect(specFile?.contents).toMatchInlineSnapshot(`
|
|
942
|
+
"import { describe, it, beforeEach, expect } from 'vitest';
|
|
943
|
+
import { InMemoryProjectionSpec } from '@event-driven-io/emmett';
|
|
944
|
+
import { projection } from './projection';
|
|
945
|
+
import type { UserJoinedProject } from '../manage-user-project/events';
|
|
946
|
+
import { UserProject } from './state';
|
|
947
|
+
|
|
948
|
+
type ProjectionEvent = UserJoinedProject;
|
|
949
|
+
|
|
950
|
+
describe('Should track user project memberships', () => {
|
|
951
|
+
let given: InMemoryProjectionSpec<ProjectionEvent>;
|
|
952
|
+
|
|
953
|
+
beforeEach(() => {
|
|
954
|
+
given = InMemoryProjectionSpec.for({ projection });
|
|
955
|
+
});
|
|
956
|
+
|
|
957
|
+
it('User joins project', () =>
|
|
958
|
+
given([])
|
|
959
|
+
.when([
|
|
960
|
+
{
|
|
961
|
+
type: 'UserJoinedProject',
|
|
962
|
+
data: {
|
|
963
|
+
userId: 'user_123',
|
|
964
|
+
projectId: 'proj_456',
|
|
965
|
+
role: 'developer',
|
|
966
|
+
},
|
|
967
|
+
metadata: {
|
|
968
|
+
streamName: 'user-projects',
|
|
969
|
+
streamPosition: 1n,
|
|
970
|
+
globalPosition: 1n,
|
|
971
|
+
},
|
|
972
|
+
},
|
|
973
|
+
])
|
|
974
|
+
.then(async (state) => {
|
|
975
|
+
const document = await state.database
|
|
976
|
+
.collection<UserProject>('UserProjectsProjection')
|
|
977
|
+
.findOne((doc) => doc.id === 'test-id');
|
|
978
|
+
|
|
979
|
+
const expected: UserProject = {
|
|
980
|
+
userId: 'user_123',
|
|
981
|
+
projectId: 'proj_456',
|
|
982
|
+
role: 'developer',
|
|
983
|
+
};
|
|
984
|
+
|
|
985
|
+
expect(document).toMatchObject(expected);
|
|
986
|
+
}));
|
|
987
|
+
});
|
|
988
|
+
"
|
|
989
|
+
`);
|
|
990
|
+
});
|
|
623
991
|
});
|