pure-orm 4.0.3 → 4.1.5

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 (204) hide show
  1. package/.benchmarks/bench-core-baseline.json +303 -0
  2. package/README.md +0 -7
  3. package/babel.config.js +4 -1
  4. package/coverage/clover.xml +1549 -1284
  5. package/coverage/coverage-final.json +103 -103
  6. package/coverage/lcov-report/dist/src/core.js.html +610 -379
  7. package/coverage/lcov-report/dist/src/driver-integrations/index.html +20 -20
  8. package/coverage/lcov-report/dist/src/driver-integrations/pgp.js.html +52 -52
  9. package/coverage/lcov-report/dist/src/index.html +28 -28
  10. package/coverage/lcov-report/dist/src/index.js.html +2 -2
  11. package/coverage/lcov-report/dist/src/orm.js.html +392 -209
  12. package/coverage/lcov-report/dist/test-utils/blog/entities.js.html +1 -1
  13. package/coverage/lcov-report/dist/test-utils/blog/index.html +1 -1
  14. package/coverage/lcov-report/dist/test-utils/blog/models/article.js.html +15 -15
  15. package/coverage/lcov-report/dist/test-utils/blog/models/article_tag.js.html +2 -2
  16. package/coverage/lcov-report/dist/test-utils/blog/models/index.html +1 -1
  17. package/coverage/lcov-report/dist/test-utils/blog/models/person.js.html +13 -13
  18. package/coverage/lcov-report/dist/test-utils/blog/models/tag.js.html +1 -1
  19. package/coverage/lcov-report/dist/test-utils/five/entities.js.html +1 -1
  20. package/coverage/lcov-report/dist/test-utils/five/index.html +1 -1
  21. package/coverage/lcov-report/dist/test-utils/five/models/index.html +1 -1
  22. package/coverage/lcov-report/dist/test-utils/five/models/line-item.js.html +4 -4
  23. package/coverage/lcov-report/dist/test-utils/five/models/order.js.html +3 -3
  24. package/coverage/lcov-report/dist/test-utils/five/models/parcel-event.js.html +6 -6
  25. package/coverage/lcov-report/dist/test-utils/five/models/parcel-line-item.js.html +7 -7
  26. package/coverage/lcov-report/dist/test-utils/five/models/parcel.js.html +2 -2
  27. package/coverage/lcov-report/dist/test-utils/fourteen/entities.js.html +1 -1
  28. package/coverage/lcov-report/dist/test-utils/fourteen/index.html +1 -1
  29. package/coverage/lcov-report/dist/test-utils/fourteen/models/customer.js.html +1 -1
  30. package/coverage/lcov-report/dist/test-utils/fourteen/models/index.html +1 -1
  31. package/coverage/lcov-report/dist/test-utils/fourteen/models/person.js.html +1 -1
  32. package/coverage/lcov-report/dist/test-utils/nine/entities.js.html +1 -1
  33. package/coverage/lcov-report/dist/test-utils/nine/index.html +1 -1
  34. package/coverage/lcov-report/dist/test-utils/nine/models/feature-switch.js.html +6 -6
  35. package/coverage/lcov-report/dist/test-utils/nine/models/index.html +1 -1
  36. package/coverage/lcov-report/dist/test-utils/order/entities.js.html +9 -9
  37. package/coverage/lcov-report/dist/test-utils/order/index.html +1 -1
  38. package/coverage/lcov-report/dist/test-utils/order/models/index.html +14 -14
  39. package/coverage/lcov-report/dist/test-utils/order/models/line-item.js.html +24 -24
  40. package/coverage/lcov-report/dist/test-utils/order/models/order.js.html +41 -41
  41. package/coverage/lcov-report/dist/test-utils/order/models/product-variant.js.html +32 -32
  42. package/coverage/lcov-report/dist/test-utils/order/models/product.js.html +17 -17
  43. package/coverage/lcov-report/dist/test-utils/order/models/utm-source.js.html +12 -12
  44. package/coverage/lcov-report/dist/test-utils/order-more/entities.js.html +1 -1
  45. package/coverage/lcov-report/dist/test-utils/order-more/index.html +1 -1
  46. package/coverage/lcov-report/dist/test-utils/order-more/models/actual-product-variant.js.html +3 -3
  47. package/coverage/lcov-report/dist/test-utils/order-more/models/color.js.html +6 -6
  48. package/coverage/lcov-report/dist/test-utils/order-more/models/customer.js.html +3 -3
  49. package/coverage/lcov-report/dist/test-utils/order-more/models/gender.js.html +4 -4
  50. package/coverage/lcov-report/dist/test-utils/order-more/models/index.html +23 -23
  51. package/coverage/lcov-report/dist/test-utils/order-more/models/inventory-level.js.html +11 -11
  52. package/coverage/lcov-report/dist/test-utils/order-more/models/line-item.js.html +15 -15
  53. package/coverage/lcov-report/dist/test-utils/order-more/models/order.js.html +39 -39
  54. package/coverage/lcov-report/dist/test-utils/order-more/models/parcel-event.js.html +6 -6
  55. package/coverage/lcov-report/dist/test-utils/order-more/models/parcel-line-item.js.html +7 -7
  56. package/coverage/lcov-report/dist/test-utils/order-more/models/parcel.js.html +2 -2
  57. package/coverage/lcov-report/dist/test-utils/order-more/models/physical-address.js.html +12 -12
  58. package/coverage/lcov-report/dist/test-utils/order-more/models/product-variant-image.js.html +7 -7
  59. package/coverage/lcov-report/dist/test-utils/order-more/models/product-variant.js.html +22 -22
  60. package/coverage/lcov-report/dist/test-utils/order-more/models/product.js.html +11 -11
  61. package/coverage/lcov-report/dist/test-utils/order-more/models/refund.js.html +11 -11
  62. package/coverage/lcov-report/dist/test-utils/order-more/models/shipment-actual-product-variant.js.html +9 -9
  63. package/coverage/lcov-report/dist/test-utils/order-more/models/shipment.js.html +4 -4
  64. package/coverage/lcov-report/dist/test-utils/order-more/models/size.js.html +4 -4
  65. package/coverage/lcov-report/dist/test-utils/order-more/models/utm-medium.js.html +15 -15
  66. package/coverage/lcov-report/dist/test-utils/order-more/models/utm-source.js.html +17 -17
  67. package/coverage/lcov-report/dist/test-utils/six/entities.js.html +1 -1
  68. package/coverage/lcov-report/dist/test-utils/six/index.html +1 -1
  69. package/coverage/lcov-report/dist/test-utils/six/models/customer.js.html +3 -3
  70. package/coverage/lcov-report/dist/test-utils/six/models/index.html +21 -21
  71. package/coverage/lcov-report/dist/test-utils/six/models/line-item.js.html +10 -10
  72. package/coverage/lcov-report/dist/test-utils/six/models/order.js.html +13 -13
  73. package/coverage/lcov-report/dist/test-utils/six/models/parcel-line-item.js.html +2 -2
  74. package/coverage/lcov-report/dist/test-utils/six/models/parcel.js.html +2 -2
  75. package/coverage/lcov-report/dist/test-utils/thirteen/entities.js.html +1 -1
  76. package/coverage/lcov-report/dist/test-utils/thirteen/index.html +1 -1
  77. package/coverage/lcov-report/dist/test-utils/thirteen/models/audience.js.html +2 -2
  78. package/coverage/lcov-report/dist/test-utils/thirteen/models/brand.js.html +2 -2
  79. package/coverage/lcov-report/dist/test-utils/thirteen/models/category.js.html +1 -1
  80. package/coverage/lcov-report/dist/test-utils/thirteen/models/index.html +14 -14
  81. package/coverage/lcov-report/dist/test-utils/thirteen/models/member.js.html +2 -2
  82. package/coverage/lcov-report/dist/test-utils/thirteen/models/passion.js.html +2 -2
  83. package/coverage/lcov-report/dist/test-utils/thirteen/models/product.js.html +10 -10
  84. package/coverage/lcov-report/dist/test-utils/thirteen/models/recommendation-audience.js.html +2 -2
  85. package/coverage/lcov-report/dist/test-utils/thirteen/models/recommendation.js.html +2 -2
  86. package/coverage/lcov-report/dist/test-utils/three/index.html +1 -1
  87. package/coverage/lcov-report/dist/test-utils/three/results.js.html +1 -1
  88. package/coverage/lcov-report/dist/test-utils/twelve/entities.js.html +1 -1
  89. package/coverage/lcov-report/dist/test-utils/twelve/index.html +1 -1
  90. package/coverage/lcov-report/dist/test-utils/twelve/models/index.html +1 -1
  91. package/coverage/lcov-report/dist/test-utils/twelve/models/member.js.html +1 -1
  92. package/coverage/lcov-report/dist/test-utils/twelve/models/prompt.js.html +2 -2
  93. package/coverage/lcov-report/dist/test-utils/two/index.html +1 -1
  94. package/coverage/lcov-report/dist/test-utils/two/results.js.html +1 -1
  95. package/coverage/lcov-report/index.html +103 -103
  96. package/coverage/lcov-report/src/core.ts.html +853 -415
  97. package/coverage/lcov-report/src/driver-integrations/index.html +20 -20
  98. package/coverage/lcov-report/src/driver-integrations/pgp.ts.html +58 -58
  99. package/coverage/lcov-report/src/index.html +28 -28
  100. package/coverage/lcov-report/src/index.ts.html +1 -1
  101. package/coverage/lcov-report/src/orm.ts.html +361 -298
  102. package/coverage/lcov-report/test-utils/blog/entities.ts.html +1 -1
  103. package/coverage/lcov-report/test-utils/blog/index.html +1 -1
  104. package/coverage/lcov-report/test-utils/blog/models/article.ts.html +15 -15
  105. package/coverage/lcov-report/test-utils/blog/models/article_tag.ts.html +2 -2
  106. package/coverage/lcov-report/test-utils/blog/models/index.html +1 -1
  107. package/coverage/lcov-report/test-utils/blog/models/person.ts.html +13 -13
  108. package/coverage/lcov-report/test-utils/blog/models/tag.ts.html +1 -1
  109. package/coverage/lcov-report/test-utils/five/entities.ts.html +1 -1
  110. package/coverage/lcov-report/test-utils/five/index.html +1 -1
  111. package/coverage/lcov-report/test-utils/five/models/index.html +1 -1
  112. package/coverage/lcov-report/test-utils/five/models/line-item.ts.html +4 -4
  113. package/coverage/lcov-report/test-utils/five/models/order.ts.html +3 -3
  114. package/coverage/lcov-report/test-utils/five/models/parcel-event.ts.html +6 -6
  115. package/coverage/lcov-report/test-utils/five/models/parcel-line-item.ts.html +7 -7
  116. package/coverage/lcov-report/test-utils/five/models/parcel.ts.html +2 -2
  117. package/coverage/lcov-report/test-utils/fourteen/entities.ts.html +1 -1
  118. package/coverage/lcov-report/test-utils/fourteen/index.html +1 -1
  119. package/coverage/lcov-report/test-utils/fourteen/models/customer.ts.html +1 -1
  120. package/coverage/lcov-report/test-utils/fourteen/models/index.html +1 -1
  121. package/coverage/lcov-report/test-utils/fourteen/models/person.ts.html +1 -1
  122. package/coverage/lcov-report/test-utils/nine/entities.ts.html +1 -1
  123. package/coverage/lcov-report/test-utils/nine/index.html +1 -1
  124. package/coverage/lcov-report/test-utils/nine/models/feature-switch.ts.html +8 -8
  125. package/coverage/lcov-report/test-utils/nine/models/index.html +1 -1
  126. package/coverage/lcov-report/test-utils/order/entities.ts.html +2 -2
  127. package/coverage/lcov-report/test-utils/order/index.html +1 -1
  128. package/coverage/lcov-report/test-utils/order/models/index.html +14 -14
  129. package/coverage/lcov-report/test-utils/order/models/line-item.ts.html +18 -18
  130. package/coverage/lcov-report/test-utils/order/models/order.ts.html +36 -36
  131. package/coverage/lcov-report/test-utils/order/models/product-variant.ts.html +27 -27
  132. package/coverage/lcov-report/test-utils/order/models/product.ts.html +13 -13
  133. package/coverage/lcov-report/test-utils/order/models/utm-source.ts.html +8 -8
  134. package/coverage/lcov-report/test-utils/order-more/entities.ts.html +1 -1
  135. package/coverage/lcov-report/test-utils/order-more/index.html +1 -1
  136. package/coverage/lcov-report/test-utils/order-more/models/actual-product-variant.ts.html +3 -3
  137. package/coverage/lcov-report/test-utils/order-more/models/color.ts.html +6 -6
  138. package/coverage/lcov-report/test-utils/order-more/models/customer.ts.html +3 -3
  139. package/coverage/lcov-report/test-utils/order-more/models/gender.ts.html +4 -4
  140. package/coverage/lcov-report/test-utils/order-more/models/index.html +23 -23
  141. package/coverage/lcov-report/test-utils/order-more/models/inventory-level.ts.html +11 -11
  142. package/coverage/lcov-report/test-utils/order-more/models/line-item.ts.html +15 -15
  143. package/coverage/lcov-report/test-utils/order-more/models/order.ts.html +45 -45
  144. package/coverage/lcov-report/test-utils/order-more/models/parcel-event.ts.html +6 -6
  145. package/coverage/lcov-report/test-utils/order-more/models/parcel-line-item.ts.html +7 -7
  146. package/coverage/lcov-report/test-utils/order-more/models/parcel.ts.html +2 -2
  147. package/coverage/lcov-report/test-utils/order-more/models/physical-address.ts.html +12 -12
  148. package/coverage/lcov-report/test-utils/order-more/models/product-variant-image.ts.html +7 -7
  149. package/coverage/lcov-report/test-utils/order-more/models/product-variant.ts.html +22 -22
  150. package/coverage/lcov-report/test-utils/order-more/models/product.ts.html +11 -11
  151. package/coverage/lcov-report/test-utils/order-more/models/refund.ts.html +11 -11
  152. package/coverage/lcov-report/test-utils/order-more/models/shipment-actual-product-variant.ts.html +9 -9
  153. package/coverage/lcov-report/test-utils/order-more/models/shipment.ts.html +4 -4
  154. package/coverage/lcov-report/test-utils/order-more/models/size.ts.html +4 -4
  155. package/coverage/lcov-report/test-utils/order-more/models/utm-medium.ts.html +15 -15
  156. package/coverage/lcov-report/test-utils/order-more/models/utm-source.ts.html +17 -17
  157. package/coverage/lcov-report/test-utils/six/entities.ts.html +1 -1
  158. package/coverage/lcov-report/test-utils/six/index.html +1 -1
  159. package/coverage/lcov-report/test-utils/six/models/customer.ts.html +3 -3
  160. package/coverage/lcov-report/test-utils/six/models/index.html +21 -21
  161. package/coverage/lcov-report/test-utils/six/models/line-item.ts.html +10 -10
  162. package/coverage/lcov-report/test-utils/six/models/order.ts.html +13 -13
  163. package/coverage/lcov-report/test-utils/six/models/parcel-line-item.ts.html +2 -2
  164. package/coverage/lcov-report/test-utils/six/models/parcel.ts.html +2 -2
  165. package/coverage/lcov-report/test-utils/thirteen/entities.ts.html +1 -1
  166. package/coverage/lcov-report/test-utils/thirteen/index.html +1 -1
  167. package/coverage/lcov-report/test-utils/thirteen/models/audience.ts.html +2 -2
  168. package/coverage/lcov-report/test-utils/thirteen/models/brand.ts.html +2 -2
  169. package/coverage/lcov-report/test-utils/thirteen/models/category.ts.html +1 -1
  170. package/coverage/lcov-report/test-utils/thirteen/models/index.html +14 -14
  171. package/coverage/lcov-report/test-utils/thirteen/models/member.ts.html +2 -2
  172. package/coverage/lcov-report/test-utils/thirteen/models/passion.ts.html +2 -2
  173. package/coverage/lcov-report/test-utils/thirteen/models/product.ts.html +10 -10
  174. package/coverage/lcov-report/test-utils/thirteen/models/recommendation-audience.ts.html +2 -2
  175. package/coverage/lcov-report/test-utils/thirteen/models/recommendation.ts.html +2 -2
  176. package/coverage/lcov-report/test-utils/three/index.html +1 -1
  177. package/coverage/lcov-report/test-utils/three/results.js.html +1 -1
  178. package/coverage/lcov-report/test-utils/twelve/entities.ts.html +1 -1
  179. package/coverage/lcov-report/test-utils/twelve/index.html +1 -1
  180. package/coverage/lcov-report/test-utils/twelve/models/index.html +1 -1
  181. package/coverage/lcov-report/test-utils/twelve/models/member.ts.html +1 -1
  182. package/coverage/lcov-report/test-utils/twelve/models/prompt.ts.html +2 -2
  183. package/coverage/lcov-report/test-utils/two/index.html +1 -1
  184. package/coverage/lcov-report/test-utils/two/results.js.html +1 -1
  185. package/coverage/lcov.info +2196 -2047
  186. package/dist/src/core.d.ts +6 -0
  187. package/dist/src/core.js +265 -188
  188. package/dist/src/core.spec.js +424 -0
  189. package/dist/src/driver-integrations/pgp.spec.d.ts +1 -0
  190. package/dist/src/driver-integrations/pgp.spec.js +376 -0
  191. package/dist/src/orm.d.ts +1 -1
  192. package/dist/src/orm.js +137 -76
  193. package/dist/src/orm.spec.js +535 -85
  194. package/dist/test-utils/nine/models/feature-switch.d.ts +2 -2
  195. package/dist/test-utils/nine/models/feature-switch.ts +2 -2
  196. package/package.json +5 -3
  197. package/scripts/bench-core.js +636 -0
  198. package/scripts/check-bench-scenarios.js +47 -0
  199. package/src/core.spec.ts +498 -2
  200. package/src/core.ts +373 -227
  201. package/src/driver-integrations/pgp.spec.ts +444 -0
  202. package/src/orm.spec.ts +592 -88
  203. package/src/orm.ts +149 -128
  204. package/test-utils/nine/models/feature-switch.ts +2 -2
