@izara_project/izara-core-generate-service-code 1.0.100 → 1.0.102

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 (26) hide show
  1. package/package.json +1 -1
  2. package/src/emit/application/crud/templates/CreateLogicalLib.ejs +6 -0
  3. package/src/emit/application/custom/customFlowS3.js +226 -6
  4. package/src/emit/application/custom/customFlows.js +22 -14
  5. package/src/emit/application/custom/emitCustomFlowApplicationArtifacts.js +5 -4
  6. package/src/emit/application/custom/templates/s3/ConfirmComplete_HdrSqs.ejs +1 -0
  7. package/src/emit/application/custom/templates/s3/ConfirmComplete_Main.ejs +28 -17
  8. package/src/emit/application/custom/templates/s3/ProcessS3File_Main.ejs +234 -23
  9. package/src/emit/application/custom/templates/s3/Start_HdrWbs.ejs +65 -0
  10. package/src/emit/application/custom/templates/s3/Start_Main.ejs +40 -17
  11. package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_HdrSqs.ejs → legacy/ConfirmAfterUpload_HdrSqs.ejs} +1 -0
  12. package/src/emit/application/custom/templates/s3/{ConfirmAfterUpload_Main.ejs → legacy/ConfirmAfterUpload_Main.ejs} +1 -1
  13. package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +1 -1
  14. package/src/emit/infrastructure/resources/dynamoResourceArtifacts.js +6 -2
  15. package/src/plan/domainRegistry.js +2 -1
  16. package/test/unit/application/createTargetRoleHandler.test.js +1 -1
  17. package/test/unit/application/customFlowS3.test.js +284 -2
  18. package/test/unit/infrastructure/resourceArtifacts.test.js +4 -0
  19. /package/src/emit/application/custom/templates/s3/{ConfirmReserved_HdrSqs.ejs → legacy/ConfirmReserved_HdrSqs.ejs} +0 -0
  20. /package/src/emit/application/custom/templates/s3/{ConfirmReserved_Main.ejs → legacy/ConfirmReserved_Main.ejs} +0 -0
  21. /package/src/emit/application/custom/templates/s3/{CreateObject_HdrWbs.ejs → legacy/CreateObject_HdrWbs.ejs} +0 -0
  22. /package/src/emit/application/custom/templates/s3/{CreateObject_Main.ejs → legacy/CreateObject_Main.ejs} +0 -0
  23. /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_HdrSqs.ejs → legacy/CreatePreSignUrl_HdrSqs.ejs} +0 -0
  24. /package/src/emit/application/custom/templates/s3/{CreatePreSignUrl_Main.ejs → legacy/CreatePreSignUrl_Main.ejs} +0 -0
  25. /package/src/emit/application/custom/templates/s3/{ReservedLimit_HdrSqs.ejs → legacy/ReservedLimit_HdrSqs.ejs} +0 -0
  26. /package/src/emit/application/custom/templates/s3/{ReservedLimit_Main.ejs → legacy/ReservedLimit_Main.ejs} +0 -0
@@ -26,6 +26,12 @@ import snsSharedLib from '@izara_project/izara-core-library-sns';
26
26
  import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
27
27
  import lambdaSharedLib from '@izara_project/izara-core-library-lambda';
28
28
  import { sns } from '@izara_project/izara-core-library-external-request';
29
+ import { consts } from '@izara_project/izara-middleware';
30
+ <% if (isSimplifiedS3) { -%>
31
+ import dynamodbSharedLib from '@izara_project/izara-core-library-dynamodb';
32
+ import { utils } from '@izara_project/izara-core-library-service-schemas';
33
+ const { createFlowTypeConcat } = utils;
34
+ <% } -%>
29
35
 
30
36
  //(<optionalRequired>)
31
37
  //(</optionalRequired>)
