@izara_project/izara-core-generate-service-code 1.0.93 → 1.0.94

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.
@@ -26,6 +26,9 @@ import lambdaSharedLib from '@izara_project/izara-core-library-lambda';
26
26
  <% if (typeof flow !== 'undefined' && flow.statusType === 'storedCache') { %>
27
27
  import storedCacheSharedLib from '@izara_project/izara-core-library-stored-cache';
28
28
  <% } %>
29
+ <% if (typeof flow !== 'undefined' && flow.statusType === 'triggerCache') { %>
30
+ import { triggeredCacheSharedLibV2 } from '@izara_project/izara-core-library-trigger-cache';
31
+ <% } %>
29
32
 
30
33
  //(<optionalImport>)
31
34
  import { getObjectSchema } from '@izara_project/izara-core-library-service-schemas';
@@ -73,6 +76,70 @@ export default async function <%- typeof functionName !== 'undefined' ? function
73
76
  callingFlowConfig
74
77
  );
75
78
 
79
+ <% if (typeof flow !== 'undefined' && flow.statusType === 'triggerCache') { -%>
80
+
81
+ //(<beforeCheckTriggeredCache>)
82
+ //(</beforeCheckTriggeredCache>)
83
+
84
+ const checkTriggerReseted =
85
+ await triggeredCacheSharedLibV2.checkTriggerResetedV2(
86
+ _izContext,
87
+ {
88
+ serviceTag: process.env.iz_serviceTag,
89
+ flowTag: '<%= flowTag %>'
90
+ },
91
+ //(<triggerCacheIdentifiers>)
92
+ {}
93
+ //(</triggerCacheIdentifiers>)
94
+ //(<triggerCacheUniqueRequestId>)
95
+ //(</triggerCacheUniqueRequestId>)
96
+ //(<triggerCacheSettings>)
97
+ //(</triggerCacheSettings>)
98
+ );
99
+
100
+ _izContext.logger.debug(
101
+ `[${process.env.iz_serviceTag}:<%-
102
+ typeof functionName !== 'undefined'
103
+ ? functionName
104
+ : upperCaseFlowTag
105
+ %>] checkTriggerReseted:`,
106
+ checkTriggerReseted
107
+ );
108
+
109
+ //(<afterCheckTriggerReseted>)
110
+ //(</afterCheckTriggerReseted>)
111
+
112
+ const triggeredCacheResult =
113
+ await triggeredCacheSharedLibV2.checkTriggeredCacheV2(
114
+ _izContext,
115
+ {
116
+ serviceTag: process.env.iz_serviceTag,
117
+ flowTag: '<%= flowTag %>'
118
+ },
119
+ //(<triggerCacheCheckIdentifiers>)
120
+ {}
121
+ //(</triggerCacheCheckIdentifiers>)
122
+ );
123
+
124
+ _izContext.logger.debug(
125
+ `[${process.env.iz_serviceTag}:<%-
126
+ typeof functionName !== 'undefined'
127
+ ? functionName
128
+ : upperCaseFlowTag
129
+ %>] triggeredCacheResult:`,
130
+ triggeredCacheResult
131
+ );
132
+
133
+ //(<afterCheckTriggeredCache>)
134
+ //(</afterCheckTriggeredCache>)
135
+
136
+ if (triggeredCacheResult?.hasPendingTasks) {
137
+ //(<dispatchTriggerFlowHook>)
138
+ //(</dispatchTriggerFlowHook>)
139
+ return triggeredCacheResult;
140
+ }
141
+
142
+ <% } -%>
76
143
  <% if (typeof flow !== 'undefined' && flow.statusType === 'storedCache') { -%>
77
144
 
78
145
  //(<beforeCheckCache>)
@@ -107,6 +174,12 @@ export default async function <%- typeof functionName !== 'undefined' ? function
107
174
 
108
175
  //(<checkStoredCacheHook>)
109
176
  //(</checkStoredCacheHook>)
177
+
178
+ if (cacheMainStatus === 'complete') {
179
+ //(<returnStoredCacheHit>)
180
+ return cacheMain;
181
+ //(</returnStoredCacheHit>)
182
+ }
110
183
  }
111
184
  <% } -%>
