@izara_project/izara-core-generate-service-code 1.0.82 → 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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@izara_project/izara-core-generate-service-code",
3
- "version": "1.0.82",
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) {
@@ -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
 
@@ -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
 
@@ -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
  }
@@ -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) {
@@ -280,7 +280,7 @@ test('emits GetRangeHdrApi when an object defines a sortKey identifier', async (
280
280
  const rootPath = await createProject(true);
281
281
  await writeFile(
282
282
  join(rootPath, 'app/src/schemas/ObjectSchemas/Order.js'),
283
- "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"
284
284
  );
285
285
  const result = await generateCrudApplicationArtifacts(rootPath, {
286
286
  serviceTag: 'orders'
@@ -352,7 +352,7 @@ async function createProject(withCrudObject) {
352
352
  await writeFile(
353
353
  join(schemaRoot, 'ObjectSchemas/Order.js'),
354
354
  withCrudObject
355
- ? "export default { objectType: 'order', generatedBy: 'userGenerated', storageResources: { graph: { storageType: 'graph' } } };\n"
355
+ ? "export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
356
356
  : "export default { objectType: 'order' };\n"
357
357
  );
358
358
  return rootPath;
@@ -368,7 +368,7 @@ async function createRbacProject(withHook = false, withGraph = false) {
368
368
  await mkdir(join(schemaRoot, 'FlowSchemas'), { recursive: true });
369
369
  await writeFile(
370
370
  join(schemaRoot, 'ObjectSchemas/Order.js'),
371
- `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`
372
372
  );
373
373
  if (withHook) {
374
374
  await writeFile(
@@ -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
+ });
@@ -346,7 +346,7 @@ async function createCrudProject() {
346
346
  await mkdir(objectDirectory, { recursive: true });
347
347
  await writeFile(
348
348
  join(objectDirectory, 'Order.js'),
349
- "export default { objectType: 'order', generatedBy: 'userGenerated', storageResources: { graph: { storageType: 'graph' } } };\n"
349
+ "export default { objectType: 'order', generatedBy: 'user', storageResources: { graph: { storageType: 'graph' } } };\n"
350
350
  );
351
351
  return rootPath;
352
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'),
@@ -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