@@ -33,7 +39,8 @@ import { sns } from '@izara_project/izara-core-library-external-request';
33
39
  /**
34
40
  * Step 2: ProcessAfterUpload (Deferred-Node S3 Pipeline - ADR-016)
35
41
  * Triggered by S3 ObjectCreated event. Inspects file, prepares entity payload,
36
- * and publishes to Create_In SNS topic with callingFlow targeting ConfirmAfterUpload.
42
+ * and publishes to Create_In SNS topic with callingFlow
43
+ * targeting ConfirmAfterUpload.
37
44
  *
38
45
  * @param {Object} _izContext
39
46
  * @param {Object} bucket - S3 bucket record
@@ -50,7 +57,15 @@ export default async function <%- functionName %>(
50
57
  passBackProperties = {}
51
58
  ) {
52
59
  try {
53
- _izContext.logger.debug('----- <%- functionName %> started -----', {
60
+ _izContext.logger.info(
61
+ '----- <%- functionName %> started -----',
62
+ {
63
+ bucketName: bucket.name,
64
+ key: field.key,
65
+ size: field.size
66
+ }
67
+ );
68
+ _izContext.logger.debug('<%- functionName %> incoming parameters:', {
54
69
  bucket,
55
70
  field,
56
71
  passBackProperties
@@ -58,8 +73,24 @@ export default async function <%- functionName %>(
58
73
 
59
74
  const s3Key = field.key;
60
75
  const actualByteSize = field.size;
76
+
77
+ if (!s3Key || s3Key.endsWith('/') || actualByteSize === 0) {
78
+ _izContext.logger.warn('Skipping directory marker or 0-byte object:', {
79
+ bucketName: bucket.name,
80
+ key: s3Key,
81
+ size: actualByteSize
82
+ });
83
+ return { status: 'skipped_empty_or_dir', s3Key };
84
+ }
85
+
61
86
  const actualSizeMb = actualByteSize / (1024 * 1024);
62
87
 
88
+ _izContext.logger.debug('S3 file details:', {
89
+ s3Key,
90
+ actualByteSize,
91
+ actualSizeMb
92
+ });
93
+
63
94
  let s3Metadata = {};
64
95
  try {
65
96
  const headResult = await s3.send(
@@ -68,13 +99,19 @@ export default async function <%- functionName %>(
68
99
  Key: s3Key
69
100
  })
70
101
  );
102
+
103
+ _izContext.logger.debug('HeadObjectCommand raw result:', headResult);
71
104
  s3Metadata = headResult.Metadata || {};
105
+ _izContext.logger.info('Parsed S3 Metadata:', { s3Metadata });
72
106
  } catch (headErr) {
73
- _izContext.logger.warn('Could not retrieve S3 object metadata via HeadObjectCommand:', {
74
- bucket: bucket.name,
75
- key: s3Key,
76
- error: headErr.message
77
- });
107
+ _izContext.logger.warn(
108
+ 'Could not retrieve S3 object metadata via HeadObjectCommand:',
109
+ {
110
+ bucket: bucket.name,
111
+ key: s3Key,
112
+ error: headErr.message
113
+ }
114
+ );
78
115
  }
79
116
 
80
117
  //(<extractIdentifiersFromKeyOrMetadata>)
@@ -98,23 +135,164 @@ export default async function <%- functionName %>(
98
135
  <% } -%>
99
136
  //(</extractIdentifiersFromKeyOrMetadata>)
100
137
 
138
+ <% if (isSimplifiedS3) { -%>
139
+ const flowTypeTag = createFlowTypeConcat(_izContext, {
140
+ flowTag: '<%- flowTag %>',
141
+ serviceTag: process.env.iz_serviceTag
142
+ });
143
+ const taskKey = `${flowTypeTag}${rawId}`;
144
+ const taskTableName = await dynamodbSharedLib.tableName(
145
+ _izContext,
146
+ 'WebSocketTask'
147
+ );
148
+
149
+ let resolvedConnectionId =
150
+ s3Metadata.connectionid || s3Metadata.connectionId || '';
151
+ if (!resolvedConnectionId) {
152
+ const queryTasks = await dynamodbSharedLib.query(
153
+ _izContext,
154
+ taskTableName,
155
+ { taskKey }
156
+ );
157
+ if (queryTasks && queryTasks.Items && queryTasks.Items.length > 0) {
158
+ resolvedConnectionId = queryTasks.Items[0].connectionId;
159
+ } else {
160
+ _izContext.logger.warn(
161
+ `[Idempotency] No task record found for <%- objectType %>Id: ${rawId}.` +
162
+ ' Aborting dispatch to Create_In.'
163
+ );
164
+ return {
165
+ status: 'unauthorized_or_unknown_upload',
166
+ s3Key,
167
+ <%- objectType %>Id: rawId
168
+ };
169
+ }
170
+ }
171
+
172
+ try {
173
+ await dynamodbSharedLib.updateItem(
174
+ _izContext,
175
+ taskTableName,
176
+ {
177
+ taskKey,
178
+ connectionId: resolvedConnectionId
179
+ },
180
+ {
181
+ UpdateExpression: 'SET uploadClaimed = :claimed, claimedAt = :now',
182
+ ConditionExpression:
183
+ 'attribute_exists(taskKey) AND attribute_not_exists(uploadClaimed)',
184
+ ExpressionAttributeValues: {
185
+ ':claimed': true,
186
+ ':now': Date.now()
187
+ }
188
+ }
189
+ );
190
+ _izContext.logger.info(
191
+ `[Idempotency] Successfully claimed upload for <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: ${rawId}`
192
+ );
193
+ } catch (claimErr) {
194
+ if (claimErr.name === 'ConditionalCheckFailedException') {
195
+ _izContext.logger.warn(
196
+ `[Idempotency] Duplicate or unauthorized S3 upload detected for` +
197
+ ` <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: ${rawId}. Aborting dispatch to Create_In.`
198
+ );
199
+ return {
200
+ status: 'duplicate_upload_ignored',
201
+ s3Key,
202
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
203
+ };
204
+ }
205
+ _izContext.logger.error(
206
+ '[Idempotency] Failed to execute atomic claim on WebSocketTask:',
207
+ claimErr
208
+ );
209
+ throw claimErr;
210
+ }
211
+ <% } -%>
212
+
101
213
  //(<endpointHook>)
102
214
  //(</endpointHook>)
103
215
 
104
216
  <% if (typeof nextStepFunctionName === 'string' && nextStepFunctionName) { -%>
105
- const metaUserId =
106
- s3Metadata.submittedbyuserid ||
107
- s3Metadata.submittedByUserId ||
108
- s3Metadata.userid ||
109
- s3Metadata.userId ||
110
- _izContext.userId ||
111
- '';
217
+ const metaUserId = s3Metadata.submittedbyuserid || '';
218
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
219
+ <% for (const rel of flowRelationships) { -%>
220
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
221
+ <% const metaKey = targetIdKey.toLowerCase(); -%>
222
+ const meta<%- targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1) %> = s3Metadata.<%- metaKey %> || '';
223
+ <% } -%>
224
+ <% } -%>
225
+
226
+ _izContext.logger.info('Extracted identifiers and metadata:', {
227
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
228
+ parentId,
229
+ submittedByUserId: metaUserId,
230
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
231
+ <% for (const rel of flowRelationships) { -%>
232
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
233
+ <%- targetIdKey %>: meta<%- targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1) %>,
234
+ <% } -%>
235
+ <% } -%>
236
+ fileName: fileNameWithExt
237
+ });
238
+
239
+ if (metaUserId) {
240
+ _izContext.correlationIds?.set(consts.SUBMITTED_BY_USER_ID, metaUserId);
241
+ }
242
+
243
+ //(<relationshipsHook>)
244
+ let relationships = [];
245
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
246
+ <% for (const rel of flowRelationships) { -%>
247
+ <% const targetIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
248
+ <% const targetObj = rel.targetObjType || {}; -%>
249
+ <% const relTypeObj = rel.relType || {}; -%>
250
+ <% const relDir = rel.relationshipDirection || 'to'; -%>
251
+ <% const varName = `meta${targetIdKey.charAt(0).toUpperCase() + targetIdKey.slice(1)}`; -%>
252
+ if (<%- varName %>) {
253
+ relationships.push({
254
+ targetObjType: {
255
+ objectType: '<%- targetObj.objectType %>',
256
+ serviceTag: '<%- targetObj.serviceTag %>'
257
+ },
258
+ targetIdentifiers: {
259
+ <%- targetIdKey %>: <%- varName %>
260
+ },
261
+ relType: {
262
+ relationshipTag: '<%- relTypeObj.relationshipTag %>',
263
+ serviceTag: '<%- relTypeObj.serviceTag %>'
264
+ },
265
+ relationshipProperties: <%- JSON.stringify(rel.relationshipProperties || {}) %>,
266
+ relationshipDirection: '<%- relDir %>'
267
+ });
268
+ _izContext.logger.info('Built relationship for <%- targetObj.objectType %>:', {
269
+ relationships
270
+ });
271
+ } else {
272
+ _izContext.logger.warn(
273
+ '<%- varName %> is empty! ' +
274
+ 'No <%- relTypeObj.relationshipTag %> relationship attached.'
275
+ );
276
+ }
277
+ <% } -%>
278
+ <% } -%>
279
+ //(</relationshipsHook>)
280
+
112
281
  let messageObject = {
113
282
  objectType: '<%- objectType %>',
114
283
  submittedByUserId: metaUserId,
284
+ <% if (typeof identifierKeys !== 'undefined' && identifierKeys && identifierKeys.length > 1) { -%>
115
285
  identifiers: {
116
- <%- objectType %>Id: rawId
286
+ <%- identifierKeys[0] %>: rawId,
287
+ <% for (let i = 1; i < identifierKeys.length; i++) { -%>
288
+ <%- identifierKeys[i] %>: <%- identifierKeys[i] %><%- i < identifierKeys.length - 1 ? ',' : '' %>
289
+ <% } -%>
117
290
  },
291
+ <% } else { -%>
292
+ identifiers: {
293
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
294
+ },
295
+ <% } -%>
118
296
  <% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 1) { -%>
119
297
  targetIdentifiers: {
120
298
  <%- parentIdKeys[0] %>: parentId,
@@ -127,37 +305,69 @@ export default async function <%- functionName %>(
127
305
  <%- parentIdKey %>: parentId
128
306
  },
129
307
  <% } -%>
308
+ relationships,
130
309
  fieldNames: {
310
+ <% if (typeof identifierKeys !== 'undefined' && identifierKeys && identifierKeys.length > 1) { -%>
311
+ <%- identifierKeys[0] %>: rawId,
312
+ <% for (let i = 1; i < identifierKeys.length; i++) { -%>
313
+ <%- identifierKeys[i] %>: <%- identifierKeys[i] %>,
314
+ <% } -%>
315
+ <% } else { -%>
316
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
317
+ <% } -%>
131
318
  //(<fieldNamesHook>)
132
319
  //(</fieldNamesHook>)
133
320
  }
134
321
  };
135
322
 
323
+ const confirmCallbackFunction =
324
+ await lambdaSharedLib.lambdaFunctionName(
325
+ _izContext,
326
+ '<%- nextStepFunctionName %>'
327
+ );
328
+ _izContext.logger.debug('Resolved confirm callback function:', {
329
+ confirmCallbackFunction
330
+ });
331
+
136
332
  messageObject =
137
333
  callingFlowSharedLib.addCallingFlowToSnsRequestMessageObject(
138
334
  messageObject,
139
335
  callingFlowSharedLib.addParentCallingFlowConfig(
140
336
  passBackProperties,
141
337
  callingFlowSharedLib.createCallingFlowConfig(
142
- await lambdaSharedLib.lambdaFunctionName(
143
- _izContext,
144
- '<%- nextStepFunctionName %>'
145
- ),
146
- {}
338
+ confirmCallbackFunction,
339
+ { rawId: rawId, <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId }
147
340
  )
148
341
  )
149
342
  );
150
343
 
344
+ const topicArn =
345
+ snsSharedLib.snsTopicArnByFlowSchema(_izContext, 'Create_In');
346
+
151
347
  let sendMessageToCreateEndpoint = {
152
348
  Message: JSON.stringify(messageObject),
153
- TopicArn: snsSharedLib.snsTopicArnByFlowSchema(_izContext, 'Create_In')
349
+ TopicArn: topicArn
154
350
  };
155
351
 
352
+ _izContext.logger.info('Publishing to Create_In SNS topic:', {
353
+ topicArn,
354
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId,
355
+ parentId<%- (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) ? flowRelationships.map(r => { const k = r.targetIdentifierKey || `${r.targetObjType.objectType}Id`; return `,\n ${k}: meta${k.charAt(0).toUpperCase() + k.slice(1)}`; }).join('') : '' %>
356
+ });
156
357
  _izContext.logger.debug(
157
- 'sendMessageToCreateEndpoint',
358
+ 'Full Create_In message payload:',
359
+ sendMessageToCreateEndpoint
360
+ );
361
+
362
+ const publishResult = await sns.publishAsync(
363
+ _izContext,
158
364
  sendMessageToCreateEndpoint
159
365
  );
160
- await sns.publishAsync(_izContext, sendMessageToCreateEndpoint);
366
+ _izContext.logger.info('Successfully published to Create_In SNS:', {
367
+ publishResult,
368
+ s3Key,
369
+ <%- (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id` %>: rawId
370
+ });
161
371
 
162
372
  return {
163
373
  status: 'dispatched',
@@ -175,3 +385,4 @@ export default async function <%- functionName %>(
175
385
  throw err;
176
386
  }
177
387
  }
388
+
@@ -0,0 +1,65 @@
1
+ /*
2
+ Copyright (C) 2026 Sven Mason <http://izara.io>
3
+
4
+ This program is free software: you can redistribute it and/or modify
5
+ it under the terms of the GNU Affero General Public License as
6
+ published by the Free Software Foundation, either version 3 of the
7
+ License, or (at your option) any later version.
8
+
9
+ This program is distributed in the hope that it will be useful,
10
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ GNU Affero General Public License for more details.
13
+
14
+ You should have received a copy of the GNU Affero General Public License
15
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ */
17
+
18
+ import {
19
+ middlewareHandler as middleware,
20
+ response
21
+ } from '@izara_project/izara-middleware';
22
+ import callingFlowSharedLib from '@izara_project/izara-core-library-calling-flow';
23
+ import { webSocket } from '@izara_project/izara-core-library-external-request';
24
+ const { postToConnection } = webSocket;
25
+
26
+ import <%= functionName %> from './<%= functionName %>_Main.js';
27
+
28
+ export const main = middleware.wrap(async (event, context) => {
29
+ event._izContext.logger.debug('Event:', event);
30
+
31
+ try {
32
+ if (event.requestContext) {
33
+ const route = event.requestContext.routeKey;
34
+
35
+ switch (route) {
36
+ case '<%- route %>':
37
+ console.log('event in route webSocket', event);
38
+ let eventParams = JSON.parse(event.body);
39
+
40
+ let callingFlow = eventParams?.callingFlow
41
+ ? eventParams.callingFlow
42
+ : {};
43
+ await <%= functionName %>(
44
+ event._izContext,
45
+ eventParams,
46
+ callingFlowSharedLib.addCallingFlowToPassOnProperties(callingFlow)
47
+ //(<additionalParams>)
48
+ //(</additionalParams>)
49
+ );
50
+
51
+ break;
52
+ }
53
+ } else {
54
+ const connId = event.connId;
55
+ await postToConnection(event._izContext, { message: event.message }, connId);
56
+ }
57
+
58
+ return response.webSocketSuccess();
59
+ } catch (err) {
60
+ const connId = event.requestContext.connectionId;
61
+ event._izContext.logger.error('Error, WebSocket: ', err);
62
+ await postToConnection(event._izContext, { message: err.message }, connId);
63
+ return response.failure(err);
64
+ }
65
+ });
@@ -15,7 +15,7 @@ You should have received a copy of the GNU Affero General Public License
15
15
  along with this program. If not, see <http://www.gnu.org/licenses/>.
