@nocobase/database 0.7.0-alpha.9 → 0.7.1-alpha.6

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 (325) hide show
  1. package/lib/collection-importer.js +85 -68
  2. package/lib/collection.d.ts +6 -2
  3. package/lib/collection.js +371 -210
  4. package/lib/database.d.ts +23 -4
  5. package/lib/database.js +599 -273
  6. package/lib/fields/array-field.js +45 -25
  7. package/lib/fields/belongs-to-field.js +101 -54
  8. package/lib/fields/belongs-to-many-field.js +98 -53
  9. package/lib/fields/boolean-field.js +24 -9
  10. package/lib/fields/context-field.js +77 -45
  11. package/lib/fields/date-field.js +24 -9
  12. package/lib/fields/field.d.ts +4 -1
  13. package/lib/fields/field.js +231 -75
  14. package/lib/fields/formula-field.d.ts +19 -0
  15. package/lib/fields/formula-field.js +184 -0
  16. package/lib/fields/has-inverse-field.js +4 -2
  17. package/lib/fields/has-many-field.js +105 -56
  18. package/lib/fields/has-one-field.js +105 -54
  19. package/lib/fields/index.d.ts +5 -1
  20. package/lib/fields/index.js +290 -32
  21. package/lib/fields/json-field.js +36 -16
  22. package/lib/fields/number-field.js +53 -26
  23. package/lib/fields/password-field.js +120 -73
  24. package/lib/fields/radio-field.js +75 -47
  25. package/lib/fields/relation-field.js +41 -28
  26. package/lib/fields/sort-field.js +165 -89
  27. package/lib/fields/string-field.js +24 -9
  28. package/lib/fields/text-field.js +24 -9
  29. package/lib/fields/time-field.js +24 -9
  30. package/lib/fields/uid-field.js +57 -28
  31. package/lib/fields/uuid-field.d.ts +9 -0
  32. package/lib/fields/uuid-field.js +39 -0
  33. package/lib/fields/virtual-field.js +24 -9
  34. package/lib/filter-parser.js +288 -179
  35. package/lib/index.d.ts +1 -0
  36. package/lib/index.js +224 -29
  37. package/lib/magic-attribute-model.js +123 -71
  38. package/lib/migration.d.ts +35 -0
  39. package/lib/migration.js +90 -0
  40. package/lib/mock-database.d.ts +1 -0
  41. package/lib/mock-database.js +69 -34
  42. package/lib/model-hook.d.ts +5 -5
  43. package/lib/model-hook.js +109 -60
  44. package/lib/model.js +116 -81
  45. package/lib/operators/array.js +136 -96
  46. package/lib/operators/association.js +30 -14
  47. package/lib/operators/boolean.d.ts +13 -0
  48. package/lib/operators/boolean.js +35 -0
  49. package/lib/operators/date.js +78 -34
  50. package/lib/operators/empty.js +113 -75
  51. package/lib/operators/index.js +15 -3
  52. package/lib/operators/ne.js +27 -12
  53. package/{esm/operators/ne.d.ts → lib/operators/notIn.d.ts} +2 -2
  54. package/lib/operators/notIn.js +29 -0
  55. package/lib/operators/string.js +56 -35
  56. package/lib/operators/utils.js +18 -10
  57. package/lib/options-parser.js +345 -215
  58. package/lib/playground.js +66 -53
  59. package/lib/relation-repository/belongs-to-many-repository.js +281 -198
  60. package/lib/relation-repository/belongs-to-repository.js +10 -6
  61. package/lib/relation-repository/hasmany-repository.js +168 -121
  62. package/lib/relation-repository/hasone-repository.js +10 -6
  63. package/lib/relation-repository/multiple-relation-repository.d.ts +3 -3
  64. package/lib/relation-repository/multiple-relation-repository.js +263 -148
  65. package/lib/relation-repository/relation-repository.d.ts +1 -1
  66. package/lib/relation-repository/relation-repository.js +163 -93
  67. package/lib/relation-repository/single-relation-repository.d.ts +6 -6
  68. package/lib/relation-repository/single-relation-repository.js +145 -99
  69. package/lib/relation-repository/types.js +4 -2
  70. package/lib/repository.d.ts +4 -7
  71. package/lib/repository.js +473 -291
  72. package/lib/transaction-decorator.js +80 -67
  73. package/lib/update-associations.d.ts +1 -2
  74. package/lib/update-associations.js +525 -321
  75. package/lib/update-guard.js +160 -117
  76. package/package.json +9 -9
  77. package/src/__tests__/collection.test.ts +27 -0
  78. package/src/__tests__/database.test.ts +47 -0
  79. package/src/__tests__/fields/formula-field.test.ts +69 -0
  80. package/src/__tests__/fields/uuid-field.test.ts +30 -0
  81. package/src/__tests__/fixtures/migrations/m1.ts +7 -0
  82. package/src/__tests__/fixtures/migrations/m2.ts +7 -0
  83. package/src/__tests__/hooks/afterCreateWithAssociations.test.ts +33 -0
  84. package/src/__tests__/migrator.test.ts +70 -0
  85. package/src/__tests__/model-hook.test.ts +54 -0
  86. package/src/__tests__/operator/notIn.test.ts +33 -0
  87. package/src/__tests__/option-parser.test.ts +30 -6
  88. package/src/__tests__/relation-repository/belongs-to-many-repository.test.ts +1 -1
  89. package/src/__tests__/sequelize-hooks.test.ts +69 -0
  90. package/src/__tests__/sort.test.ts +51 -0
  91. package/src/__tests__/update-associations.test.ts +3 -3
  92. package/src/collection-importer.ts +12 -20
  93. package/src/collection.ts +26 -2
  94. package/src/database.ts +144 -14
  95. package/src/fields/field.ts +88 -1
  96. package/src/fields/formula-field.ts +106 -0
  97. package/src/fields/index.ts +6 -0
  98. package/src/fields/password-field.ts +2 -0
  99. package/src/fields/uuid-field.ts +21 -0
  100. package/src/index.ts +1 -0
  101. package/src/migration.ts +76 -0
  102. package/src/mock-database.ts +2 -1
  103. package/src/model-hook.ts +26 -22
  104. package/src/operators/boolean.ts +18 -0
  105. package/src/operators/index.ts +2 -0
  106. package/src/operators/notIn.ts +12 -0
  107. package/src/options-parser.ts +14 -10
  108. package/src/relation-repository/multiple-relation-repository.ts +14 -6
  109. package/src/relation-repository/relation-repository.ts +12 -6
  110. package/src/relation-repository/single-relation-repository.ts +11 -7
  111. package/src/repository.ts +20 -10
  112. package/src/update-associations.ts +2 -3
  113. package/esm/collection-importer.d.ts +0 -7
  114. package/esm/collection-importer.js +0 -49
  115. package/esm/collection-importer.js.map +0 -1
  116. package/esm/collection.d.ts +0 -73
  117. package/esm/collection.js +0 -224
  118. package/esm/collection.js.map +0 -1
  119. package/esm/database.d.ts +0 -101
  120. package/esm/database.js +0 -275
  121. package/esm/database.js.map +0 -1
  122. package/esm/fields/array-field.d.ts +0 -11
  123. package/esm/fields/array-field.js +0 -26
  124. package/esm/fields/array-field.js.map +0 -1
  125. package/esm/fields/belongs-to-field.d.ts +0 -12
  126. package/esm/fields/belongs-to-field.js +0 -57
  127. package/esm/fields/belongs-to-field.js.map +0 -1
  128. package/esm/fields/belongs-to-many-field.d.ts +0 -11
  129. package/esm/fields/belongs-to-many-field.js +0 -55
  130. package/esm/fields/belongs-to-many-field.js.map +0 -1
  131. package/esm/fields/boolean-field.d.ts +0 -8
  132. package/esm/fields/boolean-field.js +0 -8
  133. package/esm/fields/boolean-field.js.map +0 -1
  134. package/esm/fields/context-field.d.ts +0 -13
  135. package/esm/fields/context-field.js +0 -43
  136. package/esm/fields/context-field.js.map +0 -1
  137. package/esm/fields/date-field.d.ts +0 -8
  138. package/esm/fields/date-field.js +0 -8
  139. package/esm/fields/date-field.js.map +0 -1
  140. package/esm/fields/field.d.ts +0 -37
  141. package/esm/fields/field.js +0 -74
  142. package/esm/fields/field.js.map +0 -1
  143. package/esm/fields/has-inverse-field.d.ts +0 -4
  144. package/esm/fields/has-inverse-field.js +0 -2
  145. package/esm/fields/has-inverse-field.js.map +0 -1
  146. package/esm/fields/has-many-field.d.ts +0 -64
  147. package/esm/fields/has-many-field.js +0 -58
  148. package/esm/fields/has-many-field.js.map +0 -1
  149. package/esm/fields/has-one-field.d.ts +0 -64
  150. package/esm/fields/has-one-field.js +0 -57
  151. package/esm/fields/has-one-field.js.map +0 -1
  152. package/esm/fields/index.d.ts +0 -40
  153. package/esm/fields/index.js +0 -21
  154. package/esm/fields/index.js.map +0 -1
  155. package/esm/fields/json-field.d.ts +0 -14
  156. package/esm/fields/json-field.js +0 -17
  157. package/esm/fields/json-field.js.map +0 -1
  158. package/esm/fields/number-field.d.ts +0 -32
  159. package/esm/fields/number-field.js +0 -28
  160. package/esm/fields/number-field.js.map +0 -1
  161. package/esm/fields/password-field.d.ts +0 -21
  162. package/esm/fields/password-field.js +0 -71
  163. package/esm/fields/password-field.js.map +0 -1
  164. package/esm/fields/radio-field.d.ts +0 -14
  165. package/esm/fields/radio-field.js +0 -49
  166. package/esm/fields/radio-field.js.map +0 -1
  167. package/esm/fields/relation-field.d.ts +0 -20
  168. package/esm/fields/relation-field.js +0 -27
  169. package/esm/fields/relation-field.js.map +0 -1
  170. package/esm/fields/sort-field.d.ts +0 -16
  171. package/esm/fields/sort-field.js +0 -90
  172. package/esm/fields/sort-field.js.map +0 -1
  173. package/esm/fields/string-field.d.ts +0 -8
  174. package/esm/fields/string-field.js +0 -8
  175. package/esm/fields/string-field.js.map +0 -1
  176. package/esm/fields/text-field.d.ts +0 -8
  177. package/esm/fields/text-field.js +0 -8
  178. package/esm/fields/text-field.js.map +0 -1
  179. package/esm/fields/time-field.d.ts +0 -8
  180. package/esm/fields/time-field.js +0 -8
  181. package/esm/fields/time-field.js.map +0 -1
  182. package/esm/fields/uid-field.d.ts +0 -10
  183. package/esm/fields/uid-field.js +0 -27
  184. package/esm/fields/uid-field.js.map +0 -1
  185. package/esm/fields/virtual-field.d.ts +0 -8
  186. package/esm/fields/virtual-field.js +0 -8
  187. package/esm/fields/virtual-field.js.map +0 -1
  188. package/esm/filter-parser.d.ts +0 -27
  189. package/esm/filter-parser.js +0 -185
  190. package/esm/filter-parser.js.map +0 -1
  191. package/esm/index.d.ts +0 -15
  192. package/esm/index.js +0 -16
  193. package/esm/index.js.map +0 -1
  194. package/esm/magic-attribute-model.d.ts +0 -7
  195. package/esm/magic-attribute-model.js +0 -70
  196. package/esm/magic-attribute-model.js.map +0 -1
  197. package/esm/mock-database.d.ts +0 -22
  198. package/esm/mock-database.js +0 -34
  199. package/esm/mock-database.js.map +0 -1
  200. package/esm/model-hook.d.ts +0 -12
  201. package/esm/model-hook.js +0 -61
  202. package/esm/model-hook.js.map +0 -1
  203. package/esm/model.d.ts +0 -15
  204. package/esm/model.js +0 -80
  205. package/esm/model.js.map +0 -1
  206. package/esm/operators/array.d.ts +0 -26
  207. package/esm/operators/array.js +0 -105
  208. package/esm/operators/array.js.map +0 -1
  209. package/esm/operators/association.d.ts +0 -10
  210. package/esm/operators/association.js +0 -14
  211. package/esm/operators/association.js.map +0 -1
  212. package/esm/operators/date.d.ts +0 -34
  213. package/esm/operators/date.js +0 -35
  214. package/esm/operators/date.js.map +0 -1
  215. package/esm/operators/empty.d.ts +0 -28
  216. package/esm/operators/empty.js +0 -58
  217. package/esm/operators/empty.js.map +0 -1
  218. package/esm/operators/index.d.ts +0 -2
  219. package/esm/operators/index.js +0 -2
  220. package/esm/operators/index.js.map +0 -1
  221. package/esm/operators/ne.js +0 -12
  222. package/esm/operators/ne.js.map +0 -1
  223. package/esm/operators/string.d.ts +0 -21
  224. package/esm/operators/string.js +0 -35
  225. package/esm/operators/string.js.map +0 -1
  226. package/esm/operators/utils.d.ts +0 -4
  227. package/esm/operators/utils.js +0 -11
  228. package/esm/operators/utils.js.map +0 -1
  229. package/esm/options-parser.d.ts +0 -31
  230. package/esm/options-parser.js +0 -225
  231. package/esm/options-parser.js.map +0 -1
  232. package/esm/playground.d.ts +0 -1
  233. package/esm/playground.js +0 -53
  234. package/esm/playground.js.map +0 -1
  235. package/esm/relation-repository/belongs-to-many-repository.d.ts +0 -36
  236. package/esm/relation-repository/belongs-to-many-repository.js +0 -199
  237. package/esm/relation-repository/belongs-to-many-repository.js.map +0 -1
  238. package/esm/relation-repository/belongs-to-repository.d.ts +0 -17
  239. package/esm/relation-repository/belongs-to-repository.js +0 -4
  240. package/esm/relation-repository/belongs-to-repository.js.map +0 -1
  241. package/esm/relation-repository/hasmany-repository.d.ts +0 -23
  242. package/esm/relation-repository/hasmany-repository.js +0 -125
  243. package/esm/relation-repository/hasmany-repository.js.map +0 -1
  244. package/esm/relation-repository/hasone-repository.d.ts +0 -17
  245. package/esm/relation-repository/hasone-repository.js +0 -4
  246. package/esm/relation-repository/hasone-repository.js.map +0 -1
  247. package/esm/relation-repository/multiple-relation-repository.d.ts +0 -23
  248. package/esm/relation-repository/multiple-relation-repository.js +0 -149
  249. package/esm/relation-repository/multiple-relation-repository.js.map +0 -1
  250. package/esm/relation-repository/relation-repository.d.ts +0 -32
  251. package/esm/relation-repository/relation-repository.js +0 -93
  252. package/esm/relation-repository/relation-repository.js.map +0 -1
  253. package/esm/relation-repository/single-relation-repository.d.ts +0 -23
  254. package/esm/relation-repository/single-relation-repository.js +0 -96
  255. package/esm/relation-repository/single-relation-repository.js.map +0 -1
  256. package/esm/relation-repository/types.d.ts +0 -7
  257. package/esm/relation-repository/types.js +0 -2
  258. package/esm/relation-repository/types.js.map +0 -1
  259. package/esm/repository.d.ts +0 -165
  260. package/esm/repository.js +0 -276
  261. package/esm/repository.js.map +0 -1
  262. package/esm/transaction-decorator.d.ts +0 -1
  263. package/esm/transaction-decorator.js +0 -63
  264. package/esm/transaction-decorator.js.map +0 -1
  265. package/esm/update-associations.d.ts +0 -60
  266. package/esm/update-associations.js +0 -362
  267. package/esm/update-associations.js.map +0 -1
  268. package/esm/update-guard.d.ts +0 -26
  269. package/esm/update-guard.js +0 -122
  270. package/esm/update-guard.js.map +0 -1
  271. package/lib/collection-importer.js.map +0 -1
  272. package/lib/collection.js.map +0 -1
  273. package/lib/database.js.map +0 -1
  274. package/lib/fields/array-field.js.map +0 -1
  275. package/lib/fields/belongs-to-field.js.map +0 -1
  276. package/lib/fields/belongs-to-many-field.js.map +0 -1
  277. package/lib/fields/boolean-field.js.map +0 -1
  278. package/lib/fields/context-field.js.map +0 -1
  279. package/lib/fields/date-field.js.map +0 -1
  280. package/lib/fields/field.js.map +0 -1
  281. package/lib/fields/has-inverse-field.js.map +0 -1
  282. package/lib/fields/has-many-field.js.map +0 -1
  283. package/lib/fields/has-one-field.js.map +0 -1
  284. package/lib/fields/index.js.map +0 -1
  285. package/lib/fields/json-field.js.map +0 -1
  286. package/lib/fields/number-field.js.map +0 -1
  287. package/lib/fields/password-field.js.map +0 -1
  288. package/lib/fields/radio-field.js.map +0 -1
  289. package/lib/fields/relation-field.js.map +0 -1
  290. package/lib/fields/sort-field.js.map +0 -1
  291. package/lib/fields/string-field.js.map +0 -1
  292. package/lib/fields/text-field.js.map +0 -1
  293. package/lib/fields/time-field.js.map +0 -1
  294. package/lib/fields/uid-field.js.map +0 -1
  295. package/lib/fields/virtual-field.js.map +0 -1
  296. package/lib/filter-parser.js.map +0 -1
  297. package/lib/index.js.map +0 -1
  298. package/lib/magic-attribute-model.js.map +0 -1
  299. package/lib/mock-database.js.map +0 -1
  300. package/lib/model-hook.js.map +0 -1
  301. package/lib/model.js.map +0 -1
  302. package/lib/operators/array.js.map +0 -1
  303. package/lib/operators/association.js.map +0 -1
  304. package/lib/operators/date.js.map +0 -1
  305. package/lib/operators/empty.js.map +0 -1
  306. package/lib/operators/index.js.map +0 -1
  307. package/lib/operators/ne.js.map +0 -1
  308. package/lib/operators/string.js.map +0 -1
  309. package/lib/operators/utils.js.map +0 -1
  310. package/lib/options-parser.js.map +0 -1
  311. package/lib/playground.js.map +0 -1
  312. package/lib/relation-repository/belongs-to-many-repository.js.map +0 -1
  313. package/lib/relation-repository/belongs-to-repository.js.map +0 -1
  314. package/lib/relation-repository/hasmany-repository.js.map +0 -1
  315. package/lib/relation-repository/hasone-repository.js.map +0 -1
  316. package/lib/relation-repository/multiple-relation-repository.js.map +0 -1
  317. package/lib/relation-repository/relation-repository.js.map +0 -1
  318. package/lib/relation-repository/single-relation-repository.js.map +0 -1
  319. package/lib/relation-repository/types.js.map +0 -1
  320. package/lib/repository.js.map +0 -1
  321. package/lib/transaction-decorator.js.map +0 -1
  322. package/lib/update-associations.js.map +0 -1
  323. package/lib/update-guard.js.map +0 -1
  324. package/tsconfig.build.json +0 -9
  325. package/tsconfig.json +0 -5
