@auto-engineer/narrative 0.13.0 → 0.13.1

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.
Files changed (89) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +11 -0
  3. package/dist/src/commands/export-schema-runner.js +1 -1
  4. package/dist/src/commands/export-schema-runner.js.map +1 -1
  5. package/dist/src/fluent-builder.js +3 -3
  6. package/dist/src/fluent-builder.js.map +1 -1
  7. package/dist/src/getNarratives.specs.js +149 -153
  8. package/dist/src/getNarratives.specs.js.map +1 -1
  9. package/dist/src/id/addAutoIds.d.ts.map +1 -1
  10. package/dist/src/id/addAutoIds.js +23 -10
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +54 -45
  13. package/dist/src/id/addAutoIds.specs.js.map +1 -1
  14. package/dist/src/id/hasAllIds.d.ts.map +1 -1
  15. package/dist/src/id/hasAllIds.js +8 -3
  16. package/dist/src/id/hasAllIds.js.map +1 -1
  17. package/dist/src/id/hasAllIds.specs.js +142 -215
  18. package/dist/src/id/hasAllIds.specs.js.map +1 -1
  19. package/dist/src/index.d.ts +6 -8
  20. package/dist/src/index.d.ts.map +1 -1
  21. package/dist/src/index.js +3 -3
  22. package/dist/src/index.js.map +1 -1
  23. package/dist/src/loader/graph.d.ts.map +1 -1
  24. package/dist/src/loader/graph.js +13 -6
  25. package/dist/src/loader/graph.js.map +1 -1
  26. package/dist/src/loader/ts-utils.d.ts +1 -0
  27. package/dist/src/loader/ts-utils.d.ts.map +1 -1
  28. package/dist/src/loader/ts-utils.js +95 -16
  29. package/dist/src/loader/ts-utils.js.map +1 -1
  30. package/dist/src/model-to-narrative.specs.js +531 -449
  31. package/dist/src/model-to-narrative.specs.js.map +1 -1
  32. package/dist/src/narrative-context.d.ts +8 -8
  33. package/dist/src/narrative-context.d.ts.map +1 -1
  34. package/dist/src/narrative-context.js +111 -301
  35. package/dist/src/narrative-context.js.map +1 -1
  36. package/dist/src/narrative-context.specs.js +15 -55
  37. package/dist/src/narrative-context.specs.js.map +1 -1
  38. package/dist/src/narrative.d.ts +19 -22
  39. package/dist/src/narrative.d.ts.map +1 -1
  40. package/dist/src/narrative.js +42 -71
  41. package/dist/src/narrative.js.map +1 -1
  42. package/dist/src/samples/test-with-ids.narrative.js +13 -29
  43. package/dist/src/samples/test-with-ids.narrative.js.map +1 -1
  44. package/dist/src/schema.d.ts +2704 -8293
  45. package/dist/src/schema.d.ts.map +1 -1
  46. package/dist/src/schema.js +26 -47
  47. package/dist/src/schema.js.map +1 -1
  48. package/dist/src/slice-builder.js +3 -3
  49. package/dist/src/slice-builder.js.map +1 -1
  50. package/dist/src/transformers/model-to-narrative/generators/flow.d.ts.map +1 -1
  51. package/dist/src/transformers/model-to-narrative/generators/flow.js +118 -74
  52. package/dist/src/transformers/model-to-narrative/generators/flow.js.map +1 -1
  53. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts +9 -1
  54. package/dist/src/transformers/model-to-narrative/generators/gwt.d.ts.map +1 -1
  55. package/dist/src/transformers/model-to-narrative/generators/gwt.js +112 -112
  56. package/dist/src/transformers/model-to-narrative/generators/gwt.js.map +1 -1
  57. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts +1 -1
  58. package/dist/src/transformers/model-to-narrative/generators/imports.d.ts.map +1 -1
  59. package/dist/src/transformers/model-to-narrative/generators/imports.js +13 -9
  60. package/dist/src/transformers/model-to-narrative/generators/imports.js.map +1 -1
  61. package/dist/src/transformers/narrative-to-model/index.d.ts.map +1 -1
  62. package/dist/src/transformers/narrative-to-model/index.js +50 -23
  63. package/dist/src/transformers/narrative-to-model/index.js.map +1 -1
  64. package/dist/src/transformers/narrative-to-model/type-inference.specs.js +100 -90
  65. package/dist/src/transformers/narrative-to-model/type-inference.specs.js.map +1 -1
  66. package/dist/tsconfig.tsbuildinfo +1 -1
  67. package/package.json +5 -5
  68. package/src/commands/export-schema-runner.ts +3 -1
  69. package/src/fluent-builder.ts +3 -3
  70. package/src/getNarratives.specs.ts +168 -176
  71. package/src/id/addAutoIds.specs.ts +54 -47
  72. package/src/id/addAutoIds.ts +28 -11
  73. package/src/id/hasAllIds.specs.ts +147 -245
  74. package/src/id/hasAllIds.ts +11 -4
  75. package/src/index.ts +9 -12
  76. package/src/loader/graph.ts +23 -6
  77. package/src/loader/ts-utils.ts +169 -26
  78. package/src/model-to-narrative.specs.ts +531 -449
  79. package/src/narrative-context.specs.ts +73 -116
  80. package/src/narrative-context.ts +127 -374
  81. package/src/narrative.ts +70 -120
  82. package/src/samples/test-with-ids.narrative.ts +23 -31
  83. package/src/schema.ts +33 -52
  84. package/src/slice-builder.ts +3 -3
  85. package/src/transformers/model-to-narrative/generators/flow.ts +191 -85
  86. package/src/transformers/model-to-narrative/generators/gwt.ts +195 -178
  87. package/src/transformers/model-to-narrative/generators/imports.ts +13 -9
  88. package/src/transformers/narrative-to-model/index.ts +87 -26
  89. package/src/transformers/narrative-to-model/type-inference.specs.ts +100 -90