16
16
  */
17
17
 
18
- import { v7 as uuidv7 } from 'uuid';
18
+ import { identifierUuid } from '@izara_project/izara-shared-core';
19
19
  import { createPresignedPost } from '@aws-sdk/s3-presigned-post';
20
20
  import { S3Client } from '@aws-sdk/client-s3';
21
21
  import { consts } from '@izara_project/izara-middleware';
@@ -99,7 +99,8 @@ export default async function <%- functionName %>(
99
99
  }
100
100
  <% } -%>
101
101
 
102
- const <%- objectType %>Id = uuidv7();
102
+ <% const targetIdName = (typeof identifierKey !== 'undefined' && identifierKey) ? identifierKey : `${objectType}Id`; -%>
103
+ const <%- targetIdName %> = identifierUuid();
103
104
 
104
105
  <% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
105
106
  const targetParentId = requestParams.targetIdentifiers?.<%- parentIdKeys[0] %>;
@@ -123,6 +124,13 @@ export default async function <%- functionName %>(
123
124
  //(<createKeyForUploadFile>)
124
125
  let importType = '<%- defaultImportType %>';
125
126
  let uploadPath = '';
127
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
128
+ <% for (const rel of flowRelationships) { -%>
129
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
130
+ const <%- relIdKey %> =
131
+ requestParams.fieldNames?.<%- relIdKey %> || requestParams.file?.<%- relIdKey %>;
132
+ <% } -%>
133
+ <% } -%>
126
134
  //(</createKeyForUploadFile>)
