@reldens/storage 0.110.0 → 0.112.0

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/CLAUDE.md CHANGED
@@ -284,6 +284,26 @@ All generated entity relations follow the `related_*` prefix pattern:
284
284
 
285
285
  This pattern is consistent across all ORM drivers and is defined in `entities-config.js`.
286
286
 
287
+ ## Reference Properties Delete Rule
288
+
289
+ Every generated `type: 'reference'` property also carries the foreign key referential action as
290
+ `onDelete: '<rule>'`, so consumers can tell what a parent delete does to the child rows without querying the
291
+ schema themselves.
292
+
293
+ - `MysqlTablesProvider` joins `information_schema.REFERENTIAL_CONSTRAINTS` on the FK constraint name and stores
294
+ the raw `DELETE_RULE` as `column.referencedDeleteRule`.
295
+ - `BaseGenerator.mapDeleteRule()` normalizes it to camel case, matching the Prisma vocabulary: `CASCADE` becomes
296
+ `cascade`, `SET NULL` becomes `setNull`, `NO ACTION` becomes `noAction`, `SET DEFAULT` becomes `setDefault`,
297
+ `RESTRICT` stays `restrict`.
298
+ - `BaseGenerator.addReferenceDeleteRule()` pushes the attribute, called from
299
+ `EntitiesGeneration.addTypeAttribute()` right after the `alias`. Only the entities are affected, the ORM model
300
+ generation does not emit it.
301
+ - The property is omitted when there is no rule to report, so an entity generated before this existed simply has
302
+ no `onDelete` and consumers must treat a missing value as unknown rather than assuming a default.
303
+
304
+ Regenerating entities is required after changing a foreign key referential action: the rule is read live from
305
+ `information_schema`, not from `prisma/schema.prisma`, and `prisma db pull` alone does not update it.
306
+
287
307
  ## Prisma Driver Validation System
288
308
 
289
309
  ### ensureRequiredFields() Method
@@ -16,6 +16,28 @@ class BaseGenerator
16
16
  return result;
17
17
  }
18
18
 
19
+ addReferenceDeleteRule(props, column)
20
+ {
21
+ let deleteRule = this.mapDeleteRule(column.referencedDeleteRule);
22
+ if(!deleteRule){
23
+ return;
24
+ }
25
+ props.push('onDelete: \''+deleteRule+'\'');
26
+ }
27
+
28
+ mapDeleteRule(deleteRule)
29
+ {
30
+ if(!deleteRule){
31
+ return '';
32
+ }
33
+ let ruleParts = String(deleteRule).toLowerCase().split(' ');
34
+ let mappedRule = ruleParts.shift();
35
+ for(let rulePart of ruleParts){
36
+ mappedRule += rulePart.charAt(0).toUpperCase()+rulePart.slice(1);
37
+ }
38
+ return mappedRule;
39
+ }
40
+
19
41
  }
20
42
 
21
43
  module.exports.BaseGenerator = BaseGenerator;
@@ -228,6 +228,7 @@ class EntitiesGeneration extends BaseGenerator
228
228
  props.push('reference: \''+referenceTable+'\'');
229
229
  let alias = this.generateForwardRelationKey(referenceTable, columnName, referenceCounts);
230
230
  props.push('alias: \''+alias+'\'');
231
+ this.addReferenceDeleteRule(props, column);
231
232
  return;
232
233
  }
233
234
  if('date' === type || 'datetime' === type || 'timestamp' === type){
@@ -53,16 +53,19 @@ class MySQLTablesProvider
53
53
  };
54
54
  }
55
55
  let foreignKeysQuery = 'SELECT ' +
56
- ' TABLE_NAME, ' +
57
- ' COLUMN_NAME, ' +
58
- ' REFERENCED_TABLE_NAME, ' +
59
- ' REFERENCED_COLUMN_NAME ' +
56
+ ' keyColumnUsage.TABLE_NAME, ' +
57
+ ' keyColumnUsage.COLUMN_NAME, ' +
58
+ ' keyColumnUsage.REFERENCED_TABLE_NAME, ' +
59
+ ' keyColumnUsage.REFERENCED_COLUMN_NAME, ' +
60
+ ' referentialConstraints.DELETE_RULE ' +
60
61
  'FROM ' +
61
- ' information_schema.KEY_COLUMN_USAGE ' +
62
+ ' information_schema.KEY_COLUMN_USAGE keyColumnUsage ' +
63
+ ' LEFT JOIN information_schema.REFERENTIAL_CONSTRAINTS referentialConstraints ' +
64
+ ' ON referentialConstraints.CONSTRAINT_SCHEMA = keyColumnUsage.CONSTRAINT_SCHEMA ' +
65
+ ' AND referentialConstraints.CONSTRAINT_NAME = keyColumnUsage.CONSTRAINT_NAME ' +
62
66
  'WHERE ' +
