@auto-engineer/narrative 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.
Files changed (89) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +22 -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 +50 -12
  11. package/dist/src/id/addAutoIds.js.map +1 -1
  12. package/dist/src/id/addAutoIds.specs.js +396 -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 +28 -5
  16. package/dist/src/id/hasAllIds.js.map +1 -1
  17. package/dist/src/id/hasAllIds.specs.js +407 -214
  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 +3205 -8287
  45. package/dist/src/schema.d.ts.map +1 -1
  46. package/dist/src/schema.js +29 -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 +424 -47
  72. package/src/id/addAutoIds.ts +57 -13
  73. package/src/id/hasAllIds.specs.ts +400 -223
  74. package/src/id/hasAllIds.ts +32 -6
  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 +36 -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
@@ -406,85 +406,150 @@ function addRequestToChain(
406
406
  return chain;
407
407
  }
408
408
 
409
- /**
410
- * Convert schema example structure to GWT format expected by buildGwtSpecBlock
411
- */
412
- function convertExampleToGWT(example: Example, _sliceType: 'command' | 'query' | 'react' | 'experience'): GWTBlock {
413
- const gwtBlock: GWTBlock = {
414
- then: [],
415
- };
409
+ interface StepWithDocString {
410
+ keyword: 'Given' | 'When' | 'Then' | 'And';
411
+ text: string;
412
+ docString?: Record<string, unknown>;
413
+ }
416
414
 
417
- // Add description metadata
418
- (gwtBlock as { description?: string }).description = example.description;
415
+ interface StepWithError {
416
+ keyword: 'Then';
417
+ error: { type: string; message?: string };
418
+ }
419
419
 
420
- // Convert given
421
- if (example.given) {
422
- gwtBlock.given = example.given.map((given) => {
423
- if ('stateRef' in given) {
424
- return { eventRef: given.stateRef, exampleData: given.exampleData };
425
- } else if ('eventRef' in given) {
426
- return { eventRef: given.eventRef, exampleData: given.exampleData };
427
- }
428
- return given;
429
- });
430
- }
420
+ type Step = StepWithDocString | StepWithError;
431
421
 
432
- // Convert when
433
- if (example.when !== null && example.when !== undefined) {
434
- if (Array.isArray(example.when)) {
435
- // Array of events for react slices
436
- gwtBlock.when = example.when.map((when) => {
437
- if ('eventRef' in when) {
438
- return { eventRef: when.eventRef, exampleData: when.exampleData };
439
- } else if ('commandRef' in when) {
440
- return { commandRef: when.commandRef, exampleData: when.exampleData };
441
- }
442
- return when;
443
- });
444
- } else {
445
- // Single object - could be command (command slices) or event (query slices)
446
- if ('commandRef' in example.when) {
447
- // Command for command slices
448
- gwtBlock.when = {
449
- commandRef: example.when.commandRef,
450
- exampleData: example.when.exampleData,
451
- };
452
- } else if ('eventRef' in example.when) {
453
- // Event for query slices
454
- gwtBlock.when = {
455
- eventRef: example.when.eventRef,
456
- exampleData: example.when.exampleData,
457
- };
458
- }
422
+ function isStepWithError(step: Step): step is StepWithError {
423
+ return 'error' in step;
424
+ }
425
+
426
+ interface OldFormatThenItem {
427
+ eventRef?: string;
428
+ stateRef?: string;
429
+ commandRef?: string;
430
+ exampleData: Record<string, unknown>;
431
+ }
432
+
433
+ interface OldFormatExample {
434
+ description?: string;
435
+ name?: string;
436
+ given?: Array<{ eventRef?: string; stateRef?: string; exampleData: Record<string, unknown> }>;
437
+ when?:
438
+ | { commandRef?: string; eventRef?: string; exampleData: Record<string, unknown> }
439
+ | Array<{ eventRef: string; exampleData: Record<string, unknown> }>;
440
+ then?: OldFormatThenItem[];
441
+ steps?: Step[];
442
+ }
443
+
444
+ function processErrorStep(step: StepWithError, gwtBlock: GWTBlock): void {
445
+ gwtBlock.then.push({
446
+ errorType: step.error.type as 'IllegalStateError' | 'ValidationError' | 'NotFoundError',
447
+ message: step.error.message,
448
+ });
449
+ }
450
+
451
+ function processGivenStep(step: StepWithDocString, gwtBlock: GWTBlock): void {
452
+ if (!gwtBlock.given) gwtBlock.given = [];
453
+ gwtBlock.given.push({ eventRef: step.text, exampleData: step.docString ?? {} });
454
+ }
455
+
456
+ function processWhenStep(
457
+ step: StepWithDocString,
458
+ sliceType: 'command' | 'query' | 'react' | 'experience',
459
+ gwtBlock: GWTBlock,
460
+ ): void {
461
+ if (sliceType === 'command') {
462
+ gwtBlock.when = { commandRef: step.text, exampleData: step.docString ?? {} };
463
+ } else if (sliceType === 'react' || sliceType === 'query') {
464
+ const eventData = { eventRef: step.text, exampleData: step.docString ?? {} };
465
+ if (!gwtBlock.when) {
466
+ gwtBlock.when = [eventData];
467
+ } else if (Array.isArray(gwtBlock.when)) {
468
+ gwtBlock.when.push(eventData);
459
469
  }
460
470
  }
471
+ }
461
472
 
462
- // Convert then
463
- gwtBlock.then = example.then.map((then) => {
464
- if ('eventRef' in then) {
465
- return { eventRef: then.eventRef, exampleData: then.exampleData };
466
- } else if ('commandRef' in then) {
467
- return { commandRef: then.commandRef, exampleData: then.exampleData };
468
- } else if ('stateRef' in then) {
469
- return { stateRef: then.stateRef, exampleData: then.exampleData };
470
- } else {
471
- // Error case - return as-is
472
- return then;
473
+ function processThenStep(step: StepWithDocString, gwtBlock: GWTBlock): void {
474
+ gwtBlock.then.push({ eventRef: step.text, exampleData: step.docString ?? {} });
475
+ }
476
+
477
+ function processStepsFormat(
478
+ steps: Step[],
479
+ sliceType: 'command' | 'query' | 'react' | 'experience',
480
+ gwtBlock: GWTBlock,
481
+ ): void {
482
+ let effectiveKeyword: 'Given' | 'When' | 'Then' = 'Given';
483
+
484
+ for (const step of steps) {
485
+ if (isStepWithError(step)) {
486
+ processErrorStep(step, gwtBlock);
487
+ continue;
473
488
  }
474
- });
489
+
490
+ if (step.keyword !== 'And') {
491
+ effectiveKeyword = step.keyword;
492
+ }
493
+
494
+ if (effectiveKeyword === 'Given') {
495
+ processGivenStep(step, gwtBlock);
496
+ } else if (effectiveKeyword === 'When') {
497
+ processWhenStep(step, sliceType, gwtBlock);
498
+ } else if (effectiveKeyword === 'Then') {
499
+ processThenStep(step, gwtBlock);
500
+ }
501
+ }
502
+ }
503
+
504
+ function mapThenItem(t: OldFormatThenItem): GWTBlock['then'][0] {
505
+ if (t.eventRef !== undefined && t.eventRef !== '') return { eventRef: t.eventRef, exampleData: t.exampleData };
506
+ if (t.commandRef !== undefined && t.commandRef !== '')
507
+ return { commandRef: t.commandRef, exampleData: t.exampleData };
508
+ if (t.stateRef !== undefined && t.stateRef !== '') return { stateRef: t.stateRef, exampleData: t.exampleData };
509
+ return { eventRef: '', exampleData: t.exampleData };
510
+ }
511
+
512
+ function processOldFormat(oldExample: OldFormatExample, gwtBlock: GWTBlock): void {
513
+ if (oldExample.given !== undefined && oldExample.given.length > 0) {
514
+ gwtBlock.given = oldExample.given.map((g) => ({
515
+ eventRef: g.eventRef ?? g.stateRef ?? '',
516
+ exampleData: g.exampleData,
517
+ }));
518
+ }
519
+
520
+ if (oldExample.when !== undefined) {
521
+ gwtBlock.when = oldExample.when as GWTBlock['when'];
522
+ }
523
+
524
+ if (oldExample.then !== undefined && oldExample.then.length > 0) {
525
+ gwtBlock.then = oldExample.then.map(mapThenItem);
526
+ }
527
+ }
528
+
529
+ function convertExampleToGWT(example: Example, sliceType: 'command' | 'query' | 'react' | 'experience'): GWTBlock {
530
+ const gwtBlock: GWTBlock = { then: [] };
531
+ const oldExample = example as OldFormatExample;
532
+ (gwtBlock as { name?: string }).name = oldExample.name ?? oldExample.description;
533
+
534
+ if (oldExample.steps !== undefined && oldExample.steps.length > 0) {
535
+ processStepsFormat(oldExample.steps, sliceType, gwtBlock);
536
+ } else {
537
+ processOldFormat(oldExample, gwtBlock);
538
+ }
475
539
 
476
540
  return gwtBlock;
477
541
  }
478
542
 
479
- type RuleType = { id?: string; description: string; examples: Example[] };
543
+ type RuleType = { id?: string; name: string; examples: Example[] };
480
544
  type RuleGroup = { rule: RuleType; examples: Example[] };
545
+ type SpecType = { type: 'gherkin'; feature: string; rules: RuleType[] };
481
546
 
482
547
  function buildRuleGroups(rules: RuleType[]): Map<string, RuleGroup> {
483
548
  const ruleGroups = new Map<string, RuleGroup>();
484
549
 
485
550
  for (const rule of rules) {
486
551
  const ruleId = rule.id ?? 'no-id';
487
- const ruleKey = `${ruleId}:${rule.description}`;
552
+ const ruleKey = `${ruleId}:${rule.name}`;
488
553
 
489
554
  if (ruleGroups.has(ruleKey)) {
490
555
  const existingGroup = ruleGroups.get(ruleKey)!;
@@ -513,21 +578,74 @@ function buildConsolidatedRules(
513
578
  ruleDescription?: string;
514
579
  exampleDescription?: string;
515
580
  ruleId?: string;
581
+ exampleId?: string;
516
582
  };
517
583
 
518
- extendedGwtBlock.ruleDescription = rule.description;
519
- extendedGwtBlock.exampleDescription = example.description;
584
+ extendedGwtBlock.ruleDescription = rule.name;
585
+ const oldFormatExample = example as { name?: string; description?: string };
586
+ extendedGwtBlock.exampleDescription = oldFormatExample.name ?? oldFormatExample.description;
520
587
  extendedGwtBlock.ruleId = rule.id;
588
+ extendedGwtBlock.exampleId = example.id;
521
589
 
522
590
  return extendedGwtBlock;
523
591
  });
524
592
 
525
- allRuleStatements.push(buildConsolidatedGwtSpecBlock(ts, f, rule, gwtBlocks, sliceType, messages));
593
+ allRuleStatements.push(
594
+ buildConsolidatedGwtSpecBlock(ts, f, { id: rule.id, description: rule.name }, gwtBlocks, sliceType, messages),
595
+ );
526
596
  }
527
597
 
528
598
  return allRuleStatements;
529
599
  }
530
600
 
601
+ function buildSingleSpecStatements(
602
+ ts: typeof import('typescript'),
603
+ f: tsNS.NodeFactory,
604
+ spec: SpecType,
605
+ sliceType: 'command' | 'query' | 'react' | 'experience',
606
+ messages?: Array<{ type: string; name: string; fields: Array<{ name: string; type: string; required: boolean }> }>,
607
+ ): tsNS.Statement {
608
+ const ruleGroups = buildRuleGroups(spec.rules);
609
+ const allRuleStatements = buildConsolidatedRules(ts, f, ruleGroups, sliceType, messages);
610
+
611
+ const arrowFunction = f.createArrowFunction(
612
+ undefined,
613
+ undefined,
614
+ [],
615
+ undefined,
616
+ f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
617
+ f.createBlock(allRuleStatements, true),
618
+ );
619
+
620
+ const args: tsNS.Expression[] = [];
621
+ if (spec.feature && spec.feature.trim() !== '') {
622
+ args.push(f.createStringLiteral(spec.feature));
623
+ }
624
+ args.push(arrowFunction);
625
+
626
+ return f.createExpressionStatement(f.createCallExpression(f.createIdentifier('specs'), undefined, args));
627
+ }
628
+
629
+ interface OldSpecFormat {
630
+ name?: string;
631
+ rules?: Array<{ description?: string; id?: string; examples?: Example[] }>;
632
+ }
633
+
634
+ function convertOldSpecToNewFormat(oldSpec: OldSpecFormat): SpecType | null {
635
+ if (oldSpec.rules === undefined || oldSpec.rules.length === 0) {
636
+ return null;
637
+ }
638
+ return {
639
+ type: 'gherkin',
640
+ feature: oldSpec.name ?? '',
641
+ rules: oldSpec.rules.map((r) => ({
642
+ id: r.id,
643
+ name: r.description ?? '',
644
+ examples: r.examples ?? [],
645
+ })),
646
+ };
647
+ }
648
+
531
649
  function buildServerStatements(
532
650
  ts: typeof import('typescript'),
533
651
  f: tsNS.NodeFactory,
@@ -542,28 +660,16 @@ function buildServerStatements(
542
660
  }
543
661
 
544
662
  if (server.specs !== null && server.specs !== undefined) {
545
- const ruleGroups = buildRuleGroups(server.specs.rules as RuleType[]);
546
- const allRuleStatements = buildConsolidatedRules(ts, f, ruleGroups, sliceType, messages);
547
-
548
- const arrowFunction = f.createArrowFunction(
549
- undefined,
550
- undefined,
551
- [],
552
- undefined,
553
- f.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
554
- f.createBlock(allRuleStatements, true),
555
- );
556
-
557
- const args: tsNS.Expression[] = [];
558
- if (server.specs.name && server.specs.name.trim() !== '') {
559
- args.push(f.createStringLiteral(server.specs.name));
663
+ if (Array.isArray(server.specs)) {
664
+ for (const spec of server.specs as SpecType[]) {
665
+ statements.push(buildSingleSpecStatements(ts, f, spec, sliceType, messages));
666
+ }
667
+ } else {
668
+ const convertedSpec = convertOldSpecToNewFormat(server.specs as OldSpecFormat);
669
+ if (convertedSpec !== null) {
670
+ statements.push(buildSingleSpecStatements(ts, f, convertedSpec, sliceType, messages));
671
+ }
560
672
  }
561
- args.push(arrowFunction);
562
-
563
- const specsStatement = f.createExpressionStatement(
564
- f.createCallExpression(f.createIdentifier('specs'), undefined, args),
565
- );
566
- statements.push(specsStatement);
567
673
  }
568
674
 
569
675
  return statements;