127
135
  const Key = `<%- s3KeyPrefix %>${uploadPath}`;
128
136
  const maximumSize = maxFileSizeMb * 1048576;
@@ -132,25 +140,39 @@ export default async function <%- functionName %>(
132
140
  Key: Key,
133
141
  Conditions: [
134
142
  ['content-length-range', 0, maximumSize],
135
- ['eq', '$x-amz-meta-<%- objectType %>Id', <%- objectType %>Id],
143
+ ['eq', '$x-amz-meta-<%- targetIdName %>', <%- targetIdName %>],
136
144
  <% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
137
145
  <% for (const key of parentIdKeys) { -%>
138
146
  ['eq', '$x-amz-meta-<%- key %>', target<%- key.charAt(0).toUpperCase() + key.slice(1) %>],
139
147
  <% } -%>
140
148
  <% } -%>
141
149
  ['starts-with', '$x-amz-meta-submittedByUserId', ''],
142
- ['starts-with', '$Content-Type', '<%- contentTypePrefix %>']
150
+ ['starts-with', '$Content-Type', '<%- contentTypePrefix %>'],
151
+ ['starts-with', '$x-amz-meta-connectionId', ''],
152
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
153
+ <% for (const rel of flowRelationships) { -%>
154
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
155
+ ['starts-with', '$x-amz-meta-<%- relIdKey %>', ''],
156
+ <% } -%>
157
+ <% } -%>
143
158
  //(<conditionCreateUrlLink>)
