@izara_project/izara-core-generate-service-code 1.0.81 → 1.0.83

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 (30) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +23 -0
  3. package/src/compile/loadSchemas.js +5 -0
  4. package/src/emit/crud/crudActionPlan.js +7 -4
  5. package/src/emit/crud/emitDynamicCrudFlowSchemas.js +1 -1
  6. package/src/emit/crud/templates/CreateEndpoint_Main.ejs +28 -5
  7. package/src/emit/crud/templates/DeleteEndpoint_Main.ejs +5 -2
  8. package/src/emit/crud/templates/FlowEndpoint_HdrWbs.ejs +2 -2
  9. package/src/emit/crud/templates/FlowMain_Wbs.ejs +6 -2
  10. package/src/emit/crud/templates/UpdateEndpoint_Main.ejs +1 -1
  11. package/src/emit/custom/customFlows.js +2 -0
  12. package/src/emit/custom/templates/s3/CreateObject_HdrWbs.ejs +2 -2
  13. package/src/emit/custom/templates/s3/CreatePreSignUrl_Main.ejs +2 -1
  14. package/src/emit/custom/templates/s3/ReservedLimit_Main.ejs +1 -1
  15. package/src/emit/rbac/emitDynamicRbacFlowSchemas.js +2 -2
  16. package/src/emit/relationship/emitDynamicRelationshipFlowSchemas.js +1 -1
  17. package/src/emit/relationship/templates/ChangeRelationship_Main.ejs +9 -2
  18. package/src/emit/resource/emitResourceArtifacts.js +9 -5
  19. package/src/emit/system/emitSystemWebSocketArtifacts.js +2 -1
  20. package/src/emit/system/templates/RegisterComplete_Main.ejs +3 -3
  21. package/src/emit/system/templates/Register_HdrWbs.ejs +2 -2
  22. package/src/emit/system/templates/Register_Main.ejs +4 -4
  23. package/src/emit/system/templates/WebSocketComplete_Main.ejs +1 -1
  24. package/src/emit/systemText/emitSystemTextSchemas.js +10 -9
  25. package/test/crudApplicationArtifacts.test.js +21 -10
  26. package/test/customFlowApplicationArtifacts.test.js +8 -0
  27. package/test/dynamicCrudFlowSchemas.test.js +21 -11
  28. package/test/publicApi.test.js +3 -2
  29. package/test/resourceArtifacts.test.js +2 -2
  30. package/test/systemTextSchemas.test.js +9 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.81",
3
+ "version": "1.0.83",
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": {
@@ -3,6 +3,29 @@ export function validateFlowSchema(flowSchema) {
3
3
  throw new TypeError('flowSchema must be an object');
4
4
  }
5
5
 
6
+ if (flowSchema.initiateBy !== undefined) {
7
+ const validInitiateBy = [
8
+ 'user',
9
+ 'systemWithNoUser',
10
+ 'systemWithUser',
11
+ 'system'
12
+ ];
13
+ if (!validInitiateBy.includes(flowSchema.initiateBy)) {
14
+ throw new Error(
15
+ `FlowSchema validation error: initiateBy must be one of ${JSON.stringify(validInitiateBy)}, got '${flowSchema.initiateBy}'`
16
+ );
17
+ }
18
+ }
19
+
20
+ if (flowSchema.handleObj !== undefined) {
21
+ const validHandleObj = ['one', 'multi'];
22
+ if (!validHandleObj.includes(flowSchema.handleObj)) {
23
+ throw new Error(
24
+ `FlowSchema validation error: handleObj must be one of ${JSON.stringify(validHandleObj)}, got '${flowSchema.handleObj}'`
25
+ );
26
+ }
27
+ }
28
+
6
29
  const steps = flowSchema.flowSteps;
