@izara_project/izara-core-generate-service-code 1.0.87 → 1.0.89

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 (32) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +25 -0
  3. package/src/compile/loadSchemas.js +199 -10
  4. package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +3 -7
  5. package/src/emit/crud/crudActionPlan.js +105 -6
  6. package/src/emit/crud/emitCrudApplicationArtifacts.js +63 -3
  7. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +15 -32
  8. package/src/emit/crud/templates/CreateAfterHookCallback_Main.ejs +121 -0
  9. package/src/emit/crud/templates/CreateBeforeHookCallback_Main.ejs +108 -0
  10. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +140 -340
  11. package/src/emit/crud/templates/CreateLogicalLib.ejs +338 -82
  12. package/src/emit/crud/templates/CreateNodeCallback_Main.ejs +90 -0
  13. package/src/emit/crud/templates/DeleteAfterHookCallback_Main.ejs +118 -0
  14. package/src/emit/crud/templates/DeleteBeforeHookCallback_Main.ejs +99 -0
  15. package/src/emit/crud/templates/DeleteComplete_Main.ejs +124 -10
  16. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +151 -179
  17. package/src/emit/crud/templates/DeleteLogicalLib.ejs +244 -27
  18. package/src/emit/crud/templates/UpdateAfterHookCallback_Main.ejs +120 -0
  19. package/src/emit/crud/templates/UpdateBeforeHookCallback_Main.ejs +112 -0
  20. package/src/emit/crud/templates/UpdateComplete_Main.ejs +104 -10
  21. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +166 -185
  22. package/src/emit/crud/templates/UpdateLogicalLib.ejs +123 -3
  23. package/src/emit/custom/customFlows.js +3 -1
  24. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +8 -2
  25. package/src/emit/rbac/templates/CancelUsage_Main.ejs +34 -14
  26. package/src/emit/rbac/templates/ConfirmUsage_Main.ejs +34 -14
  27. package/src/emit/resource/emitResourceArtifacts.js +119 -59
  28. package/test/crudApplicationArtifacts.test.js +876 -0
  29. package/test/dynamicCrudFlowSchemas.test.js +10 -5
  30. package/test/dynamicRbacFlowSchemas.test.js +24 -2
  31. package/test/graphDrivenFlowSchema.test.js +36 -0
  32. package/test/resourceArtifacts.test.js +271 -12
@@ -42,6 +42,30 @@ test('emits CRUD handlers and function YAML from schema data without reading gen
42
42
  'utf8'
43
43
  );
44
44
  assert.match(createMain, /CreateLogicalLib/);
45
+ assert.match(
46
+ createMain,
47
+ /validateFieldNames\(_izContext, requestParams, objectSchemas, errorsFound\)/
48
+ );
49
+ assert.doesNotMatch(
50
+ createMain,
51
+ /const \{ createDataDetailsLib \} = dataDetailsLib;/
52
+ );
53
+ const createLogicalLib = await readFile(
54
+ join(
55
+ rootPath,
56
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
57
+ ),
58
+ 'utf8'
59
+ );
60
+ assert.match(
61
+ createLogicalLib,
62
+ /export async function resolveAndGenerateIdentifiers\(\s*_izContext,\s*requestParams,\s*objectSchema,\s*errorsFound\s*\)/
63
+ );
64
+ assert.match(createLogicalLib, /return \{\s*objectSchemas\s*\};/);
65
+ assert.match(
66
+ createLogicalLib,
67
+ /export function validateFieldNames\(\s*_izContext,\s*requestParams,\s*objectSchemas,\s*errorsFound\s*\)/
68
+ );
45
69
  assert.doesNotMatch(createMain, /FlowHook|beforeLogical|codeHookTag/);
46
70
  const yaml = await readFile(
47
71
  join(rootPath, 'app/sls_yaml/generatedCode/source/flow-objects.yml'),
@@ -273,6 +297,26 @@ test('emits Complete handlers and inlines sync RBAC for Update with Graph', asyn
273
297
  );
274
298
  });
275
299
 