144
159
  //(</conditionCreateUrlLink>)
145
160
  ],
146
161
  Fields: {
147
- 'x-amz-meta-<%- objectType %>Id': <%- objectType %>Id,
162
+ 'x-amz-meta-<%- targetIdName %>': <%- targetIdName %>,
148
163
  <% if (typeof parentIdKeys !== 'undefined' && parentIdKeys && parentIdKeys.length > 0) { -%>
149
164
  <% for (const key of parentIdKeys) { -%>
150
165
  'x-amz-meta-<%- key %>': target<%- key.charAt(0).toUpperCase() + key.slice(1) %>,
151
166
  <% } -%>
152
167
  <% } -%>
153
168
  'x-amz-meta-submittedByUserId': submittedByUserId,
169
+ 'x-amz-meta-connectionId': connectionId || 'NONE',
170
+ <% if (typeof flowRelationships !== 'undefined' && Array.isArray(flowRelationships) && flowRelationships.length > 0) { -%>
171
+ <% for (const rel of flowRelationships) { -%>
172
+ <% const relIdKey = rel.targetIdentifierKey || `${rel.targetObjType.objectType}Id`; -%>
173
+ 'x-amz-meta-<%- relIdKey %>': <%- relIdKey %> || '',
174
+ <% } -%>
175
+ <% } -%>
154
176
  //(<fieldCreateUrlLink>)
155
177
  //(</fieldCreateUrlLink>)
156
178
  },