112
185
  <% if (typeof flow !== 'undefined' && flow.isRbac && !flow.generatedCodeConfig) { -%>
@@ -242,7 +242,11 @@ export function roleTableNames(state, roleName) {
242
242
  tables.push('UserUploadRecords', 'ReservedDataMain');
243
243
  }
244
244
  if (flow.statusType === 'storedCache') {
245
- const object = state.objectTablesByType.get(flow.objType?.objectType);
245
+ const targetObjectType =
246
+ typeof flow.objType === 'string'
247
+ ? flow.objType
248
+ : flow.objType?.objectType;
249
+ const object = state.objectTablesByType.get(targetObjectType);
246
250
  if (object) {
247
251
  tables.push(object);
248
252
  }
@@ -43,13 +43,14 @@ async function _emitSystemArtifacts(objects) {
43
43
  return { artifacts: [], functions: [] };
44
44
  }
45
45
 
46
- const hasFindDataEntries = buildFindDataEntries(objects).length > 0;
46
+ const findDataEntries = buildFindDataEntries(objects);
47
+ const hasFindDataEntries = findDataEntries.length > 0;
47
48
 
48
49
  const artifacts = [];
49
50
 
50
51
  if (hasFindDataEntries) {
51
52
  const findDataMain = await render('FindData_Main.ejs', {
52
- datas: buildFindDataEntries(objects)
53
+ datas: findDataEntries
53
54
  });
54
55
  artifacts.push(
55
56
  { path: `${FIND_DATA_SOURCE}/FindData_Main.js`, content: findDataMain },
@@ -85,21 +86,21 @@ async function _emitSystemArtifacts(objects) {
85
86
 
86
87
  return {
87
88
  artifacts,
88
- functions: renderSystemYamlFunctions()
89
+ functions: renderSystemYamlFunctions(hasFindDataEntries)
89
90
  };
90
91
  }
91
92
 
92
- function renderSystemYamlFunctions() {
93
- const findDataYaml = renderFindDataYaml();
94
- const processLogicalYaml = renderProcessLogicalYaml();
95
- const processLogicalPaginateDsqYaml = renderProcessLogicalPaginateDsqYaml();
96
- const processLogicalPaginateSqsYaml = renderProcessLogicalPaginateSqsYaml();
97
- return [
98
- findDataYaml,
99
- processLogicalYaml,
100
- processLogicalPaginateDsqYaml,
101
- processLogicalPaginateSqsYaml
102
- ];
93
+ function renderSystemYamlFunctions(hasFindDataEntries = true) {
94
+ const functions = [];
95
+ if (hasFindDataEntries) {
96
+ functions.push(renderFindDataYaml());
97
+ }
98
+ functions.push(
99
+ renderProcessLogicalYaml(),
100
+ renderProcessLogicalPaginateDsqYaml(),
101
+ renderProcessLogicalPaginateSqsYaml()
102
+ );
103
+ return functions;
103
104
  }
104
105
 
105
106
  function renderFindDataYaml() {
package/test/core.test.js CHANGED
@@ -34,6 +34,19 @@ test('compiles a request and commits only declared relative artifacts', async ()
34
34
  test('rejects invalid generation inputs before writing files', async () => {
35
35
  assert.throws(() => compileProject({}), /rootPath/);
36
36
 
37
+ const corruptPath = await mkdtemp(
38
+ join(tmpdir(), 'generate-service-code-v2-corrupt-yml-')
39
+ );
40
+ await mkdir(join(corruptPath, 'config'), { recursive: true });
41
+ await writeFile(
42
+ join(corruptPath, 'config/serverless.config.yml'),
43
+ 'main_config: [unbalanced brackets:'
44
+ );
45
+ assert.throws(
46
+ () => compileProject({ rootPath: corruptPath }),
47
+ /Failed to read or parse configuration at/
48
+ );
49
+
37
50
  const outputPath = await mkdtemp(
38
51
  join(tmpdir(), 'generate-service-code-v2-invalid-')
39
52
  );
@@ -254,7 +254,7 @@ test('removes only V2-owned custom-flow application artifacts when supported eve
254
254
  await generateCustomFlowApplicationArtifacts(rootPath);
255
255
  await writeFile(
256
256
  join(rootPath, 'app/src/schemas/FlowSchemas/Flows.js'),
257
- "export default [{ flowTag: 'later', event: ['unsupported'] }];\n"
257
+ "export default [];\n"
258
258
  );
259
259
 
260
260
  assert.deepEqual(
@@ -469,6 +469,53 @@ test('emits and removes the trigger-cache artifact suite when triggerFlowTypes i
469
469
  );
470
470
  });
471
471
 
472
+ test('triggerCache entrypoint Cart_Main.js contains correct library import and logic', async () => {
473
+ const rootPath = await createTriggerCacheProject(['OrderCheckout']);
474
+ await generateCustomFlowApplicationArtifacts(rootPath);
475
+
476
+ const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
477
+ const mainContent = await readFile(
478
+ join(rootPath, `${source}/Cart_Main.js`),
479
+ 'utf8'
480
+ );
481
+
482
+ assert.match(
483
+ mainContent,
484
+ /import \{ triggeredCacheSharedLibV2 \} from '@izara_project\/izara-core-library-trigger-cache'/,
485
+ 'should import triggeredCacheSharedLibV2'
486
+ );
487
+ assert.match(
488
+ mainContent,
489
+ /triggeredCacheSharedLibV2\.checkTriggerResetedV2/,
490
+ 'should call checkTriggerResetedV2'
491
+ );
492
+ assert.match(
493
+ mainContent,
494
+ /triggeredCacheSharedLibV2\.checkTriggeredCacheV2/,
495
+ 'should call checkTriggeredCacheV2'
496
+ );
497
+ assert.match(
498
+ mainContent,
499
+ /\/\/\(<beforeCheckTriggeredCache>\)/,
500
+ 'should have beforeCheckTriggeredCache hook'
501
+ );
502
+ assert.match(
503
+ mainContent,
504
+ /\/\/\(<dispatchTriggerFlowHook>\)/,
505
+ 'should have dispatchTriggerFlowHook hook'
506
+ );
507
+ assert.match(
508
+ mainContent,
509
+ /if \(triggeredCacheResult\?\.hasPendingTasks\)/,
510
+ 'should guard dispatch with hasPendingTasks check'
511
+ );
512
+ assert.doesNotMatch(
513
+ mainContent,
514
+ /checkStoredCacheStatus/,
515
+ 'should not contain storedCache logic'
516
+ );
517
+ });
518
+
472
519
  test('uses custom CheckTrigger step if provided by developer in flowSteps', async () => {
473
520
  const rootPath = await mkdtemp(
474
521
  join(tmpdir(), 'generate-service-code-v2-trigger-cache-custom-')
@@ -494,28 +541,77 @@ test('uses custom CheckTrigger step if provided by developer in flowSteps', asyn
494
541
  assert.match(flowYaml, /^CartCheckTriggerCustomStepHdrSqs:/m);
495
542
  });
496
543
 
497
- test('does not emit check trigger-cache artifacts when triggerFlowTypes is empty, null, undefined, or missing', async () => {
544
+ test('uses full {flowTag}{stepName} queue name when stepName starts with flowTag', async () => {
545
+ const rootPath = await createTriggerCacheProject(['OrderCheckout']);
546
+ await writeFile(
547
+ join(rootPath, 'app/src/schemas/FlowSchemas/Cart.js'),
548
+ "export default { flowTag: 'cart', event: ['ownTopic'], statusType: 'triggerCache', triggerFlowTypes: ['OrderCheckout'], objType: { serviceTag: 'Order' }, flowSteps: [{ stepName: 'start' }, { stepName: 'CartCheckX', event: ['extTopic'] }] };\n"
549
+ );
550
+ await generateCustomFlowApplicationArtifacts(rootPath);
551
+ await generateResourceArtifacts(rootPath);
552
+
553
+ const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
554
+ for (const fileName of [
555
+ 'CartCartCheckX_Main.js',
556
+ 'CartCartCheckX_HdrSqs.js'
557
+ ]) {
558
+ const path = join(rootPath, source, fileName);
559
+ await access(path);
560
+ await parseAsEsm(path);
561
+ }
562
+
563
+ const flowYaml = await readFile(
564
+ join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
565
+ 'utf8'
566
+ );
567
+ assert.match(flowYaml, /^CartCartCheckXHdrSqs:/m);
568
+ assert.match(
569
+ await readFile(join(rootPath, `${source}/CartCartCheckX_HdrSqs.js`), 'utf8'),
570
+ /CartCartCheckXHdrSqs/
571
+ );
572
+
573
+ const resources = await readFile(
574
+ join(
575
+ rootPath,
576
+ 'resource/sls_yaml/generatedCode/source/generated-sns-sqs-custom-flows.yml'
577
+ ),
578
+ 'utf8'
579
+ );
580
+ assert.match(
581
+ resources,
582
+ new RegExp(
583
+ 'QueueName: \\$\\{self:custom\\.iz_resourcePrefix\\}CartCartCheckXHdrSqs'
584
+ )
585
+ );
586
+ });
587
+
588
+ test('rejects when statusType triggerCache has triggerFlowTypes empty, null, undefined, or missing', async () => {
498
589
  for (const emptyTriggerFlowTypes of [[], null, undefined]) {
499
590
  const rootPath = await createTriggerCacheProject(emptyTriggerFlowTypes);
500
- await generateCustomFlowApplicationArtifacts(rootPath);
501
- await generateResourceArtifacts(rootPath);
502
-
503
- const source = 'app/src/generatedCode/FlowSchemas/Cart/source';
504
- await assert.rejects(
505
- readFile(join(rootPath, `${source}/CartCheckTriggerFlows_Main.js`))
506
- );
507
591
  await assert.rejects(
508
- readFile(join(rootPath, `${source}/CartCheckTriggerFlows_HdrSqs.js`))
592
+ generateCustomFlowApplicationArtifacts(rootPath),
593
+ /FlowSchema validation error.*triggerCache.*requires a non-empty 'triggerFlowTypes' array/
509
594
  );
510
-
511
- const flowYaml = await readFile(
512
- join(rootPath, 'app/sls_yaml/generatedCode/source/flow-schema.yml'),
513
- 'utf8'
514
- );
515
- assert.doesNotMatch(flowYaml, /CartCheckTriggerFlowsHdrSqs/);
516
595
  }
517
596
  });
518
597
 
598
+ test('rejects when statusType storedCache cannot resolve DynamoDB table from objType', async () => {
599
+ const rootPath = await mkdtemp(
600
+ join(tmpdir(), 'generate-service-code-v2-stored-cache-err-')
601
+ );
602
+ roots.add(rootPath);
603
+ const schemaRoot = join(rootPath, 'app/src/schemas/FlowSchemas');
604
+ await mkdir(schemaRoot, { recursive: true });
605
+ await writeFile(
606
+ join(schemaRoot, 'CacheFlow.js'),
607
+ "export default { flowTag: 'cacheFlow', event: ['ownTopic'], statusType: 'storedCache', objType: { objectType: 'missingObj' }, flowSteps: [{ stepName: 'Start' }] };\n"
608
+ );
609
+ await assert.rejects(
610
+ generateCustomFlowApplicationArtifacts(rootPath),
611
+ /FlowSchema validation error.*storedCache.*cannot resolve DynamoDB tableName/
612
+ );
613
+ });
614
+
519
615
  test('rejects an EventBridge schedule without a deployable name and rate', () => {
520
616
  assert.throws(
521
617
  () =>
@@ -565,7 +661,7 @@ export default [
565
661
  { ...base, flowTag: 'cache', event: ['ownTopic'], statusType: 'storedCache', objType: { objectType: 'order', serviceTag: 'Current' } },
566
662
  { ...base, flowTag: 'mixed', event: ['ownTopic', 'extTopic'] },
567
663
  { ...base, flowTag: 'generated', event: ['ownTopic'], generatedCodeConfig: { generatedCodeTag: 'create' } },
568
- { ...base, flowTag: 'upload', event: ['s3'] }
664
+ { ...base, flowTag: 'upload', event: ['s3'], bucketName: 'UploadBucket' }
569
665
  ];
570
666
  `
571
667
  );
@@ -1271,3 +1367,49 @@ test('emits handler and YAML for flow with event generateCode and invocationType
1271
1367
  assert.match(yaml, /GenSyncHdrInv:/);
1272
1368
  });
1273
1369
 
1370
+ test('rejects FlowSchema with event storedCache at loader', async () => {
1371
+ const rootPath = await createProject();
1372
+ await writeFile(
1373
+ join(rootPath, 'app/src/schemas/FlowSchemas/BadEvent.js'),
1374
+ `export default {
1375
+ flowTag: 'badEvent',
1376
+ event: ['storedCache'],
1377
+ statusType: 'storedCache',
1378
+ objType: { objectType: 'order' },
1379
+ flowSteps: [{ stepName: 'Start' }]
1380
+ };\n`
1381
+ );
1382
+
1383
+ await assert.rejects(
1384
+ () => generateCustomFlowApplicationArtifacts(rootPath),
1385
+ /Flow 'badEvent' event\[0\] 'storedCache' is invalid\. Must be one of/
1386
+ );
1387
+ });
1388
+
1389
+ test('emits early return cacheMain on cache hit in FlowEndpoint_Main for storedCache', async () => {
1390
+ const rootPath = await createProject();
1391
+ await writeFile(
1392
+ join(rootPath, 'app/src/schemas/FlowSchemas/CacheFlow.js'),
1393
+ `export default {
1394
+ flowTag: 'cacheOrder',
1395
+ event: ['ownTopic'],
1396
+ statusType: 'storedCache',
1397
+ objType: { objectType: 'order', serviceTag: 'Current' },
1398
+ flowSteps: [{ stepName: 'Start' }]
1399
+ };\n`
1400
+ );
1401
+
1402
+ await generateCustomFlowApplicationArtifacts(rootPath);
1403
+
1404
+ const mainPath = join(
1405
+ rootPath,
1406
+ 'app/src/generatedCode/FlowSchemas/CacheOrder/source/CacheOrder_Main.js'
1407
+ );
1408
+ const mainContent = await readFile(mainPath, 'utf8');
1409
+ assert.match(mainContent, /checkStoredCacheStatus/);
1410
+ assert.match(mainContent, /if \(cacheMainStatus === 'complete'\) \{/);
1411
+ assert.match(mainContent, /return cacheMain;/);
1412
+ await parseAsEsm(mainPath);
1413
+ });
1414
+
1415
+