@@ -0,0 +1,54 @@
1
+ import { Database } from '..';
2
+ import { mockDatabase } from '.';
3
+
4
+ describe('model hook', () => {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ describe('match', () => {
16
+ test('sequelize db hooks', async () => {
17
+ const matcher = db.modelHook.match('beforeDefine');
18
+ expect(matcher).toEqual('beforeDefine');
19
+ });
20
+
21
+ test('sequelize global model hooks', async () => {
22
+ const matcher = db.modelHook.match('beforeCreate');
23
+ expect(matcher).toEqual('beforeCreate');
24
+ });
25
+
26
+ test('sequelize model hooks without existing collection', async () => {
27
+ const matcher = db.modelHook.match('posts.beforeCreate');
28
+ expect(matcher).toEqual('beforeCreate');
29
+ });
30
+
31
+ test('sequelize model hooks with existing collection', async () => {
32
+ db.collection({
33
+ name: 'posts',
34
+ fields: []
35
+ });
36
+ const matcher = db.modelHook.match('posts.beforeCreate');
37
+ expect(matcher).toEqual('beforeCreate');
38
+ });
39
+
40
+ test('customized global hooks', async () => {
41
+ const matcher = db.modelHook.match('beforeDefineCollection');
42
+ expect(matcher).toBeNull();
43
+ });
44
+
45
+ test('customized model hooks', async () => {
46
+ db.collection({
47
+ name: 'posts',
48
+ fields: []
49
+ });
50
+ const matcher = db.modelHook.match('posts.beforeCreateWithAssociations');
51
+ expect(matcher).toBeNull();
52
+ });
53
+ });
54
+ });
@@ -0,0 +1,33 @@
1
+ import { mockDatabase } from '../index';
2
+ import Database from '../../database';
3
+
4
+ describe('ne operator', () => {
5
+ let db: Database;
6
+ let Test;
7
+ beforeEach(async () => {
8
+ db = mockDatabase({});
9
+
10
+ Test = db.collection({
11
+ name: 'tests',
12
+ fields: [{ type: 'string', name: 'name' }],
13
+ });
14
+
15
+ await db.sync();
16
+ });
17
+
18
+ afterEach(async () => {
19
+ await db.close();
20
+ });
21
+
22
+ it('should notIn with null', async () => {
23
+ await db.getRepository('tests').create({});
24
+
25
+ const results = await db.getRepository('tests').count({
26
+ filter: {
27
+ 'name.$notIn': ['123'],
28
+ },
29
+ });
30
+
31
+ expect(results).toEqual(1);
32
+ });
33
+ });
@@ -1,7 +1,7 @@
1
1
  import { Collection } from '../collection';
