@auto-engineer/server-generator-apollo-emmett 1.72.0 → 1.74.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/ketchup-plan.md CHANGED
@@ -1,10 +1,8 @@
1
- # Ketchup Plan: Fix react slice scaffold ignoring Given states
1
+ # Ketchup Plan: Fix query resolver collection name casing mismatch
2
2
 
3
3
  ## TODO
4
4
 
5
5
  ## DONE
6
6
 
7
- - [x] Burst 3: Generate real commandSender.send() in react.ts.ejs with field-source mapping (89610315)
8
-
9
- - [x] Burst 1: Fix react.specs.ts.ejs — seed event store with Given state data via appendToStream (0afa65ca)
10
- - [x] Burst 2: Fix react.ts.ejs — generate aggregateStream pattern when Given states exist (8380e5a2)
7
+ - [x] Burst 1: Add regression test + fix EJS template to use projectionName for collection name (5df3ae89)
8
+ - [x] Burst 2: Remove unused projectionType from scaffoldFromSchema template locals
package/package.json CHANGED
@@ -32,8 +32,8 @@
32
32
  "uuid": "^11.0.0",
33
33
  "web-streams-polyfill": "^4.1.0",
34
34
  "zod": "^3.22.4",
35
- "@auto-engineer/narrative": "1.72.0",
36
- "@auto-engineer/message-bus": "1.72.0"
35
+ "@auto-engineer/narrative": "1.74.0",
36
+ "@auto-engineer/message-bus": "1.74.0"
37
37
  },
38
38
  "publishConfig": {
39
39
  "access": "public"
@@ -44,9 +44,9 @@
44
44
  "typescript": "^5.8.3",
45
45
  "vitest": "^3.2.4",
46
46
  "tsx": "^4.19.2",
47
- "@auto-engineer/cli": "1.72.0"
47
+ "@auto-engineer/cli": "1.74.0"
48
48
  },