7
30
  if (!Array.isArray(steps) || steps.length === 0) {
8
31
  throw new Error(
@@ -42,6 +42,11 @@ export async function loadLocalSchemaDirectory(project, directoryName) {
42
42
  if (Array.isArray(module.default)) {
43
43
  loadedSchemas = module.default;
44
44
  }
45
+ loadedSchemas = loadedSchemas.filter(
46
+ (schema) =>
47
+ schema && typeof schema === 'object' && Object.keys(schema).length > 0
48
+ );
49
+ if (loadedSchemas.length === 0) continue;
45
50
 
46
51
  if (directoryName === 'FlowSchemas') {
47
52
  for (const schema of loadedSchemas) {
@@ -31,11 +31,14 @@ export function createCrudActionPlan(
31
31
  };
32
32
  }
33
33
 
34
+ const VALID_CRUD_GENERATED_BY = new Set([
35
+ 'user',
36
+ 'systemWithUser',
37
+ 'systemWithNoUser'
38
+ ]);
39
+
34
40
  export function isCrudObject(object) {
35
- return (
36
- object?.generatedBy === 'userGenerated' ||
37
- object?.generatedBy === 'systemGenerated'
38
- );
41
+ return VALID_CRUD_GENERATED_BY.has(object?.generatedBy);
39
42
  }
40
43
 
41
44
  export function hasGraphStorageResource(objects = []) {
@@ -103,7 +103,7 @@ function _createDynamicFlow(action, plan, serviceTag = '') {
103
103
 
104
104
  return {
105
105
  flowTag: action,
106
- initiateBy: 'system',
106
+ initiateBy: 'systemWithNoUser',
107
107
  handleObj: 'one',
108
108
  statusType: 'none',
109
109
  event: plan.outputTopic ? ['ownTopic'] : ['lambdaSyncInv', 'lambdaSyncApi'],
@@ -82,7 +82,10 @@ export default async function <%- flowTag %>(
82
82
 
83
83
  // ── Resolve userId / targetId ─────────────────────────────────────────────
84
84
  let userId, targetId;
85
- if (objectSchema.generatedBy === 'userGenerated') {
85
+ if (
86
+ objectSchema.generatedBy === 'user' ||
87
+ objectSchema.generatedBy === 'systemWithUser'
88
+ ) {
86
89
  userId = _izContext.correlationIds.get(coreConsts.BASE_USER_ID);
87
90
  targetId = _izContext.correlationIds.get(coreConsts.TARGET_ID);
88
91
  if (!userId) errorsFound.push('Not have userId');
@@ -126,14 +129,34 @@ export default async function <%- flowTag %>(
126
129
  );
127
130
  _izContext.logger.debug('errorsFound', errorsFound);
128
131
 
129
- // ── Build field lists ─────────────────────────────────────────────────────
130
- const listOfRequiredOnCreate = [];
131
- const listOfOptionalOnCreate = [];
132
+ // ── Resolve fromRequiredOnCreate fields ──────────────────────────────────
133
+ if (objectSchemas.fieldNames) {
134
+ if (!requestParams.fieldNames) requestParams.fieldNames = {};
135
+ for (const [fieldName, val] of Object.entries(objectSchemas.fieldNames)) {
136
+ if (val.fromRequiredOnCreate && requestParams.fieldNames[fieldName] === undefined) {
137
+ const fromConfig = val.fromRequiredOnCreate;
138
+ const sourceField = fromConfig.fieldName || fieldName;
139
+ if (fromConfig.belongToObjType === true && targetId) {
140
+ requestParams.fieldNames[fieldName] = targetId;
141
+ } else if (fromConfig.relType && requestParams.relationships?.length > 0) {
142
+ const matchedRel = requestParams.relationships.find(
143
+ (rel) =>
144
+ rel.relType?.serviceTag === fromConfig.relType.serviceTag &&
145
+ rel.relType?.relationshipTag === fromConfig.relType.relationshipTag
146
+ );
147
+ if (matchedRel?.targetIdentifiers?.[sourceField] !== undefined) {
148
+ requestParams.fieldNames[fieldName] = matchedRel.targetIdentifiers[sourceField];
149
+ }
150
+ }
151
+ }
152
+ }
153
+ }
132
154
 
133
155
  for (const [key, val] of Object.entries(objectSchemas.fieldNames)) {
134
156
  if (
135
157
  val.randomOnCreate === true ||
136
158
  val.requiredOnCreate === true ||
159
+ val.fromRequiredOnCreate ||
137
160
  val.hashOnCreate?.length
138
161
  ) {
139
162
  listOfRequiredOnCreate.push(key);
@@ -222,7 +245,7 @@ export default async function <%- flowTag %>(
222
245
  }
223
246
 
224
247
  <%_
225
- const rbacObjects = objects.filter(o => (o?.generatedBy === 'userGenerated' || o?.generatedBy === 'systemGenerated') && hasRbacAddOn(o));
248
+ const rbacObjects = objects.filter(o => isCrudObject(o) && hasRbacAddOn(o));
226
249
  const rbacObjectTypes = rbacObjects.map(o => o.objectType);
227
250
  const rbacServiceTagMap = {};
228
251
  for (const o of rbacObjects) {
@@ -75,7 +75,10 @@ export default async function <%- flowTag %>(
75
75
  _izContext.logger.debug('objectSchema', objectSchema);
76
76
 
77
77
  // ── Resolve userId / targetId ─────────────────────────────────────────────
78
- if (objectSchema.generatedBy === 'userGenerated') {
78
+ if (
79
+ objectSchema.generatedBy === 'user' ||
80
+ objectSchema.generatedBy === 'systemWithUser'
81
+ ) {
79
82
  const userId = _izContext.correlationIds.get(coreConsts.BASE_USER_ID);
80
83
  const targetId = _izContext.correlationIds.get(coreConsts.TARGET_ID);
81
84
  if (!userId) errorsFound.push('Not have userId');
@@ -98,7 +101,7 @@ export default async function <%- flowTag %>(
98
101
  );
99
102
 
100
103
  <%_
101
- const rbacObjects = objects.filter(o => (o?.generatedBy === 'userGenerated' || o?.generatedBy === 'systemGenerated') && hasRbacAddOn(o));
104
+ const rbacObjects = objects.filter(o => isCrudObject(o) && hasRbacAddOn(o));
102
105
  const rbacObjectTypes = rbacObjects.map(o => o.objectType);
103
106
  const rbacServiceTagMap = {};
104
107
  for (const o of rbacObjects) {
@@ -116,7 +116,7 @@ export const main = middleware.wrap(async (event, context) => {
116
116
  } else {
117
117
  const connId = event.connId;
118
118
  // await postMessage({ message: event.message }, connId);
119
- await postToConnection({ message: event.message }, connId);
119
+ await postToConnection(event._izContext, { message: event.message }, connId);
120
120
  }
121
121
 
122
122
  return response.webSocketSuccess();
@@ -124,7 +124,7 @@ export const main = middleware.wrap(async (event, context) => {
124
124
  const connId = event.requestContext.connectionId;
125
125
  event._izContext.logger.error('Error, WebSocket: ', err);
126
126
  // await sendErrorMessage({ message: err.message }, connId);
127
- await postToConnection({ message: err.message }, connId);
127
+ await postToConnection(event._izContext, { message: err.message }, connId);
128
128
  return response.failure(err);
129
129
  }
130
130
  });
@@ -70,7 +70,10 @@ export default async function <%- typeof functionName !== 'undefined' ? function
70
70
 
71
71
  _izContext.logger.debug('flowSchema', flowSchema);
72
72
 
73
- if (flowSchema.initiateBy === 'user') {
73
+ if (
74
+ flowSchema.initiateBy === 'user' ||
75
+ flowSchema.initiateBy === 'systemWithUser'
76
+ ) {
74
77
  _izContext.correlationIds.set(coreConsts.USER_AGENT, 'webSocket');
75
78
  }
76
79
 
@@ -78,6 +81,7 @@ export default async function <%- typeof functionName !== 'undefined' ? function
78
81
 
79
82
  if (!flowSchema) {
80
83
  await postToConnection(
84
+ _izContext,
81
85
  {
82
86
  message: {
83
87
  error: `flowTag not found ${requestParams.action}`
@@ -99,7 +103,7 @@ export default async function <%- typeof functionName !== 'undefined' ? function
99
103
  callingFlowSharedLib.createCallingFlowConfig(
100
104
  await lambdaSharedLib.lambdaFunctionName(
101
105
  _izContext,
102
- 'WebSocketComplete'
106
+ 'WebSocketCompleteHdrSqs'
103
107
  ),
104
108
  {}
105
109
  )
@@ -94,7 +94,7 @@ export default async function <%- flowTag %>(
94
94
  );
95
95
 
96
96
  <%_
97
- const rbacObjects = objects.filter(o => (o?.generatedBy === 'userGenerated' || o?.generatedBy === 'systemGenerated') && hasRbacAddOn(o));
97
+ const rbacObjects = objects.filter(o => isCrudObject(o) && hasRbacAddOn(o));
98
98
  const rbacObjectTypes = rbacObjects.map(o => o.objectType);
99
99
  const rbacServiceTagMap = {};
100
100
  for (const o of rbacObjects) {
@@ -23,6 +23,7 @@ export function supportedCustomFlows(flows) {
23
23
  }
24
24
 
25
25
  export function customFlowRoleName(flowTag) {
26
+ if (!flowTag || typeof flowTag !== 'string') return '';
26
27
  return `${upperFirst(flowTag)}Role`;
27
28
  }
28
29
 
@@ -137,6 +138,7 @@ export function generatedDsqFlows(flows) {
137
138
  }
138
139
 
139
140
  export function upperFirst(value) {
141
+ if (typeof value !== 'string' || value.length === 0) return '';
140
142
  return value[0].toUpperCase() + value.slice(1);
141
143
  }
142
144
 
@@ -77,7 +77,7 @@ export const main = middleware.wrap(async (event, context) => {
77
77
  } else {
78
78
  const connId = event.connId;
79
79
  // await postMessage({ message: event.message }, connId);
80
- await postToConnection({ message: event.message }, connId);
80
+ await postToConnection(event._izContext, { message: event.message }, connId);
81
81
  }
82
82
 
83
83
  return response.webSocketSuccess();
@@ -85,7 +85,7 @@ export const main = middleware.wrap(async (event, context) => {
85
85
  const connId = event.requestContext.connectionId;
86
86
  event._izContext.logger.error('Error, WebSocket: ', err);
87
87
  // await sendErrorMessage({ message: err.message }, connId);
88
- await postToConnection({ message: err.message }, connId);
88
+ await postToConnection(event._izContext, { message: err.message }, connId);
89
89
  return response.failure(err);
90
90
  }
91
91
  });
@@ -171,6 +171,7 @@ export default async function createPreSignUrl(
171
171
  await sns.publishAsync(_izContext, sendMessageToCancelReserved);
172
172
 
173
173
  await postToConnection(
174
+ _izContext,
174
175
  { message: 'reserved has reach limit' },
175
176
  connectionId
176
177
  );
@@ -254,7 +255,7 @@ export default async function createPreSignUrl(
254
255
  // calling flow own service
255
256
  await lambdaSharedLib.lambdaFunctionName(
256
257
  _izContext,
257
- `WebSocketComplete`
258
+ `WebSocketCompleteHdrSqs`
258
259
  ),
259
260
  {}
260
261
  )
@@ -89,7 +89,7 @@ export default async function reservedLimit(
89
89
  _izContext.logger.debug('res::', res);
90
90
 
91
91
  if (res.passStatus === false) {
92
- postToConnection({ message: 'static limit over' }, connectionId);
92
+ await postToConnection(_izContext, { message: 'static limit over' }, connectionId);
93
93
  let identifiers = hash({
94
94
  //(<hashIdentifiers>)
95
95
  //(</hashIdentifiers>)
@@ -93,7 +93,7 @@ async function _artifact(flowTag, flow) {
93
93
  function _createSharedRbacFlow(flowTag, event) {
94
94
  return {
95
95
  flowTag,
96
- initiateBy: 'system',
96
+ initiateBy: 'systemWithNoUser',
97
97
  statusType: 'none',
98
98
  event,
99
99
  outputTopic: true,
@@ -119,7 +119,7 @@ function _createUserRbacFlow(flowTag, serviceTag, objectType) {
119
119
  flowTag,
120
120
  objType: { serviceTag, objectType },
121
121
  event: ['ownTopic'],
122
- initiateBy: 'system',
122
+ initiateBy: 'systemWithNoUser',
123
123
  handleObj: 'one',
124
124
  statusType: 'none',
125
125
  outputTopic: true,
@@ -96,7 +96,7 @@ function createDynamicRelationshipFlow(action) {
96
96
 
97
97
  return {
98
98
  flowTag: action,
99
- initiateBy: 'system',
99
+ initiateBy: 'systemWithNoUser',
100
100
  handleObj: 'one',
101
101
  statusType: 'none',
102
102
  event: outputTopic
@@ -191,8 +191,15 @@ export default async function <%- functionName %>(
191
191
  */
192
192
  // ? [Fix]: The legacy logic incorrectly blocked "user" from regular execution and allowed non-user values.
193
193
  // ? It has been corrected to require changeBy === "user" for normal execution.
194
- if (canChangeToRelType.changeBy !== 'user') {
195
- errorsFound.push(`CanChangeToRelType changeBy is not user`);
194
+ const isUserProcess = Boolean(_izContext.correlationIds?.get(consts.BASE_USER_ID));
195
+ if (isUserProcess) {
196
+ if (canChangeToRelType.changeBy !== 'user' && canChangeToRelType.changeBy !== 'systemWithUser') {
197
+ errorsFound.push(`CanChangeToRelType changeBy is not user or systemWithUser`);
198
+ }
199
+ } else {
200
+ if (canChangeToRelType.changeBy !== 'systemWithNoUser' && canChangeToRelType.changeBy !== 'system') {
201
+ errorsFound.push(`CanChangeToRelType changeBy is not systemWithNoUser`);
202
+ }
196
203
  }
197
204
  }
198
205
  });
@@ -333,7 +333,8 @@ function createResourceState({ objects, relationships, flows }) {
333
333
  (flow) =>
334
334
  (flow?.outputTopic ||
335
335
  (includesEvent(flow, 'ownTopic') &&
336
- flow.initiateBy === 'user')) &&
336
+ (flow.initiateBy === 'user' ||
337
+ flow.initiateBy === 'systemWithUser'))) &&
337
338
  upperFirst(flow.flowTag) === topic
338
339
  ) ||
339
340
  (relationships.length > 0 &&
@@ -899,11 +900,14 @@ function keyName(identifier) {
899
900
  );
900
901
  }
901
902
 
903
+ const VALID_CRUD_GENERATED_BY = new Set([
904
+ 'user',
905
+ 'systemWithUser',
906
+ 'systemWithNoUser'
907
+ ]);
908
+
902
909
  function isCrudObject(object) {
903
- return (
904
- object?.generatedBy === 'userGenerated' ||
905
- object?.generatedBy === 'systemGenerated'
906
- );
910
+ return VALID_CRUD_GENERATED_BY.has(object?.generatedBy);
907
911
  }
908
912
  function hasSelfRbacAddOn(object) {
909
913
  return (
@@ -125,7 +125,8 @@ export function hasSystemWebSocket(
125
125
  (flow.event.includes('s3') ||
126
126
  (includeRelationshipOwnTopic &&
127
127
  flow.event.includes('ownTopic') &&
128
- flow.initiateBy === 'user'))
128
+ (flow.initiateBy === 'user' ||
129
+ flow.initiateBy === 'systemWithUser')))
129
130
  )
130
131
  );
131
132
  }
@@ -93,9 +93,9 @@ export default async function registerCompleteMain(
93
93
  await Promise.all(
94
94
  tasks.Items.map(async (task) => {
95
95
  if (!result) {
96
- await postToConnection({ message: 'not have record' }, task.connectionId);
96
+ await postToConnection(_izContext, { message: 'not have record' }, task.connectionId);
97
97
  } else {
98
- await postToConnection({ message: result }, task.connectionId);
98
+ await postToConnection(_izContext, { message: result }, task.connectionId);
99
99
  }
100
100
  })
101
101
  );
@@ -138,7 +138,7 @@ export default async function registerCompleteMain(
138
138
  if (tasks.Items && tasks.Items.length > 0) {
139
139
  await Promise.all(
140
140
  tasks.Items.map(async (task) => {
141
- await postToConnection({ message: task }, task.connectionId);
141
+ await postToConnection(_izContext, { message: task }, task.connectionId);
142
142
  })
143
143
  );
144
144
  }
@@ -42,14 +42,14 @@ export const main = middleware.wrap(async (event, context) => {
42
42
  }
43
43
  } else {
44
44
  const connId = event.connId;
45
- await postToConnection({ message: event.message }, connId);
45
+ await postToConnection(event._izContext, { message: event.message }, connId);
46
46
  }
47
47
  return response.webSocketSuccess();
48
48
  } catch (err) {
49
49
  const connId = event.requestContext?.connectionId;
50
50
  event._izContext.logger.error('Error, WebSocket: ', err);
51
51
  if (connId) {
52
- await postToConnection({ message: err.message }, connId);
52
+ await postToConnection(event._izContext, { message: err.message }, connId);
53
53
  }
54
54
  return response.failure(err);
55
55
  }
@@ -55,7 +55,7 @@ export default async function registerMain(
55
55
  );
56
56
 
57
57
  if (!result) {
58
- await postToConnection({ message: 'not have record' }, connectionId);
58
+ await postToConnection(_izContext, { message: 'not have record' }, connectionId);
59
59
  } else {
60
60
  for (const [fieldName, fieldNameSetting] of Object.entries(
61
61
  objectSchema.fieldNames
@@ -77,7 +77,7 @@ export default async function registerMain(
77
77
  }
78
78
  );
79
79
  } else {
80
- await postToConnection({ message: result }, connectionId);
80
+ await postToConnection(_izContext, { message: result }, connectionId);
81
81
  }
82
82
  }
83
83
  }
@@ -121,10 +121,10 @@ export default async function registerMain(
121
121
  if (tasks.Items && tasks.Items.length > 0) {
122
122
  await Promise.all(
123
123
  tasks.Items.map(async (task) => {
124
- await postToConnection({ message: task }, task.connectionId);
124
+ await postToConnection(_izContext, { message: task }, task.connectionId);
125
125
  })
126
126
  );
127
- await postToConnection({ message: tasks.Items[0] }, connectionId);
127
+ await postToConnection(_izContext, { message: tasks.Items[0] }, connectionId);
128
128
  return tasks.Items[0];
129
129
  } else {
130
130
  await dynamodbSharedLib.putItem(
@@ -76,7 +76,7 @@ export default async function WebSocketComplete(
76
76
  (tasks.Items ?? []).map(async (task) => {
77
77
  //(<beforePostMessage>)
78
78
  //(</beforePostMessage>)
79
- await postToConnection({ message: requestParams }, task.connectionId);
79
+ await postToConnection(_izContext, { message: requestParams }, task.connectionId);
80
80
  await dynamodbSharedLib.deleteItem(
81
81
  _izContext,
82
82
  await dynamodbSharedLib.tableName(_izContext, 'WebSocketTask'),
@@ -84,8 +84,8 @@ export async function emitSystemTextSchemas({
84
84
  }
85
85
 
86
86
  for (const fieldName of fieldNames) {
87
- const nameSpace = `${objectType}_${objServiceTag}_description`;
88
- const systemTextTag = `${fieldName}_${objectType}_${objServiceTag}`;
87
+ const nameSpace = `${objectType}|${objServiceTag}|description`;
88
+ const systemTextTag = `${fieldName}|${objectType}|${objServiceTag}`;
89
89
  const openHook = `//(<${objectType}_${objServiceTag}${fieldName}Hook>)`;
90
90
  const closeHook = `//(</${objectType}_${objServiceTag}${fieldName}Hook>)`;
91
91
  datas.push({
@@ -100,7 +100,7 @@ export async function emitSystemTextSchemas({
100
100
  // 2. Process Notifications
101
101
  for (const notification of safeNotifications) {
102
102
  if (notification?.notificationGrouping && notification?.notificationTag) {
103
- const nameSpace = `sysNotification_${notification.notificationGrouping}`;
103
+ const nameSpace = `sysNotification|${notification.notificationGrouping}`;
104
104
  const systemTextTag = notification.notificationTag;
105
105
  const openHook = `//(<${notification.notificationTag}Hook>)`;
106
106
  const closeHook = `//(</${notification.notificationTag}Hook>)`;
@@ -127,8 +127,8 @@ export async function emitSystemTextSchemas({
127
127
 
128
128
  if (relData?.fieldNames && Object.keys(relData.fieldNames).length > 0) {
129
129
  for (const fieldName of Object.keys(relData.fieldNames)) {
130
- const nameSpace = `${relTag}_${relServiceTag}_description`;
131
- const systemTextTag = `${fieldName}_${relTag}_${relServiceTag}`;
130
+ const nameSpace = `${relTag}|${relServiceTag}|description`;
131
+ const systemTextTag = `${fieldName}|${relTag}|${relServiceTag}`;
132
132
  const openHook = `//(<${relTag}_${relServiceTag}${fieldName}Hook>)`;
133
133
  const closeHook = `//(</${relTag}_${relServiceTag}${fieldName}Hook>)`;
134
134
  datas.push({
@@ -177,8 +177,9 @@ export async function emitSystemTextSchemas({
177
177
  // Dynamic CRUD flows
178
178
  const hasCrudObjects = safeObjects.some(
179
179
  (object) =>
180
- object?.generatedBy === 'userGenerated' ||
181
- object?.generatedBy === 'systemGenerated'
180
+ object?.generatedBy === 'user' ||
181
+ object?.generatedBy === 'systemWithUser' ||
182
+ object?.generatedBy === 'systemWithNoUser'
182
183
  );
183
184
  if (hasCrudObjects) {
184
185
  for (const action of CRUD_ACTIONS) {
@@ -208,8 +209,8 @@ export async function emitSystemTextSchemas({
208
209
  }
209
210
 
210
211
  for (const flowTag of flowTags) {
211
- const nameSpace = `${flowTag}_${serviceTag}`;
212
- const systemTextTag = `errors_${flowTag}_${serviceTag}`;
212
+ const nameSpace = `${flowTag}|${serviceTag}`;
213
+ const systemTextTag = `errors|${flowTag}|${serviceTag}`;
213
214
  const openHook = `//(<${flowTag}_${serviceTag}Hook>)`;
214
215
  const closeHook = `//(</${flowTag}_${serviceTag}Hook>)`;
215
216
  datas.push({
@@ -67,16 +67,27 @@ test('emits CRUD handlers and function YAML from schema data without reading gen
67
67
  ),
68
68
  /checkAllAwaitingStepsFinishedWithReturnParams/
69
69
  );
70
- assert.doesNotMatch(
71
- await readFile(
72
- join(
73
- rootPath,
74
- 'app/src/generatedCode/FlowObjects/Create/source/Create_WbsMain.js'
75
- ),
76
- 'utf8'
70
+ const createWbsMain = await readFile(
71
+ join(
72
+ rootPath,
73
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_WbsMain.js'
77
74
  ),
75
+ 'utf8'
76
+ );
77
+ assert.doesNotMatch(
78
+ createWbsMain,
78
79
  /from '@izara_project\/izara-core-generate-service-code'/
79
80
  );
81
+ assert.match(createWbsMain, /'WebSocketCompleteHdrSqs'/);
82
+ assert.match(createWbsMain, /postToConnection\(\s*_izContext,/);
83
+ const createHdrWbs = await readFile(
84
+ join(
85
+ rootPath,
86
+ 'app/src/generatedCode/FlowObjects/Create/source/Create_HdrWbs.js'
87
+ ),
88
+ 'utf8'
89
+ );
90
+ assert.match(createHdrWbs, /postToConnection\(event\._izContext,/);
80
91
  await assert.rejects(
81
92
  readFile(
82
93
  join(
@@ -269,7 +280,7 @@ test('emits GetRangeHdrApi when an object defines a sortKey identifier', async (
269
280
  const rootPath = await createProject(true);
270
281
  await writeFile(
271
282
  join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
272
- "export default { objectType: 'order', generatedBy: 'userGenerated', identifiers: [{ type: 'identifier', fieldName: 'orderId' }, { type: 'sortKey', fieldName: 'createdAt' }], storageResources: { graph: { storageType: 'graph' } } };\n"
283
+ "export default { objectType: 'order', generatedBy: 'user', identifiers: [{ type: 'identifier', fieldName: 'orderId' }, { type: 'sortKey', fieldName: 'createdAt' }], storageResources: { graph: { storageType: 'graph' } } };\n"
273
284
  );
274
285
  const result = await generateCrudApplicationArtifacts(rootPath, {
275
286
  serviceTag: 'orders'
@@ -341,7 +352,7 @@ async function createProject(withCrudObject) {
341
352
  await writeFile(
342
353
  join(schemaRoot, 'ObjectSchemas/Order.js'),
343
354
  withCrudObject
344
- ? "export default { objectType: 'order', generatedBy: 'userGenerated', storageResources: { graph: { storageType: 'graph' } } };\n"
355
+ ? "export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
345
356
  : "export default { objectType: 'order' };\n"
346
357
  );
347
358
  return rootPath;
@@ -357,7 +368,7 @@ async function createRbacProject(withHook = false, withGraph = false) {
357
368
  await mkdir(join(schemaRoot, 'FlowSchemas'), { recursive: true });
358
369
  await writeFile(
359
370
  join(schemaRoot, 'ObjectSchemas/Order.js'),
360
- `export default { objectType: 'order', generatedBy: 'userGenerated', belongTo: { serviceTag: 'UserAccount', objectType: 'user' }${withGraph ? ", storageResources: { graph: { storageType: 'graph' } }" : ''} };\n`
371
+ `export default { objectType: 'order', generatedBy: 'user', belongTo: { serviceTag: 'UserAccount', objectType: 'user' }${withGraph ? ", storageResources: { graph: { storageType: 'graph' } }" : ''} };\n`
361
372
  );
362
373
  if (withHook) {
363
374
  await writeFile(
@@ -239,6 +239,14 @@ test('emits handlers, function YAML, and IAM roles for supported custom flow eve
239
239
  assert.ok(helperPath, `${path} imports GenerateCodeLibs`);
240
240
  await access(resolve(dirname(filePath), helperPath));
241
241
  }
242
+ const presignMain = await readFile(
243
+ join(
244
+ rootPath,
245
+ 'app/src/generatedCode/FlowSchemas/Upload/source/UploadCreatePreSignUrl_Main.js'
246
+ ),
247
+ 'utf8'
248
+ );
249
+ assert.match(presignMain, /`WebSocketCompleteHdrSqs`/);
242
250
  });
243
251
 
244
252
  test('removes only V2-owned custom-flow application artifacts when supported events are removed', async () => {
@@ -9,7 +9,7 @@ import { generateDynamicCrudFlowSchemas } from '../src/emit/crud/generateDynamic
9
9
 
10
10
  test('generates dynamic CRUD schemas from local schema modules', async () => {
11
11
  const rootPath = await createProject({
12
- generatedBy: 'userGenerated'
12
+ generatedBy: 'user'
13
13
  });
14
14
 
15
15
  const result = await generateDynamicCrudFlowSchemas(rootPath);
@@ -41,7 +41,7 @@ test('generates dynamic CRUD schemas from local schema modules', async () => {
41
41
 
42
42
  test('accepts singleton object exports used by existing schema files', async () => {
43
43
  const rootPath = await createProject({
44
- generatedBy: 'userGenerated',
44
+ generatedBy: 'user',
45
45
  singletonExports: true
46
46
  });
47
47
 
@@ -57,7 +57,7 @@ test('accepts singleton object exports used by existing schema files', async ()
57
57
  });
58
58
 
59
59
  test('does not generate CRUD schemas without an applicable object', async () => {
60
- const rootPath = await createProject({ generatedBy: 'userGenerated' });
60
+ const rootPath = await createProject({ generatedBy: 'user' });
61
61
  await generateDynamicCrudFlowSchemas(rootPath);
62
62
  await writeFile(
63
63
  join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
@@ -71,7 +71,7 @@ test('does not generate CRUD schemas without an applicable object', async () =>
71
71
  });
72
72
 
73
73
  test('does not allow options to override the rootPath argument', async () => {
74
- const rootPath = await createProject({ generatedBy: 'userGenerated' });
74
+ const rootPath = await createProject({ generatedBy: 'user' });
75
75
  const ignoredRootPath = await createProject({ generatedBy: 'external' });
76
76
 
77
77
  const result = await generateDynamicCrudFlowSchemas(rootPath, {
@@ -101,7 +101,7 @@ test('rejects source modules that do not default-export schema objects or arrays
101
101
 
102
102
  test('rejects deprecated FlowHook schemas before generating CRUD schemas', async () => {
103
103
  const rootPath = await createProject({
104
- generatedBy: 'systemGenerated',
104
+ generatedBy: 'systemWithNoUser',
105
105
  flow: createHookFlow(['ownTopic'])
106
106
  });
107
107
 
@@ -121,7 +121,7 @@ test('generates a direct Complete step for RBAC-only Update flows', async () =>
121
121
  join(objectDirectory, 'Order.js'),
122
122
  `export default [{
123
123
  objectType: 'order',
124
- generatedBy: 'userGenerated',
124
+ generatedBy: 'user',
125
125
  belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
126
126
  }];\n`
127
127
  );
@@ -143,13 +143,13 @@ test('generates a direct Complete step for RBAC-only Update flows', async () =>
143
143
 
144
144
  test('ignores RBAC for Create even when Graph storage is present', async () => {
145
145
  const rootPath = await createProject({
146
- generatedBy: 'userGenerated',
146
+ generatedBy: 'user',
147
147
  });
148
148
  await writeFile(
149
149
  join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
150
150
  `export default [{
151
151
  objectType: 'order',
152
- generatedBy: 'userGenerated',
152
+ generatedBy: 'user',
153
153
  belongTo: { serviceTag: 'UserAccount', objectType: 'user' }
154
154
  , storageResources: { graph: { storageType: 'graph' } }
155
155
  }];\n`
@@ -173,7 +173,7 @@ test('uses the renamed RBAC callback step when Graph storage is present', async
173
173
  join(objectDirectory, 'Order.js'),
174
174
  `export default [{
175
175
  objectType: 'order',
176
- generatedBy: 'userGenerated',
176
+ generatedBy: 'user',
177
177
  belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
178
178
  storageResources: { graph: { storageType: 'graph' } }
179
179
  }];\n`
@@ -204,7 +204,7 @@ test('chains RbacCallback and Complete for Update with RBAC and Graph', async ()
204
204
  join(objectDirectory, 'Order.js'),
205
205
  `export default [{
206
206
  objectType: 'order',
207
- generatedBy: 'userGenerated',
207
+ generatedBy: 'user',
208
208
  belongTo: { serviceTag: 'UserAccount', objectType: 'user' },
209
209
  storageResources: { graph: { storageType: 'graph' } }
210
210
  }];\n`
@@ -242,7 +242,7 @@ test('omits Complete step when no graph storage resources exist', async () => {
242
242
  join(objectDirectory, 'Order.js'),
243
243
  `export default [{
244
244
  objectType: 'order',
245
- generatedBy: 'userGenerated',
245
+ generatedBy: 'user',
246
246
  storageResources: {
247
247
  myTable: { storageType: 'dynamoDB', tableName: 'OrderTable' }
248
248
  }
@@ -318,3 +318,13 @@ function readGeneratedFile(rootPath, action) {
318
318
  'utf8'
319
319
  );
320
320
  }
321
+
322
+ test('supports new standard generatedBy values (user, systemWithUser, systemWithNoUser)', async () => {
323
+ for (const generatedBy of ['user', 'systemWithUser', 'systemWithNoUser']) {
324
+ const rootPath = await createProject({ generatedBy });
325
+ await generateDynamicCrudFlowSchemas(rootPath);
326
+ const create = await readGeneratedFlow(rootPath, 'Create');
327
+ assert.ok(create, `Create flow should be generated for generatedBy: ${generatedBy}`);
328
+ assert.deepEqual(create.flowSteps.map((s) => s.stepName), ['Start', 'Complete']);
329
+ }
330
+ });
@@ -33,7 +33,7 @@ test('generate commits schemas, application artifacts, and resources in one publ
33
33
  ),
34
34
  'utf8'
35
35
  ),
36
- /nameSpace:\s*"Create_/
36
+ /nameSpace:\s*"Create\|/
37
37
  );
38
38
  assert.match(
39
39
  await readFile(
@@ -203,6 +203,7 @@ test('generate commits the system WebSocket completion path for own-topic flows'
203
203
  );
204
204
  assert.match(webSocketComplete, /\(\<createIdentifiersFlow\>\)/);
205
205
  assert.match(webSocketComplete, /\(\<beforePostMessage\>\)/);
206
+ assert.match(webSocketComplete, /postToConnection\(_izContext,/);
206
207
  const webSocketHandler = await readFile(
207
208
  join(
208
209
  rootPath,
@@ -345,7 +346,7 @@ async function createCrudProject() {
345
346
  await mkdir(objectDirectory, { recursive: true });
346
347
  await writeFile(
347
348
  join(objectDirectory, 'Order.js'),
348
- "export default { objectType: 'order', generatedBy: 'userGenerated', storageResources: { graph: { storageType: 'graph' } } };\n"
349
+ "export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
349
350
  );
350
351
  return rootPath;
351
352
  }
@@ -250,7 +250,7 @@ test('emits DynamoDB table with number sortKey attribute type when field type is
250
250
  join(rootPath, 'app/src/schemas/ObjectSchemas/CartOrder.js'),
251
251
  "export default { " +
252
252
  "objectType: 'cartOrder', " +
253
- "generatedBy: 'userGenerated', " +
253
+ "generatedBy: 'user', " +
254
254
  "fieldNames: { " +
255
255
  " cartId: { type: 'string' }, " +
256
256
  " sortId: { type: 'number' } " +
@@ -329,7 +329,7 @@ async function createProject() {
329
329
  );
330
330
  await writeFile(
331
331
  join(schemas, 'ObjectSchemas/Order.js'),
332
- "export default { objectType: 'order', generatedBy: 'userGenerated', addOnDataStructure: [{ type: 'rbac' }], identifiers: [{ type: 'partitionKey', fieldName: 'orderId' }], storageResources: { main: { storageType: 'dynamoDB', tableName: 'Orders' } } };\n"
332
+ "export default { objectType: 'order', generatedBy: 'user', addOnDataStructure: [{ type: 'rbac' }], identifiers: [{ type: 'partitionKey', fieldName: 'orderId' }], storageResources: { main: { storageType: 'dynamoDB', tableName: 'Orders' } } };\n"
333
333
  );
334
334
  await writeFile(
335
335
  join(schemas, 'RelationshipSchemas/OrderCustomer.js'),
@@ -63,32 +63,32 @@ test('emitSystemTextSchemas produces expected structure for objects, flows, rela
63
63
 
64
64
  const content = artifacts[0].content;
65
65
  // Object fields
66
- assert.match(content, /nameSpace:\s*"product_MyService_description"/);
66
+ assert.match(content, /nameSpace:\s*"product\|MyService\|description"/);
67
67
  assert.match(
68
68
  content,
69
- /systemTextTag:\s*"productId_product_MyService"/
69
+ /systemTextTag:\s*"productId\|product\|MyService"/
70
70
  );
71
71
  assert.match(content, /\/\/\(<product_MyServiceproductIdHook>\)/);
72
72
  assert.match(
73
73
  content,
74
- /systemTextTag:\s*"version_product_MyService"/
74
+ /systemTextTag:\s*"version\|product\|MyService"/
75
75
  );
76
76
 
77
77
  // Notification
78
- assert.match(content, /nameSpace:\s*"sysNotification_OrderEvents"/);
78
+ assert.match(content, /nameSpace:\s*"sysNotification\|OrderEvents"/);
79
79
  assert.match(content, /systemTextTag:\s*"OrderPlaced"/);
80
80
  assert.match(content, /\/\/\(<OrderPlacedHook>\)/);
81
81
 
82
82
  // Relationship
83
- assert.match(content, /nameSpace:\s*"hasCategory_MyService_description"/);
83
+ assert.match(content, /nameSpace:\s*"hasCategory\|MyService\|description"/);
84
84
  assert.match(
85
85
  content,
86
- /systemTextTag:\s*"originTimestamp_hasCategory_MyService"/
86
+ /systemTextTag:\s*"originTimestamp\|hasCategory\|MyService"/
87
87
  );
88
88
 
89
89
  // Custom Flow
90
- assert.match(content, /nameSpace:\s*"ProcessOrder_MyService"/);
91
- assert.match(content, /systemTextTag:\s*"errors_ProcessOrder_MyService"/);
90
+ assert.match(content, /nameSpace:\s*"ProcessOrder\|MyService"/);
91
+ assert.match(content, /systemTextTag:\s*"errors\|ProcessOrder\|MyService"/);
92
92
  });
93
93
 
94
94
  test('emitSystemTextSchemas returns empty array when no inputs provided', async () => {
@@ -123,7 +123,7 @@ test('generate preserves user hooks inside SystemTextSchemas.js across regenerat
123
123
  );
124
124
  await writeFile(
125
125
  join(rootPath, 'app/src/schemas/ObjectSchemas/Item.js'),
126
- "export default { objectType: 'item', fieldNames: { itemId: { type: 'string' } }, generatedBy: 'userGenerated' };\n"
126
+ "export default { objectType: 'item', fieldNames: { itemId: { type: 'string' } }, generatedBy: 'user' };\n"
127
127
  );
128
128
 
129
129
  // First generate