@izara_project/izara-core-generate-service-code 1.0.97 → 1.0.99

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 (56) hide show
  1. package/package.json +1 -1
  2. package/src/compile/flowValidator.js +45 -8
  3. package/src/compile/loadSchemas.js +26 -6
  4. package/src/emit/application/coreFunction/emitCoreFunctionArtifacts.js +15 -1
  5. package/src/emit/application/coreFunction/templates/InitialSetupConfig.ejs +8 -4
  6. package/src/emit/application/crud/templates/CreateEndpoint_Main.ejs +4 -4
  7. package/src/emit/application/crud/templates/CreateLogicalLib.ejs +60 -8
  8. package/src/emit/application/crud/templates/DeleteEndpoint_Main.ejs +4 -4
  9. package/src/emit/application/crud/templates/FlowEndpoint_HdrWbs.ejs +2 -2
  10. package/src/emit/application/crud/templates/FlowMain_Wbs.ejs +2 -2
  11. package/src/emit/application/crud/templates/UpdateEndpoint_Main.ejs +4 -4
  12. package/src/emit/application/custom/customFlowS3.js +4 -4
  13. package/src/emit/application/custom/templates/FlowEndpoint_Main.ejs +2 -2
  14. package/src/emit/application/custom/templates/s3/CreateObject_Main.ejs +5 -2
  15. package/src/emit/application/custom/templates/triggerCache/CheckTriggerCache_Main.ejs +2 -2
  16. package/src/emit/application/rbac/emitDynamicRbacFlowSchemas.js +4 -1
  17. package/src/emit/application/rbac/emitRbacApplicationArtifacts.js +76 -1
  18. package/src/emit/application/rbac/templates/ReservedDynamicUsage_Main.ejs +2 -0
  19. package/src/emit/application/rbac/templates/ReservedUsageTtlExpired_HdrDss.ejs +176 -0
  20. package/src/emit/application/relationship/templates/ChangeRelationship_Main.ejs +3 -2
  21. package/src/emit/application/relationship/templates/CreateRelationship_Main.ejs +3 -2
  22. package/src/emit/application/relationship/templates/DeleteRelationship_Main.ejs +3 -2
  23. package/src/emit/application/relationship/templates/GetRelationship_Main.ejs +3 -2
  24. package/src/emit/application/relationship/templates/MoveRelationship_Main.ejs +3 -2
  25. package/src/emit/application/relationship/templates/UpdateRelationship_Main.ejs +3 -2
  26. package/src/emit/application/system/templates/RegisterComplete_Main.ejs +6 -2
  27. package/src/emit/application/system/templates/Register_Main.ejs +6 -2
  28. package/src/emit/application/system/templates/WebSocketComplete_Main.ejs +3 -3
  29. package/src/emit/infrastructure/functions/functionYamlArtifact.js +28 -0
  30. package/src/emit/infrastructure/functions/templates/DssFunction_Yaml.ejs +21 -0
  31. package/src/emit/infrastructure/resources/iamResourceArtifacts.js +46 -6
  32. package/src/emit/infrastructure/resources/templates/GeneratedLambdaRoleServiceConfig.ejs +5 -3
  33. package/src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js +75 -22
  34. package/src/emit/schemas/ledger/emitLedgerSchemas.js +33 -11
  35. package/src/emit/schemas/ledger/templates/LedgerObjectSchema.ejs +3 -0
  36. package/src/emit/schemas/ledger/templates/LedgerRelationshipSchema.ejs +1 -0
  37. package/src/emit/schemas/propertyValue/emitPropertyValueSchemas.js +329 -0
  38. package/src/generate.js +10 -73
  39. package/src/output/mergeHooks.js +169 -15
  40. package/src/plan/domainRegistry.js +31 -5
  41. package/task.md +76 -0
  42. package/test/integration/graphDrivenFlowSchema.test.js +58 -0
  43. package/test/integration/publicApi.test.js +54 -1
  44. package/test/unit/application/createTargetRoleHandler.test.js +70 -1
  45. package/test/unit/application/crudApplicationArtifacts.test.js +4 -0
  46. package/test/unit/application/customFlowApplicationArtifacts.test.js +9 -0
  47. package/test/unit/application/relationshipApplicationArtifacts.test.js +30 -0
  48. package/test/unit/infrastructure/resourceArtifacts.test.js +53 -0
  49. package/test/unit/output/mergeHooks.test.js +405 -0
  50. package/test/unit/schemas/attributeTreeSchemas.test.js +41 -63
  51. package/test/unit/schemas/attributeTreeSingleService.test.js +175 -0
  52. package/test/unit/schemas/ledgerSchemas.test.js +33 -7
  53. package/test/unit/schemas/propertyValueSchemas.test.js +265 -0
  54. package/test/unit/templates/hookTagsValidation.test.js +83 -0
  55. package/src/emit/schemas/attributeTree/emitAttributeTreeTargetSchemas.js +0 -153
  56. package/src/emit/schemas/attributeTree/resolveTargetServicePath.js +0 -111