@@ -0,0 +1,47 @@
1
+ /* eslint-disable no-console */
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ const benchPath = path.resolve(__dirname, 'bench-core.js');
6
+ const source = fs.readFileSync(benchPath, 'utf8');
7
+
8
+ const failures = [];
9
+
10
+ const requirePattern = (pattern, message) => {
11
+ if (!pattern.test(source)) {
12
+ failures.push(message);
13
+ }
14
+ };
15
+
16
+ // Guardrail: keep broad fixture-mixing scenarios.
17
+ requirePattern(
18
+ /label:\s*'stress\/mixed-fixtures x80'/,
19
+ "Missing stress scenario label 'stress/mixed-fixtures x80'."
20
+ );
21
+ requirePattern(
22
+ /label:\s*'stress\/mixed-sparse x80'/,
23
+ "Missing stress scenario label 'stress/mixed-sparse x80'."
24
+ );
25
+
26
+ // Guardrail: keep heterogeneous input families and adversarial ordering.
27
+ requirePattern(
28
+ /baseRowsSet:\s*\[[^\]]*seven[^\]]*eight[^\]]*ten[^\]]*eleven[^\]]*\]/s,
29
+ 'Missing heterogeneous baseRowsSet coverage over multiple fixtures.'
30
+ );
31
+ requirePattern(
32
+ /shuffleRows:\s*true/,
33
+ 'Missing shuffled-row stress coverage (shuffleRows: true).'
34
+ );
35
+
36
+ if (failures.length > 0) {
37
+ console.error('\nBenchmark scenario guard failed:');
38
+ for (const failure of failures) {
39
+ console.error(`- ${failure}`);
40
+ }
41
+ console.error(
42
+ '\nThis guard prevents benchmark drift back to narrow, overfit scenarios.'
43
+ );
44
+ process.exit(1);
45
+ }
46
+
47
+ console.log('Benchmark scenario guard passed.');
package/src/core.spec.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable max-len */
2
- import { createCore } from './core';
2
+ import { createCore, IModel, ICollection, IColumns } from './core';
3
3
  import { entities as orderEntities } from '../test-utils/order/entities';