@@ -9,6 +9,7 @@ describe('modelToNarrative', () => {
9
9
  data,
10
10
  describe,
11
11
  example,
12
+ given,
12
13
  gql,
13
14
  it,
14
15
  narrative,
@@ -18,6 +19,8 @@ describe('modelToNarrative', () => {
18
19
  sink,
19
20
  source,
20
21
  specs,
22
+ then,
23
+ when,
21
24
  } from '@auto-engineer/narrative';
22
25
  import type { Command, Event, State } from '@auto-engineer/narrative';
23
26
  import { AI, ProductCatalog } from '../server/src/integrations.js';
@@ -114,34 +117,36 @@ narrative('Seasonal Assistant', () => {
114
117
  data([sink().event('ShoppingCriteriaEntered').toStream('shopping-session-\${sessionId}')]);
115
118
  specs('When shopper submits criteria, a shopping session is started', () => {
116
119
  rule('Valid criteria should start a shopping session', () => {
117
- example('User submits shopping criteria for children')
118
- .when<EnterShoppingCriteria>({
120
+ example('User submits shopping criteria for children', () => {
121
+ when<EnterShoppingCriteria>({
119
122
  sessionId: 'shopper-123',
120
123
  criteria:
121
124
  'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
122
- })
123
- .then<ShoppingCriteriaEntered>({
125
+ });
126
+ then<ShoppingCriteriaEntered>({
124
127
  sessionId: 'shopper-123',
125
128
  criteria:
126
129
  'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
127
130
  });
131
+ });
128
132
  });
129
133
  });
130
134
  });
131
135
  react('creates a chat session').server(() => {
132
136
  specs('When shopping criteria are entered, request wishlist creation', () => {
133
137
  rule('Shopping criteria should trigger item suggestion', () => {
134
- example('Criteria entered triggers wishlist creation')
135
- .when<ShoppingCriteriaEntered>({
138
+ example('Criteria entered triggers wishlist creation', () => {
139
+ when<ShoppingCriteriaEntered>({
136
140
  sessionId: 'session-abc',
137
141
  criteria:
138
142
  'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
139
- })
140
- .then<SuggestShoppingItems>({
143
+ });
144
+ then<SuggestShoppingItems>({
141
145
  sessionId: 'session-abc',
142
146
  prompt:
143
147
  'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
144
148
  });
149
+ });
145
150
  });
146
151
  });
147
152
  });
@@ -158,8 +163,8 @@ narrative('Seasonal Assistant', () => {
158
163
  ]);
159
164
  specs('When chat is triggered, AI suggests items based on product catalog', () => {
160
165
  rule('AI should suggest relevant items from available products', () => {
161
- example('Product catalog with matching items generates suggestions')
162
- .given<Products>({
166
+ example('Product catalog with matching items generates suggestions', () => {
167
+ given<Products>({
163
168
  products: [
164
169
  {
165
170
  productId: 'prod-soccer-ball',
@@ -194,13 +199,13 @@ narrative('Seasonal Assistant', () => {
194
199
  imageUrl: 'https://example.com/mtg-starter.jpg',
195
200
  },
196
201
  ],
197
- })
198
- .when<SuggestShoppingItems>({
202
+ });
203
+ when<SuggestShoppingItems>({
199
204
  sessionId: 'session-abc',
200
205
  prompt:
201
206
  'I need back-to-school items for my 7-year-old daughter who loves soccer and crafts, and my 12-year-old son who is into computers and Magic the Gathering.',
202
- })
203
- .then<ShoppingItemsSuggested>({
207
+ });
208
+ then<ShoppingItemsSuggested>({
204
209
  sessionId: 'session-abc',
205
210
  suggestedItems: [
206
211
  {
@@ -229,6 +234,7 @@ narrative('Seasonal Assistant', () => {
229
234
  },
230
235
  ],
231
236
  });
237
+ });
232
238
  });
233
239
  });
234
240
  });
@@ -257,8 +263,8 @@ narrative('Seasonal Assistant', () => {
257
263
  data([source().state('SuggestedItems').fromProjection('SuggestedItemsProjection', 'sessionId')]);
258
264
  specs('Suggested items are available for viewing', () => {
259
265
  rule('Items should be available for viewing after suggestion', () => {
260
- example('Item becomes available after AI suggestion event')
261
- .when<ShoppingItemsSuggested>({
266
+ example('Item becomes available after AI suggestion event', () => {
267
+ when<ShoppingItemsSuggested>({
262
268
  sessionId: 'session-abc',
263
269
  suggestedItems: [
264
270
  {
@@ -286,8 +292,8 @@ narrative('Seasonal Assistant', () => {
286
292
  reason: 'Ideal starter set for Magic the Gathering enthusiasts',
287
293
  },
288
294
  ],
289
- })
290
- .then<SuggestedItems>({
295
+ });
296
+ then<SuggestedItems>({
291
297
  sessionId: 'session-abc',
292
298
  items: [
293
299
  {
@@ -316,6 +322,7 @@ narrative('Seasonal Assistant', () => {
316
322
  },
317
323
  ],
318
324
  });
325
+ });
319
326
  });
320
327
  });
321
328
  });
@@ -331,8 +338,8 @@ narrative('Seasonal Assistant', () => {
331
338
  data([sink().event('ItemsAddedToCart').toStream('shopping-session-\${sessionId}')]);
332
339
  specs('When shopper accepts items, they are added to cart', () => {
333
340
  rule('Accepted items should be added to the shopping cart', () => {
334
- example('User selects all suggested items for cart')
335
- .when<AddItemsToCart>({
341
+ example('User selects all suggested items for cart', () => {
342
+ when<AddItemsToCart>({
336
343
  sessionId: 'session-abc',
337
344
  items: [
338
345
  { productId: 'prod-soccer-ball', quantity: 1 },
@@ -340,8 +347,8 @@ narrative('Seasonal Assistant', () => {
340
347
  { productId: 'prod-laptop-bag', quantity: 1 },
341
348
  { productId: 'prod-mtg-starter', quantity: 1 },
342
349
  ],
343
- })
344
- .then<ItemsAddedToCart>({
350
+ });
351
+ then<ItemsAddedToCart>({
345
352
  sessionId: 'session-abc',
346
353
  items: [
347
354
  { productId: 'prod-soccer-ball', quantity: 1 },
@@ -350,6 +357,7 @@ narrative('Seasonal Assistant', () => {
350
357
  { productId: 'prod-mtg-starter', quantity: 1 },
351
358
  ],
352
359
  });
360
+ });
353
361
  });
354
362
  });
355
363
  });
@@ -477,10 +485,13 @@ narrative('Test Flow without IDs', () => {
477
485
  },
478
486
  server: {
479
487
  description: 'Product query server',
480
- specs: {
481
- name: 'Product data specs',
482
- rules: [],
483
- },
488
+ specs: [
489
+ {
490
+ type: 'gherkin',
491
+ feature: 'Product data specs',
492
+ rules: [],
493
+ },
494
+ ],
484
495
  },
485
496
  },
486
497
  ],
@@ -528,30 +539,36 @@ narrative('Test Flow with IDs', 'FLOW-123', () => {
528
539
  },
529
540
  server: {
530
541
  description: 'Command processing server',
531
- specs: {
532
- name: 'Command Processing',
533
- rules: [
534
- {
535
- id: 'RULE-ABC',
536
- description: 'Valid commands should be processed',
537
- examples: [
538
- {
539
- description: 'User submits valid command',
540
- when: {
541
- commandRef: 'ProcessCommand',
542
- exampleData: { id: 'cmd-123', action: 'create' },
542
+ specs: [
543
+ {
544
+ type: 'gherkin',
545
+ feature: 'Command Processing',
546
+ rules: [
547
+ {
548
+ id: 'RULE-ABC',
549
+ name: 'Valid commands should be processed',
550
+ examples: [
551
+ {
552
+ id: 'EX-001',
553
+ name: 'User submits valid command',
554
+ steps: [
555
+ {
556
+ keyword: 'When',
557
+ text: 'ProcessCommand',
558
+ docString: { id: 'cmd-123', action: 'create' },
559
+ },
560
+ {
561
+ keyword: 'Then',
562
+ text: 'CommandProcessed',
563
+ docString: { id: 'cmd-123', status: 'success' },
564
+ },
565
+ ],
543
566
  },
544
- then: [
545
- {
546
- eventRef: 'CommandProcessed',
547
- exampleData: { id: 'cmd-123', status: 'success' },
548
- },
549
- ],
550
- },
551
- ],
552
- },
553
- ],
554
- },
567
+ ],
568
+ },
569
+ ],
570
+ },
571
+ ],
555
572
  },
556
573
  },
557
574
  ],
@@ -581,7 +598,8 @@ narrative('Test Flow with IDs', 'FLOW-123', () => {
581
598
  integrations: [],
582
599
  };
583
600
  const code = await modelToNarrative(modelWithRuleIds);
584
- expect(code).toEqual(`import { command, example, narrative, rule, specs } from '@auto-engineer/narrative';
601
+ expect(code)
602
+ .toEqual(`import { command, example, narrative, rule, specs, then, when } from '@auto-engineer/narrative';
585
603
  import type { Command, Event } from '@auto-engineer/narrative';
586
604
  type ProcessCommand = Command<
587
605
  'ProcessCommand',
@@ -601,9 +619,10 @@ narrative('Test Flow with Rule IDs', 'FLOW-456', () => {
601
619
  command('process command', 'SLICE-789').server(() => {
602
620
  specs('Command Processing', () => {
603
621
  rule('Valid commands should be processed', 'RULE-ABC', () => {
604
- example('User submits valid command')
605
- .when<ProcessCommand>({ id: 'cmd-123', action: 'create' })
606
- .then<CommandProcessed>({ id: 'cmd-123', status: 'success' });
622
+ example('User submits valid command', () => {
623
+ when<ProcessCommand>({ id: 'cmd-123', action: 'create' });
624
+ then<CommandProcessed>({ id: 'cmd-123', status: 'success' });
625
+ });
607
626
  });
608
627
  });
609
628
  });
@@ -727,59 +746,64 @@ narrative('Questionnaire Flow', 'QUEST-001', () => {});
727
746
  },
728
747
  },
729
748
  ],
730
- specs: {
731
- name: '',
732
- rules: [
733
- {
734
- id: 'r1A3Bp9W',
735
- description: 'questionnaires show current progress',
736
- examples: [
737
- {
738
- description: 'a question has already been answered',
739
- given: [
740
- {
741
- eventRef: 'QuestionnaireLinkSent',
742
- exampleData: {
743
- questionnaireId: 'q-001',
744
- participantId: 'participant-abc',
745
- link: 'https://app.example.com/q/q-001?participant=participant-abc',
746
- sentAt: new Date('2030-01-01T09:00:00.000Z'),
749
+ specs: [
750
+ {
751
+ type: 'gherkin',
752
+ feature: '',
753
+ rules: [
754
+ {
755
+ id: 'r1A3Bp9W',
756
+ name: 'questionnaires show current progress',
757
+ examples: [
758
+ {
759
+ id: 'EX-001',
760
+ name: 'a question has already been answered',
761
+ steps: [
762
+ {
763
+ keyword: 'Given',
764
+ text: 'QuestionnaireLinkSent',
765
+ docString: {
766
+ questionnaireId: 'q-001',
767
+ participantId: 'participant-abc',
768
+ link: 'https://app.example.com/q/q-001?participant=participant-abc',
769
+ sentAt: new Date('2030-01-01T09:00:00.000Z'),
770
+ },
747
771
  },
748
- },
749
- ],
750
- when: {
751
- exampleData: {
752
- questionnaireId: 'q-001',
753
- participantId: 'participant-abc',
754
- questionId: 'q1',
755
- answer: 'Yes',
756
- savedAt: new Date('2030-01-01T09:05:00.000Z'),
757
- },
758
- eventRef: 'QuestionAnswered',
759
- },
760
- then: [
761
- {
762
- stateRef: 'QuestionnaireProgress',
763
- exampleData: {
764
- questionnaireId: 'q-001',
765
- participantId: 'participant-abc',
766
- status: 'in_progress',
767
- currentQuestionId: 'q2',
768
- remainingQuestions: ['q2', 'q3'],
769
- answers: [
770
- {
771
- questionId: 'q1',
772
- value: 'Yes',
773
- },
774
- ],
772
+ {
773
+ keyword: 'When',
774
+ text: 'QuestionAnswered',
775
+ docString: {
776
+ questionnaireId: 'q-001',
777
+ participantId: 'participant-abc',
778
+ questionId: 'q1',
779
+ answer: 'Yes',
780
+ savedAt: new Date('2030-01-01T09:05:00.000Z'),
781
+ },
775
782
  },
776
- },
777
- ],
778
- },
779
- ],
780
- },
781
- ],
782
- },
783
+ {
784
+ keyword: 'Then',
785
+ text: 'QuestionnaireProgress',
786
+ docString: {
787
+ questionnaireId: 'q-001',
788
+ participantId: 'participant-abc',
789
+ status: 'in_progress',
790
+ currentQuestionId: 'q2',
791
+ remainingQuestions: ['q2', 'q3'],
792
+ answers: [
793
+ {
794
+ questionId: 'q1',
795
+ value: 'Yes',
796
+ },
797
+ ],
798
+ },
799
+ },
800
+ ],
801
+ },
802
+ ],
803
+ },
804
+ ],
805
+ },
806
+ ],
783
807
  },
784
808
  },
785
809
  ],
@@ -899,6 +923,7 @@ narrative('Questionnaire Flow', 'QUEST-001', () => {});
899
923
  describe,
900
924
  example,
901
925
  experience,
926
+ given,
902
927
  gql,
903
928
  it,
904
929
  narrative,
@@ -906,6 +931,8 @@ narrative('Questionnaire Flow', 'QUEST-001', () => {});
906
931
  rule,
907
932
  source,
908
933
  specs,
934
+ then,
935
+ when,
909
936
  } from '@auto-engineer/narrative';
910
937
  import type { Event, State } from '@auto-engineer/narrative';
911
938
  type QuestionnaireLinkSent = Event<
@@ -974,21 +1001,21 @@ narrative('Questionnaires', 'Q9m2Kp4Lx', () => {
974
1001
  data([source().state('QuestionnaireProgress').fromProjection('Questionnaires', 'questionnaire-participantId')]);
975
1002
  specs(() => {
976
1003
  rule('questionnaires show current progress', 'r1A3Bp9W', () => {
977
- example('a question has already been answered')
978
- .given<QuestionnaireLinkSent>({
1004
+ example('a question has already been answered', () => {
1005
+ given<QuestionnaireLinkSent>({
979
1006
  questionnaireId: 'q-001',
980
1007
  participantId: 'participant-abc',
981
1008
  link: 'https://app.example.com/q/q-001?participant=participant-abc',
982
1009
  sentAt: new Date('2030-01-01T09:00:00.000Z'),
983
- })
984
- .when<QuestionAnswered>({
1010
+ });
1011
+ when<QuestionAnswered>({
985
1012
  questionnaireId: 'q-001',
986
1013
  participantId: 'participant-abc',
987
1014
  questionId: 'q1',
988
1015
  answer: 'Yes',
989
1016
  savedAt: new Date('2030-01-01T09:05:00.000Z'),
990
- })
991
- .then<QuestionnaireProgress>({
1017
+ });
1018
+ then<QuestionnaireProgress>({
992
1019
  questionnaireId: 'q-001',
993
1020
  participantId: 'participant-abc',
994
1021
  status: 'in_progress',
@@ -996,6 +1023,7 @@ narrative('Questionnaires', 'Q9m2Kp4Lx', () => {
996
1023
  remainingQuestions: ['q2', 'q3'],
997
1024
  answers: [{ questionId: 'q1', value: 'Yes' }],
998
1025
  });
1026
+ });
999
1027
  });
1000
1028
  });
1001
1029
  });
@@ -1019,71 +1047,78 @@ narrative('Questionnaires', 'Q9m2Kp4Lx', () => {
1019
1047
  },
1020
1048
  server: {
1021
1049
  description: 'Test server for duplicate rules',
1022
- specs: {
1023
- name: 'Test Rules',
1024
- rules: [
1025
- {
1026
- id: 'r1A3Bp9W',
1027
- description: 'questionnaires show current progress',
1028
- examples: [
1029
- {
1030
- description: 'a question has already been answered',
1031
- given: [
1032
- {
1033
- eventRef: 'QuestionnaireLinkSent',
1034
- exampleData: {
1035
- questionnaireId: 'q-001',
1036
- participantId: 'participant-abc',
1050
+ specs: [
1051
+ {
1052
+ type: 'gherkin',
1053
+ feature: 'Test Rules',
1054
+ rules: [
1055
+ {
1056
+ id: 'r1A3Bp9W',
1057
+ name: 'questionnaires show current progress',
1058
+ examples: [
1059
+ {
1060
+ id: 'EX-001',
1061
+ name: 'a question has already been answered',
1062
+ steps: [
1063
+ {
1064
+ keyword: 'Given',
1065
+ text: 'QuestionnaireLinkSent',
1066
+ docString: {
1067
+ questionnaireId: 'q-001',
1068
+ participantId: 'participant-abc',
1069
+ },
1037
1070
  },
1038
- },
1039
- ],
1040
- when: {
1041
- eventRef: 'QuestionAnswered',
1042
- exampleData: {
1043
- questionnaireId: 'q-001',
1044
- questionId: 'q1',
1045
- answer: 'Yes',
1046
- },
1047
- },
1048
- then: [
1049
- {
1050
- stateRef: 'QuestionnaireProgress',
1051
- exampleData: {
1052
- questionnaireId: 'q-001',
1053
- status: 'in_progress',
1071
+ {
1072
+ keyword: 'When',
1073
+ text: 'QuestionAnswered',
1074
+ docString: {
1075
+ questionnaireId: 'q-001',
1076
+ questionId: 'q1',
1077
+ answer: 'Yes',
1078
+ },
1054
1079
  },
1055
- },
1056
- ],
1057
- },
1058
- {
1059
- description: 'no questions have been answered yet',
1060
- given: [
1061
- {
1062
- eventRef: 'QuestionnaireLinkSent',
1063
- exampleData: {
1064
- questionnaireId: 'q-001',
1065
- participantId: 'participant-abc',
1080
+ {
1081
+ keyword: 'Then',
1082
+ text: 'QuestionnaireProgress',
1083
+ docString: {
1084
+ questionnaireId: 'q-001',
1085
+ status: 'in_progress',
1086
+ },
1066
1087
  },
1067
- },
1068
- ],
1069
- when: {
1070
- eventRef: 'QuestionnaireLinkSent',
1071
- exampleData: {},
1088
+ ],
1072
1089
  },
1073
- then: [
1074
- {
1075
- stateRef: 'QuestionnaireProgress',
1076
- exampleData: {
1077
- questionnaireId: 'q-001',
1078
- status: 'in_progress',
1090
+ {
1091
+ id: 'EX-002',
1092
+ name: 'no questions have been answered yet',
1093
+ steps: [
1094
+ {
1095
+ keyword: 'Given',
1096
+ text: 'QuestionnaireLinkSent',
1097
+ docString: {
1098
+ questionnaireId: 'q-001',
1099
+ participantId: 'participant-abc',
1100
+ },
1079
1101
  },
1080
- },
1081
- ],
1082
- },
1083
- ],
1084
- },
1085
- ],
1086
- },
1102
+ {
1103
+ keyword: 'When',
1104
+ text: 'QuestionnaireLinkSent',
1105
+ docString: {},
1106
+ },
1107
+ {
1108
+ keyword: 'Then',
1109
+ text: 'QuestionnaireProgress',
1110
+ docString: {
1111
+ questionnaireId: 'q-001',
1112
+ status: 'in_progress',
1113
+ },
1114
+ },
1115
+ ],
1116
+ },
1117
+ ],
1118
+ },
1119
+ ],
1120
+ },
1121
+ ],
1087
1122
  },
1088
1123
  },
1089
1124
  ],
@@ -1124,7 +1159,8 @@ narrative('Questionnaires', 'Q9m2Kp4Lx', () => {
1124
1159
  integrations: [],
1125
1160
  };
1126
1161
  const code = await modelToNarrative(modelWithDuplicateRules);
1127
- expect(code).toEqual(`import { example, narrative, query, rule, specs } from '@auto-engineer/narrative';
1162
+ expect(code)
1163
+ .toEqual(`import { example, given, narrative, query, rule, specs, then, when } from '@auto-engineer/narrative';
1128
1164
  import type { Event, State } from '@auto-engineer/narrative';
1129
1165
  type QuestionnaireLinkSent = Event<
1130
1166
  'QuestionnaireLinkSent',
@@ -1152,21 +1188,22 @@ narrative('Test Flow', 'TEST-FLOW', () => {
1152
1188
  query('test slice', 'TEST-SLICE').server(() => {
1153
1189
  specs('Test Rules', () => {
1154
1190
  rule('questionnaires show current progress', 'r1A3Bp9W', () => {
1155
- example('a question has already been answered')
1156
- .given<QuestionnaireLinkSent>({ questionnaireId: 'q-001', participantId: 'participant-abc' })
1157
- .when<QuestionAnswered>({ questionnaireId: 'q-001', questionId: 'q1', answer: 'Yes' })
1158
- .then<QuestionnaireProgress>({ questionnaireId: 'q-001', status: 'in_progress' });
1159
- example('no questions have been answered yet')
1160
- .given<QuestionnaireLinkSent>({ questionnaireId: 'q-001', participantId: 'participant-abc' })
1161
- .when<QuestionnaireLinkSent>({})
1162
- .then<QuestionnaireProgress>({ questionnaireId: 'q-001', status: 'in_progress' });
1191
+ example('a question has already been answered', () => {
1192
+ given<QuestionnaireLinkSent>({ questionnaireId: 'q-001', participantId: 'participant-abc' });
1193
+ when<QuestionAnswered>({ questionnaireId: 'q-001', questionId: 'q1', answer: 'Yes' });
1194
+ then<QuestionnaireProgress>({ questionnaireId: 'q-001', status: 'in_progress' });
1195
+ });
1196
+ example('no questions have been answered yet', () => {
1197
+ given<QuestionnaireLinkSent>({ questionnaireId: 'q-001', participantId: 'participant-abc' });
1198
+ then<QuestionnaireProgress>({ questionnaireId: 'q-001', status: 'in_progress' });
1199
+ });
1163
1200
  });
1164
1201
  });
1165
1202
  });
1166
1203
  });
1167
1204
  `);
1168
1205
  });
1169
- it('should chain multiple given examples with .and() syntax', async () => {
1206
+ it('should chain multiple given examples with and() syntax', async () => {
1170
1207
  const modelWithMultiGiven = {
1171
1208
  variant: 'specs',
1172
1209
  narratives: [
@@ -1183,84 +1220,92 @@ narrative('Test Flow', 'TEST-FLOW', () => {
1183
1220
  },
1184
1221
  server: {
1185
1222
  description: 'Multi given server rules',
1186
- specs: {
1187
- name: 'Multi Given Rules',
1188
- rules: [
1189
- {
1190
- id: 'MultiGiven',
1191
- description: 'all questions have been answered',
1192
- examples: [
1193
- {
1194
- description: 'questionnaire with multiple events',
1195
- given: [
1196
- {
1197
- stateRef: 'QuestionnaireConfig',
1198
- exampleData: {
1199
- questionnaireId: 'q-001',
1200
- numberOfQuestions: 3,
1223
+ specs: [
1224
+ {
1225
+ type: 'gherkin',
1226
+ feature: 'Multi Given Rules',
1227
+ rules: [
1228
+ {
1229
+ id: 'MultiGiven',
1230
+ name: 'all questions have been answered',
1231
+ examples: [
1232
+ {
1233
+ id: 'EX-001',
1234
+ name: 'questionnaire with multiple events',
1235
+ steps: [
1236
+ {
1237
+ keyword: 'Given',
1238
+ text: 'QuestionnaireConfig',
1239
+ docString: {
1240
+ questionnaireId: 'q-001',
1241
+ numberOfQuestions: 3,
1242
+ },
1201
1243
  },
1202
- },
1203
- {
1204
- eventRef: 'QuestionnaireLinkSent',
1205
- exampleData: {
1206
- questionnaireId: 'q-001',
1207
- participantId: 'participant-abc',
1208
- link: 'https://example.com/q/q-001',
1209
- sentAt: new Date('2030-01-01T09:00:00.000Z'),
1244
+ {
1245
+ keyword: 'And',
1246
+ text: 'QuestionnaireLinkSent',
1247
+ docString: {
1248
+ questionnaireId: 'q-001',
1249
+ participantId: 'participant-abc',
1250
+ link: 'https://example.com/q/q-001',
1251
+ sentAt: new Date('2030-01-01T09:00:00.000Z'),
1252
+ },
1210
1253
  },
1211
- },
1212
- {
1213
- eventRef: 'QuestionAnswered',
1214
- exampleData: {
1215
- questionnaireId: 'q-001',
1216
- participantId: 'participant-abc',
1217
- questionId: 'q1',
1218
- answer: 'Yes',
1219
- savedAt: new Date('2030-01-01T09:05:00.000Z'),
1254
+ {
1255
+ keyword: 'And',
1256
+ text: 'QuestionAnswered',
1257
+ docString: {
1258
+ questionnaireId: 'q-001',
1259
+ participantId: 'participant-abc',
1260
+ questionId: 'q1',
1261
+ answer: 'Yes',
1262
+ savedAt: new Date('2030-01-01T09:05:00.000Z'),
1263
+ },
1220
1264
  },
1221
- },
1222
- {
1223
- eventRef: 'QuestionAnswered',
1224
- exampleData: {
1225
- questionnaireId: 'q-001',
1226
- participantId: 'participant-abc',
1227
- questionId: 'q2',
1228
- answer: 'No',
1229
- savedAt: new Date('2030-01-01T09:10:00.000Z'),
1265
+ {
1266
+ keyword: 'And',
1267
+ text: 'QuestionAnswered',
1268
+ docString: {
1269
+ questionnaireId: 'q-001',
1270
+ participantId: 'participant-abc',
1271
+ questionId: 'q2',
1272
+ answer: 'No',
1273
+ savedAt: new Date('2030-01-01T09:10:00.000Z'),
1274
+ },
1230
1275
  },
1231
- },
1232
- ],
1233
- when: {
1234
- eventRef: 'QuestionAnswered',
1235
- exampleData: {
1236
- questionnaireId: 'q-001',
1237
- participantId: 'participant-abc',
1238
- questionId: 'q3',
1239
- answer: 'Maybe',
1240
- savedAt: new Date('2030-01-01T09:15:00.000Z'),
1241
- },
1242
- },
1243
- then: [
1244
- {
1245
- stateRef: 'QuestionnaireProgress',
1246
- exampleData: {
1247
- questionnaireId: 'q-001',
1248
- participantId: 'participant-abc',
1249
- status: 'ready_to_submit',
1250
- currentQuestionId: null,
1251
- remainingQuestions: [],
1252
- answers: [
1253
- { questionId: 'q1', value: 'Yes' },
1254
- { questionId: 'q2', value: 'No' },
1255
- ],
1276
+ {
1277
+ keyword: 'When',
1278
+ text: 'QuestionAnswered',
1279
+ docString: {
1280
+ questionnaireId: 'q-001',
1281
+ participantId: 'participant-abc',
1282
+ questionId: 'q3',
1283
+ answer: 'Maybe',
1284
+ savedAt: new Date('2030-01-01T09:15:00.000Z'),
1285
+ },
1256
1286
  },
1257
- },
1258
- ],
1259
- },
1260
- ],
1261
- },
1262
- ],
1263
- },
1287
+ {
1288
+ keyword: 'Then',
1289
+ text: 'QuestionnaireProgress',
1290
+ docString: {
1291
+ questionnaireId: 'q-001',
1292
+ participantId: 'participant-abc',
1293
+ status: 'ready_to_submit',
1294
+ currentQuestionId: null,
1295
+ remainingQuestions: [],
1296
+ answers: [
1297
+ { questionId: 'q1', value: 'Yes' },
1298
+ { questionId: 'q2', value: 'No' },
1299
+ ],
1300
+ },
1301
+ },
1302
+ ],
1303
+ },
1304
+ ],
1305
+ },
1306
+ ],
1307
+ },
1308
+ ],
1264
1309
  },
1265
1310
  },
1266
1311
  ],
@@ -1318,7 +1363,8 @@ narrative('Test Flow', 'TEST-FLOW', () => {
1318
1363
  integrations: [],
1319
1364
  };
1320
1365
  const code = await modelToNarrative(modelWithMultiGiven);
1321
- expect(code).toEqual(`import { example, narrative, query, rule, specs } from '@auto-engineer/narrative';
1366
+ expect(code)
1367
+ .toEqual(`import { and, example, given, narrative, query, rule, specs, then, when } from '@auto-engineer/narrative';
1322
1368
  import type { Event, State } from '@auto-engineer/narrative';
1323
1369
  type QuestionnaireConfig = State<
1324
1370
  'QuestionnaireConfig',
@@ -1364,36 +1410,36 @@ narrative('Multi Given Flow', 'MULTI-GIVEN', () => {
1364
1410
  query('multi given slice', 'MULTI-SLICE').server(() => {
1365
1411
  specs('Multi Given Rules', () => {
1366
1412
  rule('all questions have been answered', 'MultiGiven', () => {
1367
- example('questionnaire with multiple events')
1368
- .given<QuestionnaireConfig>({ questionnaireId: 'q-001', numberOfQuestions: 3 })
1369
- .and<QuestionnaireLinkSent>({
1413
+ example('questionnaire with multiple events', () => {
1414
+ given<QuestionnaireConfig>({ questionnaireId: 'q-001', numberOfQuestions: 3 });
1415
+ and<QuestionnaireLinkSent>({
1370
1416
  questionnaireId: 'q-001',
1371
1417
  participantId: 'participant-abc',
1372
1418
  link: 'https://example.com/q/q-001',
1373
1419
  sentAt: new Date('2030-01-01T09:00:00.000Z'),
1374
- })
1375
- .and<QuestionAnswered>({
1420
+ });
1421
+ and<QuestionAnswered>({
1376
1422
  questionnaireId: 'q-001',
1377
1423
  participantId: 'participant-abc',
1378
1424
  questionId: 'q1',
1379
1425
  answer: 'Yes',
1380
1426
  savedAt: new Date('2030-01-01T09:05:00.000Z'),
1381
- })
1382
- .and<QuestionAnswered>({
1427
+ });
1428
+ and<QuestionAnswered>({
1383
1429
  questionnaireId: 'q-001',
1384
1430
  participantId: 'participant-abc',
1385
1431
  questionId: 'q2',
1386
1432
  answer: 'No',
1387
1433
  savedAt: new Date('2030-01-01T09:10:00.000Z'),
1388
- })
1389
- .when<QuestionAnswered>({
1434
+ });
1435
+ when<QuestionAnswered>({
1390
1436
  questionnaireId: 'q-001',
1391
1437
  participantId: 'participant-abc',
1392
1438
  questionId: 'q3',
1393
1439
  answer: 'Maybe',
1394
1440
  savedAt: new Date('2030-01-01T09:15:00.000Z'),
1395
- })
1396
- .then<QuestionnaireProgress>({
1441
+ });
1442
+ then<QuestionnaireProgress>({
1397
1443
  questionnaireId: 'q-001',
1398
1444
  participantId: 'participant-abc',
1399
1445
  status: 'ready_to_submit',
@@ -1404,6 +1450,7 @@ narrative('Multi Given Flow', 'MULTI-GIVEN', () => {
1404
1450
  { questionId: 'q2', value: 'No' },
1405
1451
  ],
1406
1452
  });
1453
+ });
1407
1454
  });
1408
1455
  });
1409
1456
  });
@@ -1451,45 +1498,50 @@ narrative('Multi Given Flow', 'MULTI-GIVEN', () => {
1451
1498
  },
1452
1499
  },
1453
1500
  ],
1454
- specs: {
1455
- name: 'Database State Rules',
1456
- rules: [
1457
- {
1458
- id: 'RefState',
1459
- description: 'questionnaire config is available when referenced',
1460
- examples: [
1461
- {
1462
- description: 'config from database is accessible',
1463
- given: [
1464
- {
1465
- stateRef: 'QuestionnaireConfig',
1466
- exampleData: {
1467
- questionnaireId: 'q-001',
1468
- numberOfQuestions: 5,
1469
- title: 'Customer Satisfaction Survey',
1501
+ specs: [
1502
+ {
1503
+ type: 'gherkin',
1504
+ feature: 'Database State Rules',
1505
+ rules: [
1506
+ {
1507
+ id: 'RefState',
1508
+ name: 'questionnaire config is available when referenced',
1509
+ examples: [
1510
+ {
1511
+ id: 'EX-001',
1512
+ name: 'config from database is accessible',
1513
+ steps: [
1514
+ {
1515
+ keyword: 'Given',
1516
+ text: 'QuestionnaireConfig',
1517
+ docString: {
1518
+ questionnaireId: 'q-001',
1519
+ numberOfQuestions: 5,
1520
+ title: 'Customer Satisfaction Survey',
1521
+ },
1470
1522
  },
1471
- },
1472
- ],
1473
- when: {
1474
- eventRef: 'QuestionnaireProgress',
1475
- exampleData: {},
1476
- },
1477
- then: [
1478
- {
1479
- stateRef: 'QuestionnaireProgress',
1480
- exampleData: {
1481
- questionnaireId: 'q-001',
1482
- participantId: 'participant-abc',
1483
- status: 'in_progress',
1484
- totalQuestions: 5,
1523
+ {
1524
+ keyword: 'When',
1525
+ text: 'QuestionnaireProgress',
1526
+ docString: {},
1485
1527
  },
1486
- },
1487
- ],
1488
- },
1489
- ],
1490
- },
1491
- ],
1492
- },
1528
+ {
1529
+ keyword: 'Then',
1530
+ text: 'QuestionnaireProgress',
1531
+ docString: {
1532
+ questionnaireId: 'q-001',
1533
+ participantId: 'participant-abc',
1534
+ status: 'in_progress',
1535
+ totalQuestions: 5,
1536
+ },
1537
+ },
1538
+ ],
1539
+ },
1540
+ ],
1541
+ },
1542
+ ],
1543
+ },
1544
+ ],
1493
1545
  },
1494
1546
  },
1495
1547
  ],
@@ -1522,7 +1574,7 @@ narrative('Multi Given Flow', 'MULTI-GIVEN', () => {
1522
1574
  };
1523
1575
  const code = await modelToNarrative(modelWithReferencedStates);
1524
1576
  expect(code)
1525
- .toEqual(`import { data, example, narrative, query, rule, source, specs } from '@auto-engineer/narrative';
1577
+ .toEqual(`import { data, example, given, narrative, query, rule, source, specs, then } from '@auto-engineer/narrative';
1526
1578
  import type { State } from '@auto-engineer/narrative';
1527
1579
  type QuestionnaireProgress = State<
1528
1580
  'QuestionnaireProgress',
@@ -1549,19 +1601,19 @@ narrative('Referenced States Flow', 'REF-STATES', () => {
1549
1601
  ]);
1550
1602
  specs('Database State Rules', () => {
1551
1603
  rule('questionnaire config is available when referenced', 'RefState', () => {
1552
- example('config from database is accessible')
1553
- .given<QuestionnaireConfig>({
1604
+ example('config from database is accessible', () => {
1605
+ given<QuestionnaireConfig>({
1554
1606
  questionnaireId: 'q-001',
1555
1607
  numberOfQuestions: 5,
1556
1608
  title: 'Customer Satisfaction Survey',
1557
- })
1558
- .when<QuestionnaireProgress>({})
1559
- .then<QuestionnaireProgress>({
1609
+ });
1610
+ then<QuestionnaireProgress>({
1560
1611
  questionnaireId: 'q-001',
1561
1612
  participantId: 'participant-abc',
1562
1613
  status: 'in_progress',
1563
1614
  totalQuestions: 5,
1564
1615
  });
1616
+ });
1565
1617
  });
1566
1618
  });
1567
1619
  });
@@ -1585,48 +1637,53 @@ narrative('Referenced States Flow', 'REF-STATES', () => {
1585
1637
  },
1586
1638
  server: {
1587
1639
  description: 'Date server with Date fields',
1588
- specs: {
1589
- name: 'Date Field Rules',
1590
- rules: [
1591
- {
1592
- id: 'DateRule',
1593
- description: 'handles Date fields correctly',
1594
- examples: [
1595
- {
1596
- description: 'event with Date fields',
1597
- given: [
1598
- {
1599
- eventRef: 'TimestampedEvent',
1600
- exampleData: {
1601
- id: 'event-123',
1602
- sentAt: new Date('2030-01-01T09:00:00.000Z'),
1603
- savedAt: new Date('2030-01-01T09:05:00.000Z'),
1604
- attemptedAt: '2030-01-01T09:10:00.000Z',
1605
- submittedAt: '2030-01-01T09:15:00.000Z',
1640
+ specs: [
1641
+ {
1642
+ type: 'gherkin',
1643
+ feature: 'Date Field Rules',
1644
+ rules: [
1645
+ {
1646
+ id: 'DateRule',
1647
+ name: 'handles Date fields correctly',
1648
+ examples: [
1649
+ {
1650
+ id: 'EX-001',
1651
+ name: 'event with Date fields',
1652
+ steps: [
1653
+ {
1654
+ keyword: 'Given',
1655
+ text: 'TimestampedEvent',
1656
+ docString: {
1657
+ id: 'event-123',
1658
+ sentAt: new Date('2030-01-01T09:00:00.000Z'),
1659
+ savedAt: new Date('2030-01-01T09:05:00.000Z'),
1660
+ attemptedAt: '2030-01-01T09:10:00.000Z',
1661
+ submittedAt: '2030-01-01T09:15:00.000Z',
1662
+ },
1606
1663
  },
1607
- },
1608
- ],
1609
- when: {
1610
- eventRef: 'ProcessEvent',
1611
- exampleData: {
1612
- processedAt: '2030-01-01T10:00:00.000Z',
1613
- },
1614
- },
1615
- then: [
1616
- {
1617
- stateRef: 'ProcessState',
1618
- exampleData: {
1619
- id: 'state-123',
1620
- completedAt: '2030-01-01T11:00:00.000Z',
1621
- status: 'completed',
1664
+ {
1665
+ keyword: 'When',
1666
+ text: 'ProcessEvent',
1667
+ docString: {
1668
+ processedAt: '2030-01-01T10:00:00.000Z',
1669
+ },
1622
1670
  },
1623
- },
1624
- ],
1625
- },
1626
- ],
1627
- },
1628
- ],
1629
- },
1671
+ {
1672
+ keyword: 'Then',
1673
+ text: 'ProcessState',
1674
+ docString: {
1675
+ id: 'state-123',
1676
+ completedAt: '2030-01-01T11:00:00.000Z',
1677
+ status: 'completed',
1678
+ },
1679
+ },
1680
+ ],
1681
+ },
1682
+ ],
1683
+ },
1684
+ ],
1685
+ },
1686
+ ],
1630
1687
  },
1631
1688
  },
1632
1689
  ],
@@ -1667,7 +1724,8 @@ narrative('Referenced States Flow', 'REF-STATES', () => {
1667
1724
  integrations: [],
1668
1725
  };
1669
1726
  const code = await modelToNarrative(modelWithDateFields);
1670
- expect(code).toEqual(`import { example, narrative, query, rule, specs } from '@auto-engineer/narrative';
1727
+ expect(code)
1728
+ .toEqual(`import { example, given, narrative, query, rule, specs, then, when } from '@auto-engineer/narrative';
1671
1729
  import type { Event, State } from '@auto-engineer/narrative';
1672
1730
  type TimestampedEvent = Event<
1673
1731
  'TimestampedEvent',
@@ -1697,20 +1755,21 @@ narrative('Date Handling Flow', 'DATE-FLOW', () => {
1697
1755
  query('date handling slice', 'DATE-SLICE').server(() => {
1698
1756
  specs('Date Field Rules', () => {
1699
1757
  rule('handles Date fields correctly', 'DateRule', () => {
1700
- example('event with Date fields')
1701
- .given<TimestampedEvent>({
1758
+ example('event with Date fields', () => {
1759
+ given<TimestampedEvent>({
1702
1760
  id: 'event-123',
1703
1761
  sentAt: new Date('2030-01-01T09:00:00.000Z'),
1704
1762
  savedAt: new Date('2030-01-01T09:05:00.000Z'),
1705
1763
  attemptedAt: new Date('2030-01-01T09:10:00.000Z'),
1706
1764
  submittedAt: new Date('2030-01-01T09:15:00.000Z'),
1707
- })
1708
- .when<ProcessEvent>({ processedAt: new Date('2030-01-01T10:00:00.000Z') })
1709
- .then<ProcessState>({
1765
+ });
1766
+ when<ProcessEvent>({ processedAt: new Date('2030-01-01T10:00:00.000Z') });
1767
+ then<ProcessState>({
1710
1768
  id: 'state-123',
1711
1769
  completedAt: new Date('2030-01-01T11:00:00.000Z'),
1712
1770
  status: 'completed',
1713
1771
  });
1772
+ });
1714
1773
  });
1715
1774
  });
1716
1775
  });
@@ -1803,71 +1862,74 @@ narrative('Response Analytics', () => {
1803
1862
  },
1804
1863
  server: {
1805
1864
  description: 'Summary calculation server',
1806
- specs: {
1807
- name: 'Summary Statistics',
1808
- rules: [
1809
- {
1810
- id: 'RULE-SUMMARY',
1811
- description: 'summary shows overall todo list statistics',
1812
- examples: [
1813
- {
1814
- description: 'calculates summary from multiple todos',
1815
- given: [
1816
- {
1817
- eventRef: 'TodoAdded',
1818
- exampleData: {
1819
- todoId: 'todo-001',
1820
- description: 'Buy groceries',
1821
- status: 'pending',
1822
- addedAt: new Date('2030-01-01T09:00:00.000Z'),
1865
+ specs: [
1866
+ {
1867
+ type: 'gherkin',
1868
+ feature: 'Summary Statistics',
1869
+ rules: [
1870
+ {
1871
+ id: 'RULE-SUMMARY',
1872
+ name: 'summary shows overall todo list statistics',
1873
+ examples: [
1874
+ {
1875
+ id: 'EX-001',
1876
+ name: 'calculates summary from multiple todos',
1877
+ steps: [
1878
+ {
1879
+ keyword: 'Given',
1880
+ text: 'TodoAdded',
1881
+ docString: {
1882
+ todoId: 'todo-001',
1883
+ description: 'Buy groceries',
1884
+ status: 'pending',
1885
+ addedAt: new Date('2030-01-01T09:00:00.000Z'),
1886
+ },
1823
1887
  },
1824
- },
1825
- {
1826
- eventRef: 'TodoAdded',
1827
- exampleData: {
1828
- todoId: 'todo-002',
1829
- description: 'Write report',
1830
- status: 'pending',
1831
- addedAt: new Date('2030-01-01T09:10:00.000Z'),
1888
+ {
1889
+ keyword: 'And',
1890
+ text: 'TodoAdded',
1891
+ docString: {
1892
+ todoId: 'todo-002',
1893
+ description: 'Write report',
1894
+ status: 'pending',
1895
+ addedAt: new Date('2030-01-01T09:10:00.000Z'),
1896
+ },
1832
1897
  },
1833
- },
1834
- {
1835
- eventRef: 'TodoMarkedInProgress',
1836
- exampleData: {
1837
- todoId: 'todo-001',
1838
- markedAt: new Date('2030-01-01T10:00:00.000Z'),
1898
+ {
1899
+ keyword: 'And',
1900
+ text: 'TodoMarkedInProgress',
1901
+ docString: {
1902
+ todoId: 'todo-001',
1903
+ markedAt: new Date('2030-01-01T10:00:00.000Z'),
1904
+ },
1839
1905
  },
1840
- },
1841
- {
1842
- eventRef: 'TodoMarkedComplete',
1843
- exampleData: {
1844
- todoId: 'todo-002',
1845
- completedAt: new Date('2030-01-01T11:00:00.000Z'),
1906
+ {
1907
+ keyword: 'And',
1908
+ text: 'TodoMarkedComplete',
1909
+ docString: {
1910
+ todoId: 'todo-002',
1911
+ completedAt: new Date('2030-01-01T11:00:00.000Z'),
1912
+ },
1846
1913
  },
1847
- },
1848
- ],
1849
- when: {
1850
- eventRef: '',
1851
- exampleData: {},
1852
- },
1853
- then: [
1854
- {
1855
- stateRef: 'TodoListSummary',
1856
- exampleData: {
1857
- summaryId: 'main-summary',
1858
- totalTodos: 2,
1859
- pendingCount: 0,
1860
- inProgressCount: 1,
1861
- completedCount: 1,
1862
- completionPercentage: 50,
1914
+ {
1915
+ keyword: 'Then',
1916
+ text: 'TodoListSummary',
1917
+ docString: {
1918
+ summaryId: 'main-summary',
1919
+ totalTodos: 2,
1920
+ pendingCount: 0,
1921
+ inProgressCount: 1,
1922
+ completedCount: 1,
1923
+ completionPercentage: 50,
1924
+ },
1863
1925
  },
1864
- },
1865
- ],
1866
- },
1867
- ],
1868
- },
1869
- ],
1870
- },
1926
+ ],
1927
+ },
1928
+ ],
1929
+ },
1930
+ ],
1931
+ },
1932
+ ],
1871
1933
  },
1872
1934
  },
1873
1935
  ],
@@ -1923,7 +1985,8 @@ narrative('Response Analytics', () => {
1923
1985
  integrations: [],
1924
1986
  };
1925
1987
  const code = await modelToNarrative(modelWithEmptyWhen);
1926
- expect(code).toEqual(`import { example, narrative, query, rule, specs } from '@auto-engineer/narrative';
1988
+ expect(code)
1989
+ .toEqual(`import { and, example, given, narrative, query, rule, specs, then } from '@auto-engineer/narrative';
1927
1990
  import type { Event, State } from '@auto-engineer/narrative';
1928
1991
  type TodoAdded = Event<
1929
1992
  'TodoAdded',
@@ -1963,22 +2026,22 @@ narrative('Todo List Summary', 'TODO-001', () => {
1963
2026
  query('views completion summary', 'SUMMARY-001').server(() => {
1964
2027
  specs('Summary Statistics', () => {
1965
2028
  rule('summary shows overall todo list statistics', 'RULE-SUMMARY', () => {
1966
- example('calculates summary from multiple todos')
1967
- .given<TodoAdded>({
2029
+ example('calculates summary from multiple todos', () => {
2030
+ given<TodoAdded>({
1968
2031
  todoId: 'todo-001',
1969
2032
  description: 'Buy groceries',
1970
2033
  status: 'pending',
1971
2034
  addedAt: new Date('2030-01-01T09:00:00.000Z'),
1972
- })
1973
- .and<TodoAdded>({
2035
+ });
2036
+ and<TodoAdded>({
1974
2037
  todoId: 'todo-002',
1975
2038
  description: 'Write report',
1976
2039
  status: 'pending',
1977
2040
  addedAt: new Date('2030-01-01T09:10:00.000Z'),
1978
- })
1979
- .and<TodoMarkedInProgress>({ todoId: 'todo-001', markedAt: new Date('2030-01-01T10:00:00.000Z') })
1980
- .and<TodoMarkedComplete>({ todoId: 'todo-002', completedAt: new Date('2030-01-01T11:00:00.000Z') })
1981
- .then<TodoListSummary>({
2041
+ });
2042
+ and<TodoMarkedInProgress>({ todoId: 'todo-001', markedAt: new Date('2030-01-01T10:00:00.000Z') });
2043
+ and<TodoMarkedComplete>({ todoId: 'todo-002', completedAt: new Date('2030-01-01T11:00:00.000Z') });
2044
+ then<TodoListSummary>({
1982
2045
  summaryId: 'main-summary',
1983
2046
  totalTodos: 2,
1984
2047
  pendingCount: 0,
@@ -1986,13 +2049,14 @@ narrative('Todo List Summary', 'TODO-001', () => {
1986
2049
  completedCount: 1,
1987
2050
  completionPercentage: 50,
1988
2051
  });
2052
+ });
1989
2053
  });
1990
2054
  });
1991
2055
  });
1992
2056
  });
1993
2057
  `);
1994
- expect(code).not.toContain('.when({})');
1995
- expect(code).not.toContain('.when<');
2058
+ expect(code).not.toContain('when({})');
2059
+ expect(code).not.toContain('when<');
1996
2060
  });
1997
2061
  describe('projection DSL generation', () => {
1998
2062
  it('should generate fromSingletonProjection for singleton projections', async () => {
@@ -2025,10 +2089,13 @@ narrative('Todo List Summary', 'TODO-001', () => {
2025
2089
  },
2026
2090
  },
2027
2091
  ],
2028
- specs: {
2029
- name: 'Summary Rules',
2030
- rules: [],
2031
- },
2092
+ specs: [
2093
+ {
2094
+ type: 'gherkin',
2095
+ feature: 'Summary Rules',
2096
+ rules: [],
2097
+ },
2098
+ ],
2032
2099
  },
2033
2100
  },
2034
2101
  ],
@@ -2095,10 +2162,13 @@ narrative('Todo Summary Flow', 'TODO-SUMMARY', () => {
2095
2162
  },
2096
2163
  },
2097
2164
  ],
2098
- specs: {
2099
- name: 'Todo Rules',
2100
- rules: [],
2101
- },
2165
+ specs: [
2166
+ {
2167
+ type: 'gherkin',
2168
+ feature: 'Todo Rules',
2169
+ rules: [],
2170
+ },
2171
+ ],
2102
2172
  },
2103
2173
  },
2104
2174
  ],
@@ -2165,10 +2235,13 @@ narrative('Todo Flow', 'TODO-FLOW', () => {
2165
2235
  },
2166
2236
  },
2167
2237
  ],
2168
- specs: {
2169
- name: 'User Project Rules',
2170
- rules: [],
2171
- },
2238
+ specs: [
2239
+ {
2240
+ type: 'gherkin',
2241
+ feature: 'User Project Rules',
2242
+ rules: [],
2243
+ },
2244
+ ],
2172
2245
  },
2173
2246
  },
2174
2247
  ],
@@ -2237,10 +2310,13 @@ narrative('User Project Flow', 'USER-PROJECT-FLOW', () => {
2237
2310
  },
2238
2311
  },
2239
2312
  ],
2240
- specs: {
2241
- name: 'Summary Rules',
2242
- rules: [],
2243
- },
2313
+ specs: [
2314
+ {
2315
+ type: 'gherkin',
2316
+ feature: 'Summary Rules',
2317
+ rules: [],
2318
+ },
2319
+ ],
2244
2320
  },
2245
2321
  },
2246
2322
  {
@@ -2265,10 +2341,13 @@ narrative('User Project Flow', 'USER-PROJECT-FLOW', () => {
2265
2341
  },
2266
2342
  },
2267
2343
  ],
2268
- specs: {
2269
- name: 'Todo Rules',
2270
- rules: [],
2271
- },
2344
+ specs: [
2345
+ {
2346
+ type: 'gherkin',
2347
+ feature: 'Todo Rules',
2348
+ rules: [],
2349
+ },
2350
+ ],
2272
2351
  },
2273
2352
  },
2274
2353
  {
@@ -2293,10 +2372,13 @@ narrative('User Project Flow', 'USER-PROJECT-FLOW', () => {
2293
2372
  },
2294
2373
  },
2295
2374
  ],
2296
- specs: {
2297
- name: 'User Project Rules',
2298
- rules: [],
2299
- },
2375
+ specs: [
2376
+ {
2377
+ type: 'gherkin',
2378
+ feature: 'User Project Rules',
2379
+ rules: [],
2380
+ },
2381
+ ],
2300
2382
  },
2301
2383
  },
2302
2384
  ],