@auto-engineer/server-generator-apollo-emmett 0.13.0 → 0.13.2
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 +7 -14
- package/dist/src/codegen/extract/commands.d.ts.map +1 -1
- package/dist/src/codegen/extract/commands.js.map +1 -1
- package/dist/src/codegen/extract/data-sink.d.ts +1 -1
- package/dist/src/codegen/extract/data-sink.d.ts.map +1 -1
- package/dist/src/codegen/extract/data-sink.js +9 -31
- package/dist/src/codegen/extract/data-sink.js.map +1 -1
- package/dist/src/codegen/extract/events.d.ts +4 -8
- package/dist/src/codegen/extract/events.d.ts.map +1 -1
- package/dist/src/codegen/extract/events.js.map +1 -1
- package/dist/src/codegen/extract/gwt.d.ts +2 -2
- package/dist/src/codegen/extract/gwt.d.ts.map +1 -1
- package/dist/src/codegen/extract/gwt.js +6 -21
- package/dist/src/codegen/extract/gwt.js.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts +4 -4
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js +24 -47
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/extract/query.d.ts +5 -7
- package/dist/src/codegen/extract/query.d.ts.map +1 -1
- package/dist/src/codegen/extract/query.js +10 -8
- package/dist/src/codegen/extract/query.js.map +1 -1
- package/dist/src/codegen/extract/slice-normalizer.d.ts +29 -0
- package/dist/src/codegen/extract/slice-normalizer.d.ts.map +1 -0
- package/dist/src/codegen/extract/slice-normalizer.js +48 -0
- package/dist/src/codegen/extract/slice-normalizer.js.map +1 -0
- package/dist/src/codegen/extract/step-converter.d.ts +17 -0
- package/dist/src/codegen/extract/step-converter.d.ts.map +1 -0
- package/dist/src/codegen/extract/step-converter.js +82 -0
- package/dist/src/codegen/extract/step-converter.js.map +1 -0
- package/dist/src/codegen/extract/step-types.d.ts +30 -0
- package/dist/src/codegen/extract/step-types.d.ts.map +1 -0
- package/dist/src/codegen/extract/step-types.js +16 -0
- package/dist/src/codegen/extract/step-types.js.map +1 -0
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +7 -29
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/commands.specs.ts +33 -28
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +153 -138
- package/dist/src/codegen/templates/command/decide.specs.ts +169 -146
- package/dist/src/codegen/templates/command/events.specs.ts +43 -38
- package/dist/src/codegen/templates/command/evolve.specs.ts +38 -33
- package/dist/src/codegen/templates/command/handle.specs.ts +71 -61
- package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +122 -102
- package/dist/src/codegen/templates/command/register.specs.ts +39 -34
- package/dist/src/codegen/templates/command/state.specs.ts +39 -34
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +399 -359
- package/dist/src/codegen/templates/query/projection.specs.ts +242 -216
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +38 -12
- package/dist/src/codegen/templates/query/query.resolver.specs.ts +59 -51
- package/dist/src/codegen/templates/query/state.specs.ts +1 -1
- package/dist/src/codegen/templates/react/react.specs.specs.ts +93 -85
- package/dist/src/codegen/templates/react/react.specs.ts +135 -122
- package/dist/src/codegen/templates/react/register.specs.ts +135 -122
- package/dist/src/codegen/test-data/specVariant1.d.ts.map +1 -1
- package/dist/src/codegen/test-data/specVariant1.js +101 -90
- package/dist/src/codegen/test-data/specVariant1.js.map +1 -1
- package/dist/src/codegen/types.d.ts +5 -7
- package/dist/src/codegen/types.d.ts.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/codegen/extract/commands.ts +7 -8
- package/src/codegen/extract/data-sink.ts +16 -43
- package/src/codegen/extract/events.ts +4 -5
- package/src/codegen/extract/gwt.ts +12 -29
- package/src/codegen/extract/messages.ts +43 -70
- package/src/codegen/extract/query.ts +14 -12
- package/src/codegen/extract/slice-normalizer.ts +88 -0
- package/src/codegen/extract/step-converter.ts +124 -0
- package/src/codegen/extract/step-types.ts +52 -0
- package/src/codegen/scaffoldFromSchema.ts +8 -45
- package/src/codegen/templates/command/commands.specs.ts +33 -28
- package/src/codegen/templates/command/decide.specs.specs.ts +153 -138
- package/src/codegen/templates/command/decide.specs.ts +169 -146
- package/src/codegen/templates/command/events.specs.ts +43 -38
- package/src/codegen/templates/command/evolve.specs.ts +38 -33
- package/src/codegen/templates/command/handle.specs.ts +71 -61
- package/src/codegen/templates/command/mutation.resolver.specs.ts +122 -102
- package/src/codegen/templates/command/register.specs.ts +39 -34
- package/src/codegen/templates/command/state.specs.ts +39 -34
- package/src/codegen/templates/query/projection.specs.specs.ts +399 -359
- package/src/codegen/templates/query/projection.specs.ts +242 -216
- package/src/codegen/templates/query/projection.specs.ts.ejs +38 -12
- package/src/codegen/templates/query/query.resolver.specs.ts +59 -51
- package/src/codegen/templates/query/state.specs.ts +1 -1
- package/src/codegen/templates/react/react.specs.specs.ts +93 -85
- package/src/codegen/templates/react/react.specs.ts +135 -122
- package/src/codegen/templates/react/register.specs.ts +135 -122
- package/src/codegen/test-data/specVariant1.ts +101 -90
- package/src/codegen/types.ts +6 -4
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/message-bus": "0.13.
|
|
35
|
-
"@auto-engineer/narrative": "0.13.
|
|
34
|
+
"@auto-engineer/message-bus": "0.13.2",
|
|
35
|
+
"@auto-engineer/narrative": "0.13.2"
|
|
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.13.
|
|
46
|
+
"@auto-engineer/cli": "0.13.2"
|
|
47
47
|
},
|
|
48
|
-
"version": "0.13.
|
|
48
|
+
"version": "0.13.2",
|
|
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",
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Message, MessageDefinition } from '../types';
|
|
1
|
+
import type { Message, MessageDefinition, EventRef, CommandRef, ErrorRef } from '../types';
|
|
3
2
|
import { extractFieldsFromMessage } from './fields';
|
|
4
3
|
|
|
5
4
|
function createCommandMessage(
|
|
@@ -22,9 +21,9 @@ function createCommandMessage(
|
|
|
22
21
|
|
|
23
22
|
export function extractCommandsFromGwt(
|
|
24
23
|
gwtSpecs: Array<{
|
|
25
|
-
given?: Array<
|
|
26
|
-
when?:
|
|
27
|
-
then: Array<
|
|
24
|
+
given?: Array<EventRef | unknown>;
|
|
25
|
+
when?: CommandRef | EventRef | unknown[];
|
|
26
|
+
then: Array<EventRef | unknown | ErrorRef>;
|
|
28
27
|
}>,
|
|
29
28
|
allMessages: MessageDefinition[],
|
|
30
29
|
): { commands: Message[]; commandSchemasByName: Record<string, Message> } {
|
|
@@ -85,9 +84,9 @@ function processCommandExample(
|
|
|
85
84
|
|
|
86
85
|
export function extractCommandsFromThen(
|
|
87
86
|
gwtSpecs: Array<{
|
|
88
|
-
given?: Array<
|
|
89
|
-
when?:
|
|
90
|
-
then: Array<
|
|
87
|
+
given?: Array<EventRef | unknown>;
|
|
88
|
+
when?: CommandRef | EventRef | unknown[];
|
|
89
|
+
then: Array<EventRef | unknown | ErrorRef>;
|
|
91
90
|
}>,
|
|
92
91
|
allMessages: MessageDefinition[],
|
|
93
92
|
): { commands: Message[]; commandSchemasByName: Record<string, Message> } {
|
|
@@ -1,37 +1,26 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Slice } from '@auto-engineer/narrative';
|
|
2
|
+
import type { CommandRef, ErrorRef } from '../types';
|
|
3
|
+
import { extractGwtSpecsFromSlice, type GwtResult } from './step-converter';
|
|
2
4
|
|
|
3
5
|
function resolveStreamId(stream: string, exampleData: Record<string, unknown>): string {
|
|
4
6
|
return stream.replace(/\$\{([^}]+)\}/g, (_, key: string) => String(exampleData?.[key] ?? 'unknown'));
|
|
5
7
|
}
|
|
6
8
|
|
|
7
|
-
function
|
|
9
|
+
function extractExampleDataFromEventWhen(firstSpec: GwtResult): Record<string, unknown> {
|
|
8
10
|
if (Array.isArray(firstSpec.when)) {
|
|
9
|
-
const firstWhen = firstSpec.when[0]
|
|
10
|
-
return
|
|
11
|
+
const firstWhen = firstSpec.when[0];
|
|
12
|
+
return firstWhen?.exampleData ?? {};
|
|
11
13
|
}
|
|
12
14
|
return {};
|
|
13
15
|
}
|
|
14
16
|
|
|
15
|
-
function extractExampleDataFromCommand(firstSpec:
|
|
16
|
-
const then = firstSpec.then as (
|
|
17
|
-
const firstExample = then.find((t): t is
|
|
18
|
-
return
|
|
19
|
-
? firstExample.exampleData
|
|
20
|
-
: {};
|
|
17
|
+
function extractExampleDataFromCommand(firstSpec: GwtResult): Record<string, unknown> {
|
|
18
|
+
const then = firstSpec.then as (CommandRef | ErrorRef)[];
|
|
19
|
+
const firstExample = then.find((t): t is CommandRef => 'exampleData' in t);
|
|
20
|
+
return firstExample?.exampleData ?? {};
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
function
|
|
24
|
-
if (Array.isArray(firstSpec.when)) {
|
|
25
|
-
const firstWhen = firstSpec.when[0] as { exampleData?: Record<string, unknown> } | undefined;
|
|
26
|
-
return typeof firstWhen?.exampleData === 'object' && firstWhen.exampleData !== null ? firstWhen.exampleData : {};
|
|
27
|
-
}
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function extractExampleDataFromSpecs(
|
|
32
|
-
slice: Slice,
|
|
33
|
-
gwtSpecs: Array<{ given?: unknown; when: unknown; then: unknown }>,
|
|
34
|
-
): Record<string, unknown> {
|
|
23
|
+
function extractExampleDataFromSpecs(slice: Slice, gwtSpecs: GwtResult[]): Record<string, unknown> {
|
|
35
24
|
if (gwtSpecs.length === 0) {
|
|
36
25
|
return {};
|
|
37
26
|
}
|
|
@@ -39,31 +28,15 @@ function extractExampleDataFromSpecs(
|
|
|
39
28
|
const firstSpec = gwtSpecs[0];
|
|
40
29
|
switch (slice.type) {
|
|
41
30
|
case 'react':
|
|
42
|
-
|
|
31
|
+
case 'query':
|
|
32
|
+
return extractExampleDataFromEventWhen(firstSpec);
|
|
43
33
|
case 'command':
|
|
44
34
|
return extractExampleDataFromCommand(firstSpec);
|
|
45
|
-
case 'query':
|
|
46
|
-
return extractExampleDataFromQuery(firstSpec);
|
|
47
35
|
default:
|
|
48
36
|
return {};
|
|
49
37
|
}
|
|
50
38
|
}
|
|
51
39
|
|
|
52
|
-
function extractGwtSpecs(slice: Slice) {
|
|
53
|
-
if (!('server' in slice)) return [];
|
|
54
|
-
const specs = slice.server?.specs;
|
|
55
|
-
const rules = specs?.rules;
|
|
56
|
-
return Array.isArray(rules) && rules.length > 0
|
|
57
|
-
? rules.flatMap((rule) =>
|
|
58
|
-
rule.examples.map((example: Example) => ({
|
|
59
|
-
given: example.given,
|
|
60
|
-
when: example.when,
|
|
61
|
-
then: example.then,
|
|
62
|
-
})),
|
|
63
|
-
)
|
|
64
|
-
: [];
|
|
65
|
-
}
|
|
66
|
-
|
|
67
40
|
function isValidStreamSink(item: unknown): item is { destination: { pattern: string } } {
|
|
68
41
|
return (
|
|
69
42
|
typeof item === 'object' &&
|
|
@@ -91,11 +64,11 @@ function processStreamSink(item: unknown, exampleData: Record<string, unknown>)
|
|
|
91
64
|
|
|
92
65
|
export function getStreamFromSink(slice: Slice): { streamPattern?: string; streamId?: string } {
|
|
93
66
|
if (!('server' in slice)) return {};
|
|
94
|
-
|
|
95
|
-
const exampleData = extractExampleDataFromSpecs(slice, gwtSpecs);
|
|
96
|
-
if (!('server' in slice) || slice.server == null || !('data' in slice.server) || !Array.isArray(slice.server.data)) {
|
|
67
|
+
if (slice.server == null || !('data' in slice.server) || !Array.isArray(slice.server.data)) {
|
|
97
68
|
return {};
|
|
98
69
|
}
|
|
70
|
+
const gwtSpecs = extractGwtSpecsFromSlice(slice);
|
|
71
|
+
const exampleData = extractExampleDataFromSpecs(slice, gwtSpecs);
|
|
99
72
|
const serverData = slice.server.data;
|
|
100
73
|
|
|
101
74
|
for (const item of serverData) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Message, MessageDefinition } from '../types';
|
|
1
|
+
import type { Message, MessageDefinition, EventRef, ErrorRef } from '../types';
|
|
3
2
|
import { extractFieldsFromMessage } from './fields';
|
|
4
|
-
import { ReactGwtSpec } from './messages';
|
|
3
|
+
import type { ReactGwtSpec } from './messages';
|
|
5
4
|
|
|
6
5
|
function createEventMessage(
|
|
7
6
|
eventRef: string | undefined,
|
|
@@ -27,7 +26,7 @@ function createEventMessage(
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
export function extractEventsFromThen(
|
|
30
|
-
thenItems: Array<
|
|
29
|
+
thenItems: Array<EventRef | ErrorRef>,
|
|
31
30
|
allMessages: MessageDefinition[],
|
|
32
31
|
currentSliceName?: string,
|
|
33
32
|
currentFlowName?: string,
|
|
@@ -41,7 +40,7 @@ export function extractEventsFromThen(
|
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
export function extractEventsFromGiven(
|
|
44
|
-
givenEvents:
|
|
43
|
+
givenEvents: EventRef[] | undefined,
|
|
45
44
|
allMessages: MessageDefinition[],
|
|
46
45
|
currentSliceName?: string,
|
|
47
46
|
currentFlowName?: string,
|
|
@@ -1,52 +1,35 @@
|
|
|
1
|
-
import { Slice
|
|
2
|
-
import { GwtCondition } from '../types';
|
|
1
|
+
import type { Slice } from '@auto-engineer/narrative';
|
|
2
|
+
import type { GwtCondition, CommandRef, EventRef } from '../types';
|
|
3
|
+
import { extractGwtSpecsFromSlice, type GwtConditionWithRule } from './step-converter';
|
|
3
4
|
|
|
4
5
|
export function buildCommandGwtMapping(slice: Slice): Record<string, (GwtCondition & { failingFields?: string[] })[]> {
|
|
5
6
|
if (slice.type !== 'command') {
|
|
6
7
|
return {};
|
|
7
8
|
}
|
|
8
9
|
|
|
9
|
-
const gwtSpecs =
|
|
10
|
+
const gwtSpecs = extractGwtSpecsFromSlice(slice);
|
|
10
11
|
const mapping = buildCommandMapping(gwtSpecs);
|
|
11
12
|
return enhanceMapping(mapping);
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
function
|
|
15
|
-
|
|
16
|
-
const specs = slice.server?.specs;
|
|
17
|
-
const rules = specs?.rules;
|
|
18
|
-
return Array.isArray(rules) && rules.length > 0
|
|
19
|
-
? rules.flatMap((rule) =>
|
|
20
|
-
rule.examples.map((example: Example) => ({
|
|
21
|
-
given: example.given,
|
|
22
|
-
when: example.when,
|
|
23
|
-
then: example.then,
|
|
24
|
-
description: example.description,
|
|
25
|
-
ruleDescription: rule.description,
|
|
26
|
-
})),
|
|
27
|
-
)
|
|
28
|
-
: [];
|
|
15
|
+
function isCommandRef(when: CommandRef | EventRef[]): when is CommandRef {
|
|
16
|
+
return !Array.isArray(when) && 'commandRef' in when;
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
function buildCommandMapping(
|
|
32
|
-
gwtSpecs: Array<{ given: unknown; when: unknown; then: unknown; description?: string; ruleDescription?: string }>,
|
|
33
|
-
) {
|
|
19
|
+
function buildCommandMapping(gwtSpecs: GwtConditionWithRule[]) {
|
|
34
20
|
const mapping: Record<string, GwtCondition[]> = {};
|
|
35
21
|
|
|
36
22
|
for (const gwt of gwtSpecs) {
|
|
37
|
-
|
|
38
|
-
if (Array.isArray(gwt.when)) {
|
|
23
|
+
if (!isCommandRef(gwt.when)) {
|
|
39
24
|
continue;
|
|
40
|
-
} else {
|
|
41
|
-
const whenCmd = gwt.when as { commandRef?: string } | undefined;
|
|
42
|
-
command = whenCmd?.commandRef;
|
|
43
25
|
}
|
|
26
|
+
const command = gwt.when.commandRef;
|
|
44
27
|
if (typeof command === 'string' && command.length > 0) {
|
|
45
28
|
mapping[command] = mapping[command] ?? [];
|
|
46
29
|
mapping[command].push({
|
|
47
|
-
given: gwt.given
|
|
48
|
-
when: gwt.when
|
|
49
|
-
then: gwt.then
|
|
30
|
+
given: gwt.given,
|
|
31
|
+
when: gwt.when,
|
|
32
|
+
then: gwt.then,
|
|
50
33
|
description: gwt.description,
|
|
51
34
|
ruleDescription: gwt.ruleDescription,
|
|
52
35
|
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { extractCommandsFromGwt, extractCommandsFromThen } from './commands';
|
|
2
|
-
import {
|
|
3
|
-
import { Message, MessageDefinition } from '../types';
|
|
2
|
+
import type { Slice } from '@auto-engineer/narrative';
|
|
3
|
+
import type { Message, MessageDefinition, EventRef, CommandRef } from '../types';
|
|
4
4
|
import { extractEventsFromGiven, extractEventsFromThen, extractEventsFromWhen } from './events';
|
|
5
5
|
import { extractFieldsFromMessage } from './fields';
|
|
6
6
|
import { extractProjectionIdField, extractProjectionSingleton } from './projection';
|
|
7
7
|
import { extractStatesFromData, extractStatesFromTarget } from './states';
|
|
8
|
+
import { extractGwtFromSpecs } from './step-converter';
|
|
8
9
|
import createDebug from 'debug';
|
|
9
10
|
|
|
10
11
|
const debug = createDebug('auto:server-generator-apollo-emmett:extract:messages');
|
|
@@ -23,8 +24,8 @@ export interface ExtractedMessages {
|
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
export interface ReactGwtSpec {
|
|
26
|
-
when?:
|
|
27
|
-
then:
|
|
27
|
+
when?: EventRef[];
|
|
28
|
+
then: CommandRef[];
|
|
28
29
|
}
|
|
29
30
|
|
|
30
31
|
const EMPTY_EXTRACTED_MESSAGES: ExtractedMessages = {
|
|
@@ -59,17 +60,12 @@ function extractMessagesForCommand(slice: Slice, allMessages: MessageDefinition[
|
|
|
59
60
|
}
|
|
60
61
|
|
|
61
62
|
const specs = slice.server?.specs;
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
when: example.when,
|
|
69
|
-
then: example.then,
|
|
70
|
-
})),
|
|
71
|
-
)
|
|
72
|
-
: [];
|
|
63
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
64
|
+
debugCommand(' No specs found, returning empty');
|
|
65
|
+
return EMPTY_EXTRACTED_MESSAGES;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const gwtSpecs = extractGwtFromSpecs(specs, 'command');
|
|
73
69
|
debugCommand(' Found %d GWT specs', gwtSpecs.length);
|
|
74
70
|
|
|
75
71
|
const { commands, commandSchemasByName } = extractCommandsFromGwt(gwtSpecs, allMessages);
|
|
@@ -77,10 +73,9 @@ function extractMessagesForCommand(slice: Slice, allMessages: MessageDefinition[
|
|
|
77
73
|
debugCommand(' Command schemas: %o', Object.keys(commandSchemasByName));
|
|
78
74
|
|
|
79
75
|
const events: Message[] = gwtSpecs.flatMap((gwt): Message[] => {
|
|
80
|
-
const
|
|
81
|
-
const givenEvents = extractEventsFromGiven(givenEventsOnly, allMessages, slice.name);
|
|
76
|
+
const givenEvents = extractEventsFromGiven(gwt.given, allMessages, slice.name);
|
|
82
77
|
|
|
83
|
-
const thenEventsOnly = gwt.then.filter((item): item is
|
|
78
|
+
const thenEventsOnly = gwt.then.filter((item): item is EventRef => 'eventRef' in item);
|
|
84
79
|
const thenEvents = extractEventsFromThen(thenEventsOnly, allMessages, slice.name);
|
|
85
80
|
debugCommand(' GWT: given=%d events, then=%d events', givenEvents.length, thenEvents.length);
|
|
86
81
|
return [...givenEvents, ...thenEvents];
|
|
@@ -107,17 +102,9 @@ function extractMessagesForQuery(slice: Slice, allMessages: MessageDefinition[])
|
|
|
107
102
|
}
|
|
108
103
|
|
|
109
104
|
const specs = slice.server?.specs;
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
? rules.flatMap((rule) =>
|
|
114
|
-
rule.examples.map((example) => ({
|
|
115
|
-
given: example.given,
|
|
116
|
-
when: example.when,
|
|
117
|
-
then: example.then,
|
|
118
|
-
})),
|
|
119
|
-
)
|
|
120
|
-
: [];
|
|
105
|
+
const hasSpecs = Array.isArray(specs) && specs.length > 0;
|
|
106
|
+
|
|
107
|
+
const gwtSpecs = hasSpecs ? extractGwtFromSpecs(specs, 'query') : [];
|
|
121
108
|
debugQuery(' Found %d GWT specs', gwtSpecs.length);
|
|
122
109
|
|
|
123
110
|
const projectionIdField = extractProjectionIdField(slice);
|
|
@@ -127,35 +114,21 @@ function extractMessagesForQuery(slice: Slice, allMessages: MessageDefinition[])
|
|
|
127
114
|
debugQuery(' Projection singleton: %s', projectionSingleton);
|
|
128
115
|
|
|
129
116
|
const events: Message[] = gwtSpecs.flatMap((gwt) => {
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
.map((eventExample) => {
|
|
146
|
-
const fields = extractFieldsFromMessage(eventExample.eventRef, 'event', allMessages);
|
|
147
|
-
const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventExample.eventRef);
|
|
148
|
-
const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
|
|
149
|
-
|
|
150
|
-
return {
|
|
151
|
-
type: eventExample.eventRef,
|
|
152
|
-
fields,
|
|
153
|
-
source: 'when' as const, // Mark as 'when' source for query events
|
|
154
|
-
sourceFlowName: metadata?.sourceFlowName,
|
|
155
|
-
sourceSliceName: metadata?.sourceSliceName,
|
|
156
|
-
} as Message;
|
|
157
|
-
})
|
|
158
|
-
.filter((event): event is Message => event.type !== undefined);
|
|
117
|
+
const eventsFromWhen: EventRef[] = Array.isArray(gwt.when) ? gwt.when : [];
|
|
118
|
+
const givenEvents = extractEventsFromGiven(gwt.given, allMessages);
|
|
119
|
+
const whenEvents: Message[] = eventsFromWhen.map((eventExample) => {
|
|
120
|
+
const fields = extractFieldsFromMessage(eventExample.eventRef, 'event', allMessages);
|
|
121
|
+
const messageDef = allMessages.find((m) => m.type === 'event' && m.name === eventExample.eventRef);
|
|
122
|
+
const metadata = messageDef?.metadata as { sourceFlowName?: string; sourceSliceName?: string } | undefined;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
type: eventExample.eventRef,
|
|
126
|
+
fields,
|
|
127
|
+
source: 'when',
|
|
128
|
+
sourceFlowName: metadata?.sourceFlowName,
|
|
129
|
+
sourceSliceName: metadata?.sourceSliceName,
|
|
130
|
+
};
|
|
131
|
+
});
|
|
159
132
|
|
|
160
133
|
return [...givenEvents, ...whenEvents];
|
|
161
134
|
});
|
|
@@ -186,20 +159,20 @@ function extractMessagesForReact(slice: Slice, allMessages: MessageDefinition[])
|
|
|
186
159
|
}
|
|
187
160
|
|
|
188
161
|
const specs = slice.server?.specs;
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
when: example.when,
|
|
196
|
-
then: example.then,
|
|
197
|
-
})),
|
|
198
|
-
)
|
|
199
|
-
: [];
|
|
162
|
+
if (!Array.isArray(specs) || specs.length === 0) {
|
|
163
|
+
debugReact(' No specs found, returning empty');
|
|
164
|
+
return EMPTY_EXTRACTED_MESSAGES;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const gwtSpecs = extractGwtFromSpecs(specs, 'react');
|
|
200
168
|
debugReact(' Found %d GWT specs', gwtSpecs.length);
|
|
201
169
|
|
|
202
|
-
const
|
|
170
|
+
const reactGwtSpecs: ReactGwtSpec[] = gwtSpecs.map((gwt) => ({
|
|
171
|
+
when: Array.isArray(gwt.when) ? gwt.when : undefined,
|
|
172
|
+
then: gwt.then.filter((item): item is CommandRef => 'commandRef' in item),
|
|
173
|
+
}));
|
|
174
|
+
|
|
175
|
+
const events = extractEventsFromWhen(reactGwtSpecs, allMessages);
|
|
203
176
|
debugReact(' Extracted %d events from when', events.length);
|
|
204
177
|
|
|
205
178
|
const { commands, commandSchemasByName } = extractCommandsFromThen(gwtSpecs, allMessages);
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Slice } from '@auto-engineer/narrative';
|
|
2
|
+
import type { EventRef, StateRef } from '../types';
|
|
3
|
+
import { extractGwtFromSpecs } from './step-converter';
|
|
2
4
|
|
|
3
5
|
interface QueryGwtCondition {
|
|
4
6
|
description: string;
|
|
5
|
-
given?:
|
|
6
|
-
when:
|
|
7
|
-
then: Array<
|
|
7
|
+
given?: EventRef[];
|
|
8
|
+
when: EventRef[];
|
|
9
|
+
then: Array<StateRef>;
|
|
8
10
|
}
|
|
9
11
|
|
|
10
12
|
export function buildQueryGwtMapping(slice: Slice): QueryGwtCondition[] {
|
|
11
13
|
if (slice.type !== 'query') return [];
|
|
12
14
|
|
|
13
15
|
const specs = slice.server?.specs;
|
|
14
|
-
|
|
16
|
+
if (!Array.isArray(specs) || specs.length === 0) return [];
|
|
15
17
|
|
|
16
|
-
const
|
|
18
|
+
const gwtResults = extractGwtFromSpecs(specs, 'query');
|
|
17
19
|
|
|
18
|
-
return
|
|
19
|
-
const
|
|
20
|
-
const
|
|
20
|
+
return gwtResults.map((gwt) => {
|
|
21
|
+
const whenEvents: EventRef[] = Array.isArray(gwt.when) ? gwt.when : [];
|
|
22
|
+
const thenStates = gwt.then.filter((i): i is StateRef => 'stateRef' in i);
|
|
21
23
|
|
|
22
24
|
return {
|
|
23
|
-
description:
|
|
24
|
-
given:
|
|
25
|
+
description: gwt.description,
|
|
26
|
+
given: gwt.given.length > 0 ? gwt.given : undefined,
|
|
25
27
|
when: whenEvents,
|
|
26
|
-
then:
|
|
28
|
+
then: thenStates,
|
|
27
29
|
};
|
|
28
30
|
});
|
|
29
31
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { Slice, Spec } from '@auto-engineer/narrative';
|
|
2
|
+
import {
|
|
3
|
+
type CommandRef,
|
|
4
|
+
type EventRef,
|
|
5
|
+
type StateRef,
|
|
6
|
+
type ErrorRef,
|
|
7
|
+
type SliceType,
|
|
8
|
+
getSliceType,
|
|
9
|
+
extractGwtFromSpecs,
|
|
10
|
+
type GwtConditionWithRule,
|
|
11
|
+
} from './step-converter';
|
|
12
|
+
|
|
13
|
+
interface NormalizedExample {
|
|
14
|
+
description: string;
|
|
15
|
+
given: EventRef[];
|
|
16
|
+
when: CommandRef | EventRef[];
|
|
17
|
+
then: Array<EventRef | StateRef | CommandRef | ErrorRef>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface NormalizedRule {
|
|
21
|
+
description: string;
|
|
22
|
+
examples: NormalizedExample[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface NormalizedSpecs {
|
|
26
|
+
name: string;
|
|
27
|
+
rules: NormalizedRule[];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function gwtToNormalizedExample(gwt: GwtConditionWithRule): NormalizedExample {
|
|
31
|
+
return {
|
|
32
|
+
description: gwt.description,
|
|
33
|
+
given: gwt.given,
|
|
34
|
+
when: gwt.when,
|
|
35
|
+
then: gwt.then,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function groupGwtByRule(gwtResults: GwtConditionWithRule[]): Map<string, GwtConditionWithRule[]> {
|
|
40
|
+
const grouped = new Map<string, GwtConditionWithRule[]>();
|
|
41
|
+
for (const gwt of gwtResults) {
|
|
42
|
+
const existing = grouped.get(gwt.ruleDescription) ?? [];
|
|
43
|
+
existing.push(gwt);
|
|
44
|
+
grouped.set(gwt.ruleDescription, existing);
|
|
45
|
+
}
|
|
46
|
+
return grouped;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizeSpecsForTemplate(specs: Spec[], sliceType: SliceType): NormalizedSpecs | null {
|
|
50
|
+
if (specs.length === 0) return null;
|
|
51
|
+
|
|
52
|
+
const gwtResults = extractGwtFromSpecs(specs, sliceType);
|
|
53
|
+
const groupedByRule = groupGwtByRule(gwtResults);
|
|
54
|
+
|
|
55
|
+
const featureName = specs[0]?.feature ?? '';
|
|
56
|
+
|
|
57
|
+
const rules: NormalizedRule[] = Array.from(groupedByRule.entries()).map(([ruleName, gwts]) => ({
|
|
58
|
+
description: ruleName,
|
|
59
|
+
examples: gwts.map(gwtToNormalizedExample),
|
|
60
|
+
}));
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
name: featureName,
|
|
64
|
+
rules,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
type SliceWithServer = Slice & { server?: { specs?: Spec[] } };
|
|
69
|
+
type NormalizedSlice<T extends SliceWithServer> = Omit<T, 'server'> & {
|
|
70
|
+
server?: Omit<T['server'], 'specs'> & { specs?: NormalizedSpecs | null };
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export function normalizeSliceForTemplate<T extends SliceWithServer>(slice: T): NormalizedSlice<T> {
|
|
74
|
+
if (!('server' in slice) || !slice.server?.specs) {
|
|
75
|
+
return slice as NormalizedSlice<T>;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const sliceType = getSliceType(slice);
|
|
79
|
+
const normalizedSpecs = normalizeSpecsForTemplate(slice.server.specs, sliceType);
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
...slice,
|
|
83
|
+
server: {
|
|
84
|
+
...slice.server,
|
|
85
|
+
specs: normalizedSpecs,
|
|
86
|
+
},
|
|
87
|
+
} as NormalizedSlice<T>;
|
|
88
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import type { Example, Spec, Slice } from '@auto-engineer/narrative';
|
|
2
|
+
import {
|
|
3
|
+
type SliceType,
|
|
4
|
+
type MajorKeyword,
|
|
5
|
+
type CommandRef,
|
|
6
|
+
type EventRef,
|
|
7
|
+
type StateRef,
|
|
8
|
+
type ErrorRef,
|
|
9
|
+
isStepWithDocString,
|
|
10
|
+
isStepWithError,
|
|
11
|
+
resolveMajorKeyword,
|
|
12
|
+
getSliceType,
|
|
13
|
+
} from './step-types';
|
|
14
|
+
|
|
15
|
+
export type { CommandRef, EventRef, StateRef, ErrorRef, SliceType };
|
|
16
|
+
export { getSliceType };
|
|
17
|
+
|
|
18
|
+
export interface GwtResult {
|
|
19
|
+
given: EventRef[];
|
|
20
|
+
when: CommandRef | EventRef[];
|
|
21
|
+
then: Array<EventRef | StateRef | CommandRef | ErrorRef>;
|
|
22
|
+
description: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
interface ParsedSteps {
|
|
26
|
+
given: EventRef[];
|
|
27
|
+
whenItems: Array<{ text: string; exampleData: Record<string, unknown> }>;
|
|
28
|
+
thenItems: Array<{ text: string; exampleData: Record<string, unknown> }>;
|
|
29
|
+
thenErrors: ErrorRef[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseSteps(example: Example): ParsedSteps {
|
|
33
|
+
const given: EventRef[] = [];
|
|
34
|
+
const whenItems: Array<{ text: string; exampleData: Record<string, unknown> }> = [];
|
|
35
|
+
const thenItems: Array<{ text: string; exampleData: Record<string, unknown> }> = [];
|
|
36
|
+
const thenErrors: ErrorRef[] = [];
|
|
37
|
+
|
|
38
|
+
let effectiveKeyword: MajorKeyword = 'Given';
|
|
39
|
+
|
|
40
|
+
for (const step of example.steps) {
|
|
41
|
+
if (isStepWithError(step)) {
|
|
42
|
+
thenErrors.push({ errorType: step.error.type, message: step.error.message });
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isStepWithDocString(step)) continue;
|
|
47
|
+
|
|
48
|
+
effectiveKeyword = resolveMajorKeyword(step.keyword, effectiveKeyword);
|
|
49
|
+
const exampleData = step.docString ?? {};
|
|
50
|
+
|
|
51
|
+
if (effectiveKeyword === 'Given') {
|
|
52
|
+
given.push({ eventRef: step.text, exampleData });
|
|
53
|
+
} else if (effectiveKeyword === 'When') {
|
|
54
|
+
whenItems.push({ text: step.text, exampleData });
|
|
55
|
+
} else if (effectiveKeyword === 'Then') {
|
|
56
|
+
thenItems.push({ text: step.text, exampleData });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return { given, whenItems, thenItems, thenErrors };
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function buildGwtResult(sliceType: SliceType, parsed: ParsedSteps, description: string): GwtResult {
|
|
64
|
+
const { given, whenItems, thenItems, thenErrors } = parsed;
|
|
65
|
+
|
|
66
|
+
if (sliceType === 'command') {
|
|
67
|
+
return {
|
|
68
|
+
given,
|
|
69
|
+
when: { commandRef: whenItems[0]?.text ?? '', exampleData: whenItems[0]?.exampleData ?? {} },
|
|
70
|
+
then: [...thenItems.map((t) => ({ eventRef: t.text, exampleData: t.exampleData })), ...thenErrors],
|
|
71
|
+
description,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (sliceType === 'react') {
|
|
76
|
+
return {
|
|
77
|
+
given,
|
|
78
|
+
when: whenItems.map((w) => ({ eventRef: w.text, exampleData: w.exampleData })),
|
|
79
|
+
then: [...thenItems.map((t) => ({ commandRef: t.text, exampleData: t.exampleData })), ...thenErrors],
|
|
80
|
+
description,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
given,
|
|
86
|
+
when: whenItems.map((w) => ({ eventRef: w.text, exampleData: w.exampleData })),
|
|
87
|
+
then: [...thenItems.map((t) => ({ stateRef: t.text, exampleData: t.exampleData })), ...thenErrors],
|
|
88
|
+
description,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function stepsToGwt(example: Example, sliceType: SliceType): GwtResult {
|
|
93
|
+
const parsed = parseSteps(example);
|
|
94
|
+
return buildGwtResult(sliceType, parsed, example.name);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface GwtConditionWithRule extends GwtResult {
|
|
98
|
+
ruleDescription: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function extractGwtFromSpecs(specs: Spec[], sliceType: SliceType): GwtConditionWithRule[] {
|
|
102
|
+
const results: GwtConditionWithRule[] = [];
|
|
103
|
+
|
|
104
|
+
for (const spec of specs) {
|
|
105
|
+
for (const rule of spec.rules) {
|
|
106
|
+
for (const example of rule.examples) {
|
|
107
|
+
const gwt = stepsToGwt(example, sliceType);
|
|
108
|
+
results.push({
|
|
109
|
+
...gwt,
|
|
110
|
+
ruleDescription: rule.name,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return results;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function extractGwtSpecsFromSlice(slice: Slice): GwtConditionWithRule[] {
|
|
120
|
+
if (!('server' in slice)) return [];
|
|
121
|
+
const specs = slice.server?.specs;
|
|
122
|
+
if (!Array.isArray(specs) || specs.length === 0) return [];
|
|
123
|
+
return extractGwtFromSpecs(specs, getSliceType(slice));
|
|
124
|
+
}
|