@reldens/storage 0.116.0 → 0.118.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.
Files changed (86) hide show
  1. package/.claude/test-architecture.md +27 -17
  2. package/CLAUDE.md +67 -11
  3. package/README.md +204 -22
  4. package/bin/reldens-storage.js +43 -10
  5. package/index.js +53 -12
  6. package/lib/drizzle/drizzle-data-server.js +123 -0
  7. package/lib/drizzle/drizzle-driver.js +228 -0
  8. package/lib/drizzle/drizzle-models-generation.js +49 -0
  9. package/lib/drizzle/drizzle-modules-loader.js +32 -0
  10. package/lib/drizzle/drizzle-modules-validator.js +74 -0
  11. package/lib/entities-generator.js +36 -7
  12. package/lib/entity-templates/drizzle-model.template +28 -0
  13. package/lib/entity-templates/query-builder-model.template +17 -0
  14. package/lib/generators/base-generator.js +13 -0
  15. package/lib/generators/entities-generation.js +0 -13
  16. package/lib/generators/models-generation.js +179 -693
  17. package/lib/generators/relations-detection.js +171 -0
  18. package/lib/knex/knex-data-server.js +128 -0
  19. package/lib/knex/knex-driver.js +157 -0
  20. package/lib/knex/knex-modules-loader.js +28 -0
  21. package/lib/knex/knex-modules-validator.js +45 -0
  22. package/lib/kysely/kysely-data-server.js +128 -0
  23. package/lib/kysely/kysely-driver.js +176 -0
  24. package/lib/kysely/kysely-modules-loader.js +28 -0
  25. package/lib/kysely/kysely-modules-validator.js +53 -0
  26. package/lib/mikro-orm/mikro-orm-data-server.js +48 -29
  27. package/lib/mikro-orm/mikro-orm-driver.js +385 -121
  28. package/lib/mikro-orm/mikro-orm-models-generation.js +178 -0
  29. package/lib/mikro-orm/mikro-orm-modules-loader.js +50 -0
  30. package/lib/mikro-orm/mikro-orm-modules-validator.js +46 -0
  31. package/lib/mysql2-connection-config.js +38 -0
  32. package/lib/objection-js/objection-js-data-server.js +71 -125
  33. package/lib/objection-js/objection-js-driver.js +4 -1
  34. package/lib/objection-js/objection-js-models-generation.js +98 -0
  35. package/lib/objection-js/objection-modules-loader.js +28 -0
  36. package/lib/objection-js/objection-modules-validator.js +31 -0
  37. package/lib/package-resolver.js +45 -0
  38. package/lib/prisma/prisma-client-loader.js +11 -1
  39. package/lib/prisma/prisma-driver.js +16 -2
  40. package/lib/prisma/prisma-filter-processor.js +5 -6
  41. package/lib/prisma/prisma-models-generation.js +236 -0
  42. package/lib/prisma/prisma-schema-generator.js +5 -1
  43. package/lib/prisma/prisma-type-caster.js +14 -0
  44. package/lib/query-builder-driver.js +210 -0
  45. package/lib/query-builder-models-generation.js +80 -0
  46. package/lib/relations-loader.js +192 -0
  47. package/lib/type-mapper.js +38 -0
  48. package/package.json +7 -7
  49. package/tests/.env.test.example +6 -0
  50. package/tests/fixtures/categories-fixtures.js +8 -0
  51. package/tests/fixtures/expected-entities/entities/test-product-details-entity.js +72 -0
  52. package/tests/fixtures/expected-entities/entities-config.js +6 -4
  53. package/tests/fixtures/expected-entities/entities-translations.js +25 -14
  54. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/registered-models-mikro-orm.js +2 -0
  55. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-categories-model.js +13 -8
  56. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-product-details-model.js +80 -0
  57. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-products-model.js +20 -14
  58. package/tests/fixtures/expected-entities-mikro-orm/models/mikro-orm/test-reviews-model.js +13 -12
  59. package/tests/fixtures/expected-entities-objection-js/models/objection-js/registered-models-objection-js.js +2 -0
  60. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-product-details-model.js +33 -0
  61. package/tests/fixtures/expected-entities-objection-js/models/objection-js/test-products-model.js +9 -0
  62. package/tests/fixtures/expected-entities-prisma/models/prisma/registered-models-prisma.js +2 -0
  63. package/tests/fixtures/expected-entities-prisma/models/prisma/test-product-details-model.js +43 -0
  64. package/tests/fixtures/expected-entities-prisma/models/prisma/test-products-model.js +2 -0
  65. package/tests/fixtures/product-details-fixtures.js +22 -0
  66. package/tests/fixtures/reviews-fixtures.js +2 -1
  67. package/tests/fixtures/sql/test-schema.sql +23 -1
  68. package/tests/fixtures/table-columns-fixtures.js +97 -0
  69. package/tests/integration/test-cross-driver-equivalence.js +286 -0
  70. package/tests/integration/test-nested-filters.js +265 -408
  71. package/tests/integration/test-relations.js +199 -0
  72. package/tests/integration/test-reldens-sample-data.js +255 -0
  73. package/tests/integration/test-reldens-shape-relations.js +292 -0
  74. package/tests/run-tests.js +112 -60
  75. package/tests/unit/test-drivers.js +7 -1
  76. package/tests/unit/test-models-generation.js +174 -0
  77. package/tests/utils/column-value-assert.js +139 -0
  78. package/tests/utils/driver-registry.js +1 -1
  79. package/tests/utils/entity-projection.js +111 -0
  80. package/tests/utils/observed-value.js +41 -0
  81. package/tests/utils/projection-difference.js +151 -0
  82. package/tests/utils/relations-shape-support.js +104 -0
  83. package/tests/utils/reldens-schema-loader.js +274 -0
  84. package/tests/utils/test-helpers.js +152 -19
  85. package/tests/utils/test-runner.js +26 -5
  86. package/tests/utils/whole-result-assert.js +243 -0