300
+ test('omits CreateComplete_Main and CreateCompleteHdrSqs when hasCreateNodeCallback is true', async () => {
301
+ const rootPath = await createRbacProject(false, true);
302
+ const result = await generateCrudApplicationArtifacts(rootPath, {
303
+ serviceTag: 'orders'
304
+ });
305
+ const createPaths = result.files
306
+ .map((file) => file.path)
307
+ .filter((path) => path.includes('/FlowObjects/Create/source/'));
308
+
309
+ assert.ok(createPaths.includes(
310
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateNodeCallback_Main.js'
311
+ ));
312
+ assert.ok(!createPaths.includes(
313
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateComplete_Main.js'
314
+ ));
315
+ assert.ok(!createPaths.includes(
316
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateComplete_HdrSqs.js'
317
+ ));
318
+ });
319
+
276
320
  test('rejects deprecated FlowHook schemas', async () => {
277
321
  const rootPath = await createRbacProject(true);
278
322
 
@@ -398,6 +442,22 @@ test('emits targetIdentifiers passed directly to checkRbacPermission options in
398
442
  serviceTag: 'orders'
399
443
  });
400
444
 
445
+ const createMain = await readFile(
446
+ join(
447
+ rootPath,
448
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
449
+ ),
450
+ 'utf8'
451
+ );
452
+ assert.match(
453
+ createMain,
454
+ /targetIdentifiers:\s*requestParams\.rbacTargetIdentifiers\s*\|\|\s*requestParams\.targetIdentifiers/
455
+ );
456
+ assert.doesNotMatch(
457
+ createMain,
458
+ /const\s*\{[^}]*targetIdentifiers[^}]*\}\s*=\s*await\s*resolveSchemaAndIdentity/
459
+ );
460
+
401
461
  const updateMain = await readFile(
402
462
  join(
403
463
  rootPath,
@@ -417,6 +477,822 @@ test('emits targetIdentifiers passed directly to checkRbacPermission options in
417
477
  assert.match(deleteMain, /checkRbacPermission\([\s\S]*targetIdentifiers,[\s\S]*rbacObjType/);
418
478
  });
419
479
 
480
+ test('emits synchronous after-hook invocation in CreateLogicalLib when codeHookTag is after and invocationType is lambdaSyncInv', async () => {
481
+ const rootPath = await createProject(true);
482
+ const schemas = join(rootPath, 'app/src/schemas');
483
+ await writeFile(
484
+ join(schemas, 'FlowSchemas/AuditAfterCreate.js'),
485
+ "export default { flowTag: 'auditAfterCreate', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'after', invocationType: 'lambdaSyncInv', objType: { objectType: 'order' } } };\n"
486
+ );
487
+ const result = await generateCrudApplicationArtifacts(rootPath, {
488
+ serviceTag: 'orders'
489
+ });
490
+
491
+ const createLogicalLib = await readFile(
492
+ join(
493
+ rootPath,
494
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
495
+ ),
496
+ 'utf8'
497
+ );
498
+ assert.match(
499
+ createLogicalLib,
500
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'auditAfterCreate'\s*\)/
501
+ );
502
+ assert.match(createLogicalLib, /Hook auditAfterCreate failed/);
503
+
504
+ execFileSync(
505
+ process.execPath,
506
+ [
507
+ '--check',
508
+ join(
509
+ rootPath,
510
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
511
+ )
512
+ ],
513
+ { stdio: 'inherit' }
514
+ );
515
+
516
+ assert.ok(result.files.every((f) => !f.path.includes('CreateAfterHookCallback')));
517
+ });
518
+
519
+ test('emits synchronous after-hook invocation in CreateNodeCallback_Main when object has graph and hook is sync', async () => {
520
+ const rootPath = await createRbacProject(false, true);
521
+ const schemas = join(rootPath, 'app/src/schemas');
522
+ await writeFile(
523
+ join(schemas, 'FlowSchemas/AuditAfterCreate.js'),
524
+ "export default { flowTag: 'auditAfterCreate', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'after', invocationType: 'lambdaSyncInv', objType: { objectType: 'order' } } };\n"
525
+ );
526
+ await generateCrudApplicationArtifacts(rootPath, {
527
+ serviceTag: 'orders'
528
+ });
529
+
530
+ const nodeCallbackMain = await readFile(
531
+ join(
532
+ rootPath,
533
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateNodeCallback_Main.js'
534
+ ),
535
+ 'utf8'
536
+ );
537
+ assert.match(
538
+ nodeCallbackMain,
539
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'auditAfterCreate'\s*\)/
540
+ );
541
+ assert.match(nodeCallbackMain, /Hook auditAfterCreate failed/);
542
+
543
+ execFileSync(
544
+ process.execPath,
545
+ [
546
+ '--check',
547
+ join(
548
+ rootPath,
549
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateNodeCallback_Main.js'
550
+ )
551
+ ],
552
+ { stdio: 'inherit' }
553
+ );
554
+ });
555
+
556
+ test('emits async after-hook callback and SNS publish in CreateLogicalLib and flow-objects.yml', async () => {
557
+ const rootPath = await createProject(true);
558
+ const schemas = join(rootPath, 'app/src/schemas');
559
+ await writeFile(
560
+ join(schemas, 'FlowSchemas/NotifyAfterCreate.js'),
561
+ "export default { flowTag: 'notifyAfterCreate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'after', invocationType: 'ownTopic', objType: { objectType: 'order' } } };\n"
562
+ );
563
+ const result = await generateCrudApplicationArtifacts(rootPath, {
564
+ serviceTag: 'orders'
565
+ });
566
+
567
+ const createLogicalLib = await readFile(
568
+ join(
569
+ rootPath,
570
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
571
+ ),
572
+ 'utf8'
573
+ );
574
+ assert.match(createLogicalLib, /sns\.publishAsync\(_izContext,/);
575
+ assert.match(
576
+ createLogicalLib,
577
+ /snsTopicArnByFlowSchema\(\s*_izContext,\s*'notifyAfterCreate_In'\s*\)/
578
+ );
579
+ assert.match(createLogicalLib, /CreateAfterHookCallbackHdrSqs/);
580
+
581
+ const afterHookMain = await readFile(
582
+ join(
583
+ rootPath,
584
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateAfterHookCallback_Main.js'
585
+ ),
586
+ 'utf8'
587
+ );
588
+ assert.match(
589
+ afterHookMain,
590
+ /export default async function CreateAfterHookCallbackMain/
591
+ );
592
+ assert.match(
593
+ afterHookMain,
594
+ /TOPIC_NAME_GENERATE_CODE\.outCreateNodeComplete/
595
+ );
596
+
597
+ const afterHookHdrSqs = await readFile(
598
+ join(
599
+ rootPath,
600
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateAfterHookCallback_HdrSqs.js'
601
+ ),
602
+ 'utf8'
603
+ );
604
+ assert.match(afterHookHdrSqs, /CreateAfterHookCallbackMain/);
605
+
606
+ execFileSync(
607
+ process.execPath,
608
+ [
609
+ '--check',
610
+ join(
611
+ rootPath,
612
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateAfterHookCallback_Main.js'
613
+ )
614
+ ],
615
+ { stdio: 'inherit' }
616
+ );
617
+ execFileSync(
618
+ process.execPath,
619
+ [
620
+ '--check',
621
+ join(
622
+ rootPath,
623
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateAfterHookCallback_HdrSqs.js'
624
+ )
625
+ ],
626
+ { stdio: 'inherit' }
627
+ );
628
+ execFileSync(
629
+ process.execPath,
630
+ [
631
+ '--check',
632
+ join(
633
+ rootPath,
634
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
635
+ )
636
+ ],
637
+ { stdio: 'inherit' }
638
+ );
639
+
640
+ const yaml = await readFile(
641
+ join(rootPath, 'app/sls_yaml/generatedCode/source/flow-objects.yml'),
642
+ 'utf8'
643
+ );
644
+ assert.match(yaml, /CreateAfterHookCallbackHdrSqs:/);
645
+ assert.match(yaml, /CreateAfterHookCallback_HdrSqs\.main/);
646
+ });
647
+
648
+ test('emits async before-hook callback and SNS publish in Create_Main and flow-objects.yml', async () => {
649
+ const rootPath = await createProject(true);
650
+ const schemas = join(rootPath, 'app/src/schemas');
651
+ await writeFile(
652
+ join(schemas, 'FlowSchemas/ValidateBeforeCreate.js'),
653
+ "export default { flowTag: 'validateBeforeCreate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
654
+ );
655
+ await generateCrudApplicationArtifacts(rootPath, {
656
+ serviceTag: 'orders'
657
+ });
658
+
659
+ const createMain = await readFile(
660
+ join(
661
+ rootPath,
662
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
663
+ ),
664
+ 'utf8'
665
+ );
666
+ assert.match(createMain, /sns\.publishAsync\(_izContext,/);
667
+ assert.match(
668
+ createMain,
669
+ /snsTopicArnByFlowSchema\(\s*_izContext,\s*'validateBeforeCreate_In'\s*\)/
670
+ );
671
+ assert.match(createMain, /CreateBeforeHookCallbackHdrSqs/);
672
+
673
+ const beforeHookMain = await readFile(
674
+ join(
675
+ rootPath,
676
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_Main.js'
677
+ ),
678
+ 'utf8'
679
+ );
680
+ assert.match(
681
+ beforeHookMain,
682
+ /export default async function CreateBeforeHookCallbackMain/
683
+ );
684
+ assert.match(beforeHookMain, /CreateLogicalLib/);
685
+
686
+ const beforeHookHdrSqs = await readFile(
687
+ join(
688
+ rootPath,
689
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_HdrSqs.js'
690
+ ),
691
+ 'utf8'
692
+ );
693
+ assert.match(beforeHookHdrSqs, /CreateBeforeHookCallbackMain/);
694
+
695
+ execFileSync(
696
+ process.execPath,
697
+ [
698
+ '--check',
699
+ join(
700
+ rootPath,
701
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_Main.js'
702
+ )
703
+ ],
704
+ { stdio: 'inherit' }
705
+ );
706
+ execFileSync(
707
+ process.execPath,
708
+ [
709
+ '--check',
710
+ join(
711
+ rootPath,
712
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateBeforeHookCallback_HdrSqs.js'
713
+ )
714
+ ],
715
+ { stdio: 'inherit' }
716
+ );
717
+ execFileSync(
718
+ process.execPath,
719
+ [
720
+ '--check',
721
+ join(
722
+ rootPath,
723
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
724
+ )
725
+ ],
726
+ { stdio: 'inherit' }
727
+ );
728
+ });
729
+
730
+ test('emits sync before-hook in Create_Main with abort check', async () => {
731
+ const rootPath = await createProject(true);
732
+ const schemas = join(rootPath, 'app/src/schemas');
733
+ await writeFile(
734
+ join(schemas, 'FlowSchemas/ValidateBeforeCreateSync.js'),
735
+ "export default { flowTag: 'validateBeforeCreateSync', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'create', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
736
+ );
737
+ await generateCrudApplicationArtifacts(rootPath, {
738
+ serviceTag: 'orders'
739
+ });
740
+
741
+ const createMain = await readFile(
742
+ join(
743
+ rootPath,
744
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
745
+ ),
746
+ 'utf8'
747
+ );
748
+ assert.match(
749
+ createMain,
750
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'validateBeforeCreateSync'\s*\)/
751
+ );
752
+ assert.match(createMain, /Hook validateBeforeCreateSync failed/);
753
+
754
+ execFileSync(
755
+ process.execPath,
756
+ [
757
+ '--check',
758
+ join(
759
+ rootPath,
760
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
761
+ )
762
+ ],
763
+ { stdio: 'inherit' }
764
+ );
765
+ });
766
+
767
+ test('emits async before-hook callback and SNS publish in Update_Main and flow-objects.yml', async () => {
768
+ const rootPath = await createProject(true);
769
+ const schemas = join(rootPath, 'app/src/schemas');
770
+ await writeFile(
771
+ join(schemas, 'FlowSchemas/ValidateBeforeUpdate.js'),
772
+ "export default { flowTag: 'validateBeforeUpdate', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'update', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
773
+ );
774
+ await generateCrudApplicationArtifacts(rootPath, {
775
+ serviceTag: 'orders'
776
+ });
777
+
778
+ const updateMain = await readFile(
779
+ join(
780
+ rootPath,
781
+ 'app/src/generatedCode/FlowObjects/Update/source/Update_Main.js'
782
+ ),
783
+ 'utf8'
784
+ );
785
+ assert.match(updateMain, /sns\.publishAsync\(_izContext,/);
786
+ assert.match(
787
+ updateMain,
788
+ /snsTopicArnByFlowSchema\(\s*_izContext,\s*'validateBeforeUpdate_In'\s*\)/
789
+ );
790
+ assert.match(updateMain, /UpdateBeforeHookCallbackHdrSqs/);
791
+
792
+ const beforeHookMain = await readFile(
793
+ join(
794
+ rootPath,
795
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateBeforeHookCallback_Main.js'
796
+ ),
797
+ 'utf8'
798
+ );
799
+ assert.match(
800
+ beforeHookMain,
801
+ /export default async function UpdateBeforeHookCallbackMain/
802
+ );
803
+ assert.match(beforeHookMain, /UpdateLogicalLib/);
804
+ assert.match(beforeHookMain, /handleUpdateError/);
805
+
806
+ const beforeHookHdrSqs = await readFile(
807
+ join(
808
+ rootPath,
809
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateBeforeHookCallback_HdrSqs.js'
810
+ ),
811
+ 'utf8'
812
+ );
813
+ assert.match(beforeHookHdrSqs, /UpdateBeforeHookCallbackMain/);
814
+
815
+ execFileSync(
816
+ process.execPath,
817
+ [
818
+ '--check',
819
+ join(
820
+ rootPath,
821
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateBeforeHookCallback_Main.js'
822
+ )
823
+ ],
824
+ { stdio: 'inherit' }
825
+ );
826
+ execFileSync(
827
+ process.execPath,
828
+ [
829
+ '--check',
830
+ join(
831
+ rootPath,
832
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateBeforeHookCallback_HdrSqs.js'
833
+ )
834
+ ],
835
+ { stdio: 'inherit' }
836
+ );
837
+ execFileSync(
838
+ process.execPath,
839
+ [
840
+ '--check',
841
+ join(
842
+ rootPath,
843
+ 'app/src/generatedCode/FlowObjects/Update/source/Update_Main.js'
844
+ )
845
+ ],
846
+ { stdio: 'inherit' }
847
+ );
848
+ });
849
+
850
+ test('emits sync before-hook in Update_Main with abort check', async () => {
851
+ const rootPath = await createProject(true);
852
+ const schemas = join(rootPath, 'app/src/schemas');
853
+ await writeFile(
854
+ join(schemas, 'FlowSchemas/ValidateBeforeUpdateSync.js'),
855
+ "export default { flowTag: 'validateBeforeUpdateSync', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'update', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
856
+ );
857
+ await generateCrudApplicationArtifacts(rootPath, {
858
+ serviceTag: 'orders'
859
+ });
860
+
861
+ const updateMain = await readFile(
862
+ join(
863
+ rootPath,
864
+ 'app/src/generatedCode/FlowObjects/Update/source/Update_Main.js'
865
+ ),
866
+ 'utf8'
867
+ );
868
+ assert.match(
869
+ updateMain,
870
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'validateBeforeUpdateSync'\s*\)/
871
+ );
872
+ assert.match(updateMain, /Hook validateBeforeUpdateSync failed/);
873
+
874
+ execFileSync(
875
+ process.execPath,
876
+ [
877
+ '--check',
878
+ join(
879
+ rootPath,
880
+ 'app/src/generatedCode/FlowObjects/Update/source/Update_Main.js'
881
+ )
882
+ ],
883
+ { stdio: 'inherit' }
884
+ );
885
+ });
886
+
887
+ test('emits async before-hook callback and SNS publish in Delete_Main and flow-objects.yml', async () => {
888
+ const rootPath = await createProject(true);
889
+ const schemas = join(rootPath, 'app/src/schemas');
890
+ await writeFile(
891
+ join(schemas, 'FlowSchemas/ValidateBeforeDelete.js'),
892
+ "export default { flowTag: 'validateBeforeDelete', event: ['ownTopic'], generatedCodeConfig: { generateCodeTag: 'delete', codeHookTag: 'before', invocationType: 'ownTopic' }, objType: { objectType: 'order' } };\n"
893
+ );
894
+ await generateCrudApplicationArtifacts(rootPath, {
895
+ serviceTag: 'orders'
896
+ });
897
+
898
+ const deleteMain = await readFile(
899
+ join(
900
+ rootPath,
901
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
902
+ ),
903
+ 'utf8'
904
+ );
905
+ assert.match(deleteMain, /sns\.publishAsync\(_izContext,/);
906
+ assert.match(
907
+ deleteMain,
908
+ /snsTopicArnByFlowSchema\(\s*_izContext,\s*'validateBeforeDelete_In'\s*\)/
909
+ );
910
+ assert.match(deleteMain, /DeleteBeforeHookCallbackHdrSqs/);
911
+
912
+ const beforeHookMain = await readFile(
913
+ join(
914
+ rootPath,
915
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteBeforeHookCallback_Main.js'
916
+ ),
917
+ 'utf8'
918
+ );
919
+ assert.match(
920
+ beforeHookMain,
921
+ /export default async function DeleteBeforeHookCallbackMain/
922
+ );
923
+ assert.match(beforeHookMain, /DeleteLogicalLib/);
924
+ assert.match(beforeHookMain, /handleDeleteError/);
925
+
926
+ const beforeHookHdrSqs = await readFile(
927
+ join(
928
+ rootPath,
929
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteBeforeHookCallback_HdrSqs.js'
930
+ ),
931
+ 'utf8'
932
+ );
933
+ assert.match(beforeHookHdrSqs, /DeleteBeforeHookCallbackMain/);
934
+
935
+ execFileSync(
936
+ process.execPath,
937
+ [
938
+ '--check',
939
+ join(
940
+ rootPath,
941
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteBeforeHookCallback_Main.js'
942
+ )
943
+ ],
944
+ { stdio: 'inherit' }
945
+ );
946
+ execFileSync(
947
+ process.execPath,
948
+ [
949
+ '--check',
950
+ join(
951
+ rootPath,
952
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteBeforeHookCallback_HdrSqs.js'
953
+ )
954
+ ],
955
+ { stdio: 'inherit' }
956
+ );
957
+ execFileSync(
958
+ process.execPath,
959
+ [
960
+ '--check',
961
+ join(
962
+ rootPath,
963
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
964
+ )
965
+ ],
966
+ { stdio: 'inherit' }
967
+ );
968
+ });
969
+
970
+ test('emits sync before-hook in Delete_Main with abort check', async () => {
971
+ const rootPath = await createProject(true);
972
+ const schemas = join(rootPath, 'app/src/schemas');
973
+ await writeFile(
974
+ join(schemas, 'FlowSchemas/ValidateBeforeDeleteSync.js'),
975
+ "export default { flowTag: 'validateBeforeDeleteSync', event: ['lambdaSyncInv'], generatedCodeConfig: { generateCodeTag: 'delete', codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, objType: { objectType: 'order' } };\n"
976
+ );
977
+ await generateCrudApplicationArtifacts(rootPath, {
978
+ serviceTag: 'orders'
979
+ });
980
+
981
+ const deleteMain = await readFile(
982
+ join(
983
+ rootPath,
984
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
985
+ ),
986
+ 'utf8'
987
+ );
988
+ assert.match(
989
+ deleteMain,
990
+ /lambda\.invokeSync\(\s*_izContext,\s*await lambdaSharedLib\.lambdaFunctionName\(\s*_izContext,\s*'validateBeforeDeleteSync'\s*\)/
991
+ );
992
+ assert.match(deleteMain, /Hook validateBeforeDeleteSync failed/);
993
+
994
+ execFileSync(
995
+ process.execPath,
996
+ [
997
+ '--check',
998
+ join(
999
+ rootPath,
1000
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
1001
+ )
1002
+ ],
1003
+ { stdio: 'inherit' }
1004
+ );
1005
+ });
1006
+
1007
+ test('emits hooks across multiple endpoints when generateCodeTag is an array', async () => {
1008
+ const rootPath = await createProject(true);
1009
+ const schemas = join(rootPath, 'app/src/schemas');
1010
+ await writeFile(
1011
+ join(schemas, 'FlowSchemas/CheckOrderMulti.js'),
1012
+ "export default { flowTag: 'checkOrderMulti', event: ['lambdaSyncInv'], " +
1013
+ "generatedCodeConfig: { generateCodeTag: ['create', 'update'], " +
1014
+ "codeHookTag: 'before', invocationType: 'lambdaSyncInv' }, " +
1015
+ "objType: { objectType: 'order' } };\n"
1016
+ );
1017
+ await generateCrudApplicationArtifacts(rootPath, {
1018
+ serviceTag: 'orders'
1019
+ });
1020
+
1021
+ const createMain = await readFile(
1022
+ join(
1023
+ rootPath,
1024
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
1025
+ ),
1026
+ 'utf8'
1027
+ );
1028
+ const updateMain = await readFile(
1029
+ join(
1030
+ rootPath,
1031
+ 'app/src/generatedCode/FlowObjects/Update/source/Update_Main.js'
1032
+ ),
1033
+ 'utf8'
1034
+ );
1035
+ const deleteMain = await readFile(
1036
+ join(
1037
+ rootPath,
1038
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
1039
+ ),
1040
+ 'utf8'
1041
+ );
1042
+
1043
+ assert.match(createMain, /'checkOrderMulti'/);
1044
+ assert.match(updateMain, /'checkOrderMulti'/);
1045
+ assert.doesNotMatch(deleteMain, /'checkOrderMulti'/);
1046
+ });
1047
+
1048
+ test('emits ownTopic before-hook callbacks across multiple endpoints when generateCodeTag is an array', async () => {
1049
+ const rootPath = await createProject(true);
1050
+ const schemas = join(rootPath, 'app/src/schemas');
1051
+ await writeFile(
1052
+ join(schemas, 'FlowSchemas/ValidateOrderMulti.js'),
1053
+ "export default { flowTag: 'validateOrderMulti', event: ['ownTopic'], " +
1054
+ "generatedCodeConfig: { generateCodeTag: ['update', 'delete'], " +
1055
+ "codeHookTag: 'before', invocationType: 'ownTopic' }, " +
1056
+ "objType: { objectType: 'order' } };\n"
1057
+ );
1058
+ const result = await generateCrudApplicationArtifacts(rootPath, {
1059
+ serviceTag: 'orders'
1060
+ });
1061
+
1062
+ const updateCallbackExists = result.files.some((f) =>
1063
+ f.path.includes('UpdateBeforeHookCallback_Main.js')
1064
+ );
1065
+ const deleteCallbackExists = result.files.some((f) =>
1066
+ f.path.includes('DeleteBeforeHookCallback_Main.js')
1067
+ );
1068
+ const createCallbackExists = result.files.some((f) =>
1069
+ f.path.includes('CreateBeforeHookCallback_Main.js')
1070
+ );
1071
+
1072
+ assert.strictEqual(updateCallbackExists, true);
1073
+ assert.strictEqual(deleteCallbackExists, true);
1074
+ assert.strictEqual(createCallbackExists, false);
1075
+ });
1076
+
1077
+ test('emits synchronous ConfirmUsage and CancelUsage invocation in CreateLogicalLib when object has rbac and graph', async () => {
1078
+ const rootPath = await createRbacProject(false, true);
1079
+ await generateCrudApplicationArtifacts(rootPath, { serviceTag: 'orders' });
1080
+
1081
+ const createLogicalLib = await readFile(
1082
+ join(
1083
+ rootPath,
1084
+ 'app/src/generatedCode/FlowObjects/Create/source/CreateLogicalLib.js'
1085
+ ),
1086
+ 'utf8'
1087
+ );
1088
+
1089
+ assert.match(createLogicalLib, /ConfirmUsageHdrInv/);
1090
+ assert.match(createLogicalLib, /CancelUsageHdrInv/);
1091
+ assert.match(createLogicalLib, /lambda\.invokeSync\(/);
1092
+ assert.match(createLogicalLib, /publishCompletionMessage\(/);
1093
+ });
1094
+
1095
+ test('emits synchronous AfterHook invocation in Update and Delete handlers', async () => {
1096
+ const rootPath = await createProject(true);
1097
+ const schemas = join(rootPath, 'app/src/schemas');
1098
+ await writeFile(
1099
+ join(schemas, 'FlowSchemas/AuditOrderMulti.js'),
1100
+ "export default { flowTag: 'auditOrderMulti', event: ['lambdaSyncInv'], " +
1101
+ "generatedCodeConfig: { generateCodeTag: ['update', 'delete'], " +
1102
+ "codeHookTag: 'after', invocationType: 'lambdaSyncInv' }, " +
1103
+ "objType: { objectType: 'order' } };\n"
1104
+ );
1105
+ await generateCrudApplicationArtifacts(rootPath, {
1106
+ serviceTag: 'orders'
1107
+ });
1108
+
1109
+ const updateComplete = await readFile(
1110
+ join(
1111
+ rootPath,
1112
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateComplete_Main.js'
1113
+ ),
1114
+ 'utf8'
1115
+ );
1116
+ const updateLogicalLib = await readFile(
1117
+ join(
1118
+ rootPath,
1119
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateLogicalLib.js'
1120
+ ),
1121
+ 'utf8'
1122
+ );
1123
+ const deleteComplete = await readFile(
1124
+ join(
1125
+ rootPath,
1126
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteComplete_Main.js'
1127
+ ),
1128
+ 'utf8'
1129
+ );
1130
+ const deleteLogicalLib = await readFile(
1131
+ join(
1132
+ rootPath,
1133
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteLogicalLib.js'
1134
+ ),
1135
+ 'utf8'
1136
+ );
1137
+
1138
+ assert.match(updateComplete, /'auditOrderMulti'/);
1139
+ assert.match(updateComplete, /lambda\.invokeSync\(/);
1140
+ assert.match(updateLogicalLib, /'auditOrderMulti'/);
1141
+ assert.match(updateLogicalLib, /lambda\.invokeSync\(/);
1142
+ assert.match(deleteComplete, /'auditOrderMulti'/);
1143
+ assert.match(deleteComplete, /lambda\.invokeSync\(/);
1144
+ assert.match(deleteLogicalLib, /'auditOrderMulti'/);
1145
+ assert.match(deleteLogicalLib, /lambda\.invokeSync\(/);
1146
+ });
1147
+
1148
+ test('emits ownTopic after-hook callbacks and dispatch for update and delete', async () => {
1149
+ const rootPath = await createProject(true);
1150
+ const schemas = join(rootPath, 'app/src/schemas');
1151
+ await writeFile(
1152
+ join(schemas, 'FlowSchemas/NotifyOrderMulti.js'),
1153
+ "export default { flowTag: 'notifyOrderMulti', event: ['ownTopic'], " +
1154
+ "generatedCodeConfig: { generateCodeTag: ['update', 'delete'], " +
1155
+ "codeHookTag: 'after', invocationType: 'ownTopic' }, " +
1156
+ "objType: { objectType: 'order' } };\n"
1157
+ );
1158
+ const result = await generateCrudApplicationArtifacts(rootPath, {
1159
+ serviceTag: 'orders'
1160
+ });
1161
+
1162
+ const updateCallbackExists = result.files.some((f) =>
1163
+ f.path.includes('UpdateAfterHookCallback_Main.js')
1164
+ );
1165
+ const deleteCallbackExists = result.files.some((f) =>
1166
+ f.path.includes('DeleteAfterHookCallback_Main.js')
1167
+ );
1168
+ assert.strictEqual(updateCallbackExists, true);
1169
+ assert.strictEqual(deleteCallbackExists, true);
1170
+
1171
+ const updateComplete = await readFile(
1172
+ join(
1173
+ rootPath,
1174
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateComplete_Main.js'
1175
+ ),
1176
+ 'utf8'
1177
+ );
1178
+ const updateLogicalLib = await readFile(
1179
+ join(
1180
+ rootPath,
1181
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateLogicalLib.js'
1182
+ ),
1183
+ 'utf8'
1184
+ );
1185
+ const deleteComplete = await readFile(
1186
+ join(
1187
+ rootPath,
1188
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteComplete_Main.js'
1189
+ ),
1190
+ 'utf8'
1191
+ );
1192
+ const deleteLogicalLib = await readFile(
1193
+ join(
1194
+ rootPath,
1195
+ 'app/src/generatedCode/FlowObjects/Delete/source/DeleteLogicalLib.js'
1196
+ ),
1197
+ 'utf8'
1198
+ );
1199
+
1200
+ assert.match(updateComplete, /'notifyOrderMulti_In'/);
1201
+ assert.match(updateComplete, /UpdateAfterHookCallbackHdrSqs/);
1202
+ assert.match(updateLogicalLib, /'notifyOrderMulti_In'/);
1203
+ assert.match(updateLogicalLib, /UpdateAfterHookCallbackHdrSqs/);
1204
+ assert.match(deleteComplete, /'notifyOrderMulti_In'/);
1205
+ assert.match(deleteComplete, /DeleteAfterHookCallbackHdrSqs/);
1206
+ assert.match(deleteLogicalLib, /'notifyOrderMulti_In'/);
1207
+ assert.match(deleteLogicalLib, /DeleteAfterHookCallbackHdrSqs/);
1208
+ });
1209
+
1210
+ test('supports objType in generatedCodeConfig with string, array, and global hook', async () => {
1211
+ const rootPath = await createProject(true);
1212
+ const schemaRoot = join(rootPath, 'app/src/schemas');
1213
+ await writeFile(
1214
+ join(schemaRoot, 'ObjectSchemas/Cart.js'),
1215
+ "export default { objectType: 'cart', generatedBy: 'user' };\n"
1216
+ );
1217
+ await writeFile(
1218
+ join(schemaRoot, 'FlowSchemas/HookWithCustomObjType.js'),
1219
+ `export default {
1220
+ flowTag: 'hookWithCustomObj',
1221
+ objType: { objectType: 'rootAudit' },
1222
+ event: ['lambdaSyncInv'],
1223
+ generatedCodeConfig: {
1224
+ generateCodeTag: 'create',
1225
+ codeHookTag: 'before',
1226
+ invocationType: 'lambdaSyncInv',
1227
+ objType: 'cart'
1228
+ }
1229
+ };\n`
1230
+ );
1231
+ await writeFile(
1232
+ join(schemaRoot, 'FlowSchemas/HookWithMultiObjType.js'),
1233
+ `export default {
1234
+ flowTag: 'hookWithMultiObj',
1235
+ objType: { objectType: 'rootAudit' },
1236
+ event: ['ownTopic'],
1237
+ outputTopic: true,
1238
+ generatedCodeConfig: {
1239
+ generateCodeTag: 'update',
1240
+ codeHookTag: 'after',
1241
+ invocationType: 'ownTopic',
1242
+ objType: ['order', 'cart']
1243
+ }
1244
+ };\n`
1245
+ );
1246
+ await writeFile(
1247
+ join(schemaRoot, 'FlowSchemas/GlobalHook.js'),
1248
+ `export default {
1249
+ flowTag: 'globalDeleteHook',
1250
+ event: ['lambdaSyncInv'],
1251
+ generatedCodeConfig: {
1252
+ generateCodeTag: 'delete',
1253
+ codeHookTag: 'before',
1254
+ invocationType: 'lambdaSyncInv'
1255
+ }
1256
+ };\n`
1257
+ );
1258
+
1259
+ await generateCrudApplicationArtifacts(rootPath, {
1260
+ serviceTag: 'orders'
1261
+ });
1262
+
1263
+ // 1. Before hook with custom objType 'cart' should be attached to cart, not rootAudit
1264
+ const createMain = await readFile(
1265
+ join(
1266
+ rootPath,
1267
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_Main.js'
1268
+ ),
1269
+ 'utf8'
1270
+ );
1271
+ assert.match(createMain, /if \(requestParams\.objectType === 'cart'\)/);
1272
+ assert.match(createMain, /hookPayload_hookWithCustomObj/);
1273
+
1274
+ // 2. After hook with multi objType ['order', 'cart'] should check array
1275
+ const updateComplete = await readFile(
1276
+ join(
1277
+ rootPath,
1278
+ 'app/src/generatedCode/FlowObjects/Update/source/UpdateComplete_Main.js'
1279
+ ),
1280
+ 'utf8'
1281
+ );
1282
+ assert.match(updateComplete, /\["order","cart"\]\.includes/);
1283
+ assert.match(updateComplete, /hookPayload_hookWithMultiObj/);
1284
+
1285
+ // 3. Global delete hook without objType should be present
1286
+ const deleteMain = await readFile(
1287
+ join(
1288
+ rootPath,
1289
+ 'app/src/generatedCode/FlowObjects/Delete/source/Delete_Main.js'
1290
+ ),
1291
+ 'utf8'
1292
+ );
1293
+ assert.match(deleteMain, /hookPayload_globalDeleteHook/);
1294
+ });
1295
+
420
1296
  async function createProject(withCrudObject) {
421
1297
  const rootPath = await mkdtemp(
422
1298
  join(tmpdir(), 'generate-service-code-v2-crud-application-')