2
- import { mockDatabase } from './index';
3
- import { OptionsParser } from '../options-parser';
4
2
  import { Database } from '../database';
3
+ import { OptionsParser } from '../options-parser';
4
+ import { mockDatabase } from './index';
5
5
 
6
6
  describe('option parser', () => {
7
7
  let db: Database;
@@ -81,7 +81,12 @@ describe('option parser', () => {
81
81
  ],
82
82
  });
83
83
  });
84
+
84
85
  test('with sort option', () => {
86
+ if (db.inDialect('mysql')) {
87
+ expect(1).toBe(1);
88
+ return;
89
+ }
85
90
  let options: any = {
86
91
  sort: ['id'],
87
92
  };
@@ -90,7 +95,7 @@ describe('option parser', () => {
90
95
  collection: User,
91
96
  });
92
97
  let params = parser.toSequelizeParams();
93
- expect(params['order']).toEqual([['id', 'ASC']]);
98
+ expect(params['order']).toEqual([['id', 'ASC NULLS LAST']]);
94
99
 
95
100
  options = {
96
101
  sort: ['id', '-posts.title', 'posts.comments.createdAt'],
@@ -101,9 +106,9 @@ describe('option parser', () => {
101
106
  });
102
107
  params = parser.toSequelizeParams();
103
108
  expect(params['order']).toEqual([
104
- ['id', 'ASC'],
105
- [Post.model, 'title', 'DESC'],
106
- [Post.model, Comment.model, 'createdAt', 'ASC'],
109
+ ['id', 'ASC NULLS LAST'],
110
+ [Post.model, 'title', 'DESC NULLS LAST'],
111
+ [Post.model, Comment.model, 'createdAt', 'ASC NULLS LAST'],
107
112
  ]);
108
113
  });
109
114
 
@@ -182,4 +187,23 @@ describe('option parser', () => {
182
187
 
183
188
  expect(params['include'][0]['attributes']['exclude']).toContain('id');
184
189
  });
190
+
191
+ test('option parser with multiple association', () => {
192
+ // fields with association field
193
+ const options = {
194
+ appends: ['user', 'comments.id', 'tags.id'],
195
+ };
196
+
197
+ const parser = new OptionsParser(options, {
198
+ collection: Post,
199
+ });
200
+
201
+ const params = parser.toSequelizeParams();
202
+ expect(params.include.length).toBe(3);
203
+ expect(params.include[0].association).toBe('user');
204
+ expect(params.include[1].association).toBe('comments');
205
+ expect(params.include[1].attributes).toEqual(['id']);
206
+ expect(params.include[2].association).toBe('tags');
207
+ expect(params.include[2].attributes).toEqual(['id']);
208
+ });
185
209
  });