@@ -162,24 +184,25 @@ export default async function <%- functionName %>(
162
184
 
163
185
  const uploadUrl = await createPresignedPost(client, createPreSignUrlParam);
164
186
 
165
- if (connectionId) {
166
- await dynamodbSharedLib.putItem(
167
- _izContext,
168
- await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
169
- {
170
- taskKey: `${createFlowTypeConcat(_izContext, { flowTag: '<%- flowTag %>', serviceTag: process.env.iz_serviceTag })}${<%- objectType %>Id}`,
171
- connectionId: connectionId,
172
- expirationTime: Math.floor(Date.now() / 1000) + 7200
173
- }
174
- );
187
+ const resolvedConnectionId = connectionId || 'NONE';
188
+ await dynamodbSharedLib.putItem(
189
+ _izContext,
190
+ await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
191
+ {
192
+ taskKey: `${createFlowTypeConcat(_izContext, { flowTag: '<%- flowTag %>', serviceTag: process.env.iz_serviceTag })}${<%- targetIdName %>}`,
193
+ connectionId: resolvedConnectionId,
194
+ expirationTime: Math.floor(Date.now() / 1000) + 7200
195
+ }
196
+ );
175
197
 
198
+ if (connectionId) {
176
199
  await postToConnection(
177
200
  _izContext,
178
201
  {
179
202
  status: 'success',
180
203
  data: {
181
204
  uploadUrl: uploadUrl,
182
- <%- objectType %>Id: <%- objectType %>Id
205
+ <%- targetIdName %>: <%- targetIdName %>
183
206
  }
184
207
  },
185
208
  connectionId
@@ -188,7 +211,7 @@ export default async function <%- functionName %>(
188
211
 
189
212
  return {
190
213
  uploadUrl: uploadUrl,
191
- <%- objectType %>Id: <%- objectType %>Id
214
+ <%- targetIdName %>: <%- targetIdName %>
192
215
  };
193
216
  } catch (err) {
194
217
  _izContext.logger.error('Error in <%- functionName %>:', err);
@@ -95,6 +95,7 @@ export const main = middleware.wrap(async (event, context) => {
95
95
  let returnPromise = await recordHandlerSharedLib.recordHandler(
96
96
  record,
97
97
  <%= functionName %>,
98
+ '<%= functionName %><%= handlerType %>',
98
99
  passOnProperties
99
100
  );
100
101
 
@@ -64,7 +64,7 @@ export default async function <%- functionName %>(
64
64
  { taskKey: taskKey }
65
65
  );
66
66
 
67
- if (taskRecord && taskRecord.connectionId) {
67
+ if (taskRecord && taskRecord.connectionId && taskRecord.connectionId !== 'NONE') {
68
68
  await postToConnection(
69
69
  _izContext,
70
70
  {
@@ -7,7 +7,7 @@
7
7
  - stream:
8
8
  type: dynamodb
9
9
  arn:
10
- Fn::GetAtt: [<%= tableName %>Table, StreamArn]
10
+ Fn::ImportValue: ${self:custom.iz_resourcePrefix}<%= tableName %>StreamArn
11
11
  batchSize: <%= batchSize %>
12
12
  startingPosition: <%= startingPosition %>
13
13
  <% if (Array.isArray(filterPatterns) && filterPatterns.length > 0) { -%>
@@ -11,8 +11,12 @@ export async function renderDynamo(state) {
11
11
  )
12
12
  ]);
13
13
  const indentedTables = tables.map((t) => indentYaml(t, 2)).join('\n');
14
+ let content = system;
14
15
  if (indentedTables) {
15
- return `${system}\n${indentedTables}`;
16
+ content = `${content}\n${indentedTables}`;
16
17
  }
17
- return `${system}`;
18
+ if (state.selfRbac) {
19
+ content = `${content}\n\nOutputs:\n RunningTotalReservedUsageStreamArn:\n Value:\n Fn::GetAtt: [RunningTotalReservedUsageTable, StreamArn]\n Export:\n Name: \${self:custom.iz_resourcePrefix}RunningTotalReservedUsageStreamArn\n`;
20
+ }
21
+ return content;
18
22
  }
@@ -145,7 +145,8 @@ export async function resolveEmissionGroups(context) {
145
145
  ]);
146
146
 
147
147
  const custom = await emitCustomFlowApplicationArtifacts(flows, {
148
- objects
148
+ objects,
149
+ relationships
149
150
  });
150
151
 
151
152
  let systemFunctions = [];
@@ -332,7 +332,7 @@ test('generates synchronous HdrInv and HdrApi handlers for FlowLimit flows', asy
332
332
  /ReservedUsageTtlExpiredHdrDss:\n\s+handler: src\/generatedCode\/FlowLimit\/ReservedUsageTtlExpired\/source\/ReservedUsageTtlExpired_HdrDss\.main/
333
333
  );
334
334
  assert.match(limitYaml, /type: dynamodb/);
335
- assert.match(limitYaml, /RunningTotalReservedUsageTable/);
335
+ assert.match(limitYaml, /RunningTotalReservedUsageStreamArn/);
336
336
  assert.match(limitYaml, /eventName: \["REMOVE"\]/);
337
337
  });
338
338