4
4
  import { entities as blogEntities } from '../test-utils/blog/entities';
5
5
  import { entities as orderMoreEntities } from '../test-utils/order-more/entities';
@@ -9,7 +9,10 @@ import { entities as sixEntities } from '../test-utils/six/entities';
9
9
  import { entities as twelveEntities } from '../test-utils/twelve/entities';
10
10
  import { entities as thirteenEntities } from '../test-utils/thirteen/entities';
11
11
  import { entities as fourteenEntities } from '../test-utils/fourteen/entities';
12
- import { Articles } from '../test-utils/blog/models/article';
12
+ import { Articles, Article } from '../test-utils/blog/models/article';
13
+ import { Order, Orders } from '../test-utils/order/models/order';
14
+ import { UtmSource } from '../test-utils/order/models/utm-source';
15
+ import { FeatureSwitch } from '../test-utils/nine/models/feature-switch';
13
16
  const two = require('../test-utils/two/results');
14
17
  const three = require('../test-utils/three/results');
15
18
  const one = require('../test-utils/one/results.json');
@@ -26,6 +29,19 @@ const thirteen = require('../test-utils/thirteen/results.json');
26
29
  const fourteen = require('../test-utils/fourteen/results.json');
27
30
 
28
31
  describe('createFromDatabase', () => {
32
+ test('models can be JSON.stringified without circular errors', () => {
33
+ const core = createCore({ entities: orderEntities });
34
+ const orders = core.createFromDatabase(one);
35
+ const order = orders.models[0];
36
+
37
+ expect(() => JSON.stringify(order)).not.toThrow();
38
+ const serialized = JSON.parse(JSON.stringify(order));
39
+ expect(serialized.id).toEqual(3866);
40
+ expect(serialized.utmSource.id).toEqual(6);
41
+ // Reverse collections remain usable in-memory but are not serialized.
42
+ expect(serialized.utmSource.orders).toBeUndefined();
43
+ });
44
+
29
45
  test('multiple rows reduce to one nested object (with all one-to-one or one-to-many tables)', () => {
30
46
  const core = createCore({ entities: orderEntities });
31
47
  const orders = core.createFromDatabase(one);
@@ -1186,3 +1202,483 @@ test('tables', () => {
1186
1202
  '"product".id as "product#id", "product".vendor_id as "product#vendor_id", "product".value as "product#value", "product".label as "product#label", "product".product_type as "product#product_type", "product".created_date as "product#created_date", "product".updated_date as "product#updated_date", "product".published_date as "product#published_date", "product".category as "product#category"'
1187
1203
  );
1188
1204
  });
1205
+
1206
+ /* -------------------------------------------------------------------------*/
1207
+ /* getEntityByModel --------------------------------------------------------*/
1208
+ /* -------------------------------------------------------------------------*/
1209
+
1210
+ describe('getEntityByModel', () => {
1211
+ test('returns entity for a model instance', () => {
1212
+ const core = createCore({ entities: orderEntities });
1213
+ const order = new Order({ id: 1, email: 'x@x.com' });
1214
+ const entity = core.getEntityByModel(order);
1215
+ expect(entity.tableName).toEqual('order');
1216
+ expect(entity.displayName).toEqual('order');
1217
+ expect(entity.Model).toBe(Order);
1218
+ expect(entity.Collection).toBe(Orders);
1219
+ });
1220
+
1221
+ test('returns correct entity among multiple registered entities', () => {
1222
+ const core = createCore({ entities: orderEntities });
1223
+ const utm = new UtmSource({
1224
+ id: 1,
1225
+ value: 'google',
1226
+ label: 'Google',
1227
+ internal: 'false'
1228
+ });
1229
+ const entity = core.getEntityByModel(utm);
1230
+ expect(entity.tableName).toEqual('utm_source');
1231
+ expect(entity.displayName).toEqual('utmSource');
1232
+ });
1233
+
1234
+ test('throws for an unregistered model class', () => {
1235
+ const core = createCore({ entities: nineEntities });
1236
+ const order = new Order({ id: 1 });
1237
+ expect(() => core.getEntityByModel(order)).toThrow(
1238
+ 'Could not find entity for class'
1239
+ );
1240
+ });
1241
+
1242
+ test('entity has correct propertyNames derived from columns', () => {
1243
+ const core = createCore({ entities: orderEntities });
1244
+ const order = new Order({ id: 1 });
1245
+ const entity = core.getEntityByModel(order);
1246
+ expect(entity.propertyNames).toContain('id');
1247
+ expect(entity.propertyNames).toContain('email');
1248
+ expect(entity.propertyNames).toContain('browserIP');
1249
+ expect(entity.propertyNames).toContain('utmSourceId');
1250
+ });
1251
+
1252
+ test('entity has correct columnNames derived from columns', () => {
1253
+ const core = createCore({ entities: orderEntities });
1254
+ const order = new Order({ id: 1 });
1255
+ const entity = core.getEntityByModel(order);
1256
+ expect(entity.columnNames).toContain('id');
1257
+ expect(entity.columnNames).toContain('email');
1258
+ expect(entity.columnNames).toContain('browser_ip');
1259
+ expect(entity.columnNames).toContain('utm_source_id');
1260
+ });
1261
+
1262
+ test('entity has correct prefixedColumnNames', () => {
1263
+ const core = createCore({ entities: orderEntities });
1264
+ const order = new Order({ id: 1 });
1265
+ const entity = core.getEntityByModel(order);
1266
+ expect(entity.prefixedColumnNames).toContain('order#id');
1267
+ expect(entity.prefixedColumnNames).toContain('order#email');
1268
+ expect(entity.prefixedColumnNames).toContain('order#browser_ip');
1269
+ });
1270
+
1271
+ test('entity has correct references', () => {
1272
+ const core = createCore({ entities: orderEntities });
1273
+ const order = new Order({ id: 1 });
1274
+ const entity = core.getEntityByModel(order);
1275
+ expect(entity.references).toEqual({ utmSourceId: UtmSource });
1276
+ });
1277
+
1278
+ test('entity defaults primaryKeys to [id] when none specified', () => {
1279
+ const core = createCore({ entities: orderEntities });
1280
+ const order = new Order({ id: 1 });
1281
+ const entity = core.getEntityByModel(order);
1282
+ expect(entity.primaryKeys).toEqual(['id']);
1283
+ });
1284
+
1285
+ test('entity selectColumnsClause is correctly formed', () => {
1286
+ const core = createCore({ entities: nineEntities });
1287
+ const fs = new FeatureSwitch({ id: 'x', label: 'x', on: true });
1288
+ const entity = core.getEntityByModel(fs);
1289
+ expect(entity.selectColumnsClause).toEqual(
1290
+ '"feature_switch".id as "feature_switch#id", "feature_switch".label as "feature_switch#label", "feature_switch".on as "feature_switch#on"'
1291
+ );
1292
+ });
1293
+ });
1294
+
1295
+ /* -------------------------------------------------------------------------*/
1296
+ /* getEntityByTableName ----------------------------------------------------*/
1297
+ /* -------------------------------------------------------------------------*/
1298
+
1299
+ describe('getEntityByTableName', () => {
1300
+ test('returns entity for a valid table name', () => {
1301
+ const core = createCore({ entities: orderEntities });
1302
+ const entity = core.getEntityByTableName('order');
1303
+ expect(entity.tableName).toEqual('order');
1304
+ expect(entity.Model).toBe(Order);
1305
+ });
1306
+
1307
+ test('returns correct entity among multiple registered entities', () => {
1308
+ const core = createCore({ entities: orderEntities });
1309
+ const entity = core.getEntityByTableName('utm_source');
1310
+ expect(entity.tableName).toEqual('utm_source');
1311
+ expect(entity.displayName).toEqual('utmSource');
1312
+ expect(entity.Model).toBe(UtmSource);
1313
+ });
1314
+
1315
+ test('throws for a non-existent table name', () => {
1316
+ const core = createCore({ entities: orderEntities });
1317
+ expect(() => core.getEntityByTableName('nonexistent')).toThrow(
1318
+ 'Could not find entity for table nonexistent'
1319
+ );
1320
+ });
1321
+ });
1322
+
1323
+ /* -------------------------------------------------------------------------*/
1324
+ /* Entity configuration edge cases -----------------------------------------*/
1325
+ /* -------------------------------------------------------------------------*/
1326
+
1327
+ describe('entity configuration', () => {
1328
+ test('custom displayName is used instead of camelCase tableName', () => {
1329
+ class Widget implements IModel {
1330
+ id: number;
1331
+ constructor(props: any) {
1332
+ this.id = props.id;
1333
+ }
1334
+ }
1335
+ class Widgets implements ICollection<Widget> {
1336
+ models: Array<Widget>;
1337
+ constructor({ models }: any) {
1338
+ this.models = models;
1339
+ }
1340
+ }
1341
+ const core = createCore({
1342
+ entities: [
1343
+ {
1344
+ tableName: 'widget_thing',
1345
+ displayName: 'myWidget',
1346
+ columns: ['id'],
1347
+ Model: Widget,
1348
+ Collection: Widgets
1349
+ }
1350
+ ]
1351
+ });
1352
+ expect(core.tables.myWidget).toBeDefined();
1353
+ expect(core.tables.myWidget.columns).toEqual(
1354
+ '"widget_thing".id as "widget_thing#id"'
1355
+ );
1356
+ });
1357
+
1358
+ test('custom collectionDisplayName is used', () => {
1359
+ class Goose implements IModel {
1360
+ id: number;
1361
+ constructor(props: any) {
1362
+ this.id = props.id;
1363
+ }
1364
+ }
1365
+ class Geese implements ICollection<Goose> {
1366
+ models: Array<Goose>;
1367
+ constructor({ models }: any) {
1368
+ this.models = models;
1369
+ }
1370
+ }
1371
+ const core = createCore({
1372
+ entities: [
1373
+ {
1374
+ tableName: 'goose',
1375
+ collectionDisplayName: 'geese',
1376
+ columns: ['id'],
1377
+ Model: Goose,
1378
+ Collection: Geese
1379
+ }
1380
+ ]
1381
+ });
1382
+ const entity = core.getEntityByTableName('goose');
1383
+ expect(entity.collectionDisplayName).toEqual('geese');
1384
+ });
1385
+
1386
+ test('default collectionDisplayName appends "s" to displayName', () => {
1387
+ const core = createCore({ entities: orderEntities });
1388
+ const entity = core.getEntityByTableName('order');
1389
+ expect(entity.collectionDisplayName).toEqual('orders');
1390
+ });
1391
+
1392
+ test('function-based columns are resolved', () => {
1393
+ class Item implements IModel {
1394
+ id: number;
1395
+ name: string;
1396
+ constructor(props: any) {
1397
+ this.id = props.id;
1398
+ this.name = props.name;
1399
+ }
1400
+ }
1401
+ class Items implements ICollection<Item> {
1402
+ models: Array<Item>;
1403
+ constructor({ models }: any) {
1404
+ this.models = models;
1405
+ }
1406
+ }
1407
+ const columnsFn: IColumns = () => ['id', 'name'];
1408
+ const core = createCore({
1409
+ entities: [
1410
+ {
1411
+ tableName: 'item',
1412
+ columns: columnsFn,
1413
+ Model: Item,
1414
+ Collection: Items
1415
+ }
1416
+ ]
1417
+ });
1418
+ const entity = core.getEntityByTableName('item');
1419
+ expect(entity.columnNames).toEqual(['id', 'name']);
1420
+ expect(entity.propertyNames).toEqual(['id', 'name']);
1421
+ });
1422
+
1423
+ test('custom primary key is used', () => {
1424
+ class Tenant implements IModel {
1425
+ slug: string;
1426
+ name: string;
1427
+ constructor(props: any) {
1428
+ this.slug = props.slug;
1429
+ this.name = props.name;
1430
+ }
1431
+ }
1432
+ class Tenants implements ICollection<Tenant> {
1433
+ models: Array<Tenant>;
1434
+ constructor({ models }: any) {
1435
+ this.models = models;
1436
+ }
1437
+ }
1438
+ const core = createCore({
1439
+ entities: [
1440
+ {
1441
+ tableName: 'tenant',
1442
+ columns: [{ column: 'slug', primaryKey: true }, 'name'],
1443
+ Model: Tenant,
1444
+ Collection: Tenants
1445
+ }
1446
+ ]
1447
+ });
1448
+ const entity = core.getEntityByTableName('tenant');
1449
+ expect(entity.primaryKeys).toEqual(['slug']);
1450
+ });
1451
+
1452
+ test('composite primary keys', () => {
1453
+ class Mapping implements IModel {
1454
+ aId: number;
1455
+ bId: number;
1456
+ constructor(props: any) {
1457
+ this.aId = props.aId;
1458
+ this.bId = props.bId;
1459
+ }
1460
+ }
1461
+ class Mappings implements ICollection<Mapping> {
1462
+ models: Array<Mapping>;
1463
+ constructor({ models }: any) {
1464
+ this.models = models;
1465
+ }
1466
+ }
1467
+ const core = createCore({
1468
+ entities: [
1469
+ {
1470
+ tableName: 'mapping',
1471
+ columns: [
1472
+ { column: 'a_id', primaryKey: true },
1473
+ { column: 'b_id', primaryKey: true }
1474
+ ],
1475
+ Model: Mapping,
1476
+ Collection: Mappings
1477
+ }
1478
+ ]
1479
+ });
1480
+ const entity = core.getEntityByTableName('mapping');
1481
+ expect(entity.primaryKeys).toEqual(['a_id', 'b_id']);
1482
+ });
1483
+
1484
+ test('column with explicit property name overrides camelCase', () => {
1485
+ class Thing implements IModel {
1486
+ myIP: string;
1487
+ constructor(props: any) {
1488
+ this.myIP = props.myIP;
1489
+ }
1490
+ }
1491
+ class Things implements ICollection<Thing> {
1492
+ models: Array<Thing>;
1493
+ constructor({ models }: any) {
1494
+ this.models = models;
1495
+ }
1496
+ }
1497
+ const core = createCore({
1498
+ entities: [
1499
+ {
1500
+ tableName: 'thing',
1501
+ columns: [{ column: 'ip_address', property: 'myIP' }],
1502
+ Model: Thing,
1503
+ Collection: Things
1504
+ }
1505
+ ]
1506
+ });
1507
+ const entity = core.getEntityByTableName('thing');
1508
+ expect(entity.propertyNames).toEqual(['myIP']);
1509
+ expect(entity.columnNames).toEqual(['ip_address']);
1510
+ });
1511
+
1512
+ test('getPkId returns concatenated primary key values', () => {
1513
+ class Mapping implements IModel {
1514
+ a_id: number;
1515
+ b_id: number;
1516
+ constructor(props: any) {
1517
+ this.a_id = props.aId;
1518
+ this.b_id = props.bId;
1519
+ }
1520
+ }
1521
+ class Mappings implements ICollection<Mapping> {
1522
+ models: Array<Mapping>;
1523
+ constructor({ models }: any) {
1524
+ this.models = models;
1525
+ }
1526
+ }
1527
+ const core = createCore({
1528
+ entities: [
1529
+ {
1530
+ tableName: 'mapping',
1531
+ columns: [
1532
+ { column: 'a_id', primaryKey: true },
1533
+ { column: 'b_id', primaryKey: true }
1534
+ ],
1535
+ Model: Mapping,
1536
+ Collection: Mappings
1537
+ }
1538
+ ]
1539
+ });
1540
+ const entity = core.getEntityByTableName('mapping');
1541
+ const m = new Mapping({ aId: 5, bId: 10 });
1542
+ expect(entity.getPkId(m)).toEqual('510');
1543
+ });
1544
+
1545
+ test('entity with no columns other than id', () => {
1546
+ class Simple implements IModel {
1547
+ id: number;
1548
+ constructor(props: any) {
1549
+ this.id = props.id;
1550
+ }
1551
+ }
1552
+ class Simples implements ICollection<Simple> {
1553
+ models: Array<Simple>;
1554
+ constructor({ models }: any) {
1555
+ this.models = models;
1556
+ }
1557
+ }
1558
+ const core = createCore({
1559
+ entities: [
1560
+ {
1561
+ tableName: 'simple',
1562
+ columns: ['id'],
1563
+ Model: Simple,
1564
+ Collection: Simples
1565
+ }
1566
+ ]
1567
+ });
1568
+ const entity = core.getEntityByTableName('simple');
1569
+ expect(entity.columnNames).toEqual(['id']);
1570
+ expect(entity.propertyNames).toEqual(['id']);
1571
+ expect(entity.primaryKeys).toEqual(['id']);
1572
+ expect(entity.references).toEqual({});
1573
+ });
1574
+ });
1575
+
1576
+ /* -------------------------------------------------------------------------*/
1577
+ /* createFromDatabase error/edge cases -------------------------------------*/
1578
+ /* -------------------------------------------------------------------------*/
1579
+
1580
+ describe('createFromDatabase edge cases', () => {
1581
+ test('handles a single row (not an array)', () => {
1582
+ const core = createCore({ entities: nineEntities });
1583
+ const row = {
1584
+ 'feature_switch#id': 'test_switch',
1585
+ 'feature_switch#label': 'Test',
1586
+ 'feature_switch#on': true
1587
+ };
1588
+ const result = core.createFromDatabase(row);
1589
+ expect(result.models.length).toEqual(1);
1590
+ expect(result.models[0].id).toEqual('test_switch');
1591
+ });
1592
+
1593
+ test('throws when column names are not namespaced', () => {
1594
+ const core = createCore({ entities: nineEntities });
1595
+ expect(() =>
1596
+ core.createFromDatabase([{ id: 1, label: 'test', on: true }])
1597
+ ).toThrow('Column names must be namespaced to table');
1598
+ });
1599
+
1600
+ test('handles meta_ prefixed columns', () => {
1601
+ class Widget implements IModel {
1602
+ [key: string]: any;
1603
+ id: number;
1604
+ constructor(props: any) {
1605
+ this.id = props.id;
1606
+ Object.assign(this, props);
1607
+ }
1608
+ }
1609
+ class Widgets implements ICollection<Widget> {
1610
+ models: Array<Widget>;
1611
+ constructor({ models }: any) {
1612
+ this.models = models;
1613
+ }
1614
+ }
1615
+ const core = createCore({
1616
+ entities: [
1617
+ {
1618
+ tableName: 'widget',
1619
+ columns: ['id'],
1620
+ Model: Widget,
1621
+ Collection: Widgets
1622
+ }
1623
+ ]
1624
+ });
1625
+ const result = core.createFromDatabase([
1626
+ { 'widget#id': 1, 'widget#meta_count': 42 }
1627
+ ]);
1628
+ expect(result.models[0].id).toEqual(1);
1629
+ expect(result.models[0].metaCount).toEqual(42);
1630
+ });
1631
+
1632
+ test('throws for non-meta unrecognized columns', () => {
1633
+ class Widget implements IModel {
1634
+ id: number;
1635
+ constructor(props: any) {
1636
+ this.id = props.id;
1637
+ }
1638
+ }
1639
+ class Widgets implements ICollection<Widget> {
1640
+ models: Array<Widget>;
1641
+ constructor({ models }: any) {
1642
+ this.models = models;
1643
+ }
1644
+ }
1645
+ const core = createCore({
1646
+ entities: [
1647
+ {
1648
+ tableName: 'widget',
1649
+ columns: ['id'],
1650
+ Model: Widget,
1651
+ Collection: Widgets
1652
+ }
1653
+ ]
1654
+ });
1655
+ expect(() =>
1656
+ core.createFromDatabase([{ 'widget#id': 1, 'widget#unknown_col': 'x' }])
1657
+ ).toThrow('No property name for "unknown_col"');
1658
+ });
1659
+ });
1660
+
1661
+ /* -------------------------------------------------------------------------*/
1662
+ /* tables property ---------------------------------------------------------*/
1663
+ /* -------------------------------------------------------------------------*/
1664
+
1665
+ describe('tables property', () => {
1666
+ test('uses displayName as key', () => {
1667
+ const core = createCore({ entities: blogEntities });
1668
+ expect(core.tables.article).toBeDefined();
1669
+ expect(core.tables.person).toBeDefined();
1670
+ expect(core.tables.articleTag).toBeDefined();
1671
+ expect(core.tables.tag).toBeDefined();
1672
+ });
1673
+
1674
+ test('each table entry has a columns string', () => {
1675
+ const core = createCore({ entities: nineEntities });
1676
+ expect(typeof core.tables.featureSwitch.columns).toBe('string');
1677
+ expect(core.tables.featureSwitch.columns).toContain('feature_switch');
1678
+ });
1679
+
1680
+ test('single entity produces single table entry', () => {
1681
+ const core = createCore({ entities: nineEntities });
1682
+ expect(Object.keys(core.tables).length).toEqual(1);
1683
+ });
1684
+ });