@@ -17,6 +17,7 @@ class TestRunner
17
17
  this.failedCount = 0;
18
18
  this.currentSuite = '';
19
19
  this.currentGroup = '';
20
+ this.totalDuration = 0;
20
21
  }
21
22
 
22
23
  suite(name)
@@ -38,22 +39,42 @@ class TestRunner
38
39
  try {
39
40
  await testFn();
40
41
  this.passedCount++;
41
- let duration = Date.now() - startTime;
42
- Logger.info(' ✔ '+name+' ('+duration+'ms)');
42
+ this.logTestResult(name, startTime, false);
43
43
  } catch(error) {
44
44
  this.failedCount++;
45
- let duration = Date.now() - startTime;
46
- Logger.error(' ✖ '+name+' ('+duration+'ms)');
45
+ this.logTestResult(name, startTime, true);
47
46
  Logger.error(' Error: '+error.message);
48
47
  }
49
48
  }
50
49
 
50
+ fail(name, message)
51
+ {
52
+ this.testCount++;
53
+ this.failedCount++;
54
+ Logger.error(' ✖ '+name);
55
+ Logger.error(' Error: '+message);
56
+ return this.getResults();
57
+ }
58
+
59
+ logTestResult(name, startTime, hasFailed)
60
+ {
61
+ let duration = Date.now() - startTime;
62
+ this.totalDuration += duration;
63
+ if(hasFailed){
64
+ Logger.error(' ✖ '+name+' ('+duration+'ms)');
65
+ return duration;
66
+ }
67
+ Logger.info(' ✔ '+name+' ('+duration+'ms)');
68
+ return duration;
69
+ }
70
+
51
71
  getResults()
52
72
  {
53
73
  return {
54
74
  total: this.testCount,
55
75
  passed: this.passedCount,
56
- failed: this.failedCount
76
+ failed: this.failedCount,
77
+ duration: this.totalDuration
57
78
  };
58
79
  }
59
80
 
