@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.
- package/package.json +1 -1
- package/src/compile/compileProject.js +3 -1
- package/src/compile/flowValidator.js +182 -18
- package/src/compile/loadSchemas.js +144 -9
- package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +1 -1
- package/src/emit/custom/customFlowS3.js +9 -4
- package/src/emit/custom/customFlowSteps.js +14 -6
- package/src/emit/custom/customFlows.js +27 -6
- package/src/emit/custom/templates/FlowEndpoint_Main.ejs +73 -0
- package/src/emit/resource/iamResourceArtifacts.js +5 -1
- package/src/emit/system/emitSystemFindDataArtifacts.js +15 -14
- package/test/core.test.js +13 -0
- package/test/customFlowApplicationArtifacts.test.js +159 -17
- package/test/graphDrivenFlowSchema.test.js +325 -0
- package/test/resourceArtifacts.test.js +46 -1
- package/test/systemFindDataArtifacts.test.js +73 -0
|
@@ -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
|
|
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
|
|
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:
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
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 [
|
|
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('
|
|
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
|
-
|
|
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
|
+
|