49
- "version": "1.72.0",
49
+ "version": "1.74.0",
50
50
  "scripts": {
51
51
  "generate:server": "tsx src/cli/index.ts",
52
52
  "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",
@@ -608,7 +608,6 @@ async function prepareTemplateData(
608
608
  projectionIdField,
609
609
  projectionSingleton,
610
610
  projectionName,
611
- projectionType: projectionName != null ? pascalCase(projectionName) : undefined,
612
611
  parsedRequest: slice.type === 'query' && slice.request != null ? parseGraphQlRequest(slice.request) : undefined,
613
612
  messages: allMessages,
614
613
  message:
@@ -957,6 +957,128 @@ describe('query.resolver.ts.ejs', () => {
957
957
  `);
958
958
  });
959
959
 
960
+ it('should use original-case projection name as collection name, not PascalCase', async () => {
961
+ const spec: SpecsSchema = {
962
+ variant: 'specs',
963
+ narratives: [
964
+ {
965
+ name: 'video-flow',
966
+ slices: [
967
+ {
968
+ type: 'query',
969
+ name: 'browse-videos',
970
+ request: `
971
+ query BrowseVideos($category: String) {
972
+ browseVideos(category: $category) {
973
+ videoId
974
+ title
975
+ }
976
+ }
977
+ `,
978
+ client: { specs: [] },
979
+ server: {
980
+ description: '',
981
+ data: {
982
+ items: [
983
+ {
984
+ origin: {
985
+ type: 'projection',
986
+ idField: 'videoId',
987
+ name: 'videos',
988
+ },
989
+ target: {
990
+ type: 'State',
991
+ name: 'VideoListing',
992
+ },
993
+ },
994
+ ],
995
+ },
996
+ specs: [],
997
+ },
998
+ },
999
+ ],
1000
+ },
1001
+ ],
1002
+ messages: [
1003
+ {
1004
+ type: 'state',
1005
+ name: 'VideoListing',
1006
+ fields: [
1007
+ { name: 'videoId', type: 'string', required: true },
1008
+ { name: 'title', type: 'string', required: true },
1009
+ ],
1010
+ },
1011
+ ],
1012
+ };
1013
+
1014
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
1015
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
1016
+
1017
+ expect(resolverFile?.contents).toContain("new ReadModel<VideoListing>(ctx.database, 'videos')");
1018
+ expect(resolverFile?.contents).not.toContain("'Videos'");
1019
+ });
1020
+
1021
+ it('should use original-case projection name in singleton collection lookup', async () => {
1022
+ const spec: SpecsSchema = {
1023
+ variant: 'specs',
1024
+ narratives: [
1025
+ {
1026
+ name: 'video-flow',
1027
+ slices: [
1028
+ {
1029
+ type: 'query',
1030
+ name: 'views-video-stats',
1031
+ request: `
1032
+ query VideoStats {
1033
+ videoStats {
1034
+ totalViews
1035
+ totalVideos
1036
+ }
1037
+ }
1038
+ `,
1039
+ client: { specs: [] },
1040
+ server: {
1041
+ description: '',
1042
+ data: {
1043
+ items: [
1044
+ {
1045
+ origin: {
1046
+ type: 'projection',
1047
+ name: 'videoStats',
1048
+ singleton: true,
1049
+ },
1050
+ target: {
1051
+ type: 'State',
1052
+ name: 'VideoStats',
1053
+ },
1054
+ },
1055
+ ],
1056
+ },
1057
+ specs: [],
1058
+ },
1059
+ },
1060
+ ],
1061
+ },
1062
+ ],
1063
+ messages: [
1064
+ {
1065
+ type: 'state',
1066
+ name: 'VideoStats',
1067
+ fields: [
1068
+ { name: 'totalViews', type: 'number', required: true },
1069
+ { name: 'totalVideos', type: 'number', required: true },
1070
+ ],
1071
+ },
1072
+ ],
1073
+ };
1074
+
1075
+ const { plans } = await generateScaffoldFilePlans(spec.narratives, spec.messages, undefined, 'src/domain/flows');
1076
+ const resolverFile = plans.find((p) => p.outputPath.endsWith('query.resolver.ts'));
1077
+
1078
+ expect(resolverFile?.contents).toContain("ctx.database.collection<VideoStats>('videoStats').findOne()");
1079
+ expect(resolverFile?.contents).not.toContain("'VideoStats'");
1080
+ });
1081
+
960
1082
  it('should generate @ObjectType classes for referenced message types', async () => {
961
1083
  const spec: SpecsSchema = {
962
1084
  variant: 'specs',
@@ -4,7 +4,7 @@ const projection = slice?.server?.data?.items?.[0]?.origin;
4
4
  const isSingleton = projection?.singleton === true;
5
5
  const queryName = parsedRequest?.queryName ?? camelCase(sliceName);
6
6
  const viewType = target?.name ? pascalCase(target.name) : 'UnknownView';
7
- const projectionType = projection?.name ? pascalCase(projection.name) : 'UnknownProjection';
7
+ const collectionName = projectionName || 'unknown-collection';
8
8
  const message = messages?.find(m => m.name === viewType);
9
9
  const resolverClassName = `${pascalCase(slice.name)}QueryResolver`;
10
10
  const usesID = parsedRequest?.args?.some(arg => graphqlType(arg.tsType) === 'ID');
@@ -111,7 +111,7 @@ async <%= queryName %>(
111
111
  %> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
112
112
  <% } } %>
113
113
  ): Promise<<%= viewType %>> {
114
- const result = await ctx.database.collection<<%= viewType %>>('<%= projectionType %>').findOne();
114
+ const result = await ctx.database.collection<<%= viewType %>>('<%= collectionName %>').findOne();
115
115
 
116
116
  if (!result) {
117
117
  return {
@@ -154,7 +154,7 @@ async <%= queryName %>(
154
154
  %> @Arg('<%= arg.name %>', () => <%= gqlType %>, { nullable: true }) <%= arg.name %>?: <%= tsType %><%= i < parsedRequest.args.length - 1 ? ',' : '' %>
155
155
  <% } } %>
156
156
  ): Promise<<%= viewType %>[]> {
157
- const model = new ReadModel<<%= viewType %>>(ctx.database, '<%= projectionType %>');
157
+ const model = new ReadModel<<%= viewType %>>(ctx.database, '<%= collectionName %>');
158
158
 
159
159
  // ## IMPLEMENTATION INSTRUCTIONS ##
160
160
  // You can query the projection using the ReadModel API: