@auto-engineer/server-generator-apollo-emmett 1.138.0 → 1.140.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 +4 -4
- package/.turbo/turbo-type-check.log +1 -1
- package/CHANGELOG.md +39 -0
- package/DEBUG.md +4 -4
- package/dist/src/codegen/extract/data-sink.d.ts +2 -2
- package/dist/src/codegen/extract/data-sink.d.ts.map +1 -1
- package/dist/src/codegen/extract/data-sink.js +2 -2
- package/dist/src/codegen/extract/data-sink.js.map +1 -1
- package/dist/src/codegen/extract/events.d.ts +3 -3
- package/dist/src/codegen/extract/events.d.ts.map +1 -1
- package/dist/src/codegen/extract/events.js +11 -11
- 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 +2 -2
- package/dist/src/codegen/extract/gwt.js.map +1 -1
- package/dist/src/codegen/extract/imports.d.ts +4 -4
- package/dist/src/codegen/extract/imports.d.ts.map +1 -1
- package/dist/src/codegen/extract/imports.js +8 -8
- package/dist/src/codegen/extract/imports.js.map +1 -1
- package/dist/src/codegen/extract/messages.d.ts +2 -2
- package/dist/src/codegen/extract/messages.d.ts.map +1 -1
- package/dist/src/codegen/extract/messages.js +9 -9
- package/dist/src/codegen/extract/messages.js.map +1 -1
- package/dist/src/codegen/extract/projection.d.ts +7 -7
- package/dist/src/codegen/extract/projection.d.ts.map +1 -1
- package/dist/src/codegen/extract/projection.js +3 -3
- package/dist/src/codegen/extract/projection.js.map +1 -1
- package/dist/src/codegen/extract/query.d.ts +2 -2
- package/dist/src/codegen/extract/query.d.ts.map +1 -1
- package/dist/src/codegen/extract/query.js.map +1 -1
- package/dist/src/codegen/extract/slice-normalizer.d.ts +4 -4
- package/dist/src/codegen/extract/slice-normalizer.d.ts.map +1 -1
- package/dist/src/codegen/extract/slice-normalizer.js +7 -7
- package/dist/src/codegen/extract/slice-normalizer.js.map +1 -1
- package/dist/src/codegen/extract/states.d.ts +3 -3
- package/dist/src/codegen/extract/states.d.ts.map +1 -1
- package/dist/src/codegen/extract/states.js.map +1 -1
- package/dist/src/codegen/extract/step-converter.d.ts +7 -7
- package/dist/src/codegen/extract/step-converter.d.ts.map +1 -1
- package/dist/src/codegen/extract/step-converter.js +12 -12
- package/dist/src/codegen/extract/step-converter.js.map +1 -1
- package/dist/src/codegen/extract/step-types.d.ts +4 -4
- package/dist/src/codegen/extract/step-types.d.ts.map +1 -1
- package/dist/src/codegen/extract/step-types.js +1 -1
- package/dist/src/codegen/extract/step-types.js.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.d.ts +12 -12
- package/dist/src/codegen/scaffoldFromSchema.d.ts.map +1 -1
- package/dist/src/codegen/scaffoldFromSchema.js +113 -101
- package/dist/src/codegen/scaffoldFromSchema.js.map +1 -1
- package/dist/src/codegen/templates/command/commands.specs.ts +3 -3
- package/dist/src/codegen/templates/command/decide.specs.specs.ts +52 -52
- package/dist/src/codegen/templates/command/decide.specs.ts +12 -12
- package/dist/src/codegen/templates/command/decide.specs.ts.ejs +1 -1
- package/dist/src/codegen/templates/command/events.specs.ts +3 -3
- package/dist/src/codegen/templates/command/evolve.specs.ts +3 -3
- package/dist/src/codegen/templates/command/handle.specs.ts +13 -13
- package/dist/src/codegen/templates/command/mutation.resolver.specs.ts +19 -19
- package/dist/src/codegen/templates/command/register.specs.ts +3 -3
- package/dist/src/codegen/templates/command/state.specs.ts +3 -3
- package/dist/src/codegen/templates/query/events.specs.ts +4 -4
- package/dist/src/codegen/templates/query/projection.specs.specs.ts +60 -60
- package/dist/src/codegen/templates/query/projection.specs.ts +54 -29
- package/dist/src/codegen/templates/query/projection.specs.ts.ejs +2 -2
- package/dist/src/codegen/templates/query/query.resolver.specs.ts +63 -63
- package/dist/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
- package/dist/src/codegen/templates/query/state.specs.ts +9 -9
- package/dist/src/codegen/templates/react/react.specs.specs.ts +15 -15
- package/dist/src/codegen/templates/react/react.specs.ts +16 -16
- package/dist/src/codegen/templates/react/react.specs.ts.ejs +9 -9
- package/dist/src/codegen/templates/react/react.ts.ejs +5 -5
- package/dist/src/codegen/templates/react/react.ts.specs.ts +33 -33
- package/dist/src/codegen/templates/react/register.specs.ts +7 -7
- package/dist/src/codegen/templates/react/register.ts.ejs +4 -4
- package/dist/src/codegen/test-data/specVariant1.d.ts.map +1 -1
- package/dist/src/codegen/test-data/specVariant1.js +3 -2
- package/dist/src/codegen/test-data/specVariant1.js.map +1 -1
- package/dist/src/codegen/types.d.ts +2 -2
- package/dist/src/codegen/types.d.ts.map +1 -1
- package/dist/src/commands/generate-server.d.ts +21 -21
- package/dist/src/commands/generate-server.d.ts.map +1 -1
- package/dist/src/commands/generate-server.js +81 -63
- package/dist/src/commands/generate-server.js.map +1 -1
- package/dist/src/commands/initialize-server.d.ts.map +1 -1
- package/dist/src/commands/initialize-server.js +2 -2
- package/dist/src/commands/initialize-server.js.map +1 -1
- package/dist/src/domain/flows/shared/types.d.ts +14 -0
- package/dist/src/domain/flows/shared/types.d.ts.map +1 -0
- package/dist/src/domain/flows/shared/types.js +2 -0
- package/dist/src/domain/flows/shared/types.js.map +1 -0
- package/dist/src/domain/flows/shared/types.ts +15 -0
- package/dist/src/domain/narratives/shared/types.d.ts +14 -0
- package/dist/src/domain/narratives/shared/types.d.ts.map +1 -0
- package/dist/src/domain/narratives/shared/types.js +2 -0
- package/dist/src/domain/narratives/shared/types.js.map +1 -0
- package/dist/src/domain/narratives/shared/types.ts +15 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/server.js +3 -3
- package/dist/src/server.js.map +1 -1
- package/dist/src/server.ts +3 -3
- package/dist/src/utils/loadRegisterFiles.d.ts +2 -2
- package/dist/src/utils/loadRegisterFiles.d.ts.map +1 -1
- package/dist/src/utils/loadRegisterFiles.js.map +1 -1
- package/dist/src/utils/loadRegisterFiles.ts +5 -5
- package/dist/src/utils/loadResolvers.js +1 -1
- package/dist/src/utils/loadResolvers.js.map +1 -1
- package/dist/src/utils/loadResolvers.ts +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/src/codegen/extract/data-sink.ts +5 -5
- package/src/codegen/extract/events.ts +15 -15
- package/src/codegen/extract/gwt.ts +4 -4
- package/src/codegen/extract/imports.specs.ts +19 -19
- package/src/codegen/extract/imports.ts +13 -13
- package/src/codegen/extract/messages.specs.ts +30 -30
- package/src/codegen/extract/messages.ts +16 -16
- package/src/codegen/extract/projection.specs.ts +22 -22
- package/src/codegen/extract/projection.ts +9 -9
- package/src/codegen/extract/query.ts +2 -2
- package/src/codegen/extract/slice-normalizer.specs.ts +11 -11
- package/src/codegen/extract/slice-normalizer.ts +14 -14
- package/src/codegen/extract/states.ts +4 -4
- package/src/codegen/extract/step-converter.specs.ts +9 -9
- package/src/codegen/extract/step-converter.ts +15 -15
- package/src/codegen/extract/step-types.specs.ts +12 -12
- package/src/codegen/extract/step-types.ts +4 -4
- package/src/codegen/findEventSource.specs.ts +23 -23
- package/src/codegen/scaffoldErrors.specs.ts +4 -4
- package/src/codegen/scaffoldFromSchema.filter.specs.ts +32 -32
- package/src/codegen/scaffoldFromSchema.ts +146 -132
- package/src/codegen/templates/command/commands.specs.ts +3 -3
- package/src/codegen/templates/command/decide.specs.specs.ts +52 -52
- package/src/codegen/templates/command/decide.specs.ts +12 -12
- package/src/codegen/templates/command/decide.specs.ts.ejs +1 -1
- package/src/codegen/templates/command/events.specs.ts +3 -3
- package/src/codegen/templates/command/evolve.specs.ts +3 -3
- package/src/codegen/templates/command/handle.specs.ts +13 -13
- package/src/codegen/templates/command/mutation.resolver.specs.ts +19 -19
- package/src/codegen/templates/command/register.specs.ts +3 -3
- package/src/codegen/templates/command/state.specs.ts +3 -3
- package/src/codegen/templates/query/events.specs.ts +4 -4
- package/src/codegen/templates/query/projection.specs.specs.ts +60 -60
- package/src/codegen/templates/query/projection.specs.ts +54 -29
- package/src/codegen/templates/query/projection.specs.ts.ejs +2 -2
- package/src/codegen/templates/query/query.resolver.specs.ts +63 -63
- package/src/codegen/templates/query/query.resolver.ts.ejs +1 -1
- package/src/codegen/templates/query/state.specs.ts +9 -9
- package/src/codegen/templates/react/react.specs.specs.ts +15 -15
- package/src/codegen/templates/react/react.specs.ts +16 -16
- package/src/codegen/templates/react/react.specs.ts.ejs +9 -9
- package/src/codegen/templates/react/react.ts.ejs +5 -5
- package/src/codegen/templates/react/react.ts.specs.ts +33 -33
- package/src/codegen/templates/react/register.specs.ts +7 -7
- package/src/codegen/templates/react/register.ts.ejs +4 -4
- package/src/codegen/test-data/specVariant1.json +1 -1
- package/src/codegen/test-data/specVariant1.ts +3 -2
- package/src/codegen/test-data/specVariant2.json +1 -1
- package/src/codegen/types.ts +2 -2
- package/src/commands/generate-server.specs.ts +81 -79
- package/src/commands/generate-server.ts +110 -88
- package/src/commands/initialize-server.specs.ts +4 -4
- package/src/commands/initialize-server.ts +5 -2
- package/src/domain/flows/shared/types.ts +15 -0
- package/src/domain/narratives/shared/types.ts +15 -0
- package/src/index.ts +1 -1
- package/src/server.ts +3 -3
- package/src/utils/loadRegisterFiles.ts +5 -5
- package/src/utils/loadResolvers.ts +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path, { dirname } from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
-
import type { Model,
|
|
4
|
+
import type { Model, Moment, Scene } from '@auto-engineer/narrative';
|
|
5
5
|
import { camelCase, pascalCase } from 'change-case';
|
|
6
6
|
import createDebug from 'debug';
|
|
7
7
|
import ejs from 'ejs';
|
|
@@ -11,8 +11,8 @@ import { ensureDirExists, ensureDirPath, toKebabCase } from './utils/path';
|
|
|
11
11
|
const debug = createDebug('auto:server-generator-apollo-emmett:scaffold');
|
|
12
12
|
const debugTemplate = createDebug('auto:server-generator-apollo-emmett:scaffold:template');
|
|
13
13
|
const debugFiles = createDebug('auto:server-generator-apollo-emmett:scaffold:files');
|
|
14
|
-
const
|
|
15
|
-
const
|
|
14
|
+
const debugScene = createDebug('auto:server-generator-apollo-emmett:scaffold:flow');
|
|
15
|
+
const debugMoment = createDebug('auto:server-generator-apollo-emmett:scaffold:moment');
|
|
16
16
|
const debugPlan = createDebug('auto:server-generator-apollo-emmett:scaffold:plan');
|
|
17
17
|
const debugFormat = createDebug('auto:server-generator-apollo-emmett:scaffold:format');
|
|
18
18
|
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
isInlineObject,
|
|
21
21
|
isInlineObjectArray,
|
|
22
22
|
parseInlineObjectFields,
|
|
23
|
-
|
|
23
|
+
parseMomentRequest,
|
|
24
24
|
} from '@auto-engineer/narrative';
|
|
25
25
|
import {
|
|
26
26
|
baseTs,
|
|
@@ -45,8 +45,8 @@ import {
|
|
|
45
45
|
import { getStreamFromSink } from './extract/data-sink';
|
|
46
46
|
import { buildEventIdFieldMap } from './extract/projection';
|
|
47
47
|
import { buildArgToStateFieldMap } from './extract/query';
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
48
|
+
import { normalizeMomentForTemplate } from './extract/slice-normalizer';
|
|
49
|
+
import { extractGwtSpecsFromMoment, type GwtResult } from './extract/step-converter';
|
|
50
50
|
import type { GwtCondition, Message, MessageDefinition } from './types';
|
|
51
51
|
|
|
52
52
|
export class TemplateRenderError extends Error {
|
|
@@ -60,16 +60,16 @@ export class TemplateRenderError extends Error {
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
export class ScaffoldError extends Error {
|
|
63
|
-
readonly
|
|
64
|
-
readonly
|
|
65
|
-
readonly
|
|
63
|
+
readonly sceneName: string;
|
|
64
|
+
readonly momentName: string;
|
|
65
|
+
readonly momentType: string;
|
|
66
66
|
|
|
67
|
-
constructor(
|
|
68
|
-
super(`Scaffold failed for ${
|
|
67
|
+
constructor(sceneName: string, momentName: string, momentType: string, cause: unknown) {
|
|
68
|
+
super(`Scaffold failed for ${sceneName}/${momentName} (${momentType})`, { cause });
|
|
69
69
|
this.name = 'ScaffoldError';
|
|
70
|
-
this.
|
|
71
|
-
this.
|
|
72
|
-
this.
|
|
70
|
+
this.sceneName = sceneName;
|
|
71
|
+
this.momentName = momentName;
|
|
72
|
+
this.momentType = momentType;
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -269,8 +269,8 @@ export interface FilePlan {
|
|
|
269
269
|
|
|
270
270
|
export interface DuplicateCommandInfo {
|
|
271
271
|
command: string;
|
|
272
|
-
|
|
273
|
-
|
|
272
|
+
existingScene: string;
|
|
273
|
+
existingMoment: string;
|
|
274
274
|
}
|
|
275
275
|
|
|
276
276
|
export interface FieldIssue {
|
|
@@ -537,19 +537,19 @@ function formatDataObject(exampleData: Record<string, unknown>, schema: Message
|
|
|
537
537
|
|
|
538
538
|
async function generateFileForTemplate(
|
|
539
539
|
templateFile: string,
|
|
540
|
-
slice:
|
|
541
|
-
|
|
540
|
+
slice: Moment,
|
|
541
|
+
momentDir: string,
|
|
542
542
|
templateData: Record<string, unknown>,
|
|
543
543
|
unionToEnumName: Map<string, string> = new Map(),
|
|
544
544
|
): Promise<FilePlan> {
|
|
545
545
|
debugFiles('Generating file from template: %s', templateFile);
|
|
546
|
-
debugFiles('
|
|
547
|
-
debugFiles('
|
|
546
|
+
debugFiles(' Moment type: %s', slice.type);
|
|
547
|
+
debugFiles(' Moment directory: %s', momentDir);
|
|
548
548
|
|
|
549
549
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
550
550
|
const templatePath = path.join(__dirname, './templates', slice.type, templateFile);
|
|
551
551
|
const fileName = templateFile.replace(/\.ts\.ejs$/, '.ts');
|
|
552
|
-
const outputPath = path.join(
|
|
552
|
+
const outputPath = path.join(momentDir, fileName);
|
|
553
553
|
|
|
554
554
|
debugFiles(' Template path: %s', templatePath);
|
|
555
555
|
debugFiles(' Output path: %s', outputPath);
|
|
@@ -627,8 +627,8 @@ function extractUsedErrors(gwtMapping: Record<string, (GwtCondition & { failingF
|
|
|
627
627
|
}
|
|
628
628
|
|
|
629
629
|
async function prepareTemplateData(
|
|
630
|
-
slice:
|
|
631
|
-
|
|
630
|
+
slice: Moment,
|
|
631
|
+
scene: Scene,
|
|
632
632
|
commands: Message[],
|
|
633
633
|
events: Message[],
|
|
634
634
|
states: Message[],
|
|
@@ -637,9 +637,9 @@ async function prepareTemplateData(
|
|
|
637
637
|
projectionSingleton: boolean | undefined,
|
|
638
638
|
allMessages?: MessageDefinition[],
|
|
639
639
|
integrations?: Model['integrations'],
|
|
640
|
-
|
|
640
|
+
scenes?: Scene[],
|
|
641
641
|
): Promise<Record<string, unknown>> {
|
|
642
|
-
debug('Preparing template data for
|
|
642
|
+
debug('Preparing template data for moment: %s (scene: %s)', slice.name, scene.name);
|
|
643
643
|
debug(' Commands: %d', commands.length);
|
|
644
644
|
debug(' Events: %d', events.length);
|
|
645
645
|
debug(' States: %d', states.length);
|
|
@@ -662,11 +662,11 @@ async function prepareTemplateData(
|
|
|
662
662
|
const uniqueCommands = Array.from(new Map(commands.map((c) => [c.type, c])).values());
|
|
663
663
|
debug(' Unique commands: %d', uniqueCommands.length);
|
|
664
664
|
|
|
665
|
-
const
|
|
665
|
+
const allowedForMoment = new Set(Object.keys(gwtMapping));
|
|
666
666
|
const filteredCommands =
|
|
667
|
-
|
|
667
|
+
allowedForMoment.size > 0 ? uniqueCommands.filter((c) => allowedForMoment.has(c.type)) : uniqueCommands;
|
|
668
668
|
|
|
669
|
-
const eventImportGroups = groupEventImports({
|
|
669
|
+
const eventImportGroups = groupEventImports({ currentMomentName: slice.name, currentSceneName: scene.name, events });
|
|
670
670
|
const allEventTypesArray = getAllEventTypes(events);
|
|
671
671
|
const allEventTypes = createEventUnionType(events);
|
|
672
672
|
const selfImportedTypes = new Set(
|
|
@@ -683,9 +683,9 @@ async function prepareTemplateData(
|
|
|
683
683
|
messagesByName,
|
|
684
684
|
);
|
|
685
685
|
|
|
686
|
-
const
|
|
686
|
+
const normalizedMoment = normalizeMomentForTemplate(slice, allMessages);
|
|
687
687
|
|
|
688
|
-
const normalizedRules =
|
|
688
|
+
const normalizedRules = normalizedMoment.server?.specs?.rules ?? [];
|
|
689
689
|
const eventCommandPairs: Array<{ eventType: string; commandType: string }> = [];
|
|
690
690
|
const seenPairKeys = new Set<string>();
|
|
691
691
|
for (const rule of normalizedRules) {
|
|
@@ -703,16 +703,16 @@ async function prepareTemplateData(
|
|
|
703
703
|
}
|
|
704
704
|
|
|
705
705
|
const eventIdFieldMap =
|
|
706
|
-
slice.type === 'query' &&
|
|
706
|
+
slice.type === 'query' && scenes ? buildEventIdFieldMap(events, scenes, findEventSource) : undefined;
|
|
707
707
|
|
|
708
708
|
const stateFieldNames = new Set((queryMessage?.fields ?? []).map((f) => f.name));
|
|
709
709
|
const argToStateFieldMap =
|
|
710
710
|
slice.type === 'query' ? buildArgToStateFieldMap(queryGwtMapping, stateFieldNames, slice.mappings) : undefined;
|
|
711
711
|
|
|
712
712
|
return {
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
slice:
|
|
713
|
+
sceneName: scene.name,
|
|
714
|
+
momentName: slice.name,
|
|
715
|
+
slice: normalizedMoment,
|
|
716
716
|
stream: { pattern: streamPattern, id: streamId },
|
|
717
717
|
commands: filteredCommands,
|
|
718
718
|
events,
|
|
@@ -724,7 +724,7 @@ async function prepareTemplateData(
|
|
|
724
724
|
projectionIdField,
|
|
725
725
|
projectionSingleton,
|
|
726
726
|
projectionName,
|
|
727
|
-
parsedRequest:
|
|
727
|
+
parsedRequest: parseMomentRequest(slice),
|
|
728
728
|
messages: allMessages,
|
|
729
729
|
message:
|
|
730
730
|
slice.type === 'query' && allMessages
|
|
@@ -744,17 +744,17 @@ async function prepareTemplateData(
|
|
|
744
744
|
|
|
745
745
|
function annotateEventSources(
|
|
746
746
|
events: Message[],
|
|
747
|
-
|
|
747
|
+
scenes: Scene[],
|
|
748
748
|
fallbackFlowName: string,
|
|
749
|
-
|
|
750
|
-
|
|
749
|
+
fallbackMomentName: string,
|
|
750
|
+
momentType: string,
|
|
751
751
|
): void {
|
|
752
752
|
debug('Annotating event sources for %d events', events.length);
|
|
753
753
|
for (const event of events) {
|
|
754
|
-
const match = findEventSource(
|
|
755
|
-
event.
|
|
756
|
-
event.
|
|
757
|
-
debug(' Event %s:
|
|
754
|
+
const match = findEventSource(scenes, event.type);
|
|
755
|
+
event.sourceSceneName = match?.sceneName ?? fallbackFlowName;
|
|
756
|
+
event.sourceMomentName = match?.momentName ?? (momentType === 'react' ? undefined : fallbackMomentName);
|
|
757
|
+
debug(' Event %s: scene=%s, slice=%s', event.type, event.sourceSceneName, event.sourceMomentName);
|
|
758
758
|
}
|
|
759
759
|
}
|
|
760
760
|
|
|
@@ -762,113 +762,113 @@ function hasEventInGwtSpecs(gwtSpecs: GwtResult[], eventType: string): boolean {
|
|
|
762
762
|
return gwtSpecs.some((g) => g.then.some((t) => 'eventRef' in t && t.eventRef === eventType));
|
|
763
763
|
}
|
|
764
764
|
|
|
765
|
-
function
|
|
765
|
+
function canMomentProduceEvent(slice: Moment): boolean {
|
|
766
766
|
return ['command', 'react'].includes(slice.type) && 'server' in slice && Boolean(slice.server?.specs);
|
|
767
767
|
}
|
|
768
768
|
|
|
769
|
-
export function findEventSource(
|
|
770
|
-
|
|
769
|
+
export function findEventSource(scenes: Scene[], eventType: string): { sceneName: string; momentName: string } | null {
|
|
770
|
+
debugMoment('Finding source for event: %s', eventType);
|
|
771
771
|
|
|
772
|
-
for (const
|
|
773
|
-
for (const slice of
|
|
774
|
-
if (
|
|
775
|
-
const gwtSpecs =
|
|
772
|
+
for (const scene of scenes) {
|
|
773
|
+
for (const slice of scene.moments) {
|
|
774
|
+
if (canMomentProduceEvent(slice)) {
|
|
775
|
+
const gwtSpecs = extractGwtSpecsFromMoment(slice);
|
|
776
776
|
if (hasEventInGwtSpecs(gwtSpecs, eventType)) {
|
|
777
|
-
|
|
778
|
-
return {
|
|
777
|
+
debugMoment(' Found event source in scene: %s, slice: %s', scene.name, slice.name);
|
|
778
|
+
return { sceneName: scene.name, momentName: slice.name };
|
|
779
779
|
}
|
|
780
780
|
}
|
|
781
781
|
if ('server' in slice && slice.server?.data?.items) {
|
|
782
782
|
for (const item of slice.server.data.items) {
|
|
783
783
|
if (item.target.type === 'Event' && item.target.name === eventType) {
|
|
784
|
-
|
|
785
|
-
return {
|
|
784
|
+
debugMoment(' Found event source in data target: scene=%s, slice=%s', scene.name, slice.name);
|
|
785
|
+
return { sceneName: scene.name, momentName: slice.name };
|
|
786
786
|
}
|
|
787
787
|
}
|
|
788
788
|
}
|
|
789
789
|
}
|
|
790
790
|
}
|
|
791
791
|
|
|
792
|
-
|
|
792
|
+
debugMoment(' No source found for event: %s', eventType);
|
|
793
793
|
return null;
|
|
794
794
|
}
|
|
795
795
|
|
|
796
796
|
function annotateCommandSources(
|
|
797
797
|
commands: Message[],
|
|
798
|
-
|
|
798
|
+
scenes: Scene[],
|
|
799
799
|
fallbackFlowName: string,
|
|
800
|
-
|
|
801
|
-
|
|
800
|
+
fallbackMomentName: string,
|
|
801
|
+
momentType: string,
|
|
802
802
|
): void {
|
|
803
803
|
debug('Annotating command sources for %d commands', commands.length);
|
|
804
804
|
for (const command of commands) {
|
|
805
|
-
const match = findCommandSource(
|
|
806
|
-
command.
|
|
807
|
-
command.
|
|
808
|
-
debug(' Command %s:
|
|
805
|
+
const match = findCommandSource(scenes, command.type);
|
|
806
|
+
command.sourceSceneName = match?.sceneName ?? fallbackFlowName;
|
|
807
|
+
command.sourceMomentName = match?.momentName ?? (momentType === 'react' ? undefined : fallbackMomentName);
|
|
808
|
+
debug(' Command %s: scene=%s, slice=%s', command.type, command.sourceSceneName, command.sourceMomentName);
|
|
809
809
|
}
|
|
810
810
|
}
|
|
811
811
|
|
|
812
|
-
function findCommandSource(
|
|
813
|
-
|
|
814
|
-
for (const
|
|
815
|
-
for (const slice of
|
|
812
|
+
function findCommandSource(scenes: Scene[], commandType: string): { sceneName: string; momentName: string } | null {
|
|
813
|
+
debugMoment('Finding source for command: %s', commandType);
|
|
814
|
+
for (const scene of scenes) {
|
|
815
|
+
for (const slice of scene.moments) {
|
|
816
816
|
if (slice.type !== 'command') continue;
|
|
817
817
|
|
|
818
|
-
const gwtSpecs =
|
|
818
|
+
const gwtSpecs = extractGwtSpecsFromMoment(slice);
|
|
819
819
|
if (gwtSpecs.some((g) => !Array.isArray(g.when) && 'commandRef' in g.when && g.when.commandRef === commandType)) {
|
|
820
|
-
|
|
821
|
-
return {
|
|
820
|
+
debugMoment(' Found command source in scene: %s, slice: %s', scene.name, slice.name);
|
|
821
|
+
return { sceneName: scene.name, momentName: slice.name };
|
|
822
822
|
}
|
|
823
823
|
}
|
|
824
824
|
}
|
|
825
|
-
|
|
825
|
+
debugMoment(' No source found for command: %s', commandType);
|
|
826
826
|
return null;
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
-
async function
|
|
830
|
-
slice:
|
|
831
|
-
|
|
832
|
-
|
|
829
|
+
async function generateFilesForMoment(
|
|
830
|
+
slice: Moment,
|
|
831
|
+
scene: Scene,
|
|
832
|
+
momentDir: string,
|
|
833
833
|
messages: MessageDefinition[],
|
|
834
|
-
|
|
834
|
+
scenes: Scene[],
|
|
835
835
|
unionToEnumName: Map<string, string>,
|
|
836
836
|
integrations?: Model['integrations'],
|
|
837
|
-
registeredCommands?: Map<string, {
|
|
837
|
+
registeredCommands?: Map<string, { sceneName: string; momentName: string }>,
|
|
838
838
|
): Promise<{ plans: FilePlan[]; duplicateCommands: DuplicateCommandInfo[] }> {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
839
|
+
debugMoment('Generating files for moment: %s (type: %s)', slice.name, slice.type);
|
|
840
|
+
debugMoment(' Scene: %s', scene.name);
|
|
841
|
+
debugMoment(' Output directory: %s', momentDir);
|
|
842
842
|
|
|
843
843
|
const duplicateCommands: DuplicateCommandInfo[] = [];
|
|
844
844
|
|
|
845
845
|
const templates = defaultFilesByType[slice.type];
|
|
846
846
|
if (!templates?.length) {
|
|
847
|
-
|
|
847
|
+
debugMoment(' No templates found for moment type: %s', slice.type);
|
|
848
848
|
return { plans: [], duplicateCommands };
|
|
849
849
|
}
|
|
850
|
-
|
|
850
|
+
debugMoment(' Found %d templates for moment type', templates.length);
|
|
851
851
|
|
|
852
852
|
if (
|
|
853
853
|
slice.type === 'react' &&
|
|
854
854
|
!slice.server?.specs?.some((spec) => spec.rules?.some((rule) => rule.examples?.length > 0))
|
|
855
855
|
) {
|
|
856
|
-
|
|
856
|
+
debugMoment(' Skipping react slice with no examples: %s', slice.name);
|
|
857
857
|
return { plans: [], duplicateCommands };
|
|
858
858
|
}
|
|
859
859
|
|
|
860
860
|
const extracted = extractMessagesFromSpecs(slice, messages);
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
'💡 Events for
|
|
861
|
+
debugMoment(' Extracted messages:');
|
|
862
|
+
debugMoment(' Commands: %d', extracted.commands.length);
|
|
863
|
+
debugMoment(' Events: %d', extracted.events.length);
|
|
864
|
+
debugMoment(' States: %d', extracted.states.length);
|
|
865
|
+
debugMoment(
|
|
866
|
+
'💡 Events for moment',
|
|
867
867
|
slice.name,
|
|
868
868
|
extracted.events.map((e) => e.type),
|
|
869
869
|
);
|
|
870
|
-
annotateEventSources(extracted.events,
|
|
871
|
-
annotateCommandSources(extracted.commands,
|
|
870
|
+
annotateEventSources(extracted.events, scenes, scene.name, slice.name, slice.type);
|
|
871
|
+
annotateCommandSources(extracted.commands, scenes, scene.name, slice.name, slice.type);
|
|
872
872
|
|
|
873
873
|
let filteredTemplates = templates;
|
|
874
874
|
|
|
@@ -880,33 +880,33 @@ async function generateFilesForSlice(
|
|
|
880
880
|
for (const ct of commandTypes) {
|
|
881
881
|
const existing = registeredCommands.get(ct);
|
|
882
882
|
if (existing) {
|
|
883
|
-
|
|
883
|
+
debugMoment(
|
|
884
884
|
' Duplicate command handler detected: %s (already in %s/%s)',
|
|
885
885
|
ct,
|
|
886
|
-
existing.
|
|
887
|
-
existing.
|
|
886
|
+
existing.sceneName,
|
|
887
|
+
existing.momentName,
|
|
888
888
|
);
|
|
889
889
|
duplicateCommands.push({
|
|
890
890
|
command: ct,
|
|
891
|
-
|
|
892
|
-
|
|
891
|
+
existingScene: existing.sceneName,
|
|
892
|
+
existingMoment: existing.momentName,
|
|
893
893
|
});
|
|
894
894
|
}
|
|
895
895
|
}
|
|
896
896
|
filteredTemplates = templates.filter((t) => t !== 'register.ts.ejs');
|
|
897
|
-
|
|
897
|
+
debugMoment(' Filtered out register.ts.ejs for duplicate command moment');
|
|
898
898
|
}
|
|
899
899
|
|
|
900
900
|
for (const ct of commandTypes) {
|
|
901
901
|
if (!registeredCommands.has(ct)) {
|
|
902
|
-
registeredCommands.set(ct, {
|
|
902
|
+
registeredCommands.set(ct, { sceneName: scene.name, momentName: slice.name });
|
|
903
903
|
}
|
|
904
904
|
}
|
|
905
905
|
}
|
|
906
906
|
|
|
907
907
|
const templateData = await prepareTemplateData(
|
|
908
908
|
slice,
|
|
909
|
-
|
|
909
|
+
scene,
|
|
910
910
|
extracted.commands,
|
|
911
911
|
extracted.events,
|
|
912
912
|
extracted.states,
|
|
@@ -915,28 +915,29 @@ async function generateFilesForSlice(
|
|
|
915
915
|
extracted.projectionSingleton,
|
|
916
916
|
messages,
|
|
917
917
|
integrations,
|
|
918
|
-
|
|
918
|
+
scenes,
|
|
919
919
|
);
|
|
920
920
|
|
|
921
|
-
|
|
921
|
+
debugMoment(' Generating %d files from templates', filteredTemplates.length);
|
|
922
922
|
const plans = await Promise.all(
|
|
923
923
|
filteredTemplates.map((template) =>
|
|
924
|
-
generateFileForTemplate(template, slice,
|
|
924
|
+
generateFileForTemplate(template, slice, momentDir, templateData, unionToEnumName),
|
|
925
925
|
),
|
|
926
926
|
);
|
|
927
|
-
|
|
927
|
+
debugMoment(' Generated %d file plans for moment: %s', plans.length, slice.name);
|
|
928
928
|
return { plans, duplicateCommands };
|
|
929
929
|
}
|
|
930
930
|
|
|
931
931
|
export async function generateScaffoldFilePlans(
|
|
932
|
-
|
|
932
|
+
scenes: Scene[],
|
|
933
933
|
messages: Model['messages'],
|
|
934
934
|
integrations?: Model['integrations'],
|
|
935
|
-
baseDir = 'src/domain/
|
|
936
|
-
|
|
935
|
+
baseDir = 'src/domain/narratives',
|
|
936
|
+
affectedMomentIds?: Set<string>,
|
|
937
|
+
narratives?: Model['narratives'],
|
|
937
938
|
): Promise<ScaffoldResult> {
|
|
938
939
|
debug('Generating scaffold file plans');
|
|
939
|
-
debug(' Number of
|
|
940
|
+
debug(' Number of scenes: %d', scenes.length);
|
|
940
941
|
debug(' Number of messages: %d', messages?.length ?? 0);
|
|
941
942
|
debug(' Base directory: %s', baseDir);
|
|
942
943
|
|
|
@@ -969,46 +970,59 @@ export async function generateScaffoldFilePlans(
|
|
|
969
970
|
const { enums, unionToEnumName } = extractEnumsFromMessages(sanitizedMessages);
|
|
970
971
|
debug(' Extracted %d enums from messages', enums.length);
|
|
971
972
|
|
|
972
|
-
const domainBaseDir = baseDir.replace(/\/
|
|
973
|
+
const domainBaseDir = baseDir.replace(/\/scenes$/, '');
|
|
973
974
|
await writeSharedTypes(domainBaseDir, enums);
|
|
974
975
|
|
|
975
976
|
const allPlans: FilePlan[] = [];
|
|
976
977
|
const allDuplicateCommands: DuplicateCommandInfo[] = [];
|
|
977
|
-
const registeredCommands = new Map<string, {
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
const
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
978
|
+
const registeredCommands = new Map<string, { sceneName: string; momentName: string }>();
|
|
979
|
+
|
|
980
|
+
const sceneToNarrative = new Map<string, string>();
|
|
981
|
+
if (narratives) {
|
|
982
|
+
for (const narrative of narratives) {
|
|
983
|
+
for (const sceneId of narrative.sceneIds) {
|
|
984
|
+
sceneToNarrative.set(sceneId, narrative.name);
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
for (const scene of scenes) {
|
|
990
|
+
debugScene('Processing scene: %s', scene.name);
|
|
991
|
+
const narrativeName = scene.id ? sceneToNarrative.get(scene.id) : undefined;
|
|
992
|
+
const sceneDirName = narrativeName
|
|
993
|
+
? `${toKebabCase(narrativeName)}_${toKebabCase(scene.name)}`
|
|
994
|
+
: toKebabCase(scene.name);
|
|
995
|
+
const sceneDir = ensureDirPath(baseDir, sceneDirName);
|
|
996
|
+
debugScene(' Scene directory: %s', sceneDir);
|
|
997
|
+
debugScene(' Number of moments: %d', scene.moments.length);
|
|
998
|
+
|
|
999
|
+
for (const slice of scene.moments) {
|
|
1000
|
+
if (affectedMomentIds) {
|
|
1001
|
+
const sliceId = `${toKebabCase(scene.name)}/${toKebabCase(slice.name)}`;
|
|
1002
|
+
if (!affectedMomentIds.has(sliceId)) continue;
|
|
989
1003
|
}
|
|
990
|
-
|
|
991
|
-
const
|
|
992
|
-
|
|
1004
|
+
debugScene(' Processing slice: %s (type: %s)', slice.name, slice.type);
|
|
1005
|
+
const momentDir = ensureDirPath(sceneDir, toKebabCase(slice.name));
|
|
1006
|
+
debugScene(' Moment directory: %s', momentDir);
|
|
993
1007
|
try {
|
|
994
|
-
const { plans, duplicateCommands } = await
|
|
1008
|
+
const { plans, duplicateCommands } = await generateFilesForMoment(
|
|
995
1009
|
slice,
|
|
996
|
-
|
|
997
|
-
|
|
1010
|
+
scene,
|
|
1011
|
+
momentDir,
|
|
998
1012
|
sanitizedMessages,
|
|
999
|
-
|
|
1013
|
+
scenes,
|
|
1000
1014
|
unionToEnumName,
|
|
1001
1015
|
integrations,
|
|
1002
1016
|
registeredCommands,
|
|
1003
1017
|
);
|
|
1004
|
-
|
|
1018
|
+
debugScene(' Generated %d plans for moment', plans.length);
|
|
1005
1019
|
allPlans.push(...plans);
|
|
1006
1020
|
allDuplicateCommands.push(...duplicateCommands);
|
|
1007
1021
|
} catch (error) {
|
|
1008
|
-
throw new ScaffoldError(
|
|
1022
|
+
throw new ScaffoldError(scene.name, slice.name, slice.type, error);
|
|
1009
1023
|
}
|
|
1010
1024
|
}
|
|
1011
|
-
|
|
1025
|
+
debugScene(' Completed scene: %s', scene.name);
|
|
1012
1026
|
}
|
|
1013
1027
|
|
|
1014
1028
|
debug('Total file plans generated: %d', allPlans.length);
|
|
@@ -1036,16 +1050,16 @@ export async function writeScaffoldFilePlans(plans: FilePlan[]) {
|
|
|
1036
1050
|
}
|
|
1037
1051
|
|
|
1038
1052
|
export async function scaffoldFromSchema(
|
|
1039
|
-
|
|
1053
|
+
scenes: Scene[],
|
|
1040
1054
|
messages: Model['messages'],
|
|
1041
|
-
baseDir = 'src/domain/
|
|
1055
|
+
baseDir = 'src/domain/narratives',
|
|
1042
1056
|
): Promise<void> {
|
|
1043
1057
|
debug('Starting scaffold from schema');
|
|
1044
|
-
debug('
|
|
1058
|
+
debug(' Scenes: %d', scenes.length);
|
|
1045
1059
|
debug(' Messages: %d', messages?.length ?? 0);
|
|
1046
1060
|
debug(' Base directory: %s', baseDir);
|
|
1047
1061
|
|
|
1048
|
-
const { plans } = await generateScaffoldFilePlans(
|
|
1062
|
+
const { plans } = await generateScaffoldFilePlans(scenes, messages, undefined, baseDir);
|
|
1049
1063
|
debug('Generated %d file plans, writing to disk...', plans.length);
|
|
1050
1064
|
|
|
1051
1065
|
await writeScaffoldFilePlans(plans);
|
|
@@ -6,10 +6,10 @@ describe('commands.ts.ejs', () => {
|
|
|
6
6
|
it('should generate correct command file', async () => {
|
|
7
7
|
const spec: SpecsSchema = {
|
|
8
8
|
variant: 'specs',
|
|
9
|
-
|
|
9
|
+
scenes: [
|
|
10
10
|
{
|
|
11
11
|
name: 'Host creates a listing',
|
|
12
|
-
|
|
12
|
+
moments: [
|
|
13
13
|
{
|
|
14
14
|
type: 'command',
|
|
15
15
|
name: 'Create listing',
|
|
@@ -77,7 +77,7 @@ describe('commands.ts.ejs', () => {
|
|
|
77
77
|
],
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const { plans } = await generateScaffoldFilePlans(spec.
|
|
80
|
+
const { plans } = await generateScaffoldFilePlans(spec.scenes, spec.messages, undefined, 'src/domain/narratives');
|
|
81
81
|
const commandFile = plans.find((p) => p.outputPath.endsWith('commands.ts'));
|
|
82
82
|
|
|
83
83
|
expect(commandFile?.contents).toMatchInlineSnapshot(`
|