@@ -308,7 +308,7 @@ describe('belongs to many', () => {
308
308
  offset: 0,
309
309
  });
310
310
 
311
- console.log(tags);
311
+ // console.log(tags);
312
312
  });
313
313
 
314
314
  test('update raw attribute', async () => {
@@ -0,0 +1,69 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './index';
3
+
4
+ // TODO
5
+ describe('sequelize-hooks', () => {
6
+ let db: Database;
7
+
8
+ beforeEach(async () => {
9
+ db = mockDatabase();
10
+ await db.sync();
11
+ });
12
+
13
+ afterEach(async () => {
14
+ await db.close();
15
+ });
16
+
17
+ test('exec order', async () => {
18
+ const collection = db.collection({
19
+ name: 't_test',
20
+ });
21
+ const orders = [];
22
+ db.on('beforeCreate', () => {
23
+ orders.push('beforeCreate');
24
+ });
25
+ db.on('t_test.beforeCreate', () => {
26
+ orders.push('model.beforeCreate');
27
+ });
28
+ db.on('afterCreate', () => {
29
+ orders.push('afterCreate');
30
+ });
31
+ db.on('t_test.afterCreate', () => {
32
+ orders.push('model.afterCreate');
33
+ });
34
+ await collection.sync();
35
+ await collection.model.create();
36
+ expect(orders).toEqual([
37
+ 'model.beforeCreate',
38
+ 'beforeCreate',
39
+ 'model.afterCreate',
40
+ 'afterCreate'
41
+ ]);
42
+ });
43
+
44
+ describe('afterSync', () => {
45
+ test('singular name', async () => {
46
+ const collection = db.collection({
47
+ name: 't_test',
48
+ });
49
+ const spy = jest.fn();
50
+ db.on('t_test.afterSync', () => {
51
+ spy('afterSync');
52
+ });
53
+ await collection.sync();
54
+ expect(spy).toHaveBeenCalledTimes(1);
55
+ });
56
+
57
+ test('plural name', async () => {
58
+ const collection = db.collection({
59
+ name: 't_tests',
60
+ });
61
+ const spy = jest.fn();
62
+ db.on('t_tests.afterSync', () => {
63
+ spy('afterSync');
64
+ });
65
+ await collection.sync();
66
+ expect(spy).toHaveBeenCalledTimes(1);
67
+ });
68
+ });
69
+ });
@@ -0,0 +1,51 @@
1
+ import { Database } from '../database';
2
+ import { mockDatabase } from './';
3
+
4
+ describe('sort', function () {
5
+ let db: Database;
6
+
7
+ beforeEach(async () => {
8
+ db = mockDatabase();
9
+ });
10
+
11
+ afterEach(async () => {
12
+ await db.close();
13
+ });
14
+
15
+ test('order nulls last', async () => {
16
+ const Test = db.collection({
17
+ name: 'test',
18
+ fields: [
19
+ {
20
+ type: 'integer',
21
+ name: 'num',
22
+ },
23
+ ],
24
+ });
25
+ await Test.sync();
26
+ await Test.model.bulkCreate([
27
+ {
28
+ num: 3,
29
+ },
30
+ {
31
+ num: 2,
32
+ },
33
+ {
34
+ num: null,
35
+ },
36
+ {
37
+ num: 1,
38
+ },
39
+ ]);
40
+ const items = await Test.repository.find({
41
+ sort: '-num',
42
+ });
43
+ const nums = items.map((item) => item.get('num'));
44
+ expect(nums).toEqual([3,2,1,null]);
45
+ const items2 = await Test.repository.find({
46
+ sort: 'num',
47
+ });
48
+ const nums2 = items2.map((item) => item.get('num'));
49
+ expect(nums2).toEqual([1,2,3,null]);
50
+ });
51
+ });
@@ -173,7 +173,7 @@ describe('update associations', () => {
173
173
  await updateAssociations(user1, {
174
174
  posts: post1,
175
175
  });
176
- console.log(JSON.stringify(user1, null, 2));
176
+
177
177
  expect(user1.toJSON()).toMatchObject({
178
178
  name: 'user1',
179
179
  });
@@ -191,7 +191,7 @@ describe('update associations', () => {
191
191
  name: 'post111',
192
192
  },
193
193
  });
194
- console.log(JSON.stringify(user1, null, 2));
194
+
195
195
  expect(user1.toJSON()).toMatchObject({
196
196
  name: 'user1',
197
197
  });
@@ -216,7 +216,7 @@ describe('update associations', () => {
216
216
  post3,
217
217
  ],
218
218
  });
219
- console.log(JSON.stringify(user1, null, 2));
219
+
220
220
  expect(user1.toJSON()).toMatchObject({
221
221
  name: 'user1',
222
222
  });
@@ -1,20 +1,10 @@
1
- import * as fs from 'fs';
2
- import lodash from 'lodash';
3
1
  import path from 'path';
2
+ import { readdir } from 'fs/promises';
3
+ import { isPlainObject } from 'lodash';
4
+ import { requireModule } from '@nocobase/utils';
4
5
 
5
6
  export type ImportFileExtension = 'js' | 'ts' | 'json';
6
7
 
7
- async function requireModule(module: any) {
8
- if (typeof module === 'string') {
9
- module = require(module);
10
- }
11
-
12
- if (typeof module !== 'object') {
13
- return module;
14
- }
15
- return module.__esModule ? module.default : module;
16
- }
17
-
18
8
  export class ImporterReader {
19
9
  directory: string;
20
10
  extensions: Set<string>;
@@ -30,11 +20,10 @@ export class ImporterReader {
30
20
  }
31
21
 
32
22
  async read() {
33
- const modules = (
34
- await fs.promises.readdir(this.directory, {
35
- encoding: 'utf-8',
36
- })
37
- )
23
+ const files = await readdir(this.directory, {
24
+ encoding: 'utf-8',
25
+ });
26
+ const modules = files
38
27
  .filter((fileName) => {
39
28
  if (fileName.endsWith('.d.ts')) {
40
29
  return false;
@@ -42,8 +31,11 @@ export class ImporterReader {
42
31
  const ext = path.parse(fileName).ext.replace('.', '');
43
32
  return this.extensions.has(ext);
44
33
  })
45
- .map(async (fileName) => await requireModule(path.join(this.directory, fileName)));
34
+ .map((fileName) => {
35
+ const mod = requireModule(path.join(this.directory, fileName));
36
+ return typeof mod === 'function' ? mod() : mod;
37
+ });
46
38
 
47
- return (await Promise.all(modules)).filter((module) => lodash.isPlainObject(module));
39
+ return (await Promise.all(modules)).filter((module) => isPlainObject(module));
48
40
  }
49
41
  }
package/src/collection.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import merge from 'deepmerge';
2
2
  import { EventEmitter } from 'events';
3
3
  import { default as lodash, default as _ } from 'lodash';
4
- import { col, ModelCtor, ModelOptions, SyncOptions } from 'sequelize';
4
+ import { ModelCtor, ModelOptions, QueryInterfaceDropTableOptions, SyncOptions, Transactionable } from 'sequelize';
5
5
  import { Database } from './database';
6
6
  import { Field, FieldOptions } from './fields';
7
7
  import { Model } from './model';
@@ -53,6 +53,10 @@ export class Collection<
53
53
  return this.options.name;
54
54
  }
55
55
 
56
+ get db() {
57
+ return this.context.database;
58
+ }
59
+
56
60
  constructor(options: CollectionOptions, context?: CollectionContext) {
57
61
  super();
58
62
  this.context = context;
@@ -186,6 +190,26 @@ export class Collection<
186
190
  }
187
191
  }
188
192
 
193
+ remove() {
194
+ this.context.database.removeCollection(this.name);
195
+ }
196
+
197
+ async removeFromDb(options?: QueryInterfaceDropTableOptions) {
198
+ if (
199
+ await this.existsInDb({
200
+ transaction: options?.transaction,
201
+ })
202
+ ) {
203
+ const queryInterface = this.db.sequelize.getQueryInterface();
204
+ await queryInterface.dropTable(this.model.tableName, options);
205
+ }
206
+ this.remove();
207
+ }
208
+
209
+ async existsInDb(options?: Transactionable) {
210
+ return this.db.collectionExistsInDb(this.name, options);
211
+ }
212
+
189
213
  removeField(name) {
190
214
  if (!this.fields.has(name)) {
191
215
  return;
@@ -195,7 +219,7 @@ export class Collection<
195
219
  if (bool) {
196
220
  this.emit('field.afterRemove', field);
197
221
  }
198
- return bool;
222
+ return field as Field;
199
223
  }
200
224
 
201
225
  /**
package/src/database.ts CHANGED
@@ -1,12 +1,26 @@
1
1
  import { applyMixins, AsyncEmitter } from '@nocobase/utils';
2
2
  import merge from 'deepmerge';
3
3
  import { EventEmitter } from 'events';
4
+ import glob from 'glob';
4
5
  import lodash from 'lodash';
5
- import { ModelCtor, Op, Options, QueryInterfaceDropAllTablesOptions, Sequelize, SyncOptions, Utils } from 'sequelize';
6
+ import { basename, isAbsolute, resolve } from 'path';
7
+ import {
8
+ ModelCtor,
9
+ Op,
10
+ Options,
11
+ QueryInterfaceDropAllTablesOptions,
12
+ QueryOptions,
13
+ Sequelize,
14
+ SyncOptions,
15
+ Transactionable,
16
+ Utils
17
+ } from 'sequelize';
18
+ import { SequelizeStorage, Umzug } from 'umzug';
6
19
  import { Collection, CollectionOptions, RepositoryType } from './collection';
7
20
  import { ImporterReader, ImportFileExtension } from './collection-importer';
8
21
  import * as FieldTypes from './fields';
9
22
  import { Field, FieldContext, RelationField } from './fields';
23
+ import { Migrations } from './migration';
10
24
  import { Model } from './model';
11
25
  import { ModelHook } from './model-hook';
12
26
  import extendOperators from './operators';
@@ -26,9 +40,10 @@ interface MapOf<T> {
26
40
 
27
41
  export interface IDatabaseOptions extends Options {
28
42
  tablePrefix?: string;
43
+ migrator?: any;
29
44
  }
30
45
 
31
- export type DatabaseOptions = IDatabaseOptions | Sequelize;
46
+ export type DatabaseOptions = IDatabaseOptions;
32
47
 
33
48
  interface RegisterOperatorsContext {
34
49
  db?: Database;
@@ -41,10 +56,19 @@ export interface CleanOptions extends QueryInterfaceDropAllTablesOptions {
41
56
  drop?: boolean;
42
57
  }
43
58
 
59
+ export type AddMigrationsOptions = {
60
+ context?: any;
61
+ namespace?: string;
62
+ extensions?: string[];
63
+ directory: string;
64
+ };
65
+
44
66
  type OperatorFunc = (value: any, ctx?: RegisterOperatorsContext) => any;
45
67
 
46
68
  export class Database extends EventEmitter implements AsyncEmitter {
47
69
  sequelize: Sequelize;
70
+ migrator: Umzug;
71
+ migrations: Migrations;
48
72
  fieldTypes = new Map();
49
73
  options: IDatabaseOptions;
50
74
  models = new Map<string, ModelCtor<Model>>();
@@ -61,13 +85,30 @@ export class Database extends EventEmitter implements AsyncEmitter {
61
85
  constructor(options: DatabaseOptions) {
62
86
  super();
63
87
 
64
- if (options instanceof Sequelize) {
65
- this.sequelize = options;
66
- } else {
67
- this.sequelize = new Sequelize(options);
68
- this.options = options;
88
+ const opts = {
89
+ sync: {
90
+ alter: {
91
+ drop: false,
92
+ },
93
+ force: false,
94
+ },
95
+ ...options,
96
+ };
97
+
98
+ if (options.storage && options.storage !== ':memory:') {
99
+ if (!isAbsolute(options.storage)) {
100
+ opts.storage = resolve(process.cwd(), options.storage);
101
+ }
102
+ }
103
+
104
+ if (options.dialect === 'sqlite') {
105
+ delete opts.timezone;
106
+ } else if (!opts.timezone) {
107
+ opts.timezone = '+00:00';
69
108
  }
70
109
 
110
+ this.sequelize = new Sequelize(opts);
111
+ this.options = opts;
71
112
  this.collections = new Map();
72
113
  this.modelHook = new ModelHook(this);
73
114
 
@@ -92,6 +133,62 @@ export class Database extends EventEmitter implements AsyncEmitter {
92
133
  }
93
134
 
94
135
  this.initOperators();
136
+
137
+ const migratorOptions: any = this.options.migrator || {};
138
+
139
+ const context = {
140
+ db: this,
141
+ sequelize: this.sequelize,
142
+ queryInterface: this.sequelize.getQueryInterface(),
143
+ ...migratorOptions.context,
144
+ };
145
+
146
+ this.migrations = new Migrations(context);
147
+ this.migrator = new Umzug({
148
+ logger: migratorOptions.logger || console,
149
+ migrations: this.migrations.callback(),
150
+ context,
151
+ storage: new SequelizeStorage({
152
+ modelName: `${this.options.tablePrefix || ''}migrations`,
153
+ ...migratorOptions.storage,
154
+ sequelize: this.sequelize,
155
+ }),
156
+ });
157
+ }
158
+
159
+ addMigration(item) {
160
+ return this.migrations.add(item);
161
+ }
162
+
163
+ addMigrations(options: AddMigrationsOptions) {
164
+ const { namespace, context, extensions = ['js', 'ts'], directory } = options;
165
+ const patten = `${directory}/*.{${extensions.join(',')}}`;
166
+ const files = glob.sync(patten, {
167
+ ignore: ['**/*.d.ts'],
168
+ });
169
+ for (const file of files) {
170
+ let filename = basename(file);
171
+ filename = filename.substring(0, filename.lastIndexOf('.')) || filename;
172
+ this.migrations.add({
173
+ name: namespace ? `${namespace}/${filename}` : filename,
174
+ migration: this.requireModule(file),
175
+ context,
176
+ });
177
+ }
178
+ }
179
+
180
+ inDialect(...dialect: string[]) {
181
+ return dialect.includes(this.sequelize.getDialect());
182
+ }
183
+
184
+ private requireModule(module: any) {
185
+ if (typeof module === 'string') {
186
+ module = require(module);
187
+ }
188
+ if (typeof module !== 'object') {
189
+ return module;
190
+ }
191
+ return module.__esModule ? module.default : module;
95
192
  }
