@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.93",
3
+ "version": "1.0.94",
4
4
  "description": "Code for locally generating per service files",
5
5
  "homepage": "https://bitbucket.org/izara-core-support-services/izara-core-support-services-generate-service-code#readme",
6
6
  "repository": {
@@ -32,7 +32,9 @@ export function compileProject(request = {}) {
32
32
  }
33
33
  }
34
34
  } catch (err) {
35
- console.log('Could not read serverless.config.yml', err);
35
+ throw new Error(
36
+ `Failed to read or parse configuration at ${configPath}: ${err.message}`
37
+ );
36
38
  }
37
39
  }
38
40
 
@@ -1,8 +1,67 @@
1
+ export const VALID_EVENT_ENUM = [
2
+ 'eventBridge',
3
+ 'extTopic',
4
+ 'lambdaSyncInv',
5
+ 'lambdaSyncApi',
6
+ 'ownTopic',
7
+ 's3',
8
+ 'dsq',
9
+ 'generateCode'
10
+ ];
11
+
1
12
  export function validateFlowSchema(flowSchema) {
2
13
  if (!flowSchema || typeof flowSchema !== 'object') {
3
14
  throw new TypeError('flowSchema must be an object');
4
15
  }
5
16
 
17
+ if (
18
+ typeof flowSchema.flowTag !== 'string' ||
19
+ flowSchema.flowTag.trim() === ''
20
+ ) {
21
+ throw new Error(
22
+ 'FlowSchema validation error: flowTag is required and must be a non-empty string'
23
+ );
24
+ }
25
+ const flowTag = flowSchema.flowTag;
26
+
27
+ if (!Array.isArray(flowSchema.event) || flowSchema.event.length === 0) {
28
+ throw new Error(
29
+ `FlowSchema validation error: Flow '${flowTag}' requires a non-empty 'event' array`
30
+ );
31
+ }
32
+
33
+ for (const [eIdx, eventName] of flowSchema.event.entries()) {
34
+ if (
35
+ typeof eventName !== 'string' ||
36
+ !VALID_EVENT_ENUM.includes(eventName)
37
+ ) {
38
+ throw new Error(
39
+ `FlowSchema validation error: Flow '${flowTag}' event[${eIdx}] '${eventName}' is invalid. Must be one of ${JSON.stringify(VALID_EVENT_ENUM)}`
40
+ );
41
+ }
42
+ }
43
+
44
+ if (
45
+ flowSchema.event.includes('generateCode') &&
46
+ flowSchema.event.length > 1
47
+ ) {
48
+ throw new Error(
49
+ `FlowSchema validation error: Flow '${flowTag}' with event 'generateCode' must be a standalone event and cannot co-exist with other events`
50
+ );
51
+ }
52
+
53
+ if (flowSchema.event.includes('s3')) {
54
+ if (
55
+ !flowSchema.bucketName ||
56
+ typeof flowSchema.bucketName !== 'string' ||
57
+ flowSchema.bucketName.trim() === ''
58
+ ) {
59
+ throw new Error(
60
+ `FlowSchema validation error: Flow '${flowTag}' with event 's3' requires 'bucketName' as a non-empty string`
61
+ );
62
+ }
63
+ }
64
+
6
65
  if (flowSchema.initiateBy !== undefined) {
7
66
  const validInitiateBy = [
8
67
  'user',
@@ -25,6 +84,110 @@ export function validateFlowSchema(flowSchema) {
25
84
  }
26
85
  }
27
86
 
87
+ if (flowSchema.statusType !== undefined) {
88
+ const validStatusType = [
89
+ 'none',
90
+ 'statusField',
91
+ 'storedCache',
92
+ 'triggerCache'
93
+ ];
94
+ if (!validStatusType.includes(flowSchema.statusType)) {
95
+ throw new Error(
96
+ `FlowSchema validation error: Flow '${flowTag}' statusType must be one of ${JSON.stringify(validStatusType)}, got '${flowSchema.statusType}'`
97
+ );
98
+ }
99
+
100
+ if (flowSchema.statusType !== 'none') {
101
+ if (!flowSchema.objType) {
102
+ throw new Error(
103
+ `FlowSchema validation error: Flow '${flowTag}' with statusType '${flowSchema.statusType}' requires 'objType'`
104
+ );
105
+ }
106
+
107
+ if (flowSchema.statusType === 'storedCache') {
108
+ const objTypeVal =
109
+ typeof flowSchema.objType === 'string'
110
+ ? flowSchema.objType
111
+ : flowSchema.objType?.objectType;
112
+ if (typeof objTypeVal !== 'string' || objTypeVal.trim() === '') {
113
+ throw new Error(
114
+ `FlowSchema validation error: Flow '${flowTag}' with statusType 'storedCache' requires 'objType' to be a non-empty string or object with string 'objectType'`
115
+ );
116
+ }
117
+ }
118
+
119
+ if (flowSchema.statusType === 'triggerCache') {
120
+ if (
121
+ !Array.isArray(flowSchema.triggerFlowTypes) ||
122
+ flowSchema.triggerFlowTypes.length === 0
123
+ ) {
124
+ throw new Error(
125
+ `FlowSchema validation error: Flow '${flowTag}' with statusType 'triggerCache' requires a non-empty 'triggerFlowTypes' array`
126
+ );
127
+ }
128
+ for (const [tIdx, item] of flowSchema.triggerFlowTypes.entries()) {
129
+ if (!item || (typeof item !== 'string' && typeof item !== 'object')) {
130
+ throw new Error(
131
+ `FlowSchema validation error: Flow '${flowTag}' triggerFlowTypes[${tIdx}] must be a non-empty string or object`
132
+ );
133
+ }
134
+ if (typeof item === 'string' && item.trim() === '') {
135
+ throw new Error(
136
+ `FlowSchema validation error: Flow '${flowTag}' triggerFlowTypes[${tIdx}] must not be an empty string`
137
+ );
138
+ }
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ if (
145
+ flowSchema.event.includes('generateCode') &&
146
+ !flowSchema.generatedCodeConfig
147
+ ) {
148
+ throw new Error(
149
+ `FlowSchema validation error: Flow '${flowTag}' with event 'generateCode' requires 'generatedCodeConfig'`
150
+ );
151
+ }
152
+
153
+ if (flowSchema.generatedCodeConfig) {
154
+ const config = flowSchema.generatedCodeConfig;
155
+ if (
156
+ config.invocationType !== undefined &&
157
+ !['ownTopic', 'lambdaSyncInv'].includes(config.invocationType)
158
+ ) {
159
+ throw new Error(
160
+ `FlowSchema validation error: Flow '${flowTag}' generatedCodeConfig.invocationType '${config.invocationType}' is invalid. Must be 'ownTopic' or 'lambdaSyncInv'`
161
+ );
162
+ }
163
+ const isOwnTopic = flowSchema.event.includes('ownTopic');
164
+ const isDsq = flowSchema.event.includes('dsq');
165
+ const isHook = Boolean(
166
+ config.generateCodeTag ||
167
+ config.invocationType ||
168
+ config.objType ||
169
+ flowSchema.event.includes('generateCode') ||
170
+ (!isOwnTopic && !isDsq)
171
+ );
172
+
173
+ const hookTag = config.codeHookTag;
174
+ if (hookTag === 'beforeLogical') {
175
+ throw new Error(
176
+ `FlowSchema validation error: Flow '${flowTag}' generatedCodeConfig.codeHookTag is no longer supported`
177
+ );
178
+ }
179
+ if (isHook && !hookTag) {
180
+ throw new Error(
181
+ `FlowSchema validation error: Flow '${flowTag}' generatedCodeConfig requires 'codeHookTag' ('before' | 'after'), got '${hookTag}'`
182
+ );
183
+ }
184
+ if (hookTag !== undefined && hookTag !== 'before' && hookTag !== 'after') {
185
+ throw new Error(
186
+ `FlowSchema validation error: Flow '${flowTag}' generatedCodeConfig.codeHookTag '${hookTag}' is invalid. Must be 'before' or 'after'`
187
+ );
188
+ }
189
+ }
190
+
28
191
  const steps = flowSchema.flowSteps;
29
192
  if (!Array.isArray(steps) || steps.length === 0) {
30
193
  throw new Error(
@@ -32,10 +195,7 @@ export function validateFlowSchema(flowSchema) {
32
195
  );
33
196
  }
34
197
 
35
- let flowEvents = [];
36
- if (Array.isArray(flowSchema.event)) {
37
- flowEvents = flowSchema.event;
38
- }
198
+ const flowEvents = flowSchema.event;
39
199
  const syncEvents = ['lambdaSyncInv', 'lambdaSyncApi'];
40
200
  let hasSyncEvent = false;
41
201
  for (const eventName of flowEvents) {
@@ -52,10 +212,6 @@ export function validateFlowSchema(flowSchema) {
52
212
  }
53
213
 
54
214
  if (hasSyncEvent && steps.length > 1) {
55
- let flowTag = 'unknown';
56
- if (flowSchema.flowTag) {
57
- flowTag = flowSchema.flowTag;
58
- }
59
215
  throw new Error(
60
216
  `FlowSchema validation error: Flow '${flowTag}' with synchronous ` +
61
217
  `events ('lambdaSyncInv' | 'lambdaSyncApi') must only define a ` +
@@ -110,6 +266,17 @@ export function validateFlowSchema(flowSchema) {
110
266
  );
111
267
  }
112
268
 
269
+ if (step.stepName.toLowerCase().includes('checktrigger')) {
270
+ if (
271
+ !Array.isArray(flowSchema.triggerFlowTypes) ||
272
+ flowSchema.triggerFlowTypes.length === 0
273
+ ) {
274
+ throw new Error(
275
+ `FlowSchema validation error: Flow '${flowTag}' defines step '${step.stepName}' but requires a non-empty 'triggerFlowTypes' array`
276
+ );
277
+ }
278
+ }
279
+
113
280
  if (Array.isArray(step.nextSteps) && step.nextSteps.length > 0) {
114
281
  for (let nIdx = 0; nIdx < step.nextSteps.length; nIdx++) {
115
282
  const item = step.nextSteps[nIdx];
@@ -128,9 +295,10 @@ export function validateFlowSchema(flowSchema) {
128
295
  );
129
296
  }
130
297
  if (item.targetType === 'ownTopic') {
298
+ const ownTopicTarget = item.stepName || item.topic;
131
299
  if (
132
- typeof item.stepName !== 'string' ||
133
- item.stepName.trim() === ''
300
+ typeof ownTopicTarget !== 'string' ||
301
+ ownTopicTarget.trim() === ''
134
302
  ) {
135
303
  throw new Error(
136
304
  `FlowSchema validation error: flowSteps[${idx}].nextSteps[${nIdx}] ownTopic target requires non-empty 'stepName'`
@@ -145,10 +313,8 @@ export function validateFlowSchema(flowSchema) {
145
313
  `FlowSchema validation error: flowSteps[${idx}].nextSteps[${nIdx}] extTopic target requires non-empty 'serviceTag'`
146
314
  );
147
315
  }
148
- if (
149
- typeof item.topicName !== 'string' ||
150
- item.topicName.trim() === ''
151
- ) {
316
+ const extTopicName = item.topicName || item.topic;
317
+ if (typeof extTopicName !== 'string' || extTopicName.trim() === '') {
152
318
  throw new Error(
153
319
  `FlowSchema validation error: flowSteps[${idx}].nextSteps[${nIdx}] extTopic target requires non-empty 'topicName'`
154
320
  );
@@ -203,10 +369,8 @@ export function validateFlowSchema(flowSchema) {
203
369
  `FlowSchema validation error: flowSteps[${idx}].previousSteps[${pIdx}] extTopic source requires non-empty 'serviceTag'`
204
370
  );
205
371
  }
206
- if (
207
- typeof item.topicName !== 'string' ||
208
- item.topicName.trim() === ''
209
- ) {
372
+ const extSourceName = item.topicName || item.topic;
373
+ if (typeof extSourceName !== 'string' || extSourceName.trim() === '') {
210
374
  throw new Error(
211
375
  `FlowSchema validation error: flowSteps[${idx}].previousSteps[${pIdx}] extTopic source requires non-empty 'topicName'`
212
376
  );
@@ -206,6 +206,118 @@ export async function loadLocalSchemaDirectory(
206
206
 
207
207
  if (directoryName === 'FlowSchemas') {
208
208
  for (const schema of loadedSchemas) {
209
+ if (!schema?._sourceFile) {
210
+ schema._sourceFile = entry.name;
211
+ }
212
+ if (
213
+ !schema?.flowTag ||
214
+ typeof schema.flowTag !== 'string' ||
215
+ schema.flowTag.trim() === ''
216
+ ) {
217
+ throw new Error(
218
+ `FlowSchema validation error in ${entry.name}: flowTag is required and must be a non-empty string`
219
+ );
220
+ }
221
+ const flowTag = schema.flowTag;
222
+
223
+ if (!Array.isArray(schema.event) || schema.event.length === 0) {
224
+ throw new Error(
225
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' requires a non-empty 'event' array`
226
+ );
227
+ }
228
+
229
+ for (const [eIdx, eventName] of schema.event.entries()) {
230
+ if (
231
+ typeof eventName !== 'string' ||
232
+ !VALID_EVENT_ENUM.includes(eventName)
233
+ ) {
234
+ throw new Error(
235
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' event[${eIdx}] '${eventName}' is invalid. ` +
236
+ `Must be one of ${JSON.stringify(VALID_EVENT_ENUM)}`
237
+ );
238
+ }
239
+ }
240
+
241
+ if (
242
+ schema.event.includes('generateCode') &&
243
+ schema.event.length > 1
244
+ ) {
245
+ throw new Error(
246
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' with event 'generateCode' must be a standalone event and cannot co-exist with other events`
247
+ );
248
+ }
249
+
250
+ if (schema?.statusType !== undefined && schema?.statusType !== 'none') {
251
+ const validStatusTypes = [
252
+ 'statusField',
253
+ 'storedCache',
254
+ 'triggerCache',
255
+ 'none'
256
+ ];
257
+ if (!validStatusTypes.includes(schema.statusType)) {
258
+ throw new Error(
259
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' statusType must be one of ${JSON.stringify(validStatusTypes)}, got '${schema.statusType}'`
260
+ );
261
+ }
262
+ if (!schema.objType) {
263
+ throw new Error(
264
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' with statusType '${schema.statusType}' requires 'objType'`
265
+ );
266
+ }
267
+ if (schema.statusType === 'storedCache') {
268
+ const objTypeVal =
269
+ typeof schema.objType === 'string'
270
+ ? schema.objType
271
+ : schema.objType?.objectType;
272
+ if (typeof objTypeVal !== 'string' || objTypeVal.trim() === '') {
273
+ throw new Error(
274
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' with statusType 'storedCache' requires 'objType' to be a non-empty string or object with string 'objectType'`
275
+ );
276
+ }
277
+ }
278
+ if (schema.statusType === 'triggerCache') {
279
+ if (
280
+ !Array.isArray(schema.triggerFlowTypes) ||
281
+ schema.triggerFlowTypes.length === 0
282
+ ) {
283
+ throw new Error(
284
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' with statusType 'triggerCache' requires a non-empty 'triggerFlowTypes' array`
285
+ );
286
+ }
287
+ for (const [tIdx, item] of schema.triggerFlowTypes.entries()) {
288
+ if (
289
+ !item ||
290
+ (typeof item !== 'string' && typeof item !== 'object')
291
+ ) {
292
+ throw new Error(
293
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' triggerFlowTypes[${tIdx}] must be a non-empty string or object`
294
+ );
295
+ }
296
+ if (typeof item === 'string' && item.trim() === '') {
297
+ throw new Error(
298
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' triggerFlowTypes[${tIdx}] must not be an empty string`
299
+ );
300
+ }
301
+ }
302
+ }
303
+ }
304
+ if (Array.isArray(schema?.flowSteps)) {
305
+ for (const step of schema.flowSteps) {
306
+ if (
307
+ typeof step?.stepName === 'string' &&
308
+ step.stepName.toLowerCase().includes('checktrigger')
309
+ ) {
310
+ if (
311
+ !Array.isArray(schema.triggerFlowTypes) ||
312
+ schema.triggerFlowTypes.length === 0
313
+ ) {
314
+ throw new Error(
315
+ `FlowSchema validation error in ${entry.name}: Flow '${flowTag}' defines step '${step.stepName}' but requires a non-empty 'triggerFlowTypes' array`
316
+ );
317
+ }
318
+ }
319
+ }
320
+ }
209
321
  if (
210
322
  Array.isArray(schema?.event) &&
211
323
  schema.event.includes('generateCode') &&
@@ -234,19 +346,37 @@ export async function loadLocalSchemaDirectory(
234
346
  );
235
347
  }
236
348
  }
237
- if (
238
- Object.prototype.hasOwnProperty.call(
239
- schema?.generatedCodeConfig ?? {},
240
- 'codeHookTag'
241
- )
242
- ) {
243
- const hookTag = schema.generatedCodeConfig.codeHookTag;
349
+ if (schema?.generatedCodeConfig) {
350
+ const config = schema.generatedCodeConfig;
351
+ const isOwnTopic =
352
+ Array.isArray(schema.event) && schema.event.includes('ownTopic');
353
+ const isDsq =
354
+ Array.isArray(schema.event) && schema.event.includes('dsq');
355
+ const isHook = Boolean(
356
+ config.generateCodeTag ||
357
+ config.invocationType ||
358
+ config.objType ||
359
+ (Array.isArray(schema.event) &&
360
+ schema.event.includes('generateCode')) ||
361
+ (!isOwnTopic && !isDsq)
362
+ );
363
+
364
+ const hookTag = config.codeHookTag;
244
365
  if (hookTag === 'beforeLogical') {
245
366
  throw new Error(
246
367
  `Unsupported FlowHook configuration in ${entry.name}: generatedCodeConfig.codeHookTag is no longer supported`
247
368
  );
248
369
  }
249
- if (hookTag !== 'before' && hookTag !== 'after') {
370
+ if (isHook && !hookTag) {
371
+ throw new Error(
372
+ `Unsupported FlowHook configuration in ${entry.name}: Flow '${flowTag}' with generatedCodeConfig requires 'codeHookTag' ('before' or 'after')`
373
+ );
374
+ }
375
+ if (
376
+ hookTag !== undefined &&
377
+ hookTag !== 'before' &&
378
+ hookTag !== 'after'
379
+ ) {
250
380
  throw new Error(
251
381
  `Unsupported FlowHook configuration in ${entry.name}: generatedCodeConfig.codeHookTag '${hookTag}' is invalid. Must be 'before' or 'after'`
252
382
  );
@@ -718,6 +848,11 @@ function _isTestEnvironment() {
718
848
  process.env.NODE_ENV === 'test' ||
719
849
  process.env.npm_lifecycle_event === 'test' ||
720
850
  Boolean(process.execArgv?.includes('--test')) ||
721
- Boolean(process.argv?.includes('--test'))
851
+ Boolean(process.argv?.includes('--test')) ||
852
+ Boolean(
853
+ process.argv?.some(
854
+ (arg) => arg.includes('.test.') || arg.includes('test/')
855
+ )
856
+ )
722
857
  );
723
858
  }
@@ -160,7 +160,7 @@ const tableConfigs = {
160
160
  (flow?.flowSteps || []).forEach(function(step) {
161
161
  if (Array.isArray(step?.previousSteps)) {
162
162
  const stepName = step?.stepName ? step.stepName[0].toUpperCase() + step.stepName.slice(1) : '';
163
- const functionName = stepName.startsWith(flowTag) ? stepName : `${flowTag}${stepName}`;
163
+ const functionName = `${flowTag}${stepName}`;
164
164
  const queueName = `${functionName}HdrSqs`;
165
165
  step.previousSteps.forEach(function(prev) {
166
166
  if (prev && prev.sourceType === 'extTopic' && prev.serviceTag && prev.topicName) {
@@ -88,13 +88,18 @@ export async function emitS3Flow(flow, source, handlerRoot) {
88
88
  filterPatternFunctionName
89
89
  });
90
90
  } else if (type === 's3') {
91
- let bucketName = '';
92
- if (flow.bucketName) {
93
- bucketName = flow.bucketName;
91
+ if (
92
+ !flow.bucketName ||
93
+ typeof flow.bucketName !== 'string' ||
94
+ flow.bucketName.trim() === ''
95
+ ) {
96
+ throw new Error(
97
+ `FlowSchema validation error: Flow '${flow.flowTag}' with event 's3' requires 'bucketName' as a non-empty string`
98
+ );
94
99
  }
95
100
  functionYaml = renderS3FunctionYaml({
96
101
  ...descriptor,
97
- bucketName,
102
+ bucketName: flow.bucketName,
98
103
  flowTag: flow.flowTag,
99
104
  flowStepName: ''
100
105
  });
@@ -13,10 +13,7 @@ import {
13
13
  export async function emitExtTopicStep({ flow, step, flowTag, stepName }) {
14
14
  const source = `app/src/generatedCode/FlowSchemas/${flowTag}/source`;
15
15
  const handlerRoot = `src/generatedCode/FlowSchemas/${flowTag}/source`;
16
- let functionName = `${flowTag}${stepName}`;
17
- if (stepName.startsWith(flowTag)) {
18
- functionName = stepName;
19
- }
16
+ const functionName = `${flowTag}${stepName}`;
20
17
  const mainFileName = `${functionName}_Main`;
21
18
  const startConfig = stepConfig(flow, step);
22
19
 
@@ -352,8 +349,12 @@ export function storedCacheTableName(flow, objects) {
352
349
  if (flow.statusType !== 'storedCache') {
353
350
  return null;
354
351
  }
352
+ const targetObjectType =
353
+ typeof flow.objType === 'string'
354
+ ? flow.objType
355
+ : flow.objType?.objectType;
355
356
  const candidates = objects.filter(
356
- (object) => object.objectType === flow.objType?.objectType
357
+ (object) => object.objectType === targetObjectType
357
358
  );
358
359
  let object;
359
360
  if (flow.objType?.serviceTag) {
@@ -373,7 +374,14 @@ export function storedCacheTableName(flow, objects) {
373
374
  if (targetResource?.tableName) {
374
375
  return targetResource.tableName;
375
376
  }
376
- return null;
377
+ const flowTag = flow.flowTag || 'unknown';
378
+ const targetObj = JSON.stringify(flow.objType);
379
+ console.error(
380
+ `[ERROR] [FlowSchema Validation] Flow '${flowTag}' has statusType 'storedCache' but cannot resolve DynamoDB tableName from objType ${targetObj}`
381
+ );
382
+ throw new Error(
383
+ `FlowSchema validation error: Flow '${flowTag}' has statusType 'storedCache' but cannot resolve DynamoDB tableName from objType ${targetObj}. Ensure the ObjectSchema exists and defines a DynamoDB storage resource with tableName.`
384
+ );
377
385
  }
378
386
 
379
387
  export async function renderStepMain(functionName, startConfig = null) {
@@ -14,11 +14,20 @@ export function supportedCustomFlows(flows) {
14
14
  throw new TypeError('flows must be an array');
15
15
  }
16
16
  return flows.filter((flow) => {
17
- if (typeof flow?.flowTag !== 'string' || flow.flowTag === '') {
18
- return false;
17
+ if (typeof flow?.flowTag !== 'string' || flow.flowTag.trim() === '') {
18
+ throw new Error(
19
+ 'FlowSchema validation error: flowTag is required and must be a non-empty string'
20
+ );
19
21
  }
20
- if (!Array.isArray(flow.event)) {
21
- return false;
22
+ if (!Array.isArray(flow.event) || flow.event.length === 0) {
23
+ throw new Error(
24
+ `FlowSchema validation error: Flow '${flow.flowTag}' requires a non-empty 'event' array`
25
+ );
26
+ }
27
+ if (flow.event.includes('generateCode') && flow.event.length > 1) {
28
+ throw new Error(
29
+ `FlowSchema validation error: Flow '${flow.flowTag}' with event 'generateCode' must be a standalone event and cannot co-exist with other events`
30
+ );
22
31
  }
23
32
  const hasSupportedEvent = flow.event.some((e) => SUPPORTED_EVENTS.has(e));
24
33
  if (!hasSupportedEvent) {
@@ -28,9 +37,14 @@ export function supportedCustomFlows(flows) {
28
37
  if (flowHasOwnTopic(flow)) {
29
38
  return false;
30
39
  }
31
- if (!flow.generatedCodeConfig.codeHookTag) {
40
+ if (flow.event.includes('dsq')) {
32
41
  return false;
33
42
  }
43
+ if (!flow.generatedCodeConfig.codeHookTag) {
44
+ throw new Error(
45
+ `FlowSchema validation error: Flow '${flow.flowTag}' has generatedCodeConfig without ownTopic, which requires 'codeHookTag' ('before' | 'after')`
46
+ );
47
+ }
34
48
  }
35
49
  return true;
36
50
  });
@@ -67,6 +81,11 @@ export function flowExtTopicSteps(flows) {
67
81
  }
68
82
  const hasTriggerFlowTypes =
69
83
  Array.isArray(flow.triggerFlowTypes) && flow.triggerFlowTypes.length > 0;
84
+ if (flow.statusType === 'triggerCache' && !hasTriggerFlowTypes) {
85
+ throw new Error(
86
+ `FlowSchema validation error: Flow '${flow.flowTag}' has statusType 'triggerCache' but requires a non-empty 'triggerFlowTypes' array`
87
+ );
88
+ }
70
89
  const steps = flow.flowSteps ?? [];
71
90
  const hasCheckStep = steps.some(
72
91
  (step) =>
@@ -87,7 +106,9 @@ export function flowExtTopicSteps(flows) {
87
106
  }
88
107
  const isCheckStep = step.stepName.toLowerCase().includes('checktrigger');
89
108
  if (isCheckStep && !hasTriggerFlowTypes) {
90
- return [];
109
+ throw new Error(
110
+ `FlowSchema validation error: Flow '${flow.flowTag}' defines step '${step.stepName}' but requires a non-empty 'triggerFlowTypes' array`
111
+ );
91
112
  }
92
113
  return [
93
114
  {