@izara_project/izara-core-generate-service-code 1.0.78 → 1.0.79
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 +2 -2
- package/src/compile/flowValidator.js +163 -0
- package/src/compile/loadSchemas.js +15 -5
- package/src/emit/coreFunction/emitCoreFunctionArtifacts.js +137 -7
- package/src/emit/coreFunction/templates/InitialSetupConfig.ejs +132 -17
- package/src/emit/crud/crudActionPlan.js +82 -0
- package/src/emit/crud/emitCrudApplicationArtifacts.js +162 -112
- package/src/emit/crud/emitDynamicCrudFlowSchemas.js +43 -65
- package/src/emit/crud/generateDynamicCrudFlowSchemas.js +4 -1
- package/src/emit/crud/templates/CreateEndpoint_Main.ejs +84 -512
- package/src/emit/crud/templates/CreateLogicalLib.ejs +536 -0
- package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +87 -202
- package/src/emit/crud/templates/DeleteLogicalLib.ejs +213 -0
- package/src/emit/crud/templates/FlowEndpointRbacCallback_Main.ejs +217 -0
- package/src/emit/crud/templates/GetEndpoint_Main.ejs +1 -1
- package/src/emit/crud/templates/GetRangeEndpoint_Main.ejs +9 -2
- package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +87 -444
- package/src/emit/crud/templates/UpdateLogicalLib.ejs +472 -0
- package/src/emit/custom/customFlows.js +27 -2
- package/src/emit/custom/emitCustomFlowApplicationArtifacts.js +1 -2
- package/src/emit/custom/templates/FlowEndpoint_Main.ejs +10 -3
- package/src/emit/custom/templates/FlowStep_Main.ejs +1 -1
- package/src/emit/custom/templates/s3/CreateObject_Main.ejs +1 -1
- package/src/emit/custom/templates/triggerCache/CheckTriggerCache_HdrSqs.ejs +2 -2
- package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +3 -9
- package/src/emit/rbac/emitRbacApplicationArtifacts.js +21 -16
- package/src/emit/rbac/rbacFlowTag.js +2 -7
- package/src/emit/rbac/templates/UserRbacFlow_Main.ejs +148 -21
- package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +4 -2
- package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +1 -1
- package/src/emit/relationship/templates/CreateRelationship_Main.ejs +1 -1
- package/src/emit/relationship/templates/DeleteRelationship_Main.ejs +1 -1
- package/src/emit/relationship/templates/GetRelationship_Main.ejs +1 -1
- package/src/emit/relationship/templates/MoveRelationship_Main.ejs +1 -1
- package/src/emit/relationship/templates/UpdateRelationship_Main.ejs +1 -1
- package/src/emit/resource/emitResourceArtifacts.js +83 -68
- package/src/emit/resource/templates/GeneratedLambdaRoleServiceConfig.ejs +31 -5
- package/src/emit/resource/templates/SystemDynamoDB_Yaml.ejs +21 -0
- package/src/emit/resource/templates/WebSocketSubscription_Yaml.ejs +7 -0
- package/src/emit/shared/templates/FlowHandler_HdrApi.ejs +1 -1
- package/src/emit/shared/templates/FlowHandler_HdrDsq.ejs +2 -2
- package/src/emit/shared/templates/FlowHandler_HdrInv.ejs +1 -1
- package/src/emit/shared/templates/FlowHandler_HdrSqs.ejs +2 -2
- package/src/emit/systemText/emitSystemTextSchemas.js +246 -0
- package/src/emit/systemText/templates/SystemTextSchemas.ejs +10 -0
- package/src/generate.js +55 -8
- package/test/crudApplicationArtifacts.test.js +147 -19
- package/test/customFlowApplicationArtifacts.test.js +308 -16
- package/test/dynamicCrudFlowSchemas.test.js +150 -14
- package/test/dynamicRbacFlowSchemas.test.js +2 -2
- package/test/dynamicRelationshipFlowSchemas.test.js +1 -1
- package/test/graphDrivenFlowSchema.test.js +257 -0
- package/test/publicApi.test.js +14 -5
- package/test/resourceArtifacts.test.js +25 -19
- package/test/systemTextSchemas.test.js +154 -0
- package/src/emit/crud/templates/CreateComplete_HdrSqs.ejs +0 -99
- package/src/emit/crud/templates/DeleteComplete_HdrSqs.ejs +0 -99
- package/src/emit/crud/templates/FlowEndpointBeforeLogical_Main.ejs +0 -174
- package/src/emit/crud/templates/FlowEndpointComplete_Main.ejs +0 -187
- package/src/emit/crud/templates/UpdateComplete_HdrSqs.ejs +0 -99
- package/src/emit/resource/templates/ResourceBeforeLogicalYaml.ejs +0 -47
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { validateFlowSchema } from '../src/compile/flowValidator.js';
|
|
4
|
+
|
|
5
|
+
test('validateFlowSchema passes for valid Graph-Driven FlowSchema', () => {
|
|
6
|
+
const validSchema = {
|
|
7
|
+
flowTag: 'Reservations',
|
|
8
|
+
event: ['ownTopic'],
|
|
9
|
+
flowSteps: [
|
|
10
|
+
{
|
|
11
|
+
stepName: 'Start',
|
|
12
|
+
properties: [],
|
|
13
|
+
messageAttributes: [],
|
|
14
|
+
nextSteps: [
|
|
15
|
+
{
|
|
16
|
+
targetType: 'extTopic',
|
|
17
|
+
serviceTag: 'GraphHandler',
|
|
18
|
+
topicName: 'GetDeepPathSearch_In',
|
|
19
|
+
toStepNames: ['GetDeepPathSearch']
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
stepName: 'GetDeepPathSearch',
|
|
25
|
+
event: ['extTopic'],
|
|
26
|
+
properties: [],
|
|
27
|
+
messageAttributes: [],
|
|
28
|
+
previousSteps: [
|
|
29
|
+
{ sourceType: 'ownTopic', stepName: 'Start' },
|
|
30
|
+
{
|
|
31
|
+
sourceType: 'extTopic',
|
|
32
|
+
serviceTag: 'GraphHandler',
|
|
33
|
+
topicName: 'GetDeepPathSearch_Out',
|
|
34
|
+
fromStepNames: ['Start']
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
nextSteps: [
|
|
38
|
+
{
|
|
39
|
+
targetType: 'extTopic',
|
|
40
|
+
serviceTag: 'GraphHandler',
|
|
41
|
+
topicName: 'Create_In',
|
|
42
|
+
toStepNames: ['Complete']
|
|
43
|
+
}
|
|
44
|
+
]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
stepName: 'Complete',
|
|
48
|
+
event: ['extTopic'],
|
|
49
|
+
properties: [],
|
|
50
|
+
messageAttributes: [],
|
|
51
|
+
previousSteps: [
|
|
52
|
+
{
|
|
53
|
+
sourceType: 'extTopic',
|
|
54
|
+
serviceTag: 'GraphHandler',
|
|
55
|
+
topicName: 'Create_Out',
|
|
56
|
+
fromStepNames: ['GetDeepPathSearch']
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
assert.equal(validateFlowSchema(validSchema), true);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('validateFlowSchema rejects flowSteps[0] without stepName Start', () => {
|
|
67
|
+
const invalidSchema = {
|
|
68
|
+
flowTag: 'TestFlow',
|
|
69
|
+
flowSteps: [
|
|
70
|
+
{
|
|
71
|
+
stepName: 'Begin',
|
|
72
|
+
nextSteps: [{ targetType: 'ownTopic', stepName: 'Complete' }]
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
assert.throws(
|
|
78
|
+
() => validateFlowSchema(invalidSchema),
|
|
79
|
+
/Entry step flowSteps\[0\] must have stepName fixed to 'Start'/
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('validateFlowSchema rejects entry step with previousSteps or event', () => {
|
|
84
|
+
const schemaWithPrev = {
|
|
85
|
+
flowTag: 'TestFlow',
|
|
86
|
+
flowSteps: [
|
|
87
|
+
{
|
|
88
|
+
stepName: 'Start',
|
|
89
|
+
previousSteps: [{ sourceType: 'ownTopic', stepName: 'Start' }]
|
|
90
|
+
}
|
|
91
|
+
]
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
assert.throws(
|
|
95
|
+
() => validateFlowSchema(schemaWithPrev),
|
|
96
|
+
/Entry step flowSteps\[0\] must not define 'previousSteps'/
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const schemaWithEvent = {
|
|
100
|
+
flowTag: 'TestFlow',
|
|
101
|
+
flowSteps: [
|
|
102
|
+
{
|
|
103
|
+
stepName: 'Start',
|
|
104
|
+
event: ['extTopic']
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
assert.throws(
|
|
110
|
+
() => validateFlowSchema(schemaWithEvent),
|
|
111
|
+
/Entry step flowSteps\[0\] must not define step-level 'event'/
|
|
112
|
+
);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('validateFlowSchema rejects nextSteps item missing targetType', () => {
|
|
116
|
+
const invalidSchema = {
|
|
117
|
+
flowTag: 'TestFlow',
|
|
118
|
+
flowSteps: [
|
|
119
|
+
{
|
|
120
|
+
stepName: 'Start',
|
|
121
|
+
nextSteps: [{ stepName: 'GetDeepPathSearch' }]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
assert.throws(
|
|
127
|
+
() => validateFlowSchema(invalidSchema),
|
|
128
|
+
/nextSteps\[0\] must have targetType/
|
|
129
|
+
);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test('validateFlowSchema rejects previousSteps item missing sourceType', () => {
|
|
133
|
+
const invalidSchema = {
|
|
134
|
+
flowTag: 'TestFlow',
|
|
135
|
+
flowSteps: [
|
|
136
|
+
{
|
|
137
|
+
stepName: 'Start',
|
|
138
|
+
nextSteps: [{ targetType: 'ownTopic', stepName: 'Complete' }]
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
stepName: 'Complete',
|
|
142
|
+
previousSteps: [{ stepName: 'Start' }]
|
|
143
|
+
}
|
|
144
|
+
]
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
assert.throws(
|
|
148
|
+
() => validateFlowSchema(invalidSchema),
|
|
149
|
+
/previousSteps\[0\] must have sourceType/
|
|
150
|
+
);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('validateFlowSchema rejects extTopic nextSteps missing toStepNames or serviceTag', () => {
|
|
154
|
+
const missingToSteps = {
|
|
155
|
+
flowTag: 'TestFlow',
|
|
156
|
+
flowSteps: [
|
|
157
|
+
{
|
|
158
|
+
stepName: 'Start',
|
|
159
|
+
nextSteps: [
|
|
160
|
+
{
|
|
161
|
+
targetType: 'extTopic',
|
|
162
|
+
serviceTag: 'GraphHandler',
|
|
163
|
+
topicName: 'GetDeepPathSearch_In'
|
|
164
|
+
}
|
|
165
|
+
]
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
assert.throws(
|
|
171
|
+
() => validateFlowSchema(missingToSteps),
|
|
172
|
+
/extTopic target requires non-empty 'toStepNames'/
|
|
173
|
+
);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
test('validateFlowSchema rejects extTopic previousSteps missing fromStepNames', () => {
|
|
177
|
+
const missingFromSteps = {
|
|
178
|
+
flowTag: 'TestFlow',
|
|
179
|
+
flowSteps: [
|
|
180
|
+
{
|
|
181
|
+
stepName: 'Start',
|
|
182
|
+
nextSteps: [{ targetType: 'ownTopic', stepName: 'GetDeepPathSearch' }]
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
stepName: 'GetDeepPathSearch',
|
|
186
|
+
event: ['extTopic'],
|
|
187
|
+
previousSteps: [
|
|
188
|
+
{
|
|
189
|
+
sourceType: 'extTopic',
|
|
190
|
+
serviceTag: 'GraphHandler',
|
|
191
|
+
topicName: 'GetDeepPathSearch_Out'
|
|
192
|
+
}
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
]
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
assert.throws(
|
|
199
|
+
() => validateFlowSchema(missingFromSteps),
|
|
200
|
+
/extTopic source requires non-empty 'fromStepNames'/
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test('validateFlowSchema allows optional omitted nextSteps and previousSteps', () => {
|
|
205
|
+
const minimalSchema = {
|
|
206
|
+
flowTag: 'MinimalFlow',
|
|
207
|
+
flowSteps: [
|
|
208
|
+
{
|
|
209
|
+
stepName: 'Start'
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
assert.equal(validateFlowSchema(minimalSchema), true);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test('validateFlowSchema passes for lambdaSyncInv with functionName', () => {
|
|
218
|
+
const syncInvSchema = {
|
|
219
|
+
flowTag: 'SyncInvFlow',
|
|
220
|
+
flowSteps: [
|
|
221
|
+
{
|
|
222
|
+
stepName: 'Start',
|
|
223
|
+
nextSteps: [
|
|
224
|
+
{
|
|
225
|
+
targetType: 'lambdaSyncInv',
|
|
226
|
+
serviceTag: 'UserAccountService',
|
|
227
|
+
functionName: 'VerifyAccountHdrInv'
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
}
|
|
231
|
+
]
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
assert.equal(validateFlowSchema(syncInvSchema), true);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
test('validateFlowSchema rejects lambdaSyncInv nextSteps missing functionName', () => {
|
|
238
|
+
const missingFnSchema = {
|
|
239
|
+
flowTag: 'SyncInvFlow',
|
|
240
|
+
flowSteps: [
|
|
241
|
+
{
|
|
242
|
+
stepName: 'Start',
|
|
243
|
+
nextSteps: [
|
|
244
|
+
{
|
|
245
|
+
targetType: 'lambdaSyncInv',
|
|
246
|
+
serviceTag: 'UserAccountService'
|
|
247
|
+
}
|
|
248
|
+
]
|
|
249
|
+
}
|
|
250
|
+
]
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
assert.throws(
|
|
254
|
+
() => validateFlowSchema(missingFnSchema),
|
|
255
|
+
/lambdaSyncInv target requires non-empty 'functionName'/
|
|
256
|
+
);
|
|
257
|
+
});
|
package/test/publicApi.test.js
CHANGED
|
@@ -23,8 +23,18 @@ test('generate commits schemas, application artifacts, and resources in one publ
|
|
|
23
23
|
const rootPath = await createCrudProject();
|
|
24
24
|
const result = await generate(rootPath);
|
|
25
25
|
|
|
26
|
-
assert.strictEqual(result.files.length,
|
|
26
|
+
assert.strictEqual(result.files.length, 65);
|
|
27
27
|
assert.equal(result.project.rootPath, rootPath);
|
|
28
|
+
assert.match(
|
|
29
|
+
await readFile(
|
|
30
|
+
join(
|
|
31
|
+
rootPath,
|
|
32
|
+
'app/src/schemas/SystemTextSchemas/generatedCode/SystemTextSchemas.js'
|
|
33
|
+
),
|
|
34
|
+
'utf8'
|
|
35
|
+
),
|
|
36
|
+
/nameSpace:\s*"Create_/
|
|
37
|
+
);
|
|
28
38
|
assert.match(
|
|
29
39
|
await readFile(
|
|
30
40
|
join(
|
|
@@ -298,7 +308,7 @@ test('generate removes stale CRUD schemas and application artifacts', async () =
|
|
|
298
308
|
|
|
299
309
|
const result = await generate(rootPath);
|
|
300
310
|
|
|
301
|
-
assert.equal(result.files.length,
|
|
311
|
+
assert.equal(result.files.length, 29);
|
|
302
312
|
await assert.rejects(
|
|
303
313
|
readFile(
|
|
304
314
|
join(
|
|
@@ -335,7 +345,7 @@ async function createCrudProject() {
|
|
|
335
345
|
await mkdir(objectDirectory, { recursive: true });
|
|
336
346
|
await writeFile(
|
|
337
347
|
join(objectDirectory, 'Order.js'),
|
|
338
|
-
"export default { objectType: 'order', generatedBy: 'userGenerated' };\n"
|
|
348
|
+
"export default { objectType: 'order', generatedBy: 'userGenerated', storageResources: { graph: { storageType: 'graph' } } };\n"
|
|
339
349
|
);
|
|
340
350
|
return rootPath;
|
|
341
351
|
}
|
|
@@ -361,7 +371,7 @@ async function createOwnTopicProject() {
|
|
|
361
371
|
await mkdir(flowDirectory, { recursive: true });
|
|
362
372
|
await writeFile(
|
|
363
373
|
join(flowDirectory, 'Track.js'),
|
|
364
|
-
"export default { flowTag: 'track', event: ['ownTopic'], initiateBy: 'user', outputTopic: true, statusType: 'none', generatedCodeConfig: { generatedCodeTag: 'create'
|
|
374
|
+
"export default { flowTag: 'track', event: ['ownTopic'], initiateBy: 'user', outputTopic: true, statusType: 'none', generatedCodeConfig: { generatedCodeTag: 'create' } };\n"
|
|
365
375
|
);
|
|
366
376
|
return rootPath;
|
|
367
377
|
}
|
|
@@ -413,4 +423,3 @@ test(
|
|
|
413
423
|
);
|
|
414
424
|
}
|
|
415
425
|
);
|
|
416
|
-
|
|
@@ -80,9 +80,14 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
80
80
|
roleBlock(iam, 'FlowRbacRole'),
|
|
81
81
|
/RolePermissions|ProcessCreateTargetRoleHdrSqs/
|
|
82
82
|
);
|
|
83
|
-
// Per-object RBAC flow queue (e.g.
|
|
84
|
-
assert.match(roleBlock(iam, 'FlowRbacRole'), /
|
|
83
|
+
// Per-object RBAC flow queue (e.g. ValidatorRbacHdrSqs from Order with rbac add-on)
|
|
84
|
+
assert.match(roleBlock(iam, 'FlowRbacRole'), /ValidatorRbacHdrSqs/);
|
|
85
85
|
assert.match(resources, /^ CreateRelationshipHdrSqs:/m);
|
|
86
|
+
assert.doesNotMatch(resources, /^ CreateCompleteHdrSqs:/m);
|
|
87
|
+
assert.match(resources, /^ UpdateCompleteHdrSqs:/m);
|
|
88
|
+
assert.match(resources, /^ DeleteCompleteHdrSqs:/m);
|
|
89
|
+
assert.doesNotMatch(resources, /RbacCallbackHdrSqs/);
|
|
90
|
+
assert.doesNotMatch(resources, /FlowHook|beforeLogical/);
|
|
86
91
|
assert.equal(
|
|
87
92
|
(resources.match(/^ CreateRelationshipHdrSqs:/gm) ?? []).length,
|
|
88
93
|
1
|
|
@@ -103,7 +108,14 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
103
108
|
);
|
|
104
109
|
assert.match(roleBlock(iam, 'RegisterRole'), /s3:GetObject/);
|
|
105
110
|
assert.match(roleBlock(iam, 'RegisterRole'), /RegisterComplete/);
|
|
106
|
-
assert.
|
|
111
|
+
assert.match(roleBlock(iam, 'RegisterRole'), /dynamodb:/);
|
|
112
|
+
assert.match(roleBlock(iam, 'RegisterRole'), /execute-api:ManageConnections/);
|
|
113
|
+
const procFindDataRole = roleBlock(iam, 'ProcFindDataRole');
|
|
114
|
+
assert.match(procFindDataRole, /dynamodb:UpdateItem/);
|
|
115
|
+
assert.match(procFindDataRole, /dynamodb:GetItem/);
|
|
116
|
+
assert.match(procFindDataRole, /dynamodb:Query/);
|
|
117
|
+
assert.match(procFindDataRole, /sns:Publish/);
|
|
118
|
+
assert.match(procFindDataRole, /FindData_Out/);
|
|
107
119
|
assert.doesNotMatch(
|
|
108
120
|
iam,
|
|
109
121
|
/s3:GetObjectVersion|s3:ListBucket|sqs:DeleteMessageBatch|sqs:GetQueueUrl/
|
|
@@ -123,7 +135,7 @@ test('emits queues, topics, tables, and IAM roles from schema data', async () =>
|
|
|
123
135
|
}
|
|
124
136
|
});
|
|
125
137
|
|
|
126
|
-
test('
|
|
138
|
+
test('rejects deprecated FlowHook schemas', async () => {
|
|
127
139
|
const rootPath = await createProject();
|
|
128
140
|
const flowDirectory = join(rootPath, 'app/src/schemas/FlowSchemas');
|
|
129
141
|
await writeFile(
|
|
@@ -131,21 +143,10 @@ test('emits one before-logical queue for flows that share an action', async () =
|
|
|
131
143
|
"export default [{ flowTag: 'first', event: ['ownTopic'], generatedCodeConfig: { generatedCodeTag: 'create', codeHookTag: 'beforeLogical' } }, { flowTag: 'second', event: ['extTopic'], generatedCodeConfig: { generatedCodeTag: 'create', codeHookTag: 'beforeLogical' } }];\n"
|
|
132
144
|
);
|
|
133
145
|
|
|
134
|
-
await
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
join(
|
|
138
|
-
rootPath,
|
|
139
|
-
'resource/sls_yaml/generatedCode/source/generated-sns-in-sqs.yml'
|
|
140
|
-
),
|
|
141
|
-
'utf8'
|
|
142
|
-
);
|
|
143
|
-
assert.equal(
|
|
144
|
-
(resources.match(/^ CreateBeforeLogicalHdrSqs:/gm) ?? []).length,
|
|
145
|
-
1
|
|
146
|
+
await assert.rejects(
|
|
147
|
+
generateResourceArtifacts(rootPath),
|
|
148
|
+
/generatedCodeConfig\.codeHookTag is no longer supported/
|
|
146
149
|
);
|
|
147
|
-
assert.match(resources, /SubscriptionOutFirstToCreateBeforeLogical:/);
|
|
148
|
-
assert.match(resources, /SubscriptionOutSecondToCreateBeforeLogical:/);
|
|
149
150
|
});
|
|
150
151
|
|
|
151
152
|
test('emits the shared WebSocket completion queue and output-topic subscriptions', async () => {
|
|
@@ -161,6 +162,7 @@ test('emits the shared WebSocket completion queue and output-topic subscriptions
|
|
|
161
162
|
);
|
|
162
163
|
assert.match(resources, /^ WebSocketCompleteHdrSqs:/m);
|
|
163
164
|
assert.match(resources, /SubscriptionToOutCustomWebSocketComplete:/);
|
|
165
|
+
assert.match(resources, /SubscriptionToOutCustomRegisterComplete:/);
|
|
164
166
|
const iam = await readFile(
|
|
165
167
|
join(
|
|
166
168
|
rootPath,
|
|
@@ -187,6 +189,10 @@ test('emits the shared WebSocket completion queue and output-topic subscriptions
|
|
|
187
189
|
resources,
|
|
188
190
|
/SubscriptionToOutCreateRelationshipWebSocketComplete:/
|
|
189
191
|
);
|
|
192
|
+
assert.match(
|
|
193
|
+
resources,
|
|
194
|
+
/SubscriptionToOutCreateRelationshipRegisterComplete:/
|
|
195
|
+
);
|
|
190
196
|
});
|
|
191
197
|
|
|
192
198
|
test('scopes stored-cache permissions to the flow object table', async () => {
|
|
@@ -294,7 +300,7 @@ async function createProject() {
|
|
|
294
300
|
);
|
|
295
301
|
await writeFile(
|
|
296
302
|
join(schemas, 'FlowSchemas/Upload.js'),
|
|
297
|
-
"export default { flowTag: 'upload', event: ['s3'], s3Subscription: { topicName: 'OutCreate', queueName: 'UploadReservedLimitHdrSqs' } };\n"
|
|
303
|
+
"export default { flowTag: 'upload', event: ['s3'], outputTopic: true, s3Subscription: { topicName: 'OutCreate', queueName: 'UploadReservedLimitHdrSqs' } };\n"
|
|
298
304
|
);
|
|
299
305
|
return rootPath;
|
|
300
306
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { readFile, rm, writeFile, mkdir } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import test, { after } from 'node:test';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
emitSystemTextSchemas,
|
|
9
|
+
systemTextSchemaManagedPaths
|
|
10
|
+
} from '../src/emit/systemText/emitSystemTextSchemas.js';
|
|
11
|
+
import { generate } from '../src/generate.js';
|
|
12
|
+
|
|
13
|
+
const roots = new Set();
|
|
14
|
+
after(() =>
|
|
15
|
+
Promise.all(
|
|
16
|
+
[...roots].map((root) => rm(root, { recursive: true, force: true }))
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
test('emitSystemTextSchemas produces expected structure for objects, flows, relationships, and notifications', async () => {
|
|
21
|
+
const artifacts = await emitSystemTextSchemas({
|
|
22
|
+
project: { serviceTag: 'MyService' },
|
|
23
|
+
objects: [
|
|
24
|
+
{
|
|
25
|
+
objectType: 'product',
|
|
26
|
+
fieldNames: {
|
|
27
|
+
productId: { type: 'string' },
|
|
28
|
+
price: { type: 'number' }
|
|
29
|
+
},
|
|
30
|
+
addOnDataStructure: [
|
|
31
|
+
{
|
|
32
|
+
type: 'versionedData',
|
|
33
|
+
fieldNames: {
|
|
34
|
+
version: { type: 'number' }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
],
|
|
40
|
+
relationships: [
|
|
41
|
+
{
|
|
42
|
+
hasCategory: {
|
|
43
|
+
fieldNames: {
|
|
44
|
+
originTimestamp: { type: 'number' }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
],
|
|
49
|
+
flows: [{ flowTag: 'ProcessOrder' }],
|
|
50
|
+
notifications: [
|
|
51
|
+
{
|
|
52
|
+
notificationGrouping: 'OrderEvents',
|
|
53
|
+
notificationTag: 'OrderPlaced'
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
assert.equal(artifacts.length, 1);
|
|
59
|
+
assert.equal(
|
|
60
|
+
artifacts[0].path,
|
|
61
|
+
'app/src/schemas/SystemTextSchemas/generatedCode/SystemTextSchemas.js'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const content = artifacts[0].content;
|
|
65
|
+
// Object fields
|
|
66
|
+
assert.match(content, /nameSpace:\s*"product_MyService_description"/);
|
|
67
|
+
assert.match(
|
|
68
|
+
content,
|
|
69
|
+
/systemTextTag:\s*"productId_product_MyService"/
|
|
70
|
+
);
|
|
71
|
+
assert.match(content, /\/\/\(<product_MyServiceproductIdHook>\)/);
|
|
72
|
+
assert.match(
|
|
73
|
+
content,
|
|
74
|
+
/systemTextTag:\s*"version_product_MyService"/
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// Notification
|
|
78
|
+
assert.match(content, /nameSpace:\s*"sysNotification_OrderEvents"/);
|
|
79
|
+
assert.match(content, /systemTextTag:\s*"OrderPlaced"/);
|
|
80
|
+
assert.match(content, /\/\/\(<OrderPlacedHook>\)/);
|
|
81
|
+
|
|
82
|
+
// Relationship
|
|
83
|
+
assert.match(content, /nameSpace:\s*"hasCategory_MyService_description"/);
|
|
84
|
+
assert.match(
|
|
85
|
+
content,
|
|
86
|
+
/systemTextTag:\s*"originTimestamp_hasCategory_MyService"/
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
// Custom Flow
|
|
90
|
+
assert.match(content, /nameSpace:\s*"ProcessOrder_MyService"/);
|
|
91
|
+
assert.match(content, /systemTextTag:\s*"errors_ProcessOrder_MyService"/);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test('emitSystemTextSchemas returns empty array when no inputs provided', async () => {
|
|
95
|
+
const artifacts = await emitSystemTextSchemas({
|
|
96
|
+
project: { serviceTag: 'MyService' },
|
|
97
|
+
objects: [],
|
|
98
|
+
relationships: [],
|
|
99
|
+
flows: [],
|
|
100
|
+
notifications: []
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
assert.deepEqual(artifacts, []);
|
|
104
|
+
assert.deepEqual(systemTextSchemaManagedPaths(), [
|
|
105
|
+
'app/src/schemas/SystemTextSchemas/generatedCode/SystemTextSchemas.js'
|
|
106
|
+
]);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('generate preserves user hooks inside SystemTextSchemas.js across regeneration', async () => {
|
|
110
|
+
const rootPath = join(
|
|
111
|
+
tmpdir(),
|
|
112
|
+
`system-text-hook-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
113
|
+
);
|
|
114
|
+
roots.add(rootPath);
|
|
115
|
+
await mkdir(join(rootPath, 'app/src/schemas/ObjectSchemas'), {
|
|
116
|
+
recursive: true
|
|
117
|
+
});
|
|
118
|
+
await mkdir(join(rootPath, 'config'), { recursive: true });
|
|
119
|
+
|
|
120
|
+
await writeFile(
|
|
121
|
+
join(rootPath, 'config/serverless.config.yml'),
|
|
122
|
+
'main_config:\n iz_serviceTag: TestService\n'
|
|
123
|
+
);
|
|
124
|
+
await writeFile(
|
|
125
|
+
join(rootPath, 'app/src/schemas/ObjectSchemas/Item.js'),
|
|
126
|
+
"export default { objectType: 'item', fieldNames: { itemId: { type: 'string' } }, generatedBy: 'userGenerated' };\n"
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
// First generate
|
|
130
|
+
await generate(rootPath);
|
|
131
|
+
|
|
132
|
+
const targetFile = join(
|
|
133
|
+
rootPath,
|
|
134
|
+
'app/src/schemas/SystemTextSchemas/generatedCode/SystemTextSchemas.js'
|
|
135
|
+
);
|
|
136
|
+
const initialContent = await readFile(targetFile, 'utf8');
|
|
137
|
+
assert.match(initialContent, /\/\/\(<item_TestServiceitemIdHook>\)/);
|
|
138
|
+
|
|
139
|
+
// User edits inside hook
|
|
140
|
+
const modifiedContent = initialContent.replace(
|
|
141
|
+
'//(<item_TestServiceitemIdHook>)\n //(</item_TestServiceitemIdHook>)',
|
|
142
|
+
'//(<item_TestServiceitemIdHook>)\n customTranslation: "Item ID Value",\n //(</item_TestServiceitemIdHook>)'
|
|
143
|
+
);
|
|
144
|
+
await writeFile(targetFile, modifiedContent);
|
|
145
|
+
|
|
146
|
+
// Second generate (regeneration)
|
|
147
|
+
await generate(rootPath);
|
|
148
|
+
|
|
149
|
+
const regeneratedContent = await readFile(targetFile, 'utf8');
|
|
150
|
+
assert.match(
|
|
151
|
+
regeneratedContent,
|
|
152
|
+
/customTranslation:\s*"Item ID Value"/
|
|
153
|
+
);
|
|
154
|
+
});
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { middlewareHandler as middleware } from '@izara_project/izara-middleware';
|
|
2
|
-
import { recordHandlerSharedLib } from '@izara_project/izara-core-library-record-handler';
|
|
3
|
-
import Logger from '@izara_project/izara-core-library-logger';
|
|
4
|
-
import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
|
|
5
|
-
import CreateComplete from './CreateComplete_Main.js';
|
|
6
|
-
|
|
7
|
-
//(<optionalRequire>)
|
|
8
|
-
//(</optionalRequire>)
|
|
9
|
-
|
|
10
|
-
// validate event properties in body.Message of sqs event
|
|
11
|
-
let perRecordsValidatorSchema = {
|
|
12
|
-
type: 'object',
|
|
13
|
-
properties: {
|
|
14
|
-
returnValue: {
|
|
15
|
-
type: 'object'
|
|
16
|
-
},
|
|
17
|
-
parentFlowId: {
|
|
18
|
-
type: 'string'
|
|
19
|
-
},
|
|
20
|
-
status: {
|
|
21
|
-
type: 'string'
|
|
22
|
-
},
|
|
23
|
-
errorsFound: {
|
|
24
|
-
type: 'array'
|
|
25
|
-
},
|
|
26
|
-
graphServiceTag: {
|
|
27
|
-
type: 'string'
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
|
-
required: [
|
|
31
|
-
'returnValue',
|
|
32
|
-
'parentFlowId',
|
|
33
|
-
'status',
|
|
34
|
-
'errorsFound',
|
|
35
|
-
'graphServiceTag'
|
|
36
|
-
]
|
|
37
|
-
//(<validatorSchema>)
|
|
38
|
-
//(</validatorSchema>)
|
|
39
|
-
};
|
|
40
|
-
|
|
41
|
-
let messageAttributeValidatorSchema = null;
|
|
42
|
-
middleware.setValidatorSchema(recordHandlerSharedLib.baseValidatorSchema());
|
|
43
|
-
|
|
44
|
-
export const main = middleware.wrap(async (event, context) => {
|
|
45
|
-
try {
|
|
46
|
-
let recordPromises = [];
|
|
47
|
-
|
|
48
|
-
// loop each record and send to mainFunction
|
|
49
|
-
await Promise.all(
|
|
50
|
-
event.Records.map(async (record) => {
|
|
51
|
-
let passOnProperties = [];
|
|
52
|
-
record._izContext.logger.debug('record ReceiveMsgOutHdrSqs', record);
|
|
53
|
-
|
|
54
|
-
//validate message (and MessageAttributes)
|
|
55
|
-
await recordHandlerSharedLib.validateRecord(
|
|
56
|
-
record, // one record will send to mainFunction
|
|
57
|
-
'CreateCompleteHdrSqs', // queue name that need to retry or send to dlq
|
|
58
|
-
perRecordsValidatorSchema, // schema for record.Message
|
|
59
|
-
messageAttributeValidatorSchema
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
// add argument (to invoke lambda) to passOnProperties[]
|
|
63
|
-
passOnProperties.push(record.body.Message);
|
|
64
|
-
passOnProperties.push(
|
|
65
|
-
callingFlowSharedLib.addPassBackPropertiesToPassOnProperties(
|
|
66
|
-
record.body.Message
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
//(<additionalParams>)
|
|
70
|
-
//(</additionalParams>)
|
|
71
|
-
record._izContext.logger.debug(
|
|
72
|
-
'passOnProperties in handler',
|
|
73
|
-
passOnProperties
|
|
74
|
-
);
|
|
75
|
-
|
|
76
|
-
// call recordHandlerSharedLib.recordHandler with 3 parameters and return promise(resolve)
|
|
77
|
-
let recordPromise = recordHandlerSharedLib.recordHandler(
|
|
78
|
-
record, // one record will send to mainFunction
|
|
79
|
-
CreateComplete, // mainFunction that need to invoke.
|
|
80
|
-
'CreateCompleteHdrSqs', // queue name that need to retry or send to dlq
|
|
81
|
-
passOnProperties // all parameters that mainFunction needed.
|
|
82
|
-
);
|
|
83
|
-
record._izContext.logger.debug('after recordPromise in handler');
|
|
84
|
-
recordPromises.push(recordPromise); // push promise to recordPromises
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
try {
|
|
88
|
-
await Promise.all(recordPromises);
|
|
89
|
-
return event.Records;
|
|
90
|
-
} catch {
|
|
91
|
-
Logger.debug(
|
|
92
|
-
'Promise.all(recordPromises) in handler threw error (at least one record did no resolve)'
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
} catch (error) {
|
|
96
|
-
Logger.error('Unhandled Error, CreateComplete_HdrSqs: ', error);
|
|
97
|
-
throw error;
|
|
98
|
-
}
|
|
99
|
-
});
|