96
193
 
97
194
  /**
@@ -137,9 +234,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
137
234
 
138
235
  const result = this.collections.delete(name);
139
236
 
237
+ this.sequelize.modelManager.removeModel(collection.model);
238
+
140
239
  if (result) {
141
240
  this.emit('afterRemoveCollection', collection);
142
241
  }
242
+
243
+ return collection;
143
244
  }
144
245
 
145
246
  getModel<M extends Model>(name: string) {
@@ -244,10 +345,39 @@ export class Database extends EventEmitter implements AsyncEmitter {
244
345
  }
245
346
  }
246
347
 
348
+ async collectionExistsInDb(name, options?: Transactionable) {
349
+ const tables = await this.sequelize.getQueryInterface().showAllTables({
350
+ transaction: options?.transaction,
351
+ });
352
+ return !!tables.find((table) => table === `${this.getTablePrefix()}${name}`);
353
+ }
354
+
247
355
  public isSqliteMemory() {
248
356
  return this.sequelize.getDialect() === 'sqlite' && lodash.get(this.options, 'storage') == ':memory:';
249
357
  }
250
358
 
359
+ async auth(options: QueryOptions & { retry?: number } = {}) {
360
+ const { retry = 10, ...others } = options;
361
+ const delay = (ms) => new Promise((yea) => setTimeout(yea, ms));
362
+ let count = 1;
363
+ const authenticate = async () => {
364
+ try {
365
+ await this.sequelize.authenticate(others);
366
+ console.log('Connection has been established successfully.');
367
+ return true;
368
+ } catch (error) {
369
+ if (count >= retry) {
370
+ throw error;
371
+ }
372
+ console.log('reconnecting...', count);
373
+ ++count;
374
+ await delay(500);
375
+ return await authenticate();
376
+ }
377
+ };
378
+ return await authenticate();
379
+ }
380
+
251
381
  async reconnect() {
252
382
  if (this.isSqliteMemory()) {
253
383
  return;
@@ -275,13 +405,13 @@ export class Database extends EventEmitter implements AsyncEmitter {
275
405
  return this.sequelize.close();
276
406
  }
277
407
 
278
- on(event: string | symbol, listener: (...args: any[]) => void): this {
279
- const modelEventName = this.modelHook.isModelHook(event);
280
-
281
- if (modelEventName && !this.modelHook.hasBindEvent(modelEventName)) {
282
- this.sequelize.addHook(modelEventName, this.modelHook.sequelizeHookBuilder(modelEventName));
408
+ on(event: string | symbol, listener): this {
409
+ // NOTE: to match if event is a sequelize or model type
410
+ const type = this.modelHook.match(event);
283
411
 
284
- this.modelHook.bindEvent(modelEventName);
412
+ if (type && !this.modelHook.hasBoundEvent(type)) {
413
+ this.sequelize.addHook(type, this.modelHook.buildSequelizeHook(type));
414
+ this.modelHook.bindEvent(type);
285
415
  }
286
416
 
287
417
  return super.on(event, listener);
@@ -312,7 +442,7 @@ export class Database extends EventEmitter implements AsyncEmitter {
312
442
  return result;
313
443
  }
314
444
 
315
- emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
445
+ declare emitAsync: (event: string | symbol, ...args: any[]) => Promise<boolean>;
316
446
  }
317
447
 
318
448
  export function extend(collectionOptions: CollectionOptions, mergeOptions?: MergeOptions) {