63
- ' TABLE_SCHEMA = \'' + server.config.database + '\' ' +
64
- ' AND REFERENCED_TABLE_NAME IS NOT NULL;';
65
-
67
+ ' keyColumnUsage.TABLE_SCHEMA = \'' + server.config.database + '\' ' +
68
+ ' AND keyColumnUsage.REFERENCED_TABLE_NAME IS NOT NULL;';
66
69
  let foreignKeysResult = await server.rawQuery(foreignKeysQuery);
67
70
  if(!sc.isArray(foreignKeysResult) || 0 === foreignKeysResult.length){
68
71
  return tables;
@@ -71,6 +74,7 @@ class MySQLTablesProvider
71
74
  if(tables[row.TABLE_NAME] && tables[row.TABLE_NAME].columns[row.COLUMN_NAME]){
72
75
  tables[row.TABLE_NAME].columns[row.COLUMN_NAME].referencedTable = row.REFERENCED_TABLE_NAME;
73
76
  tables[row.TABLE_NAME].columns[row.COLUMN_NAME].referencedColumn = row.REFERENCED_COLUMN_NAME;
77
+ tables[row.TABLE_NAME].columns[row.COLUMN_NAME].referencedDeleteRule = row.DELETE_RULE;
74
78
  }
75
79
  }
76
80
  return tables;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@reldens/storage",
3
3
  "scope": "@reldens",
4
- "version": "0.110.0",
4
+ "version": "0.112.0",
5
5
  "description": "Reldens - Storage",
6
6
  "author": "Damian A. Pastorini",
7
7
  "license": "MIT",
@@ -24,6 +24,7 @@ class TestProductsEntity extends EntityProperties
24
24
  type: 'reference',
25
25
  reference: 'test_categories',
26
26
  alias: 'related_test_categories',
27
+ onDelete: 'cascade',
27
28
  isRequired: true,
28
29
  dbType: 'int'
29
30
  },
@@ -24,6 +24,7 @@ class TestReviewsEntity extends EntityProperties
24
24
  type: 'reference',
25
25
  reference: 'test_products',
26
26
  alias: 'related_test_products',
27
+ onDelete: 'cascade',
27
28
  isRequired: true,
28
29
  dbType: 'int'
29
30
  },
@@ -38,9 +38,43 @@ class EntitiesGenerationTest
38
38
  await this.testAddUniqueAttribute();
39
39
  await this.testGetPropertyAttributesUnique();
40
40
  await this.testGeneratePropertiesConfigUnique();
41
+ await this.testAddReferenceDeleteRule();
41
42
  return this.runner.getResults();
42
43
  }
43
44
 
45
+ async testAddReferenceDeleteRule()
46
+ {
47
+ this.runner.group('addReferenceDeleteRule');
48
+ let generation = new EntitiesGeneration({});
49
+ await this.runner.test('should map a CASCADE rule', async () => {
50
+ let props = [];
51
+ generation.addReferenceDeleteRule(props, this.createColumn({referencedDeleteRule: 'CASCADE'}));
52
+ assert.deepStrictEqual(props, ['onDelete: \'cascade\'']);
53
+ });
54
+ await this.runner.test('should map a multi word rule to camel case', async () => {
55
+ let props = [];
56
+ generation.addReferenceDeleteRule(props, this.createColumn({referencedDeleteRule: 'SET NULL'}));
57
+ assert.deepStrictEqual(props, ['onDelete: \'setNull\'']);
58
+ let noActionProps = [];
59
+ generation.addReferenceDeleteRule(noActionProps, this.createColumn({referencedDeleteRule: 'NO ACTION'}));
60
+ assert.deepStrictEqual(noActionProps, ['onDelete: \'noAction\'']);
61
+ });
62
+ await this.runner.test('should push nothing when the column has no delete rule', async () => {
63
+ let props = [];
64
+ generation.addReferenceDeleteRule(props, this.createColumn({}));
65
+ assert.deepStrictEqual(props, []);
66
+ });
67
+ await this.runner.test('should include the delete rule on a reference property', async () => {
68
+ let column = this.createColumn({
69
+ type: 'int',
70
+ referencedTable: 'test_categories',
71
+ referencedDeleteRule: 'SET NULL'
72
+ });
73
+ let props = generation.getPropertyAttributes(column, 'category_id', {test_categories: 1});
74
+ assert.ok(-1 !== props.indexOf('onDelete: \'setNull\''));
75
+ });
76
+ }
77
+
44
78
  async testAddUniqueAttribute()
45
79
  {
46
80
  this.runner.group('addUniqueAttribute');