@@ -8,9 +8,7 @@ import {
8
8
  emitAttributeTreeObjectSchemas,
9
9
  emitAttributeTreeRelationshipSchemas,
10
10
  attributeTreeObjectSchemaManagedPaths,
11
- attributeTreeRelationshipSchemaManagedPaths,
12
- resolveTargetServicePath,
13
- emitAttributeTreeTargetSchemas
11
+ attributeTreeRelationshipSchemaManagedPaths
14
12
  } from '../../../src/emit/schemas/attributeTree/emitAttributeTreeSchemas.js';
15
13
  import { generate } from '../../../src/generate.js';
16
14
  import {
@@ -25,7 +23,7 @@ after(async () => {
25
23
  await cleanupTestProjects();
26
24
  });
27
25
 
28
- test('emitAttributeTreeObjectSchemas returns 3 object schemas per attributeTree tag', () => {
26
+ test('emitAttributeTreeObjectSchemas returns 4 object schemas per attributeTree tag', () => {
29
27
  const objects = [
30
28
  {
31
29
  objectType: 'sellOffer',
@@ -50,13 +48,14 @@ test('emitAttributeTreeObjectSchemas returns 3 object schemas per attributeTree
50
48
  ];
51
49
 
52
50
  const artifacts = emitAttributeTreeObjectSchemas({ objects });
53
- assert.equal(artifacts.length, 3);
51
+ assert.equal(artifacts.length, 4);
54
52
 
55
53
  const paths = artifacts.map((a) => a.path);
56
54
  assert.deepEqual(paths, [
57
- 'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLink.js',
58
- 'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermValue.js',
59
- 'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLabel.js'
55
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermLink.js',
56
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermValue.js',
57
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermLabel.js',
58
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermValueStandard.js'
60
59
  ]);
61
60
 
62
61
  const valueArtifact = artifacts.find((a) =>
@@ -65,6 +64,26 @@ test('emitAttributeTreeObjectSchemas returns 3 object schemas per attributeTree
65
64
  assert.match(valueArtifact.content, /"objectType": "sellOfferTermValue"/);
66
65
  assert.match(valueArtifact.content, /"sellOfferTermValueId"/);
67
66
 
67
+ const valueStandardArtifact = artifacts.find((a) =>
68
+ a.path.endsWith('sellOfferTermValueStandard.js')
69
+ );
70
+ assert.match(
71
+ valueStandardArtifact.content,
72
+ /objectType:\s*['"]sellOfferTermValueStandard['"]/
73
+ );
74
+ assert.match(
75
+ valueStandardArtifact.content,
76
+ /sellOfferTermValueStandardId/
77
+ );
78
+ assert.match(
79
+ valueStandardArtifact.content,
80
+ /\/\/\(<addOnDataStructuresellOfferTermValueStandard>\)/
81
+ );
82
+
83
+ const managedPaths = attributeTreeObjectSchemaManagedPaths(objects);
84
+ assert.equal(managedPaths.length, 4);
85
+ assert.deepEqual(managedPaths, paths);
86
+
68
87
  for (const artifact of artifacts) {
69
88
  assert.doesNotMatch(artifact.content, /dynamoDB/);
70
89
  assert.match(artifact.content, /GraphHandler/);
@@ -103,10 +122,10 @@ test('emitAttributeTreeRelationshipSchemas returns 4 relationship schemas per at
103
122
 
104
123
  const paths = artifacts.map((a) => a.path);
105
124
  assert.deepEqual(paths, [
106
- 'app/src/schemas/RelationshipSchemas/generatedAttributeTree/hasSellOfferTermLink.js',
107
- 'app/src/schemas/RelationshipSchemas/generatedAttributeTree/disabledSellOfferTermLink.js',
108
- 'app/src/schemas/RelationshipSchemas/generatedAttributeTree/isSellOfferTerm.js',
109
- 'app/src/schemas/RelationshipSchemas/generatedAttributeTree/isSellOfferTermLabel.js'
125
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/hasSellOfferTermLink.js',
126
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/disabledSellOfferTermLink.js',
127
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/isSellOfferTerm.js',
128
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/isSellOfferTermLabel.js'
110
129
  ]);
111
130
 
112
131
  const hasRel = artifacts.find((a) => a.path.endsWith('hasSellOfferTermLink.js'));
@@ -153,15 +172,20 @@ test('generate commits and cleans up attributeTree dynamic schemas', async () =>
153
172
  await generate(rootPath);
154
173
 
155
174
  const objectRelPath =
156
- 'app/src/schemas/ObjectSchemas/generatedAttributeTree/sellOfferTermLink.js';
175
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermLink.js';
176
+ const valueStandardRelPath =
177
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermValueStandard.js';
157
178
  const relRelPath =
158
- 'app/src/schemas/RelationshipSchemas/generatedAttributeTree/hasSellOfferTermLink.js';
179
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/hasSellOfferTermLink.js';
159
180
 
160
181
  const objectContent = await readProjectFile(rootPath, objectRelPath);
161
182
  assert.match(objectContent, /sellOfferTermLink/);
162
183
  assert.match(objectContent, /GraphHandler/);
163
184
  assert.doesNotMatch(objectContent, /dynamoDB/);
164
185
 
186
+ const valueStandardContent = await readProjectFile(rootPath, valueStandardRelPath);
187
+ assert.match(valueStandardContent, /sellOfferTermValueStandard/);
188
+
165
189
  const relContent = await readProjectFile(rootPath, relRelPath);
166
190
  assert.match(relContent, /hasSellOfferTermLink/);
167
191
  assert.match(relContent, /sellOfferTermLink/);
@@ -173,6 +197,9 @@ test('generate commits and cleans up attributeTree dynamic schemas', async () =>
173
197
  await assert.rejects(readProjectFile(rootPath, objectRelPath), {
174
198
  code: 'ENOENT'
175
199
  });
200
+ await assert.rejects(readProjectFile(rootPath, valueStandardRelPath), {
201
+ code: 'ENOENT'
202
+ });
176
203
  await assert.rejects(readProjectFile(rootPath, relRelPath), {
177
204
  code: 'ENOENT'
178
205
  });
@@ -244,53 +271,4 @@ async function createProjectWithAttributeTree({
244
271
  });
245
272
  }
246
273
 
247
- test('resolveTargetServicePath prioritizes dev suffix and matches attributeTag', async () => {
248
- const tmpRoot = trackTestProject(
249
- await mkdtemp(join(tmpdir(), 'izara-services-test-'))
250
- );
251
-
252
- const currentServiceDir = join(
253
- tmpRoot,
254
- 'izara-market-products-sell-offer-manager-pong'
255
- );
256
- const targetWithSuffix = join(
257
- tmpRoot,
258
- 'izara-market-products-sell-offer-term-pong'
259
- );
260
- const targetOtherDev = join(
261
- tmpRoot,
262
- 'izara-market-products-sell-offer-term-alice'
263
- );
264
- const unrelatedService = join(
265
- tmpRoot,
266
- 'izara-market-products-cart-pong'
267
- );
268
-
269
- await mkdir(join(currentServiceDir, 'app/src'), { recursive: true });
270
- await mkdir(join(targetWithSuffix, 'app/src'), { recursive: true });
271
- await mkdir(join(targetOtherDev, 'app/src'), { recursive: true });
272
- await mkdir(join(unrelatedService, 'app/src'), { recursive: true });
273
-
274
- const resolved = resolveTargetServicePath({
275
- currentServiceRoot: currentServiceDir,
276
- attributeTag: 'sellOfferTerm'
277
- });
278
-
279
- assert.equal(resolved, targetWithSuffix);
280
- });
281
-
282
- test('emitAttributeTreeTargetSchemas produces expected standard schemas', () => {
283
- const targetDir = '/dummy/path/izara-market-products-sellOfferTerm';
284
- const artifacts = emitAttributeTreeTargetSchemas({
285
- attributeTag: 'sellOfferTerm',
286
- targetServiceRoot: targetDir
287
- });
288
-
289
- assert.equal(artifacts.length, 3);
290
- const paths = artifacts.map((a) => a.path);
291
-
292
- assert.ok(paths.some((p) => p.endsWith('sellOfferTermValueStandardPropertyLabel.js')));
293
- assert.ok(paths.some((p) => p.endsWith('sellOfferTermValueStandardPropertyValue.js')));
294
- assert.ok(paths.some((p) => p.endsWith('sellOfferTermValueStandard.js')));
295
- });
296
274
 
@@ -0,0 +1,175 @@
1
+ import assert from 'node:assert/strict';
2
+ import test, { after } from 'node:test';
3
+ import { generate } from '../../../src/generate.js';
4
+ import {
5
+ createTestProject,
6
+ readProjectFile,
7
+ writeProjectFile,
8
+ cleanupTestProjects
9
+ } from '../../helpers/testProject.js';
10
+
11
+ after(async () => {
12
+ await cleanupTestProjects();
13
+ });
14
+
15
+ test('single-service attributeTree generation and propertyValue cascading', async () => {
16
+ const initialSchema = `export default [
17
+ {
18
+ objectType: 'sellOffer',
19
+ canDelete: false,
20
+ generatedBy: 'user',
21
+ addOnDataStructure: [
22
+ {
23
+ type: 'attributeTree',
24
+ attributeTreeTag: 'sellOfferTerm'
25
+ }
26
+ ],
27
+ storageResources: {
28
+ dynamoDB: {
29
+ storageType: 'dynamoDB',
30
+ tableName: 'SellOfferRecords',
31
+ serviceTag: 'SellOfferManager'
32
+ },
33
+ myGraph: {
34
+ storageType: 'graph',
35
+ graphServerTag: 'GraphHandler'
36
+ }
37
+ },
38
+ fieldNames: {
39
+ sellOfferId: {
40
+ type: 'string',
41
+ randomOnCreate: true,
42
+ canUpdate: false,
43
+ storageResourceTags: ['dynamoDB', 'myGraph']
44
+ }
45
+ },
46
+ identifiers: [
47
+ {
48
+ type: 'partitionKey',
49
+ fieldName: 'sellOfferId'
50
+ }
51
+ ]
52
+ }
53
+ ];
54
+ `;
55
+
56
+ const rootPath = await createTestProject({
57
+ prefix: 'izara-single-service-attr-tree-',
58
+ serviceTag: 'SellOfferManager',
59
+ schemas: {
60
+ 'ObjectSchemas/SellOffer.js': initialSchema
61
+ }
62
+ });
63
+
64
+ // Phase 1: generate produces all 4 ObjectSchemas and 4 RelationshipSchemas
65
+ await generate(rootPath);
66
+
67
+ const linkPath =
68
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermLink.js';
69
+ const valuePath =
70
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermValue.js';
71
+ const labelPath =
72
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermLabel.js';
73
+ const valueStandardPath =
74
+ 'app/src/schemas/ObjectSchemas/generatedCode/attributeTree/sellOfferTermValueStandard.js';
75
+
76
+ const linkContent = await readProjectFile(rootPath, linkPath);
77
+ assert.match(linkContent, /sellOfferTermLink/);
78
+
79
+ const valueContent = await readProjectFile(rootPath, valuePath);
80
+ assert.match(valueContent, /sellOfferTermValue/);
81
+
82
+ const labelContent = await readProjectFile(rootPath, labelPath);
83
+ assert.match(labelContent, /sellOfferTermLabel/);
84
+
85
+ const valueStandardContent = await readProjectFile(
86
+ rootPath,
87
+ valueStandardPath
88
+ );
89
+ assert.match(valueStandardContent, /sellOfferTermValueStandard/);
90
+ assert.match(
91
+ valueStandardContent,
92
+ /\/\/\(<addOnDataStructuresellOfferTermValueStandard>\)/
93
+ );
94
+
95
+ // Check relationship schemas
96
+ const hasLinkRelPath =
97
+ 'app/src/schemas/RelationshipSchemas/generatedCode/attributeTree/hasSellOfferTermLink.js';
98
+ const hasLinkRel = await readProjectFile(rootPath, hasLinkRelPath);
99
+ assert.match(hasLinkRel, /hasSellOfferTermLink/);
100
+
101
+ // Phase 2: developer uncomments propertyValue addOn in ValueStandard
102
+ const modifiedValueStandard = valueStandardContent.replace(
103
+ '//(<addOnDataStructuresellOfferTermValueStandard>)\n // {\n // type: \'propertyValue\',\n // propertyValuePrefix: \'sellOfferTermValueStandard\'\n // }\n //(</addOnDataStructuresellOfferTermValueStandard>)',
104
+ '//(<addOnDataStructuresellOfferTermValueStandard>)\n {\n type: \'propertyValue\',\n propertyValuePrefix: \'sellOfferTermValueStandard\'\n }\n //(</addOnDataStructuresellOfferTermValueStandard>)'
105
+ );
106
+ await writeProjectFile(rootPath, valueStandardPath, modifiedValueStandard);
107
+
108
+ await generate(rootPath);
109
+
110
+ // Verify hook preservation
111
+ const reloadedValueStandard = await readProjectFile(
112
+ rootPath,
113
+ valueStandardPath
114
+ );
115
+ assert.match(reloadedValueStandard, /type:\s*'propertyValue'/);
116
+
117
+ // Verify cascading generation: propertyValue domain in the same repo
118
+ const propLabelPath =
119
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/sellOfferTermValueStandardPropertyLabel.js';
120
+ const propValuePath =
121
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/sellOfferTermValueStandardPropertyValue.js';
122
+ const hasPropRelPath =
123
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/hasSellOfferTermValueStandardPropertyValue.js';
124
+ const disPropRelPath =
125
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/disabledSellOfferTermValueStandardPropertyValue.js';
126
+ const isPropRelPath =
127
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/isSellOfferTermValueStandardPropertyLabel.js';
128
+
129
+ const propLabel = await readProjectFile(rootPath, propLabelPath);
130
+ assert.match(propLabel, /sellOfferTermValueStandardPropertyLabel/);
131
+
132
+ const propValue = await readProjectFile(rootPath, propValuePath);
133
+ assert.match(propValue, /sellOfferTermValueStandardPropertyValue/);
134
+
135
+ const hasPropRel = await readProjectFile(rootPath, hasPropRelPath);
136
+ assert.match(hasPropRel, /hasSellOfferTermValueStandardPropertyValue/);
137
+
138
+ const disPropRel = await readProjectFile(rootPath, disPropRelPath);
139
+ assert.match(disPropRel, /disabledSellOfferTermValueStandardPropertyValue/);
140
+
141
+ const isPropRel = await readProjectFile(rootPath, isPropRelPath);
142
+ assert.match(isPropRel, /isSellOfferTermValueStandardPropertyLabel/);
143
+
144
+ // Cleanup check: remove attributeTree addOn from base schema
145
+ const cleanSchema = `export default [
146
+ {
147
+ objectType: 'sellOffer',
148
+ canDelete: false,
149
+ generatedBy: 'user',
150
+ addOnDataStructure: [],
151
+ storageResources: {
152
+ myGraph: { storageType: 'graph', graphServerTag: 'GraphHandler' }
153
+ },
154
+ fieldNames: {
155
+ sellOfferId: { type: 'string', storageResourceTags: ['myGraph'] }
156
+ },
157
+ identifiers: [{ type: 'partitionKey', fieldName: 'sellOfferId' }]
158
+ }
159
+ ];
160
+ `;
161
+ await writeProjectFile(
162
+ rootPath,
163
+ 'app/src/schemas/ObjectSchemas/SellOffer.js',
164
+ cleanSchema
165
+ );
166
+ await generate(rootPath);
167
+
168
+ // All attributeTree schemas should be removed
169
+ await assert.rejects(readProjectFile(rootPath, linkPath), { code: 'ENOENT' });
170
+ await assert.rejects(readProjectFile(rootPath, valuePath), { code: 'ENOENT' });
171
+ await assert.rejects(readProjectFile(rootPath, labelPath), { code: 'ENOENT' });
172
+ await assert.rejects(readProjectFile(rootPath, valueStandardPath), {
173
+ code: 'ENOENT'
174
+ });
175
+ });
@@ -19,7 +19,7 @@ after(async () => {
19
19
  await cleanupTestProjects();
20
20
  });
21
21
 
22
- test('emitLedgerObjectSchemas returns 1 object schema per ledger addOn', () => {
22
+ test('emitLedgerObjectSchemas returns 1 object schema per ledger addOn', async () => {
23
23
  const objects = [
24
24
  {
25
25
  objectType: 'unitStandard',
@@ -61,7 +61,7 @@ test('emitLedgerObjectSchemas returns 1 object schema per ledger addOn', () => {
61
61
  }
62
62
  ];
63
63
 
64
- const artifacts = emitLedgerObjectSchemas({ objects });
64
+ const artifacts = await emitLedgerObjectSchemas({ objects });
65
65
  assert.equal(artifacts.length, 2);
66
66
 
67
67
  const paths = artifacts.map((a) => a.path);
@@ -81,7 +81,7 @@ test('emitLedgerObjectSchemas returns 1 object schema per ledger addOn', () => {
81
81
  assert.match(entryArtifact.content, /GraphHandler/);
82
82
  });
83
83
 
84
- test('emitLedgerRelationshipSchemas returns current, old, and is relationship schemas per ledger addOn', () => {
84
+ test('emitLedgerRelationshipSchemas returns current, old, and is relationship schemas per ledger addOn', async () => {
85
85
  const objects = [
86
86
  {
87
87
  objectType: 'unitStandard',
@@ -113,7 +113,7 @@ test('emitLedgerRelationshipSchemas returns current, old, and is relationship sc
113
113
  }
114
114
  ];
115
115
 
116
- const artifacts = emitLedgerRelationshipSchemas({
116
+ const artifacts = await emitLedgerRelationshipSchemas({
117
117
  objects,
118
118
  serviceTag: 'UnitStandard'
119
119
  });
@@ -156,14 +156,40 @@ test('emitLedgerRelationshipSchemas returns current, old, and is relationship sc
156
156
  assert.match(isRel.content, /"generatedBy": "systemNoUser"/);
157
157
  assert.match(isRel.content, /"supplyLocation"/);
158
158
  assert.match(isRel.content, /"unitPacking"/);
159
+
160
+ // 4. Verify requiredOnCreate is false in links
161
+ const _parseRel = (content) => {
162
+ const raw = content
163
+ .replace(/^export default\s+/, '')
164
+ .replace(/;\s*$/, '');
165
+ return JSON.parse(raw)[0];
166
+ };
167
+
168
+ const currentRelData = Object.values(_parseRel(currentRel.content))[0];
169
+ for (const link of currentRelData.links) {
170
+ assert.equal(link.from.requiredOnCreate, false);
171
+ assert.equal(link.to.requiredOnCreate, false);
172
+ }
173
+
174
+ const oldRelData = Object.values(_parseRel(oldRel.content))[0];
175
+ for (const link of oldRelData.links) {
176
+ assert.equal(link.from.requiredOnCreate, false);
177
+ assert.equal(link.to.requiredOnCreate, false);
178
+ }
179
+
180
+ const isRelData = Object.values(_parseRel(isRel.content))[0];
181
+ for (const link of isRelData.links) {
182
+ assert.equal(link.from.requiredOnCreate, false);
183
+ assert.equal(link.to.requiredOnCreate, false);
184
+ }
159
185
  });
160
186
 
161
- test('managed paths return empty array when no ledger is present', () => {
187
+ test('managed paths return empty array when no ledger is present', async () => {
162
188
  const objects = [{ objectType: 'user', addOnDataStructure: [] }];
163
189
  assert.deepEqual(ledgerObjectSchemaManagedPaths(objects), []);
164
190
  assert.deepEqual(ledgerRelationshipSchemaManagedPaths(objects), []);
165
- assert.deepEqual(emitLedgerObjectSchemas({ objects }), []);
166
- assert.deepEqual(emitLedgerRelationshipSchemas({ objects }), []);
191
+ assert.deepEqual(await emitLedgerObjectSchemas({ objects }), []);
192
+ assert.deepEqual(await emitLedgerRelationshipSchemas({ objects }), []);
167
193
  });
168
194
 
169
195
  test('generate commits and cleans up ledger dynamic schemas', async () => {
@@ -0,0 +1,265 @@
1
+ import assert from 'node:assert/strict';
2
+ import test, { after } from 'node:test';
3
+
4
+ import {
5
+ emitPropertyValueObjectSchemas,
6
+ emitPropertyValueRelationshipSchemas,
7
+ propertyValueObjectSchemaManagedPaths,
8
+ propertyValueRelationshipSchemaManagedPaths
9
+ } from '../../../src/emit/schemas/propertyValue/emitPropertyValueSchemas.js';
10
+ import { generate } from '../../../src/generate.js';
11
+ import {
12
+ createTestProject,
13
+ readProjectFile,
14
+ writeProjectFile,
15
+ cleanupTestProjects
16
+ } from '../../helpers/testProject.js';
17
+
18
+ after(async () => {
19
+ await cleanupTestProjects();
20
+ });
21
+
22
+ test('emitPropertyValueObjectSchemas returns 2 object schemas per propertyValue addOn', () => {
23
+ const objects = [
24
+ {
25
+ objectType: 'item',
26
+ serviceTag: 'ItemService',
27
+ storageResources: {
28
+ dynamoDB: {
29
+ storageType: 'dynamoDB',
30
+ tableName: 'ItemRecords'
31
+ },
32
+ myGraph: {
33
+ storageType: 'graph',
34
+ graphServerTag: 'GraphHandler'
35
+ }
36
+ },
37
+ addOnDataStructure: [
38
+ {
39
+ type: 'propertyValue',
40
+ propertyValuePrefix: 'itemProperty'
41
+ }
42
+ ]
43
+ },
44
+ {
45
+ objectType: 'supplyNetwork',
46
+ serviceTag: 'SupplyService',
47
+ storageResources: {
48
+ myGraph: {
49
+ storageType: 'graph',
50
+ graphServerTag: 'GraphHandler'
51
+ }
52
+ },
53
+ addOnDataStructure: [
54
+ {
55
+ type: 'propertyValue'
56
+ }
57
+ ]
58
+ }
59
+ ];
60
+
61
+ const artifacts = emitPropertyValueObjectSchemas({ objects });
62
+ assert.equal(artifacts.length, 4);
63
+
64
+ const paths = artifacts.map((a) => a.path);
65
+ assert.deepEqual(paths, [
66
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/itemPropertyPropertyLabel.js',
67
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/itemPropertyPropertyValue.js',
68
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/supplyNetworkPropertyLabel.js',
69
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/supplyNetworkPropertyValue.js'
70
+ ]);
71
+
72
+ const labelArtifact = artifacts[0];
73
+ assert.match(
74
+ labelArtifact.content,
75
+ /"objectType": "itemPropertyPropertyLabel"/
76
+ );
77
+ assert.match(labelArtifact.content, /"itemPropertyPropertyLabelId"/);
78
+ assert.match(labelArtifact.content, /"generatedBy": "user"/);
79
+ assert.match(labelArtifact.content, /"canDelete": true/);
80
+ assert.doesNotMatch(labelArtifact.content, /dynamoDB/);
81
+ assert.match(labelArtifact.content, /GraphHandler/);
82
+
83
+ const valueArtifact = artifacts[1];
84
+ assert.match(
85
+ valueArtifact.content,
86
+ /"objectType": "itemPropertyPropertyValue"/
87
+ );
88
+ assert.match(valueArtifact.content, /"itemPropertyPropertyValueId"/);
89
+ });
90
+
91
+ test('emitPropertyValueRelationshipSchemas returns 3 relationship schemas per propertyValue addOn', () => {
92
+ const objects = [
93
+ {
94
+ objectType: 'item',
95
+ serviceTag: 'ItemService',
96
+ storageResources: {
97
+ myGraph: {
98
+ storageType: 'graph',
99
+ graphServerTag: 'GraphHandler'
100
+ }
101
+ },
102
+ addOnDataStructure: [
103
+ {
104
+ type: 'propertyValue',
105
+ propertyValuePrefix: 'item'
106
+ }
107
+ ]
108
+ }
109
+ ];
110
+
111
+ const artifacts = emitPropertyValueRelationshipSchemas({
112
+ objects,
113
+ serviceTag: 'ItemService'
114
+ });
115
+ assert.equal(artifacts.length, 3);
116
+
117
+ const paths = artifacts.map((a) => a.path);
118
+ assert.deepEqual(paths, [
119
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/hasItemPropertyValue.js',
120
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/disabledItemPropertyValue.js',
121
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/isItemPropertyLabel.js'
122
+ ]);
123
+
124
+ // 1. hasItemPropertyValue
125
+ const hasRel = artifacts[0];
126
+ assert.match(hasRel.content, /"hasItemPropertyValue"/);
127
+ assert.match(hasRel.content, /"generatedBy": "user"/);
128
+ assert.match(hasRel.content, /"objectType": "item"/);
129
+ assert.match(hasRel.content, /"objectType": "itemPropertyValue"/);
130
+ assert.match(hasRel.content, /"linkType": "one"/);
131
+ assert.match(hasRel.content, /"linkType": "many"/);
132
+
133
+ // 2. disabledItemPropertyValue
134
+ const disabledRel = artifacts[1];
135
+ assert.match(disabledRel.content, /"disabledItemPropertyValue"/);
136
+ assert.match(disabledRel.content, /"generatedBy": "user"/);
137
+
138
+ // 3. isItemPropertyLabel
139
+ const isRel = artifacts[2];
140
+ assert.match(isRel.content, /"isItemPropertyLabel"/);
141
+ assert.match(isRel.content, /"generatedBy": "systemNoUser"/);
142
+ assert.match(isRel.content, /"objectType": "itemPropertyLabel"/);
143
+ });
144
+
145
+ test('managed paths return empty array when no propertyValue is present', () => {
146
+ const objects = [{ objectType: 'user', addOnDataStructure: [] }];
147
+ assert.deepEqual(propertyValueObjectSchemaManagedPaths(objects), []);
148
+ assert.deepEqual(propertyValueRelationshipSchemaManagedPaths(objects), []);
149
+ assert.deepEqual(emitPropertyValueObjectSchemas({ objects }), []);
150
+ assert.deepEqual(emitPropertyValueRelationshipSchemas({ objects }), []);
151
+ });
152
+
153
+ test('generate commits and cleans up propertyValue dynamic schemas', async () => {
154
+ const rootPath = await createProjectWithPropertyValue({ hasAddOn: true });
155
+ await generate(rootPath);
156
+
157
+ const labelPath =
158
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/itemPropertyLabel.js';
159
+ const valuePath =
160
+ 'app/src/schemas/ObjectSchemas/generatedCode/propertyValue/itemPropertyValue.js';
161
+ const hasRelPath =
162
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/hasItemPropertyValue.js';
163
+ const disabledRelPath =
164
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/disabledItemPropertyValue.js';
165
+ const isRelPath =
166
+ 'app/src/schemas/RelationshipSchemas/generatedCode/propertyValue/isItemPropertyLabel.js';
167
+
168
+ const labelContent = await readProjectFile(rootPath, labelPath);
169
+ assert.match(labelContent, /itemPropertyLabel/);
170
+ assert.match(labelContent, /GraphHandler/);
171
+
172
+ const valueContent = await readProjectFile(rootPath, valuePath);
173
+ assert.match(valueContent, /itemPropertyValue/);
174
+
175
+ const hasRelContent = await readProjectFile(rootPath, hasRelPath);
176
+ assert.match(hasRelContent, /hasItemPropertyValue/);
177
+
178
+ const disabledRelContent = await readProjectFile(rootPath, disabledRelPath);
179
+ assert.match(disabledRelContent, /disabledItemPropertyValue/);
180
+
181
+ const isRelContent = await readProjectFile(rootPath, isRelPath);
182
+ assert.match(isRelContent, /isItemPropertyLabel/);
183
+
184
+ // Re-run without addOn to verify stale file cleanup
185
+ await createProjectWithPropertyValue({ rootPath, hasAddOn: false });
186
+ await generate(rootPath);
187
+
188
+ await assert.rejects(readProjectFile(rootPath, labelPath), {
189
+ code: 'ENOENT'
190
+ });
191
+ await assert.rejects(readProjectFile(rootPath, valuePath), {
192
+ code: 'ENOENT'
193
+ });
194
+ await assert.rejects(readProjectFile(rootPath, hasRelPath), {
195
+ code: 'ENOENT'
196
+ });
197
+ await assert.rejects(readProjectFile(rootPath, disabledRelPath), {
198
+ code: 'ENOENT'
199
+ });
200
+ await assert.rejects(readProjectFile(rootPath, isRelPath), {
201
+ code: 'ENOENT'
202
+ });
203
+ });
204
+
205
+ async function createProjectWithPropertyValue({
206
+ rootPath: existingRoot,
207
+ hasAddOn = true
208
+ } = {}) {
209
+ let addOnContent = ' addOnDataStructure: [],';
210
+ if (hasAddOn) {
211
+ addOnContent = `
212
+ addOnDataStructure: [
213
+ {
214
+ type: 'propertyValue',
215
+ propertyValuePrefix: 'item'
216
+ }
217
+ ],`;
218
+ }
219
+
220
+ const schemaContent = `export default [
221
+ {
222
+ objectType: 'item',
223
+ canDelete: false,
224
+ generatedBy: 'user',${addOnContent}
225
+ storageResources: {
226
+ graph: {
227
+ storageType: 'graph',
228
+ graphServerTag: 'GraphHandler'
229
+ }
230
+ },
231
+ fieldNames: {
232
+ itemId: {
233
+ type: 'string',
234
+ randomOnCreate: true,
235
+ canUpdate: false,
236
+ storageResourceTags: ['graph']
237
+ }
238
+ },
239
+ identifiers: [
240
+ {
241
+ type: 'partitionKey',
242
+ fieldName: 'itemId'
243
+ }
244
+ ]
245
+ }
246
+ ];
247
+ `;
248
+
249
+ if (existingRoot) {
250
+ await writeProjectFile(
251
+ existingRoot,
252
+ 'app/src/schemas/ObjectSchemas/Item.js',
253
+ schemaContent
254
+ );
255
+ return existingRoot;
256
+ }
257
+
258
+ return await createTestProject({
259
+ prefix: 'izara-property-value-test-',
260
+ serviceTag: 'ItemService',
261
+ schemas: {
262
+ 'ObjectSchemas/Item.js': schemaContent
263
+ }
264
+ });
265
+ }