@@ -0,0 +1,243 @@
1
+ /**
2
+ *
3
+ * Reldens - Whole Result Assert
4
+ * Full output assertions shared by every driver integration suite: the exact own enumerable key set of a
5
+ * record, the exact value of every column against the inserted fixture row, the exact length and full
6
+ * content of a list, the exact shape and content of every relation, and the exact count numbers. The very
7
+ * same expectations run on every driver, no driver is allowed an exception.
8
+ *
9
+ */
10
+
11
+ const assert = require('node:assert');
12
+ const { ColumnValueAssert } = require('./column-value-assert');
13
+ const { ObservedValue } = require('./observed-value');
14
+ const { TableColumnsFixtures } = require('../fixtures/table-columns-fixtures');
15
+ const { sc } = require('@reldens/utils');
16
+
17
+ class WholeResultAssert
18
+ {
19
+
20
+ static expectedRow(tableName, fixtureRow, overrides)
21
+ {
22
+ let tableSpec = TableColumnsFixtures[tableName];
23
+ let expectedRow = {};
24
+ for(let columnName of Object.keys(tableSpec.columns)){
25
+ expectedRow[columnName] = this.expectedColumnValue(tableSpec, fixtureRow, columnName);
26
+ }
27
+ if(sc.isObject(overrides)){
28
+ Object.assign(expectedRow, overrides);
29
+ }
30
+ return expectedRow;
31
+ }
32
+
33
+ static expectedColumnValue(tableSpec, fixtureRow, columnName)
34
+ {
35
+ if(sc.hasOwn(fixtureRow, columnName)){
36
+ return fixtureRow[columnName];
37
+ }
38
+ if(sc.hasOwn(tableSpec.defaults, columnName)){
39
+ return tableSpec.defaults[columnName];
40
+ }
41
+ return ColumnValueAssert.anyValue;
42
+ }
43
+
44
+ static expectedRows(tableName, fixtureRows)
45
+ {
46
+ let expectedRows = [];
47
+ for(let fixtureRow of fixtureRows){
48
+ expectedRows.push(this.expectedRow(tableName, fixtureRow, null));
49
+ }
50
+ return expectedRows;
51
+ }
52
+
53
+ static fixtureRecord(actualRecord, tableName, fixtureRow, label)
54
+ {
55
+ return this.record(actualRecord, {
56
+ table: tableName,
57
+ expected: this.expectedRow(tableName, fixtureRow, null),
58
+ label: label
59
+ });
60
+ }
61
+
62
+ static fixtureList(actualList, tableName, fixtureRows, label)
63
+ {
64
+ return this.list(actualList, {
65
+ table: tableName,
66
+ expected: this.expectedRows(tableName, fixtureRows),
67
+ label: label
68
+ });
69
+ }
70
+
71
+ static record(actualRecord, props)
72
+ {
73
+ let label = props.label;
74
+ assert.ok(actualRecord, label+' was not returned, observed: '+ObservedValue.describe(actualRecord));
75
+ assert.strictEqual(
76
+ typeof actualRecord,
77
+ 'object',
78
+ label+' is not a record object, observed: '+ObservedValue.describe(actualRecord)
79
+ );
80
+ let relations = sc.get(props, 'relations', {});
81
+ this.assertKeySet(actualRecord, props.table, relations, label);
82
+ ColumnValueAssert.assertColumns(actualRecord, props.table, props.expected, label);
83
+ this.assertRelations(actualRecord, relations, label);
84
+ return true;
85
+ }
86
+
87
+ static list(actualList, props)
88
+ {
89
+ let label = props.label;
90
+ assert.ok(sc.isArray(actualList), label+' is not an array, observed: '+ObservedValue.describe(actualList));
91
+ assert.strictEqual(
92
+ actualList.length,
93
+ props.expected.length,
94
+ label+' length mismatch, expected '+props.expected.length+' records but observed '+actualList.length
95
+ +' with ids: ['+this.listIds(actualList).join(', ')+']'
96
+ );
97
+ let orderedList = sc.isTrue(props, 'keepOrder') ? actualList : this.sortedById(actualList);
98
+ for(let i = 0; i < props.expected.length; i++){
99
+ this.recordAt(orderedList, props, i);
100
+ }
101
+ return true;
102
+ }
103
+
104
+ static recordAt(orderedList, props, index)
105
+ {
106
+ return this.record(orderedList[index], {
107
+ table: props.table,
108
+ expected: props.expected[index],
109
+ relations: this.relationsAt(sc.get(props, 'relations', {}), index),
110
+ label: props.label+'['+index+']'
111
+ });
112
+ }
113
+
114
+ static relationsAt(relations, index)
115
+ {
116
+ if(sc.isArray(relations)){
117
+ return relations[index];
118
+ }
119
+ return relations;
120
+ }
121
+
122
+ static count(actualCount, expectedCount, label)
123
+ {
124
+ assert.strictEqual(
125
+ typeof actualCount,
126
+ 'number',
127
+ label+' is not a number, observed: '+ObservedValue.describe(actualCount)
128
+ );
129
+ assert.strictEqual(
130
+ actualCount,
131
+ expectedCount,
132
+ label+' expected '+expectedCount+' but observed: '+ObservedValue.describe(actualCount)
133
+ );
134
+ return true;
135
+ }
136
+
137
+ static failedOperation(actualError, label)
138
+ {
139
+ assert.ok(
140
+ actualError,
141
+ label+' must be rejected by the database but no error was thrown'
142
+ );
143
+ return true;
144
+ }
145
+
146
+ static assertKeySet(actualRecord, tableName, relations, label)
147
+ {
148
+ let expectedKeys = Object.keys(TableColumnsFixtures[tableName].columns);
149
+ let allExpectedKeys = expectedKeys.concat(Object.keys(sc.isObject(relations) ? relations : {}));
150
+ let observedKeys = Object.keys(actualRecord);
151
+ let missingKeys = this.missingFrom(allExpectedKeys, observedKeys);
152
+ let notExpectedKeys = this.missingFrom(observedKeys, allExpectedKeys);
153
+ assert.strictEqual(
154
+ missingKeys.join(',')+'|'+notExpectedKeys.join(','),
155
+ '|',
156
+ label+' key set mismatch, missing: ['+missingKeys.join(', ')+'], not expected: ['
157
+ +notExpectedKeys.join(', ')+'], observed: ['+observedKeys.join(', ')+']'
158
+ );
159
+ return true;
160
+ }
161
+
162
+ static missingFrom(sourceKeys, targetKeys)
163
+ {
164
+ let missingKeys = [];
165
+ for(let key of sourceKeys){
166
+ if(-1 === targetKeys.indexOf(key)){
167
+ missingKeys.push(key);
168
+ }
169
+ }
170
+ return missingKeys;
171
+ }
172
+
173
+ static assertRelations(actualRecord, relations, label)
174
+ {
175
+ if(!sc.isObject(relations)){
176
+ return true;
177
+ }
178
+ for(let relationKey of Object.keys(relations)){
179
+ this.assertRelation(actualRecord[relationKey], relations[relationKey], label+'.'+relationKey);
180
+ }
181
+ return true;
182
+ }
183
+
184
+ static assertRelation(relationValue, relationProps, label)
185
+ {
186
+ if(null === relationProps.expected){
187
+ assert.strictEqual(
188
+ relationValue,
189
+ null,
190
+ label+' expected an explicit null relation, observed: '+ObservedValue.describe(relationValue)
191
+ );
192
+ return true;
193
+ }
194
+ if(sc.isArray(relationProps.expected)){
195
+ assert.ok(
196
+ sc.isArray(relationValue),
197
+ label+' expected an array relation, observed: '+ObservedValue.describe(relationValue)
198
+ );
199
+ return this.list(relationValue, this.relationProps(relationProps, label));
200
+ }
201
+ assert.ok(
202
+ !sc.isArray(relationValue),
203
+ label+' expected a single record relation but observed an array: '
204
+ +ObservedValue.describe(relationValue)
205
+ );
206
+ return this.record(relationValue, this.relationProps(relationProps, label));
207
+ }
208
+
209
+ static relationProps(relationProps, label)
210
+ {
211
+ return {
212
+ table: relationProps.table,
213
+ expected: relationProps.expected,
214
+ relations: sc.get(relationProps, 'relations', {}),
215
+ keepOrder: sc.get(relationProps, 'keepOrder', false),
216
+ label: label
217
+ };
218
+ }
219
+
220
+ static listIds(actualList)
221
+ {
222
+ let ids = [];
223
+ for(let actualRecord of actualList){
224
+ ids.push(sc.get(actualRecord, 'id', ObservedValue.describe(actualRecord)));
225
+ }
226
+ return ids;
227
+ }
228
+
229
+ static sortedById(actualList)
230
+ {
231
+ let orderedList = [];
232
+ for(let actualRecord of actualList){
233
+ orderedList.push(actualRecord);
234
+ }
235
+ orderedList.sort((firstRecord, secondRecord) => {
236
+ return Number(sc.get(firstRecord, 'id', 0)) - Number(sc.get(secondRecord, 'id', 0));
237
+ });
238
+ return orderedList;
239
+ }
240
+
241
+ }
242
+
243
+ module.exports.WholeResultAssert = WholeResultAssert;