Compare commits

...

349 Commits

Author SHA1 Message Date
57d2d56d00 5.2.6 2024-06-18 20:12:15 +02:00
90751002aa fix(core): update 2024-06-18 20:12:14 +02:00
7606e074a5 5.2.5 2024-06-18 20:11:41 +02:00
7ec39e397e fix(core): update 2024-06-18 20:11:40 +02:00
21d8d3dc32 5.2.4 2024-05-31 18:47:49 +02:00
6d456955d8 fix(core): update 2024-05-31 18:47:48 +02:00
d08544c782 5.2.3 2024-05-31 18:39:34 +02:00
bda9ac8a07 fix(saveableProperties): fix issue where _createdAt and _updatedAt registered saveableProperties for all document types 2024-05-31 18:39:33 +02:00
d27dafba2b 5.2.2 2024-05-31 18:25:52 +02:00
b6594de18c fix(core): update 2024-05-31 18:25:51 +02:00
d9246cbeac update description 2024-05-29 14:12:26 +02:00
9a5864656e 5.2.1 2024-04-16 07:47:25 +02:00
307f0c7277 fix(core): update 2024-04-16 07:47:24 +02:00
62dc897e73 5.2.0 2024-04-15 18:34:14 +02:00
552b344914 feat(SmartDataDbDoc): add static .getCount({}) method 2024-04-15 18:34:13 +02:00
5a2cc2406c 5.1.2 2024-04-15 14:26:22 +02:00
73a11370b6 fix(_createdAt/_updatedAt): fields are now ISO format 2024-04-15 14:26:21 +02:00
162265f353 5.1.1 2024-04-14 04:00:56 +02:00
06776d74c8 fix(core): update 2024-04-14 04:00:56 +02:00
b4cd6b0fe1 5.1.0 2024-04-14 01:26:11 +02:00
b282f69b35 feat(_createdAt & _updatedAt): adds default _createdAt and _updatedAt fields, fixes #1 2024-04-14 01:26:11 +02:00
203a284c88 5.0.43 2024-04-14 01:24:22 +02:00
30ae641a9c fix(core): update 2024-04-14 01:24:21 +02:00
cfe733621f update npmextra.json: githost 2024-04-01 21:34:27 +02:00
1f76e2478e update npmextra.json: githost 2024-04-01 19:57:58 +02:00
7d668bee05 update npmextra.json: githost 2024-03-30 21:46:55 +01:00
bef7f68360 5.0.42 2024-03-30 12:26:43 +01:00
56e9754725 fix(TypeScript): improve tsconfig.json for ES Module use 2024-03-30 12:26:42 +01:00
30d81581cf 5.0.41 2024-03-27 17:32:02 +01:00
5e9db12955 fix(core): update 2024-03-27 17:32:01 +01:00
ad2f422c86 5.0.40 2024-03-27 17:30:51 +01:00
17ce14bcb9 fix(core): update 2024-03-27 17:30:50 +01:00
32319e6e77 5.0.39 2024-03-27 17:30:15 +01:00
4cd284eaa9 fix(core): update 2024-03-27 17:30:14 +01:00
00ec2e57c2 5.0.38 2024-03-27 16:24:58 +01:00
765356ce3d fix(core): update 2024-03-27 16:24:57 +01:00
56b8581d2b 5.0.37 2024-03-26 13:22:34 +01:00
37a9df9086 fix(core): update 2024-03-26 13:22:33 +01:00
090fb668cd 5.0.36 2024-03-26 13:21:37 +01:00
a1c807261c fix(core): update 2024-03-26 13:21:36 +01:00
a2ccf15f69 5.0.35 2024-03-26 00:25:06 +01:00
84d48f1914 fix(core): update 2024-03-26 00:25:06 +01:00
1e258e5ffb 5.0.34 2024-03-22 18:36:35 +01:00
19d5f553b9 fix(core): update 2024-03-22 18:36:34 +01:00
7a257ea925 5.0.33 2023-08-21 12:39:49 +02:00
2fa1e89f34 fix(core): update 2023-08-21 12:39:48 +02:00
d6b3896dd3 5.0.32 2023-08-16 13:16:40 +02:00
49b11b17ce fix(core): update 2023-08-16 13:16:39 +02:00
4ac8a4c0cd 5.0.31 2023-08-16 12:08:28 +02:00
7f9983382a fix(core): update 2023-08-16 12:08:27 +02:00
54f529b0a7 5.0.30 2023-08-15 19:55:53 +02:00
f542463bf6 fix(core): update 2023-08-15 19:55:52 +02:00
1235ae2eb3 5.0.29 2023-08-15 19:55:23 +02:00
8166d2f7c2 fix(core): update 2023-08-15 19:55:22 +02:00
7c9f27e02f 5.0.28 2023-08-15 01:24:30 +02:00
842e4b280b fix(core): update 2023-08-15 01:24:29 +02:00
009f3297b2 5.0.27 2023-08-15 01:01:16 +02:00
2ff3a4e0b7 fix(core): update 2023-08-15 01:01:16 +02:00
0e55cd8876 5.0.26 2023-08-12 23:32:40 +02:00
eccdf3f00a fix(core): update 2023-08-12 23:32:39 +02:00
c7544133d9 5.0.25 2023-08-12 23:32:02 +02:00
c7c9acf5bd fix(core): update 2023-08-12 23:32:02 +02:00
c99ec50853 5.0.24 2023-08-10 18:06:46 +02:00
4dd9557e1d fix(core): update 2023-08-10 18:06:45 +02:00
52b34a6da1 5.0.23 2023-07-21 20:09:51 +02:00
1bf74fe04d fix(core): update 2023-07-21 20:09:51 +02:00
fdd875ad31 5.0.22 2023-07-21 20:08:50 +02:00
a7bf0c0298 fix(core): update 2023-07-21 20:08:50 +02:00
59d6336e43 5.0.21 2023-07-21 20:08:18 +02:00
e0fc81179a fix(core): update 2023-07-21 20:08:18 +02:00
5aa81a56a2 switch to new org scheme 2023-07-10 02:43:00 +02:00
9ae26177b8 5.0.20 2023-06-25 02:01:20 +02:00
26ac52d6c5 fix(core): update 2023-06-25 02:01:19 +02:00
fb39463b7d 5.0.19 2023-06-25 01:53:17 +02:00
44acba80c1 fix(core): update 2023-06-25 01:53:17 +02:00
8cf8315577 5.0.18 2023-06-25 01:51:44 +02:00
9b44b64a50 fix(core): update 2023-06-25 01:51:43 +02:00
699e25201c 5.0.17 2023-06-25 01:30:14 +02:00
2ef9aace68 fix(core): update 2023-06-25 01:30:14 +02:00
cc55a57dfd 5.0.16 2023-06-25 01:29:40 +02:00
b2df512552 fix(core): update 2023-06-25 01:29:39 +02:00
23c62fbd69 5.0.15 2023-06-25 01:28:30 +02:00
5f70ea0b05 fix(core): update 2023-06-25 01:28:29 +02:00
49a595876a 5.0.14 2023-06-25 01:27:10 +02:00
db38a1ef85 fix(core): update 2023-06-25 01:27:09 +02:00
94854638dd 5.0.13 2023-06-24 23:57:35 +02:00
902fab4cc0 fix(core): update 2023-06-24 23:57:34 +02:00
ed3b19abc5 5.0.12 2023-03-21 20:16:08 +01:00
5b88da7dce fix(core): update 2023-03-21 20:16:07 +01:00
df273e9efa 5.0.11 2023-02-06 11:43:11 +01:00
fd590e0be3 fix(core): update 2023-02-06 11:43:11 +01:00
ef97b390d4 5.0.10 2022-11-08 10:26:21 +01:00
cd14eb8bf3 fix(core): update 2022-11-08 10:26:20 +01:00
f48443dcd3 5.0.9 2022-11-01 18:23:58 +01:00
3f28ff80cb fix(core): update 2022-11-01 18:23:57 +01:00
64005a0b32 5.0.8 2022-09-12 11:37:54 +02:00
8a77bb3281 fix(core): update 2022-09-12 11:37:53 +02:00
25f50ecf51 5.0.7 2022-06-14 22:04:35 +02:00
ad87f8147b fix(core): update 2022-06-14 22:04:34 +02:00
da0c9873eb 5.0.6 2022-06-14 22:02:57 +02:00
2fcd3f1550 fix(core): update 2022-06-14 22:02:57 +02:00
f726cf4c5b 5.0.5 2022-06-05 17:19:12 +02:00
c198969fae fix(core): update 2022-06-05 17:19:12 +02:00
be1badeb23 5.0.4 2022-06-05 17:18:13 +02:00
fe065b966f fix(core): update 2022-06-05 17:18:13 +02:00
811e2490b8 5.0.3 2022-05-19 16:15:28 +02:00
206ccd40e9 fix(watcher.changeSubject): now emits correct type into observer functions 2022-05-19 16:15:28 +02:00
055298172f 5.0.2 2022-05-18 18:17:12 +02:00
278f3c8169 fix(tests): now uses @pushrocks/smartmongo backed by wiredTiger 2022-05-18 18:17:11 +02:00
0709ba921b 5.0.1 2022-05-17 23:54:26 +02:00
de1f1110b4 fix(core): update 2022-05-17 23:54:26 +02:00
30f4254428 5.0.0 2022-05-17 21:30:53 +02:00
1c4b03e647 BREAKING CHANGE(core): switch to esm 2022-05-17 21:30:53 +02:00
27cc7651ba 4.0.29 2022-05-17 21:26:17 +02:00
355a2a3f2b fix(core): update 2022-05-17 21:26:17 +02:00
a739582861 4.0.28 2022-05-17 00:33:44 +02:00
37f9a64735 fix(core): update 2022-05-17 00:33:44 +02:00
83a5170591 4.0.27 2021-11-12 19:32:54 +01:00
f94363cf31 fix(core): update 2021-11-12 19:32:54 +01:00
df02e5bb71 4.0.26 2021-11-12 19:16:11 +01:00
38e438c54f fix(core): update 2021-11-12 19:16:11 +01:00
11bc1ac6dc 4.0.25 2021-11-12 19:03:06 +01:00
3431e94ddd fix(core): update 2021-11-12 19:03:06 +01:00
739e040776 4.0.24 2021-11-12 19:02:29 +01:00
28d57efd9e fix(core): update 2021-11-12 19:02:29 +01:00
f50a61308c 4.0.23 2021-11-12 18:12:59 +01:00
42aa9f9f8a fix(core): update 2021-11-12 18:12:59 +01:00
3f591ff9d8 4.0.22 2021-11-12 18:04:08 +01:00
7b33347b4c fix(core): update 2021-11-12 18:04:08 +01:00
3f11cbf595 4.0.21 2021-11-12 17:32:43 +01:00
cf1ec7f9eb fix(core): update 2021-11-12 17:32:43 +01:00
54060deb8f 4.0.20 2021-11-12 17:22:32 +01:00
48cffb5ac2 fix(core): update 2021-11-12 17:22:31 +01:00
8301eb79a2 4.0.19 2021-11-12 16:36:25 +01:00
ad6366a294 fix(core): update 2021-11-12 16:36:25 +01:00
cb6c03ebfe 4.0.18 2021-11-12 16:23:27 +01:00
551916fe5c fix(core): update 2021-11-12 16:23:26 +01:00
9b3892c1e8 4.0.17 2021-10-18 14:37:17 +02:00
c1b1af9c5d fix(dbdoc manager access working again ): update 2021-10-18 14:37:17 +02:00
d3a3d5be9d 4.0.16 2021-10-16 23:33:22 +02:00
c9a734d879 fix(core): update 2021-10-16 23:33:21 +02:00
856e8e7d1f 4.0.15 2021-10-16 21:17:03 +02:00
7a4d557724 fix(core): update 2021-10-16 21:17:02 +02:00
7cbd0bd99b 4.0.14 2021-10-16 19:54:06 +02:00
e10f6585a5 fix(core): update 2021-10-16 19:54:05 +02:00
5c8dffdd9c 4.0.13 2021-10-02 15:03:35 +02:00
846996eeac fix(core): update 2021-10-02 15:03:35 +02:00
668df09ba0 4.0.12 2021-09-19 17:05:09 +02:00
03656f4ca0 fix(core): update 2021-09-19 17:05:08 +02:00
c4c612f3a9 4.0.11 2021-09-18 00:38:21 +02:00
e357f7581c fix(core): update 2021-09-18 00:38:20 +02:00
9697b1e48b 4.0.10 2021-09-17 22:34:15 +02:00
aeb35705d4 fix(core): update 2021-09-17 22:34:15 +02:00
236c8c6551 4.0.9 2021-09-16 19:49:55 +02:00
1f28db15e7 fix(core): update 2021-09-16 19:49:55 +02:00
86d600e287 4.0.8 2021-06-09 15:38:15 +02:00
bb81530dac fix(core): update 2021-06-09 15:38:14 +02:00
b9f9b36b87 4.0.7 2021-06-09 15:37:50 +02:00
df2fadfa01 fix(core): update 2021-06-09 15:37:49 +02:00
8b2beb3485 4.0.6 2021-06-09 14:55:56 +02:00
144a620f43 fix(core): update 2021-06-09 14:55:55 +02:00
c241247845 4.0.5 2021-06-09 14:32:14 +02:00
81e39d09e4 4.0.4 2021-06-09 14:10:08 +02:00
8e51b518b1 fix(core): update 2021-06-09 14:10:08 +02:00
8308d8d03b 4.0.3 2021-06-09 13:40:23 +02:00
97365ddf29 fix(core): update 2021-06-09 13:40:23 +02:00
55d96fa68d 4.0.2 2021-06-09 12:40:55 +02:00
54ec6accdf fix(core): update 2021-06-09 12:40:55 +02:00
fc5d092b25 4.0.1 2021-06-06 17:54:48 +02:00
dfba057562 fix(core): update 2021-06-06 17:54:47 +02:00
1ad05943b5 4.0.0 2021-06-06 17:48:37 +02:00
302e51a77f BREAKING CHANGE(filter design): filters now are congruent with their data types. Static extensions of doc base class now are fully typed with automatic reference to their child classes. 2021-06-06 17:48:37 +02:00
1330d03af2 3.1.56 2021-05-16 23:26:05 +00:00
a298e9d190 fix(core): update 2021-05-16 23:26:05 +00:00
e571ef347b 3.1.55 2021-02-05 21:16:46 +00:00
39a4c7ef3f fix(core): update 2021-02-05 21:16:45 +00:00
0d3518d990 3.1.54 2020-10-19 16:44:28 +00:00
fbdde2268c fix(core): update 2020-10-19 16:44:28 +00:00
8b6c26f45a 3.1.53 2020-10-19 16:37:45 +00:00
97e82ed75a fix(core): update 2020-10-19 16:37:44 +00:00
03baffd9fd 3.1.52 2020-09-25 21:05:22 +00:00
f29ae67580 fix(core): update 2020-09-25 21:05:21 +00:00
e550a8dbd6 3.1.51 2020-09-25 20:42:39 +00:00
cf6ef25a8d fix(core): update 2020-09-25 20:42:38 +00:00
29c512b0cc 3.1.50 2020-09-24 14:10:55 +00:00
105f69d1c9 fix(core): update 2020-09-24 14:10:54 +00:00
4c375f8465 3.1.49 2020-09-10 10:36:01 +00:00
9466af6a4a fix(core): update 2020-09-10 10:36:00 +00:00
c5aa747f42 3.1.48 2020-09-10 10:12:18 +00:00
b5f2474f65 fix(core): update 2020-09-10 10:12:17 +00:00
85f0d99934 3.1.47 2020-09-09 05:05:42 +00:00
3b2d3d9072 fix(core): update 2020-09-09 05:05:41 +00:00
3ff5c36fdf 3.1.46 2020-09-09 05:00:10 +00:00
a5acc2fe4e fix(core): update 2020-09-09 05:00:09 +00:00
9c81257101 3.1.45 2020-09-09 04:51:57 +00:00
f7342962f4 fix(core): update 2020-09-09 04:51:56 +00:00
bcd10205d3 3.1.44 2020-09-09 03:51:22 +00:00
6cab20f32d fix(core): update 2020-09-09 03:51:21 +00:00
5aaa6ad2d6 3.1.43 2020-09-09 02:50:28 +00:00
635256f2f6 fix(core): update 2020-09-09 02:50:27 +00:00
f799d3efa5 3.1.42 2020-09-09 02:21:16 +00:00
f74020ba96 fix(core): update 2020-09-09 02:21:15 +00:00
f6d6545ff5 3.1.41 2020-08-18 15:10:45 +00:00
85a196c8c1 fix(core): update 2020-08-18 15:10:44 +00:00
adb198af01 3.1.40 2020-08-18 13:28:01 +00:00
6dce9f3ff8 3.1.39 2020-08-18 13:27:05 +00:00
2f6a4ce3e5 fix(core): update 2020-08-18 13:27:04 +00:00
0984a1ade4 3.1.38 2020-08-18 12:53:45 +00:00
804701c96a fix(core): update 2020-08-18 12:53:44 +00:00
a3759fa484 3.1.37 2020-08-18 12:52:01 +00:00
ef38df62be fix(core): update 2020-08-18 12:52:00 +00:00
c17789e92e 3.1.36 2020-08-18 12:46:14 +00:00
0bf2ba554d fix(core): update 2020-08-18 12:46:14 +00:00
5cbf1a222a 3.1.35 2020-08-18 12:36:41 +00:00
f075530838 fix(core): update 2020-08-18 12:36:41 +00:00
efb83853fb 3.1.34 2020-08-18 12:35:16 +00:00
73300ca4d3 fix(core): update 2020-08-18 12:35:16 +00:00
1e946cdceb 3.1.33 2020-08-18 12:33:17 +00:00
608ff93a41 fix(core): update 2020-08-18 12:33:16 +00:00
6211953f14 3.1.32 2020-08-18 12:32:55 +00:00
99e520b776 fix(core): update 2020-08-18 12:32:54 +00:00
eda8297356 3.1.31 2020-08-18 12:05:08 +00:00
ffa52a5883 fix(core): update 2020-08-18 12:05:07 +00:00
1e83f0a0ef 3.1.30 2020-08-18 12:01:47 +00:00
0203eabdfd fix(core): update 2020-08-18 12:01:46 +00:00
72894e3ef1 3.1.29 2020-06-12 05:59:08 +00:00
bbe5f8c6a8 fix(core): update 2020-06-12 05:59:07 +00:00
02af9f5c4b 3.1.28 2020-06-11 23:08:38 +00:00
17de480272 fix(core): update 2020-06-11 23:08:38 +00:00
776fd3ee4e 3.1.27 2020-06-11 23:05:46 +00:00
3272bb7235 fix(core): update 2020-06-11 23:05:45 +00:00
116dfbc3b0 update module structure 2020-06-11 23:05:32 +00:00
3f714b1a33 3.1.26 2020-02-19 18:30:35 +00:00
e58fa57525 fix(core): update 2020-02-19 18:30:34 +00:00
8e7ad5210f 3.1.25 2020-02-08 14:37:45 +00:00
cea6c662ac fix(core): update 2020-02-08 14:37:44 +00:00
6eb43a4b9f 3.1.24 2020-02-08 14:01:55 +00:00
7f89cbeecd fix(core): update 2020-02-08 14:01:55 +00:00
a042a589a0 3.1.23 2019-09-11 12:36:03 +02:00
75b5f6af08 fix(core): update 2019-09-11 12:36:02 +02:00
e6a36f22ac 3.1.22 2019-09-11 12:12:51 +02:00
246e3486f0 fix(id field now properly populated): update 2019-09-11 12:12:50 +02:00
c1c4a29415 3.1.21 2019-09-11 11:56:41 +02:00
6448516aa0 fix(core): update 2019-09-11 11:56:41 +02:00
24a7b2dbd3 3.1.20 2019-09-04 17:41:53 +02:00
afa7c2fe61 fix(core): update 2019-09-04 17:41:53 +02:00
b768896fbe 3.1.19 2019-09-04 17:25:50 +02:00
8d7d9adef1 fix(core): update 2019-09-04 17:25:50 +02:00
ca6d3c60c0 3.1.18 2019-09-02 16:58:19 +02:00
38c1fcd53a fix(core): update 2019-09-02 16:58:19 +02:00
e433b9e212 3.1.17 2019-09-02 16:51:22 +02:00
bf3656f792 fix(core): update 2019-09-02 16:51:22 +02:00
7e9fc62ce7 3.1.16 2019-09-02 16:50:23 +02:00
670a0c63fb fix(core): update 2019-09-02 16:50:22 +02:00
64e76b3ed1 3.1.15 2019-09-02 16:42:30 +02:00
25803330de fix(core): update 2019-09-02 16:42:29 +02:00
c6cbff1308 3.1.14 2019-02-13 22:08:58 +01:00
3eeac7b936 fix(structure): format 2019-02-13 22:08:58 +01:00
761b742e21 3.1.13 2019-01-08 19:55:14 +01:00
e56def621c fix(core): update 2019-01-08 19:55:13 +01:00
ab1799f4f2 3.1.12 2019-01-08 18:59:36 +01:00
9985b849d1 3.1.11 2019-01-08 18:45:31 +01:00
1800273b25 fix(core): update 2019-01-08 18:45:30 +01:00
e715cc6d37 3.1.10 2019-01-08 18:22:48 +01:00
f7abc175aa fix(core): update 2019-01-08 18:22:48 +01:00
bbcb3a614e 3.1.9 2019-01-08 14:37:17 +01:00
941d2f0902 fix(core): update 2019-01-08 14:37:17 +01:00
5c78f83a28 3.1.8 2019-01-08 13:52:09 +01:00
124f117352 fix(core): update 2019-01-08 13:52:08 +01:00
f67da898ae 3.1.7 2019-01-08 12:39:00 +01:00
3c8f70f77e fix(core): update 2019-01-08 12:38:59 +01:00
3674d7d9a5 3.1.6 2019-01-08 00:09:43 +01:00
fe2a622488 3.1.5 2019-01-07 02:50:10 +01:00
f092eefbf3 fix(core): update 2019-01-07 02:50:09 +01:00
8d0aa361f9 3.1.4 2019-01-07 02:41:38 +01:00
692a1223a8 fix(core): update 2019-01-07 02:41:38 +01:00
ae7d101ab9 3.1.3 2019-01-07 02:38:30 +01:00
3825b15a65 fix(core): update 2019-01-07 02:38:30 +01:00
3fc41678d2 3.1.2 2018-07-10 21:45:26 +02:00
39f1b7ada1 fix(test): fix import 2018-07-10 21:45:26 +02:00
f763de2403 3.1.1 2018-07-10 21:33:49 +02:00
cba1d031b6 fix(ci): fix tools install 2018-07-10 21:33:49 +02:00
d31e6a5931 3.1.0 2018-07-10 21:27:17 +02:00
46514a2743 feat(SmartdataDoc): add custom unique indexes 2018-07-10 21:27:16 +02:00
1cf02157c4 3.0.5 2018-07-10 00:19:23 +02:00
72efa14da8 fix(test): update npmts 2018-07-10 00:19:22 +02:00
a73c2084c0 3.0.4 2018-07-10 00:17:17 +02:00
5ed557a21a fix(test): test now throws correctly 2018-07-10 00:17:16 +02:00
2b6c30ff55 3.0.3 2018-07-10 00:02:39 +02:00
12ef599333 3.0.2 2018-07-10 00:02:10 +02:00
191ea5d3c6 update to mongodb 2018-07-10 00:02:04 +02:00
bdf33ed1ca 3.0.1 2018-07-08 23:49:05 +02:00
1c76905dcd fix(structure): clean 2018-07-08 23:49:05 +02:00
4d88cea69d 3.0.0 2018-07-08 23:48:15 +02:00
8b4f7169ff BREAKING CHANGE(scope/db driver): switched to pushrocks scope and from rethinkdb to mongodb as db 2018-07-08 23:48:14 +02:00
2a1c45608f 2.0.7 2018-01-16 00:40:12 +01:00
85a6444263 feat(IConnectionOptions): export ConnectionOptions 2018-01-16 00:40:08 +01:00
766ae1d1ff 2.0.6 2018-01-14 18:07:35 +01:00
7630882312 docs(README): update 2018-01-14 18:07:29 +01:00
4aec47f207 2.0.5 2018-01-14 18:04:00 +01:00
c2e3a1ae6e docs(README): update 2018-01-14 18:03:57 +01:00
3d8f8646b1 2.0.4 2018-01-14 17:58:35 +01:00
aca9817c56 docs(README): update 2018-01-14 17:57:57 +01:00
7e341f2b50 2.0.3 2018-01-14 17:32:07 +01:00
d2ca108ef8 feat(core): now retrieves classes properly 2018-01-14 17:32:04 +01:00
e2d12f8c9c 2.0.2 2018-01-12 01:36:12 +01:00
cfcd9ab386 fix(CI): npmts now installing correctly during CI 2018-01-12 01:36:08 +01:00
3048035374 2.0.1 2018-01-12 01:34:00 +01:00
6c70cf05c4 update ci 2018-01-12 01:33:54 +01:00
04b0910883 2.0.0 2018-01-12 01:23:02 +01:00
3671fe4df4 feat(add RethinkDB as main driver and revert to docs in README): 2018-01-12 01:22:58 +01:00
bd30da1c4a fix(core): remove old code for outdated objectstorage 2018-01-07 18:15:14 +01:00
05938bf2af feat(ci): add commitizen for more consistent commit messages 2018-01-07 18:10:16 +01:00
cddb0caf1f feat(ci): add commitizen for more consistent commit messages 2018-01-07 18:08:42 +01:00
1af51dd989 update rethink integration 2018-01-07 17:58:30 +01:00
8101e49026 update deps and docs 2018-01-07 17:43:02 +01:00
2d97ab9dc5 prepare v2 of smartdata 2018-01-07 17:26:34 +01:00
3ba2bc8446 add rethink 2018-01-07 14:45:43 +01:00
5735ab8577 1.0.28 2017-11-16 14:35:30 +01:00
def671cbe4 update 2017-11-16 14:23:06 +01:00
14042151ba update tests 2017-09-13 13:47:38 +02:00
c74873d02c 1.0.27 2017-07-16 00:11:06 +02:00
800cdd655a fix rxjs dependency 2017-07-16 00:11:03 +02:00
68c0ceeb14 update 2017-07-16 00:09:54 +02:00
154dc5c1b3 Merge branch '3-plugins-are-not-exported' 2017-06-24 09:57:29 +02:00
d5c027caf0 fix import 2017-06-24 09:57:11 +02:00
30776a7da0 1.0.26 2017-06-23 16:42:08 +02:00
fedc0bd8c8 fix import 2017-06-23 16:42:03 +02:00
7b78b47a1b 1.0.25 2017-06-23 16:38:11 +02:00
a4acfc0f6e 1.0.24 2017-06-23 11:40:26 +02:00
a934f3608f remove NeDB and update to use MongoDB Atlas for testing 2017-06-23 11:40:20 +02:00
a4f3f23bed update npmextra 2017-06-18 19:53:29 +02:00
2039f1c807 update ci 2017-06-18 19:52:54 +02:00
57d1a6dd0a added npmdocker for local development 2017-02-25 11:37:05 +01:00
d504e90e47 1.0.22 2016-11-18 13:56:18 +01:00
7079340657 refactor @saveable to @svDb() 2016-11-18 13:56:15 +01:00
63ceded1b8 1.0.21 2016-11-18 13:20:47 +01:00
b45e7ee206 fix mongodb startup 2016-11-18 13:20:43 +01:00
715709f3ec 1.0.20 2016-11-18 01:00:06 +01:00
0b8adea736 improve README 2016-11-18 00:59:57 +01:00
90d9a25b3f 1.0.19 2016-11-18 00:42:29 +01:00
e49811eecd now allows adding a saveable decorator 2016-11-18 00:42:25 +01:00
9cbdf317dc 1.0.18 2016-11-17 22:41:05 +01:00
d55339013a improve README 2016-11-17 22:41:01 +01:00
9acdbd2842 1.0.17 2016-11-17 22:36:16 +01:00
dfce8cfcc0 add NeDB support 2016-11-17 22:36:12 +01:00
38446fc79b 1.0.16 2016-11-17 12:20:56 +01:00
af8c41ad6c added nedb to the mix 2016-11-17 12:20:52 +01:00
3a24f829b4 improve README 2016-09-14 01:03:25 +02:00
48 changed files with 9201 additions and 756 deletions

View File

@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @gitzone/tsdoc
npmci command tsdoc
continue-on-error: true

21
.gitignore vendored
View File

@ -1,5 +1,20 @@
node_modules/
test/data
pages/
.nogit/
# artifacts
coverage/
public/
pages/
# installs
node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_*/
# custom

View File

@ -1,43 +0,0 @@
image: hosttoday/ht-docker-node:npmts
stages:
- test
- release
before_script:
- apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv EA312927
- echo "deb http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.2.list
- apt-get update
- apt-get install -y mongodb-org
testLEGACY:
stage: test
script:
- npmci test legacy
tags:
- docker
allow_failure: true
testLTS:
stage: test
script:
- npmci test lts
tags:
- docker
testSTABLE:
stage: test
script:
- npmci test stable
tags:
- docker
release:
stage: release
environment: npm_registry
script:
- npmci publish
only:
- tags
tags:
- docker

11
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"type": "node-terminal"
}
]
}

26
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"json.schemas": [
{
"fileMatch": ["/npmextra.json"],
"schema": {
"type": "object",
"properties": {
"npmci": {
"type": "object",
"description": "settings for npmci"
},
"gitzone": {
"type": "object",
"description": "settings for gitzone",
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}
}
}
}
]
}

View File

@ -1,83 +0,0 @@
# smartdata
> Note: Still in Beta
smartdata is an ODM that adheres to TypeScript practices and uses classes to organize data.
It uses MongoDB as persistent storage.
## Intention
There are many ODMs out there, however when we searched for an ODM that uses TypeScript,
acts smart while still embracing an the NoSQL idea... we didn't find a matching solution.
This is why we started smartdata.
How MongoDB terms map to smartdata classes
MongoDB term | smartdata class
--- | ---
Database | smartdata.Db
Collection | smartdata.DbCollection
Document | smartdata.DbDoc
### class Db
represents a Database. Naturally it has .connect() etc. methods on it.
Since it is a class you can have multiple DBs defined.
```typescript
import * as smartdata from 'smartdata'
let myDb1 = new smartdata.Db('someConnectionUrl')
let myDb2 = new smartdata.Db('someConnectionUrl')
myDb1.connect()
myDb2.connect()
// continues in next block...
```
### class DbCollection
represents a collection of objects.
A collection is defined by the object class (that is extending smartdata.dbdoc) it respresents
So to get to get access to a specific collection you document
```typescript
// continues from the block before...
@Collection(myDb1)
class myObject extends smartdata.DbDoc { // read the next block about DbDoc
property1:string
property2:number
constructor(optionsArg:{
property1:string,
property2:number
}) {
super()
}
}
let myCollection = myDb1.getCollectionByName<myObject>(myObject)
```
> Alert: You NEVER instantiate a collection.
This is done for you!!!
### class DbDoc
represents a individual document in a collection
and thereby is ideally suited to extend the class you want to actually store.
DbDoc extends your class with the following methods:
* `.save()` will save (or update) the object you call it on only. Any referenced non-savable objects will not get stored.
* `.saveDeep()` does the same like `.save()`.
In addition it will look for properties that reference an object
that extends DbDoc as well and call .saveDeep() on them as well.
Loops are prevented
So now we can **store** instances of classes to Db...
How do we **get** a new class instance from a Doc in the DB?
## TypeScript
How does TypeScript play into this?
Since you define your classes in TypeScript and types flow through smartdata in a generic way
you should get all the Intellisense and type checking you love when using smartdata.
smartdata itself also bundles typings.
So you don't need to install any additional types when importing smartdata.
[![npm](https://push.rocks/assets/repo-header.svg)](https://push.rocks)

3
dist/index.d.ts vendored
View File

@ -1,3 +0,0 @@
export * from './smartdata.classes.db';
export * from './smartdata.classes.dbcollection';
export * from './smartdata.classes.dbdoc';

8
dist/index.js vendored
View File

@ -1,8 +0,0 @@
"use strict";
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(require('./smartdata.classes.db'));
__export(require('./smartdata.classes.dbcollection'));
__export(require('./smartdata.classes.dbdoc'));
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBRUEsaUJBQWMsd0JBQ2QsQ0FBQyxFQURxQztBQUN0QyxpQkFBYyxrQ0FDZCxDQUFDLEVBRCtDO0FBQ2hELGlCQUFjLDJCQUNkLENBQUMsRUFEd0MifQ==

View File

@ -1,25 +0,0 @@
/// <reference types="q" />
import * as plugins from './smartdata.plugins';
import { Objectmap } from 'lik';
import { DbCollection } from './smartdata.classes.dbcollection';
export declare type TConnectionStatus = 'disconnected' | 'connected' | 'failed';
export declare class Db {
dbUrl: string;
db: plugins.mongodb.Db;
status: TConnectionStatus;
collections: Objectmap<DbCollection<any>>;
constructor(dbUrl: string);
/**
* connects to the database that was specified during instance creation
*/
connect(): plugins.q.Promise<any>;
/**
* closes the connection to the databse
*/
close(): plugins.q.Promise<any>;
/**
* gets a collection by name: string
*/
getCollectionByName<T>(nameArg: string): plugins.q.Promise<DbCollection<T>>;
addCollection(dbCollectionArg: DbCollection<any>): void;
}

View File

@ -1,56 +0,0 @@
"use strict";
const plugins = require('./smartdata.plugins');
const lik_1 = require('lik');
class Db {
constructor(dbUrl) {
this.collections = new lik_1.Objectmap();
this.dbUrl = dbUrl;
}
// basic connection stuff ----------------------------------------------
/**
* connects to the database that was specified during instance creation
*/
connect() {
let done = plugins.q.defer();
plugins.mongodb.MongoClient.connect(this.dbUrl, (err, db) => {
if (err) {
console.log(err);
}
plugins.assert.equal(null, err);
this.db = db;
plugins.beautylog.success(`connected to database at ${this.dbUrl}`);
done.resolve(this.db);
});
return done.promise;
}
/**
* closes the connection to the databse
*/
close() {
let done = plugins.q.defer();
this.db.close();
plugins.beautylog.ok(`disconnected to database at ${this.dbUrl}`);
done.resolve();
return done.promise;
}
// advanced communication with the database --------------------------------
/**
* gets a collection by name: string
*/
getCollectionByName(nameArg) {
let done = plugins.q.defer();
let resultCollection = this.collections.find((dbCollectionArg) => {
return dbCollectionArg.name === nameArg;
});
if (resultCollection !== null) {
done.resolve(resultCollection);
}
return done.promise;
}
;
addCollection(dbCollectionArg) {
this.collections.add(dbCollectionArg);
}
}
exports.Db = Db;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEuY2xhc3Nlcy5kYi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsTUFBWSxPQUFPLFdBQU0scUJBQ3pCLENBQUMsQ0FENkM7QUFDOUMsc0JBQTBCLEtBRTFCLENBQUMsQ0FGOEI7QUFNL0I7SUFNSSxZQUFZLEtBQWE7UUFGekIsZ0JBQVcsR0FBRyxJQUFJLGVBQVMsRUFBcUIsQ0FBQTtRQUc1QyxJQUFJLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQTtJQUN0QixDQUFDO0lBRUQsd0VBQXdFO0lBRXhFOztPQUVHO0lBQ0gsT0FBTztRQUNILElBQUksSUFBSSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDNUIsT0FBTyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQyxHQUFHLEVBQUUsRUFBRTtZQUNwRCxFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsR0FBRyxDQUFDLENBQUE7WUFBQyxDQUFDO1lBQzdCLE9BQU8sQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLElBQUksRUFBRSxHQUFHLENBQUMsQ0FBQTtZQUMvQixJQUFJLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQTtZQUNaLE9BQU8sQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLDRCQUE0QixJQUFJLENBQUMsS0FBSyxFQUFFLENBQUMsQ0FBQTtZQUNuRSxJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQTtRQUN6QixDQUFDLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7SUFFRDs7T0FFRztJQUNILEtBQUs7UUFDRCxJQUFJLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQzVCLElBQUksQ0FBQyxFQUFFLENBQUMsS0FBSyxFQUFFLENBQUE7UUFDZixPQUFPLENBQUMsU0FBUyxDQUFDLEVBQUUsQ0FBQywrQkFBK0IsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDLENBQUE7UUFDakUsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztJQUVELDRFQUE0RTtJQUU1RTs7T0FFRztJQUNILG1CQUFtQixDQUFJLE9BQWU7UUFDbEMsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQXFCLENBQUE7UUFDL0MsSUFBSSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLGVBQWU7WUFDekQsTUFBTSxDQUFDLGVBQWUsQ0FBQyxJQUFJLEtBQUssT0FBTyxDQUFBO1FBQzNDLENBQUMsQ0FBQyxDQUFBO1FBQ0YsRUFBRSxDQUFDLENBQUMsZ0JBQWdCLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQztZQUM1QixJQUFJLENBQUMsT0FBTyxDQUFDLGdCQUFnQixDQUFDLENBQUE7UUFDbEMsQ0FBQztRQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3ZCLENBQUM7O0lBRUQsYUFBYSxDQUFDLGVBQWtDO1FBQzVDLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxDQUFBO0lBQ3pDLENBQUM7QUFDTCxDQUFDO0FBekRZLFVBQUUsS0F5RGQsQ0FBQSJ9

View File

@ -1,29 +0,0 @@
/// <reference types="q" />
import * as plugins from './smartdata.plugins';
import { Db } from './smartdata.classes.db';
export interface IFindOptions {
limit?: number;
}
export declare function Collection(db: Db): (constructor: any) => void;
export declare class DbCollection<T> {
collection: plugins.mongodb.Collection;
name: string;
constructor(nameArg: string, dbArg: Db);
/**
* adds a validation function that all newly inserted and updated objects have to pass
*/
addObjectValidation(funcArg: any): void;
/**
* finds an object in the DbCollection
*/
find(docMatchArg: T | any, optionsArg?: IFindOptions): plugins.q.Promise<T[]>;
/**
* inserts object into the DbCollection
*/
insertOne(docArg: T): plugins.q.Promise<void>;
/**
* inserts many objects at once into the DbCollection
*/
insertMany(docArrayArg: T[]): plugins.q.Promise<void>;
private checkDoc(doc);
}

View File

@ -1,70 +0,0 @@
"use strict";
const plugins = require('./smartdata.plugins');
function Collection(db) {
return function (constructor) {
constructor['dbCollection'] = new DbCollection(constructor.name, db);
};
}
exports.Collection = Collection;
class DbCollection {
constructor(nameArg, dbArg) {
this.name = nameArg;
this.collection = dbArg.db.collection(nameArg);
}
/**
* adds a validation function that all newly inserted and updated objects have to pass
*/
addObjectValidation(funcArg) { }
/**
* finds an object in the DbCollection
*/
find(docMatchArg, optionsArg) {
let done = plugins.q.defer();
let findCursor = this.collection.find(docMatchArg);
if (optionsArg) {
if (optionsArg.limit) {
findCursor = findCursor.limit(1);
}
}
findCursor.toArray((err, docs) => {
if (err) {
throw err;
}
done.resolve(docs);
});
return done.promise;
}
/**
* inserts object into the DbCollection
*/
insertOne(docArg) {
let done = plugins.q.defer();
this.checkDoc(docArg).then(() => {
this.collection.insertOne(docArg)
.then(() => { done.resolve(); });
});
return done.promise;
}
/**
* inserts many objects at once into the DbCollection
*/
insertMany(docArrayArg) {
let done = plugins.q.defer();
let checkDocPromiseArray = [];
for (let docArg of docArrayArg) {
checkDocPromiseArray.push(this.checkDoc(docArg));
}
plugins.q.all(checkDocPromiseArray).then(() => {
this.collection.insertMany(docArrayArg)
.then(() => { done.resolve(); });
});
return done.promise;
}
checkDoc(doc) {
let done = plugins.q.defer();
done.resolve();
return done.promise;
}
}
exports.DbCollection = DbCollection;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGJjb2xsZWN0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRkYXRhLmNsYXNzZXMuZGJjb2xsZWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxNQUFZLE9BQU8sV0FBTSxxQkFDekIsQ0FBQyxDQUQ2QztBQU85QyxvQkFBMkIsRUFBTTtJQUM3QixNQUFNLENBQUMsVUFBUyxXQUFXO1FBQ3ZCLFdBQVcsQ0FBQyxjQUFjLENBQUMsR0FBRyxJQUFJLFlBQVksQ0FBQyxXQUFXLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBQ3hFLENBQUMsQ0FBQTtBQUNMLENBQUM7QUFKZSxrQkFBVSxhQUl6QixDQUFBO0FBRUQ7SUFHSSxZQUFZLE9BQWUsRUFBRSxLQUFTO1FBQ2xDLElBQUksQ0FBQyxJQUFJLEdBQUcsT0FBTyxDQUFBO1FBQ25CLElBQUksQ0FBQyxVQUFVLEdBQUcsS0FBSyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsT0FBTyxDQUFDLENBQUE7SUFDbEQsQ0FBQztJQUVEOztPQUVHO0lBQ0gsbUJBQW1CLENBQUMsT0FBTyxJQUFJLENBQUM7SUFFaEM7O09BRUc7SUFDSCxJQUFJLENBQUMsV0FBb0IsRUFBRSxVQUF5QjtRQUNoRCxJQUFJLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBTyxDQUFBO1FBQ2pDLElBQUksVUFBVSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1FBQ2xELEVBQUUsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7WUFDYixFQUFFLENBQUMsQ0FBRSxVQUFVLENBQUMsS0FBTSxDQUFDLENBQUMsQ0FBQztnQkFBQyxVQUFVLEdBQUcsVUFBVSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUFDLENBQUM7UUFDaEUsQ0FBQztRQUNELFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxHQUFHLEVBQUUsSUFBSTtZQUN6QixFQUFFLENBQUMsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO2dCQUFDLE1BQU0sR0FBRyxDQUFBO1lBQUMsQ0FBQztZQUN0QixJQUFJLENBQUMsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO1FBQ3RCLENBQUMsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsU0FBUyxDQUFDLE1BQVM7UUFDZixJQUFJLElBQUksR0FBRyxPQUFPLENBQUMsQ0FBQyxDQUFDLEtBQUssRUFBUSxDQUFBO1FBQ2xDLElBQUksQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsSUFBSSxDQUFDO1lBQ3ZCLElBQUksQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLE1BQU0sQ0FBQztpQkFDNUIsSUFBSSxDQUFDLFFBQVEsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDdkMsQ0FBQyxDQUFDLENBQUE7UUFDRixNQUFNLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQTtJQUN2QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxVQUFVLENBQUMsV0FBZ0I7UUFDdkIsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLENBQUMsQ0FBQyxLQUFLLEVBQVEsQ0FBQTtRQUNsQyxJQUFJLG9CQUFvQixHQUE4QixFQUFFLENBQUE7UUFDeEQsR0FBRyxDQUFDLENBQUMsSUFBSSxNQUFNLElBQUksV0FBVyxDQUFDLENBQUEsQ0FBQztZQUM1QixvQkFBb0IsQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFBO1FBQ3BELENBQUM7UUFDRCxPQUFPLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNyQyxJQUFJLENBQUMsVUFBVSxDQUFDLFVBQVUsQ0FBQyxXQUFXLENBQUM7aUJBQ2xDLElBQUksQ0FBQyxRQUFRLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQSxDQUFDLENBQUMsQ0FBQyxDQUFBO1FBQ3ZDLENBQUMsQ0FBQyxDQUFBO1FBQ0YsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztJQUVPLFFBQVEsQ0FBQyxHQUFNO1FBQ25CLElBQUksSUFBSSxHQUFHLE9BQU8sQ0FBQyxDQUFDLENBQUMsS0FBSyxFQUFRLENBQUE7UUFDbEMsSUFBSSxDQUFDLE9BQU8sRUFBRSxDQUFBO1FBQ2QsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUE7SUFDdkIsQ0FBQztBQUNMLENBQUM7QUE5RFksb0JBQVksZUE4RHhCLENBQUEifQ==

View File

@ -1,9 +0,0 @@
import { DbCollection } from './smartdata.classes.dbcollection';
export declare type TDocCreation = 'db' | 'data' | 'mixed';
export declare class DbDoc<T> {
collection: DbCollection<T>;
creationType: TDocCreation;
constructor();
save(): void;
saveDeep(): void;
}

View File

@ -1,12 +0,0 @@
"use strict";
class DbDoc {
constructor() {
this.collection = this.constructor['dbCollection'];
}
save() {
}
saveDeep() {
}
}
exports.DbDoc = DbDoc;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGJkb2MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEuY2xhc3Nlcy5kYmRvYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBUUE7SUFHSTtRQUNJLElBQUksQ0FBQyxVQUFVLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxjQUFjLENBQUMsQ0FBQTtJQUN0RCxDQUFDO0lBQ0QsSUFBSTtJQUVKLENBQUM7SUFDRCxRQUFRO0lBRVIsQ0FBQztBQUNMLENBQUM7QUFaWSxhQUFLLFFBWWpCLENBQUEifQ==

View File

@ -1,5 +0,0 @@
import 'typings-global';
export import assert = require('assert');
export import beautylog = require('beautylog');
export import mongodb = require('mongodb');
export import q = require('q');

View File

@ -1,7 +0,0 @@
"use strict";
require('typings-global');
exports.assert = require('assert');
exports.beautylog = require('beautylog');
exports.mongodb = require('mongodb');
exports.q = require('q');
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsUUFBTyxnQkFDUCxDQUFDLENBRHNCO0FBQ1QsY0FBTSxXQUFXLFFBQVEsQ0FBQyxDQUFBO0FBQzFCLGlCQUFTLFdBQVcsV0FBVyxDQUFDLENBQUE7QUFDaEMsZUFBTyxXQUFXLFNBQVMsQ0FBQyxDQUFBO0FBQzVCLFNBQUMsV0FBVyxHQUFHLENBQUMsQ0FBQSJ9

View File

@ -1,19 +0,0 @@
# smartdata
smartdata is a ODM that adheres to TypeScript practices and uses classes to organize data.
It uses MongoDB as persistent storage.
## Intention
There are many ODMs out there, however when we searched for a ODM that uses TypeScript,
acts smart while still embracing an easy the NoSQL idea we didn't find a matching solution.
This is why we started smartdata
How MongoDB terms map to smartdata classes
MongoDB term | smartdata class
--- | ---
Database | smartdata.DbConnection
Collection | smartdata.DbCollection
Document | smartdata.DbDoc
[![npm](https://push.rocks/assets/repo-header.svg)](https://push.rocks)

36
npmextra.json Normal file
View File

@ -0,0 +1,36 @@
{
"npmdocker": {
"baseImage": "hosttoday/ht-docker-node:mongo",
"command": "npmci test stable",
"dockerSock": false
},
"npmci": {
"npmGlobalTools": [],
"npmAccessLevel": "public",
"npmRegistryUrl": "registry.npmjs.org"
},
"gitzone": {
"projectType": "npm",
"module": {
"githost": "code.foss.global",
"gitscope": "push.rocks",
"gitrepo": "smartdata",
"description": "An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.",
"npmPackagename": "@push.rocks/smartdata",
"license": "MIT",
"keywords": [
"data manipulation",
"NoSQL",
"MongoDB",
"TypeScript",
"data validation",
"collections",
"custom data types",
"ODM"
]
}
},
"tsdoc": {
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
}
}

View File

@ -1,38 +1,71 @@
{
"name": "smartdata",
"version": "1.0.15",
"description": "do more with data",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"name": "@push.rocks/smartdata",
"version": "5.2.6",
"private": false,
"description": "An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
"scripts": {
"test": "(npm run prepareMongo && npmts)",
"prepareMongo": "(rm -rf ./test/data && mkdir ./test/data/)"
"test": "tstest test/",
"build": "tsbuild --web --allowimplicitany",
"buildDocs": "tsdoc"
},
"repository": {
"type": "git",
"url": "git+ssh://git@gitlab.com/pushrocks/smartdata.git"
"url": "https://code.foss.global/push.rocks/smartdata.git"
},
"author": "Lossless GmbH",
"license": "MIT",
"bugs": {
"url": "https://gitlab.com/pushrocks/smartdata/issues"
},
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
"homepage": "https://code.foss.global/push.rocks/smartdata",
"dependencies": {
"@types/mongodb": "^2.1.32",
"@types/q": "0.0.30",
"beautylog": "^5.0.23",
"lik": "^1.0.15",
"mongodb": "^2.2.9",
"q": "^1.4.1",
"typings-global": "^1.0.14"
"@push.rocks/lik": "^6.0.14",
"@push.rocks/smartdelay": "^3.0.1",
"@push.rocks/smartlog": "^3.0.2",
"@push.rocks/smartmongo": "^2.0.10",
"@push.rocks/smartpromise": "^4.0.2",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smarttime": "^4.0.6",
"@push.rocks/smartunique": "^3.0.8",
"@push.rocks/taskbuffer": "^3.1.7",
"@tsclass/tsclass": "^4.0.52",
"mongodb": "^6.5.0"
},
"devDependencies": {
"@types/shelljs": "^0.3.30",
"@types/should": "^8.1.29",
"shelljs": "^0.7.4",
"should": "^11.1.0",
"smartstring": "^2.0.17",
"typings-test": "^1.0.3"
}
"@gitzone/tsbuild": "^2.1.66",
"@gitzone/tsrun": "^1.2.44",
"@gitzone/tstest": "^1.0.77",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/tapbundle": "^5.0.22",
"@types/node": "^20.11.30"
},
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_*/**/*",
"dist_ts/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 chrome versions"
],
"keywords": [
"data manipulation",
"NoSQL",
"MongoDB",
"TypeScript",
"data validation",
"collections",
"custom data types",
"ODM"
]
}

6881
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

1
qenv.yml Normal file
View File

@ -0,0 +1 @@
required:

0
readme.hints.md Normal file
View File

115
readme.md Normal file
View File

@ -0,0 +1,115 @@
# @push.rocks/smartdata
do more with data
## Install
To install `@push.rocks/smartdata`, use npm:
```bash
npm install @push.rocks/smartdata --save
```
This will add `@push.rocks/smartdata` to your project's dependencies.
## Usage
`@push.rocks/smartdata` enables efficient data handling and operation management with a focus on using MongoDB. It leverages TypeScript for strong typing and ESM syntax for modern JavaScript usage. Below are various scenarios demonstrating how to utilize this package effectively in a project.
### Setting Up and Connecting to the Database
Before interacting with the database, you need to set up and establish a connection. This is done by creating an instance of `SmartdataDb` and calling its `init` method with your MongoDB connection details.
```typescript
import { SmartdataDb } from '@push.rocks/smartdata';
// Create a new instance of SmartdataDb with MongoDB connection details
const db = new SmartdataDb({
mongoDbUrl: 'mongodb://localhost:27017',
mongoDbName: 'your-database-name',
mongoDbUser: 'your-username',
mongoDbPass: 'your-password',
});
// Initialize and connect to the database
await db.init();
```
### Defining Data Models
Data models in `@push.rocks/smartdata` are classes that represent collections and documents in your MongoDB database. Use decorators such as `@Collection`, `@unI`, and `@svDb` to define your data models.
```typescript
import { SmartDataDbDoc, Collection, unI, svDb } from '@push.rocks/smartdata';
@Collection(() => db) // Associate this model with the database instance
class User extends SmartDataDbDoc<User, User> {
@unI()
public id: string = 'unique-user-id'; // Mark 'id' as a unique index
@svDb()
public username: string; // Mark 'username' to be saved in DB
@svDb()
public email: string; // Mark 'email' to be saved in DB
constructor(username: string, email: string) {
super();
this.username = username;
this.email = email;
}
}
```
### Performing CRUD Operations
`@push.rocks/smartdata` simplifies CRUD operations with intuitive methods on model instances.
#### Create
```typescript
const newUser = new User('myUsername', 'myEmail@example.com');
await newUser.save(); // Save the new user to the database
```
#### Read
```typescript
// Fetch a single user by a unique attribute
const user = await User.getInstance({ username: 'myUsername' });
// Fetch multiple users that match criteria
const users = await User.getInstances({ email: 'myEmail@example.com' });
```
#### Update
```typescript
// Assuming 'user' is an instance of User
user.email = 'newEmail@example.com';
await user.save(); // Update the user in the database
```
#### Delete
```typescript
// Assuming 'user' is an instance of User
await user.delete(); // Delete the user from the database
```
### Advanced Usage
`@push.rocks/smartdata` also supports advanced features like watching for real-time changes in the database, handling distributed data coordination, and more. These features utilize MongoDB's capabilities to provide real-time data syncing and distributed systems coordination.
### Conclusion
With its focus on TypeScript, modern JavaScript syntax, and leveraging MongoDB's features, `@push.rocks/smartdata` offers a powerful toolkit for data handling and operations management in Node.js applications. Its design for ease of use, coupled with advanced features, makes it a versatile choice for developers looking to build efficient and scalable data-driven applications.
For more details on usage and additional features, refer to the [official documentation](https://gitlab.com/push.rocks/smartdata#README) and explore the various classes and methods provided by `@push.rocks/smartdata`.
## License and Legal Information
This repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository.
**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
### Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.
### Company Information
Task Venture Capital GmbH
Registered at District court Bremen HRB 35230 HB, Germany
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

1
test/test.d.ts vendored
View File

@ -1 +0,0 @@
import 'typings-test';

View File

@ -0,0 +1,112 @@
import { tap, expect } from '@push.rocks/tapbundle';
import * as smartmongo from '@push.rocks/smartmongo';
import type * as taskbuffer from '@push.rocks/taskbuffer';
import * as smartdata from '../ts/index.js';
import { SmartdataDistributedCoordinator, DistributedClass } from '../ts/smartdata.classes.distributedcoordinator.js'; // path might need adjusting
const totalInstances = 10;
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.test('should instantiate DistributedClass', async (tools) => {
const instance = new DistributedClass();
expect(instance).toBeInstanceOf(DistributedClass);
});
tap.test('DistributedClass should update the time', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const initialTime = distributedCoordinator.ownInstance.data.lastUpdated;
await distributedCoordinator.sendHeartbeat();
const updatedTime = distributedCoordinator.ownInstance.data.lastUpdated;
expect(updatedTime).toBeGreaterThan(initialTime);
await distributedCoordinator.stop();
});
tap.test('should instantiate SmartdataDistributedCoordinator', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
expect(distributedCoordinator).toBeInstanceOf(SmartdataDistributedCoordinator);
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should update leader status', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
await distributedCoordinator.checkAndMaybeLead();
expect(distributedCoordinator.ownInstance.data.elected).toBeOneOf([true, false]);
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should handle distributed task requests', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
taskName: "SampleTask",
taskVersion: "1.0.0", // Assuming it's a version string
taskExecutionTime: Date.now(),
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
status: 'requesting'
};
const response = await distributedCoordinator.fireDistributedTaskRequest(mockTaskRequest);
console.log(response) // based on your expected structure for the response
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should update distributed task requests', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
taskName: "SampleTask",
taskVersion: "1.0.0", // Assuming it's a version string
taskExecutionTime: Date.now(),
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
status: 'requesting'
};
await distributedCoordinator.updateDistributedTaskRequest(mockTaskRequest);
// Here, we can potentially check if a DB entry got updated or some other side-effect of the update method.
await distributedCoordinator.stop();
});
tap.test('should elect only one leader amongst multiple instances', async (tools) => {
const coordinators = Array.from({ length: totalInstances }).map(() => new SmartdataDistributedCoordinator(testDb));
await Promise.all(coordinators.map(coordinator => coordinator.start()));
const leaders = coordinators.filter(coordinator => coordinator.ownInstance.data.elected);
for (const leader of leaders) {
console.log(leader.ownInstance);
}
expect(leaders.length).toEqual(1);
// stopping clears a coordinator from being elected.
await Promise.all(coordinators.map(coordinator => coordinator.stop()));
});
tap.test('should clean up', async () => {
await smartmongoInstance.stopAndDumpToDir(`.nogit/dbdump/test.distributedcoordinator.ts`);
setTimeout(() => process.exit(), 2000);
})
tap.start({ throwOnError: true });

55
test/test.easystore.ts Normal file
View File

@ -0,0 +1,55 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage());
// the tested module
import * as smartdata from '../ts/index.js';
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();
});
let easyStore: smartdata.EasyStore<{
key1: string;
key2: string;
}>;
tap.test('should create an easystore', async () => {
easyStore = await testDb.createEasyStore('hellothere');
await easyStore.writeKey('key1', 'hello');
const retrievedKey = await easyStore.readKey('key1');
expect(retrievedKey).toEqual('hello');
});
tap.test('close', async () => {
await testDb.mongoDb.dropDatabase();
await testDb.close();
if (smartmongoInstance) {
await smartmongoInstance.stop();
}
});
tap.start();

View File

@ -1,101 +0,0 @@
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
require('typings-test');
const shelljs = require('shelljs');
const should = require('should');
const smartstring = require('smartstring');
// the tested module
const smartdata = require('../dist/index');
let mongoChildProcess;
let testDb;
let testDbCollection;
describe('mongodb', function () {
it('should start mongodb', function (done) {
this.timeout(30000);
mongoChildProcess = shelljs.exec('mongod --dbpath=./test/data --port 27017', { async: true, silent: true });
let doneCalled = false;
mongoChildProcess.stdout.on('data', function (data) {
console.log(smartstring.indent.indentWithPrefix(data, '*** MongoDB Process *** : '));
if (!doneCalled) {
if (/waiting for connections on port 27017/.test(data)) {
doneCalled = true;
done();
}
}
});
});
});
describe('smartdata', function () {
it('should establish a connection to mongodb', function (done) {
testDb = new smartdata.Db('mongodb://localhost:27017/smartdata');
testDb.connect().then(() => { done(); });
});
it('should create a collection', function () {
testDbCollection = new smartdata.DbCollection('something', testDb);
});
it('should insert a doc into the collection', function (done) {
testDbCollection.insertOne({ value1: 'test' }).then(() => { done(); });
});
it('should find all docs of testDbCollection', function (done) {
testDbCollection.find({}).then((resultArray) => {
console.log(resultArray);
should(resultArray[0].value1).equal('test');
done();
});
});
it('should insert many docs into the collection', function (done) {
testDbCollection.insertMany([
{ value1: 'test2' },
{ value1: 'test', value2: 3, value3: 'hi' }
]).then(() => { done(); });
});
it('should find a specified doc', function (done) {
testDbCollection.find({ 'value3': { '$exists': true } }).then((resultArray) => {
console.log(resultArray);
should(resultArray[0].value3).equal('hi');
done();
}).catch(console.log);
});
it('should close the db Connection', function () {
testDb.close();
});
it('should create an extended class', function () {
let TestCar = class TestCar extends smartdata.DbDoc {
constructor(optionsArg) {
super();
this.color = optionsArg.color;
}
};
TestCar = __decorate([
smartdata.Collection(testDb)
], TestCar);
;
let testCarInstance = new TestCar({
color: 'red',
property2: 2
});
should(testCarInstance.collection).be.instanceof(smartdata.DbCollection);
should(testCarInstance).be.instanceof(smartdata.DbDoc);
testCarInstance.save();
it('should get a collection for testCar', function () {
});
});
});
describe('mongodb', function () {
it('should kill mongodb', function (done) {
this.timeout(30000);
mongoChildProcess.stdout.on('data', function (data) {
if (/dbexit: rc: 0/.test(data)) {
done();
}
});
shelljs.exec('mongod --dbpath=./test/data --shutdown');
mongoChildProcess.kill('SIGTERM');
});
});
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7OztBQUFBLFFBQU8sY0FFUCxDQUFDLENBRm9CO0FBRXJCLE1BQVksT0FBTyxXQUFNLFNBQ3pCLENBQUMsQ0FEaUM7QUFDbEMsTUFBWSxNQUFNLFdBQU0sUUFDeEIsQ0FBQyxDQUQrQjtBQUNoQyxNQUFZLFdBQVcsV0FBTSxhQUc3QixDQUFDLENBSHlDO0FBRTFDLG9CQUFvQjtBQUNwQixNQUFZLFNBQVMsV0FBTSxlQUUzQixDQUFDLENBRnlDO0FBRTFDLElBQUksaUJBQWlCLENBQUE7QUFDckIsSUFBSSxNQUFvQixDQUFBO0FBUXhCLElBQUksZ0JBQXNELENBQUE7QUFFMUQsUUFBUSxDQUFDLFNBQVMsRUFBRTtJQUNoQixFQUFFLENBQUMsc0JBQXNCLEVBQUUsVUFBVSxJQUFJO1FBQ3JDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsaUJBQWlCLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQywwQ0FBMEMsRUFBRSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUE7UUFDM0csSUFBSSxVQUFVLEdBQUcsS0FBSyxDQUFBO1FBQ3RCLGlCQUFpQixDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxFQUFFLFVBQVUsSUFBSTtZQUM5QyxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsZ0JBQWdCLENBQUMsSUFBSSxFQUFFLDRCQUE0QixDQUFDLENBQUMsQ0FBQTtZQUNwRixFQUFFLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUM7Z0JBQ2QsRUFBRSxDQUFDLENBQUMsdUNBQXVDLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztvQkFDckQsVUFBVSxHQUFHLElBQUksQ0FBQTtvQkFDakIsSUFBSSxFQUFFLENBQUE7Z0JBQ1YsQ0FBQztZQUNMLENBQUM7UUFDTCxDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFDLENBQUE7QUFFRixRQUFRLENBQUMsV0FBVyxFQUFFO0lBQ2xCLEVBQUUsQ0FBQywwQ0FBMEMsRUFBRSxVQUFVLElBQUk7UUFDekQsTUFBTSxHQUFHLElBQUksU0FBUyxDQUFDLEVBQUUsQ0FBQyxxQ0FBcUMsQ0FBQyxDQUFBO1FBQ2hFLE1BQU0sQ0FBQyxPQUFPLEVBQUUsQ0FBQyxJQUFJLENBQUMsUUFBUSxJQUFJLEVBQUUsQ0FBQSxDQUFDLENBQUMsQ0FBQyxDQUFBO0lBQzNDLENBQUMsQ0FBQyxDQUFBO0lBQ0YsRUFBRSxDQUFDLDRCQUE0QixFQUFFO1FBQzdCLGdCQUFnQixHQUFHLElBQUksU0FBUyxDQUFDLFlBQVksQ0FBZSxXQUFXLEVBQUUsTUFBTSxDQUFDLENBQUE7SUFDcEYsQ0FBQyxDQUFDLENBQUE7SUFDRixFQUFFLENBQUMseUNBQXlDLEVBQUUsVUFBVSxJQUFJO1FBQ3hELGdCQUFnQixDQUFDLFNBQVMsQ0FBQyxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLElBQUksRUFBRSxDQUFBLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDekUsQ0FBQyxDQUFDLENBQUE7SUFDRixFQUFFLENBQUMsMENBQTBDLEVBQUUsVUFBVSxJQUFJO1FBQ3pELGdCQUFnQixDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxXQUFXO1lBQ3ZDLE9BQU8sQ0FBQyxHQUFHLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDeEIsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLENBQUE7WUFDM0MsSUFBSSxFQUFFLENBQUE7UUFDVixDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0lBQ0YsRUFBRSxDQUFDLDZDQUE2QyxFQUFFLFVBQVUsSUFBSTtRQUM1RCxnQkFBZ0IsQ0FBQyxVQUFVLENBQUM7WUFDeEIsRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFO1lBQ25CLEVBQUUsTUFBTSxFQUFFLE1BQU0sRUFBRSxNQUFNLEVBQUUsQ0FBQyxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7U0FDOUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxRQUFRLElBQUksRUFBRSxDQUFBLENBQUMsQ0FBQyxDQUFDLENBQUE7SUFDN0IsQ0FBQyxDQUFDLENBQUE7SUFDRixFQUFFLENBQUMsNkJBQTZCLEVBQUUsVUFBVSxJQUFJO1FBQzVDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxFQUFFLFFBQVEsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsV0FBVztZQUN0RSxPQUFPLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ3hCLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUMsTUFBTSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFBO1lBQ3pDLElBQUksRUFBRSxDQUFBO1FBQ1YsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUN6QixDQUFDLENBQUMsQ0FBQTtJQUNGLEVBQUUsQ0FBQyxnQ0FBZ0MsRUFBRTtRQUNqQyxNQUFNLENBQUMsS0FBSyxFQUFFLENBQUE7SUFDbEIsQ0FBQyxDQUFDLENBQUE7SUFDRixFQUFFLENBQUMsaUNBQWlDLEVBQUU7UUFFbEMsb0NBQXNCLFNBQVMsQ0FBQyxLQUFLO1lBRWpDLFlBQVksVUFHWDtnQkFDRyxPQUFPLENBQUE7Z0JBQ1AsSUFBSSxDQUFDLEtBQUssR0FBRyxVQUFVLENBQUMsS0FBSyxDQUFBO1lBQ2pDLENBQUM7UUFDTCxDQUFDO1FBVkQ7WUFBQyxTQUFTLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQzttQkFBQTtRQVU1QixDQUFDO1FBQ0YsSUFBSSxlQUFlLEdBQUcsSUFBSSxPQUFPLENBQUM7WUFDOUIsS0FBSyxFQUFFLEtBQUs7WUFDWixTQUFTLEVBQUUsQ0FBQztTQUNmLENBQUMsQ0FBQTtRQUNGLE1BQU0sQ0FBQyxlQUFlLENBQUMsVUFBVSxDQUFDLENBQUMsRUFBRSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDeEUsTUFBTSxDQUFDLGVBQWUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFBO1FBQ3RELGVBQWUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUN0QixFQUFFLENBQUMscUNBQXFDLEVBQUM7UUFFekMsQ0FBQyxDQUFDLENBQUE7SUFDTixDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQyxDQUFBO0FBRUYsUUFBUSxDQUFDLFNBQVMsRUFBRTtJQUNoQixFQUFFLENBQUMscUJBQXFCLEVBQUUsVUFBVSxJQUFJO1FBQ3BDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDbkIsaUJBQWlCLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLEVBQUUsVUFBVSxJQUFJO1lBQzlDLEVBQUUsQ0FBQyxDQUFDLGdCQUFnQixDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQzlCLElBQUksRUFBRSxDQUFBO1lBQ1YsQ0FBQztRQUNMLENBQUMsQ0FBQyxDQUFBO1FBQ0YsT0FBTyxDQUFDLElBQUksQ0FBQyx3Q0FBd0MsQ0FBQyxDQUFBO1FBQ3RELGlCQUFpQixDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUNyQyxDQUFDLENBQUMsQ0FBQTtBQUNOLENBQUMsQ0FBQyxDQUFBIn0=

View File

@ -1,108 +1,236 @@
import 'typings-test'
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
import * as shelljs from 'shelljs'
import * as should from 'should'
import * as smartstring from 'smartstring'
import * as mongodb from 'mongodb';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage());
// the tested module
import * as smartdata from '../dist/index'
import * as smartdata from '../ts/index.js';
let mongoChildProcess
let testDb: smartdata.Db
// =======================================
// Connecting to the database server
// =======================================
interface ITestObject1 {
value1: string
value2?: number
value3?: string
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
const totalCars = 2000;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();
});
// =======================================
// The actual tests
// =======================================
// ------
// Collections
// ------
@smartdata.Collection(() => {
return testDb;
})
class Car extends smartdata.SmartDataDbDoc<Car, Car> {
@smartdata.unI()
public index: string = smartunique.shortId();
@smartdata.svDb()
public color: string;
@smartdata.svDb()
public brand: string;
@smartdata.svDb()
public testBuffer = Buffer.from('hello');
@smartdata.svDb()
deepData = {
sodeep: 'yes',
};
constructor(colorArg: string, brandArg: string) {
super();
this.color = colorArg;
this.brand = brandArg;
}
}
let testDbCollection: smartdata.DbCollection<ITestObject1>
tap.test('should create a new id', async () => {
const newid = await Car.getNewId();
console.log(newid);
});
describe('mongodb', function () {
it('should start mongodb', function (done) {
this.timeout(30000)
mongoChildProcess = shelljs.exec('mongod --dbpath=./test/data --port 27017', { async: true, silent: true })
let doneCalled = false
mongoChildProcess.stdout.on('data', function (data) {
console.log(smartstring.indent.indentWithPrefix(data, '*** MongoDB Process *** : '))
if (!doneCalled) {
if (/waiting for connections on port 27017/.test(data)) {
doneCalled = true
done()
}
}
})
})
tap.test('should save the car to the db', async (toolsArg) => {
const myCar = new Car('red', 'Volvo');
await myCar.save();
const myCar2 = new Car('red', 'Volvo');
await myCar2.save();
let counter = 0;
const gottenCarInstance = await Car.getInstance({});
console.log(gottenCarInstance.testBuffer instanceof mongodb.Binary);
process.memoryUsage();
do {
const myCar3 = new Car('red', 'Renault');
await myCar3.save();
counter++;
if (counter % 100 === 0) {
console.log(
`Filled database with ${counter} of ${totalCars} Cars and memory usage ${
process.memoryUsage().rss / 1e6
} MB`
);
}
} while (counter < totalCars);
console.log(process.memoryUsage());
});
tap.test('expect to get instance of Car with shallow match', async () => {
const totalQueryCycles = totalCars / 2;
let counter = 0;
do {
const timeStart = Date.now();
const myCars = await Car.getInstances({
brand: 'Renault',
});
if (counter % 10 === 0) {
console.log(
`performed ${counter} of ${totalQueryCycles} total query cycles: took ${
Date.now() - timeStart
}ms to query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB`
);
}
expect(myCars[0].deepData.sodeep).toEqual('yes');
expect(myCars[0].brand).toEqual('Renault');
counter++;
} while (counter < totalQueryCycles);
});
tap.test('expect to get instance of Car with deep match', async () => {
const totalQueryCycles = totalCars / 6;
let counter = 0;
do {
const timeStart = Date.now();
const myCars2 = await Car.getInstances({
deepData: {
sodeep: 'yes',
},
});
if (counter % 10 === 0) {
console.log(
`performed ${counter} of ${totalQueryCycles} total query cycles: took ${
Date.now() - timeStart
}ms to deep query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB`
);
}
expect(myCars2[0].deepData.sodeep).toEqual('yes');
expect(myCars2[0].brand).toEqual('Volvo');
counter++;
} while (counter < totalQueryCycles);
});
tap.test('expect to get instance of Car and update it', async () => {
const myCar = await Car.getInstance<Car>({
brand: 'Volvo',
});
expect(myCar.color).toEqual('red');
myCar.color = 'blue';
await myCar.save();
});
tap.test('should be able to delete an instance of car', async () => {
const myCars = await Car.getInstances({
brand: 'Volvo',
color: 'blue',
});
console.log(myCars);
expect(myCars[0].color).toEqual('blue');
for (const myCar of myCars) {
await myCar.delete();
}
const myCar2 = await Car.getInstance<Car>({
brand: 'Volvo',
});
expect(myCar2.color).toEqual('red');
});
// tslint:disable-next-line: max-classes-per-file
@smartdata.Collection(() => {
return testDb;
})
class Truck extends smartdata.SmartDataDbDoc<Car, Car> {
@smartdata.unI()
public id: string = smartunique.shortId();
@smartdata.svDb()
public color: string;
@smartdata.svDb()
public brand: string;
constructor(colorArg: string, brandArg: string) {
super();
this.color = colorArg;
this.brand = brandArg;
}
}
tap.test('should store a new Truck', async () => {
const truck = new Truck('blue', 'MAN');
await truck.save();
const myTruck2 = await Truck.getInstance({ color: 'blue' });
expect(myTruck2.color).toEqual('blue');
myTruck2.color = 'red';
await myTruck2.save();
const myTruck3 = await Truck.getInstance({ color: 'blue' });
expect(myTruck3).toBeNull();
});
tap.test('should return a count', async () => {
const truckCount = await Truck.getCount();
expect(truckCount).toEqual(1);
})
describe('smartdata', function () {
it('should establish a connection to mongodb', function (done) {
testDb = new smartdata.Db('mongodb://localhost:27017/smartdata')
testDb.connect().then(() => { done() })
})
it('should create a collection', function () {
testDbCollection = new smartdata.DbCollection<ITestObject1>('something', testDb)
})
it('should insert a doc into the collection', function (done) {
testDbCollection.insertOne({ value1: 'test' }).then(() => { done() })
})
it('should find all docs of testDbCollection', function (done) {
testDbCollection.find({}).then((resultArray) => {
console.log(resultArray)
should(resultArray[0].value1).equal('test')
done()
})
})
it('should insert many docs into the collection', function (done) {
testDbCollection.insertMany([
{ value1: 'test2' },
{ value1: 'test', value2: 3, value3: 'hi' }
]).then(() => { done() })
})
it('should find a specified doc', function (done) {
testDbCollection.find({ 'value3': { '$exists': true } }).then((resultArray) => {
console.log(resultArray)
should(resultArray[0].value3).equal('hi')
done()
}).catch(console.log)
})
it('should close the db Connection', function () {
testDb.close()
})
it('should create an extended class', function () {
@smartdata.Collection(testDb)
class TestCar extends smartdata.DbDoc<TestCar> {
color: string
constructor(optionsArg: {
color: string,
property2: number
}) {
super()
this.color = optionsArg.color
}
};
let testCarInstance = new TestCar({
color: 'red',
property2: 2
})
should(testCarInstance.collection).be.instanceof(smartdata.DbCollection)
should(testCarInstance).be.instanceof(smartdata.DbDoc)
testCarInstance.save()
it('should get a collection for testCar',function() {
tap.test('should use a cursor', async () => {
const cursor = await Car.getCursor({});
let counter = 0;
await cursor.forEach(async (carArg) => {
counter++;
counter % 50 === 0 ? console.log(`50 more of ${carArg.color}`) : null;
});
});
})
})
})
// =======================================
// close the database connection
// =======================================
tap.test('close', async () => {
if (smartmongoInstance) {
await smartmongoInstance.stopAndDumpToDir('./.nogit/dbdump/test.ts');
} else {
await testDb.mongoDb.dropDatabase();
await testDb.close();
}
setTimeout(() => process.exit(), 2000);
});
describe('mongodb', function () {
it('should kill mongodb', function (done) {
this.timeout(30000)
mongoChildProcess.stdout.on('data', function (data) {
if (/dbexit: rc: 0/.test(data)) {
done()
}
})
shelljs.exec('mongod --dbpath=./test/data --shutdown')
mongoChildProcess.kill('SIGTERM')
})
})
tap.start({ throwOnError: true });

95
test/test.typescript.ts Normal file
View File

@ -0,0 +1,95 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage());
// the tested module
import * as smartdata from '../ts/index.js';
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
const totalCars = 2000;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();
});
// =======================================
// The actual tests
// =======================================
// ------
// Collections
// ------
@smartdata.Manager()
class Car extends smartdata.SmartDataDbDoc<Car, Car> {
@smartdata.unI()
public index: string = smartunique.shortId();
@smartdata.svDb()
public color: string;
@smartdata.svDb()
public brand: string;
@smartdata.svDb()
deepData = {
sodeep: 'yes',
};
constructor(colorArg: string, brandArg: string) {
super();
this.color = colorArg;
this.brand = brandArg;
}
}
const createCarClass = (dbArg: smartdata.SmartdataDb) => {
smartdata.setDefaultManagerForDoc({ db: dbArg }, Car);
return Car;
};
tap.test('should produce a car', async () => {
const CarClass = createCarClass(testDb);
const carInstance = new CarClass('red', 'Mercedes');
await carInstance.save();
});
tap.test('should get a car', async () => {
const car = Car.getInstance({
color: 'red',
});
});
// =======================================
// close the database connection
// =======================================
tap.test('close', async () => {
await testDb.mongoDb.dropDatabase();
await testDb.close();
if (smartmongoInstance) {
await smartmongoInstance.stop();
}
});
tap.start({ throwOnError: true });

74
test/test.watch.ts Normal file
View File

@ -0,0 +1,74 @@
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage());
// the tested module
import * as smartdata from '../ts/index.js';
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
const totalCars = 2000;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();
});
@smartdata.Collection(() => testDb)
class House extends smartdata.SmartDataDbDoc<House, House> {
@smartdata.unI()
public id: string = smartunique.shortId();
@smartdata.svDb()
public data = {
id: smartunique.shortId(),
hello: 'hello',
};
}
tap.test('should watch a collection', async (toolsArg) => {
const done = toolsArg.defer();
const watcher = await House.watch({});
watcher.changeSubject.subscribe(async (houseArg) => {
console.log('hey there, we observed a house');
await watcher.close();
done.resolve();
});
const newHouse = new House();
await newHouse.save();
console.log('saved a house');
await done.promise;
});
// =======================================
// close the database connection
// =======================================
tap.test('close', async () => {
await testDb.mongoDb.dropDatabase();
await testDb.close();
if (smartmongoInstance) {
await smartmongoInstance.stop();
}
});
tap.start({ throwOnError: true });

8
ts/00_commitinfo_data.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@push.rocks/smartdata',
version: '5.2.6',
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
}

View File

@ -1,5 +1,14 @@
import * as plugins from './smartdata.plugins'
export * from './smartdata.classes.db.js';
export * from './smartdata.classes.collection.js';
export * from './smartdata.classes.doc.js';
export * from './smartdata.classes.easystore.js';
export * from './smartdata.classes.cursor.js';
export * from './smartdata.classes.db'
export * from './smartdata.classes.dbcollection'
export * from './smartdata.classes.dbdoc'
import * as convenience from './smartadata.convenience.js';
export { convenience };
// to be removed with the next breaking update
import type * as plugins from './smartdata.plugins.js';
type IMongoDescriptor = plugins.tsclass.database.IMongoDescriptor;
export type { IMongoDescriptor };

View File

@ -0,0 +1,5 @@
import * as plugins from './smartdata.plugins.js';
export const getNewUniqueId = async (prefixArg?: string) => {
return plugins.smartunique.uni(prefixArg);
};

View File

@ -0,0 +1,298 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { SmartDataDbDoc } from './smartdata.classes.doc.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
import { CollectionFactory } from './smartdata.classes.collectionfactory.js';
export interface IFindOptions {
limit?: number;
}
/**
*
*/
export interface IDocValidationFunc<T> {
(doc: T): boolean;
}
export type TDelayed<TDelayedArg> = () => TDelayedArg;
const collectionFactory = new CollectionFactory();
/**
* This is a decorator that will tell the decorated class what dbTable to use
* @param dbArg
*/
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
const decoratedClass = class extends constructor {
public static className = constructor.name;
public static get collection() {
if (!(dbArg instanceof SmartdataDb)) {
dbArg = dbArg();
}
return collectionFactory.getCollection(constructor.name, dbArg);
}
public get collection() {
if (!(dbArg instanceof SmartdataDb)) {
dbArg = dbArg();
}
return collectionFactory.getCollection(constructor.name, dbArg);
}
};
return decoratedClass;
};
}
export interface IManager {
db: SmartdataDb;
}
export const setDefaultManagerForDoc = <T>(managerArg: IManager, dbDocArg: T): T => {
(dbDocArg as any).prototype.defaultManager = managerArg;
return dbDocArg;
};
/**
* This is a decorator that will tell the decorated class what dbTable to use
* @param dbArg
*/
export function managed<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
const decoratedClass = class extends constructor {
public static className = constructor.name;
public static get collection() {
let dbArg: SmartdataDb;
if (!managerArg) {
dbArg = this.prototype.defaultManager.db;
} else if (managerArg['db']) {
dbArg = (managerArg as TManager).db;
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
return collectionFactory.getCollection(constructor.name, dbArg);
}
public get collection() {
let dbArg: SmartdataDb;
if (!managerArg) {
//console.log(this.defaultManager.db);
//process.exit(0)
dbArg = this.defaultManager.db;
} else if (managerArg['db']) {
dbArg = (managerArg as TManager).db;
} else {
dbArg = (managerArg as TDelayed<TManager>)().db;
}
return collectionFactory.getCollection(constructor.name, dbArg);
}
public static get manager() {
let manager: TManager;
if (!managerArg) {
manager = this.prototype.defaultManager;
} else if (managerArg['db']) {
manager = managerArg as TManager;
} else {
manager = (managerArg as TDelayed<TManager>)();
}
return manager;
}
public get manager() {
let manager: TManager;
if (!managerArg) {
manager = this.defaultManager;
} else if (managerArg['db']) {
manager = managerArg as TManager;
} else {
manager = (managerArg as TDelayed<TManager>)();
}
return manager;
}
};
return decoratedClass;
};
}
/**
* @dpecrecated use @managed instead
*/
export const Manager = managed;
export class SmartdataCollection<T> {
/**
* the collection that is used
*/
public mongoDbCollection: plugins.mongodb.Collection;
public objectValidation: IDocValidationFunc<T> = null;
public collectionName: string;
public smartdataDb: SmartdataDb;
public uniqueIndexes: string[] = [];
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
// tell the collection where it belongs
this.collectionName = classNameArg;
this.smartdataDb = smartDataDbArg;
// tell the db class about it (important since Db uses different systems under the hood)
this.smartdataDb.addCollection(this);
}
/**
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
*/
public async init() {
if (!this.mongoDbCollection) {
// connect this instance to a MongoDB collection
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
const wantedCollection = availableMongoDbCollections.find((collection) => {
return collection.collectionName === this.collectionName;
});
if (!wantedCollection) {
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
console.log(`Successfully initiated Collection ${this.collectionName}`);
}
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
}
}
/**
* mark unique index
*/
public markUniqueIndexes(keyArrayArg: string[] = []) {
for (const key of keyArrayArg) {
if (!this.uniqueIndexes.includes(key)) {
this.mongoDbCollection.createIndex(key, {
unique: true,
});
// make sure we only call this once and not for every doc we create
this.uniqueIndexes.push(key);
}
}
}
/**
* adds a validation function that all newly inserted and updated objects have to pass
*/
public addDocValidation(funcArg: IDocValidationFunc<T>) {
this.objectValidation = funcArg;
}
/**
* finds an object in the DbCollection
*/
public async findOne(filterObject: any): Promise<any> {
await this.init();
const cursor = this.mongoDbCollection.find(filterObject);
const result = await cursor.next();
cursor.close();
return result;
}
public async getCursor(
filterObjectArg: any,
dbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbCursor<any>> {
await this.init();
const cursor = this.mongoDbCollection.find(filterObjectArg);
return new SmartdataDbCursor(cursor, dbDocArg);
}
/**
* finds an object in the DbCollection
*/
public async findAll(filterObject: any): Promise<any[]> {
await this.init();
const cursor = this.mongoDbCollection.find(filterObject);
const result = await cursor.toArray();
cursor.close();
return result;
}
/**
* watches the collection while applying a filter
*/
public async watch(
filterObject: any,
smartdataDbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbWatcher> {
await this.init();
const changeStream = this.mongoDbCollection.watch(
[
{
$match: filterObject,
},
],
{
fullDocument: 'updateLookup',
}
);
const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
await smartdataWatcher.readyDeferred.promise;
return smartdataWatcher;
}
/**
* create an object in the database
*/
public async insert(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
await this.init();
await this.checkDoc(dbDocArg);
this.markUniqueIndexes(dbDocArg.uniqueIndexes);
const saveableObject = await dbDocArg.createSavableObject();
const result = await this.mongoDbCollection.insertOne(saveableObject);
return result;
}
/**
* inserts object into the DbCollection
*/
public async update(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
await this.init();
await this.checkDoc(dbDocArg);
const identifiableObject = await dbDocArg.createIdentifiableObject();
const saveableObject = await dbDocArg.createSavableObject();
const updateableObject: any = {};
for (const key of Object.keys(saveableObject)) {
if (identifiableObject[key]) {
continue;
}
updateableObject[key] = saveableObject[key];
}
const result = await this.mongoDbCollection.updateOne(
identifiableObject,
{ $set: updateableObject },
{ upsert: true }
);
return result;
}
public async delete(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
await this.init();
await this.checkDoc(dbDocArg);
const identifiableObject = await dbDocArg.createIdentifiableObject();
await this.mongoDbCollection.deleteOne(identifiableObject);
}
public async getCount(filterObject: any) {
await this.init();
return this.mongoDbCollection.countDocuments(filterObject);
}
/**
* checks a Doc for constraints
* if this.objectValidation is not set it passes.
*/
private checkDoc(docArg: T): Promise<void> {
const done = plugins.smartpromise.defer<void>();
let validationResult = true;
if (this.objectValidation) {
validationResult = this.objectValidation(docArg);
}
if (validationResult) {
done.resolve();
} else {
done.reject('validation of object did not pass');
}
return done.promise;
}
}

View File

@ -0,0 +1,19 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataCollection } from './smartdata.classes.collection.js';
import { SmartdataDb } from './smartdata.classes.db.js';
export class CollectionFactory {
public collections: { [key: string]: SmartdataCollection<any> } = {};
public getCollection = (nameArg: string, dbArg: SmartdataDb): SmartdataCollection<any> => {
if (!this.collections[nameArg]) {
this.collections[nameArg] = (() => {
if (dbArg instanceof SmartdataDb) {
// tslint:disable-next-line: no-string-literal
return new SmartdataCollection(nameArg, dbArg);
}
})();
}
return this.collections[nameArg];
};
}

View File

@ -0,0 +1,46 @@
import { SmartDataDbDoc } from './smartdata.classes.doc.js';
import * as plugins from './smartdata.plugins.js';
/**
* a wrapper for the native mongodb cursor. Exposes better
*/
export class SmartdataDbCursor<T = any> {
// STATIC
// INSTANCE
public mongodbCursor: plugins.mongodb.FindCursor<T>;
private smartdataDbDoc: typeof SmartDataDbDoc;
constructor(cursorArg: plugins.mongodb.FindCursor<T>, dbDocArg: typeof SmartDataDbDoc) {
this.mongodbCursor = cursorArg;
this.smartdataDbDoc = dbDocArg;
}
public async next(closeAtEnd = true) {
const result = this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(
await this.mongodbCursor.next()
);
if (!result && closeAtEnd) {
await this.close();
}
return result;
}
public async forEach(forEachFuncArg: (itemArg: T) => Promise<any>, closeCursorAtEnd = true) {
let nextDocument: any;
do {
nextDocument = await this.mongodbCursor.next();
if (nextDocument) {
const nextClassInstance =
this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(nextDocument);
await forEachFuncArg(nextClassInstance as any);
}
} while (nextDocument);
if (closeCursorAtEnd) {
await this.close();
}
}
public async close() {
await this.mongodbCursor.close();
}
}

View File

@ -1,65 +1,84 @@
import * as plugins from './smartdata.plugins'
import { Objectmap } from 'lik'
import * as plugins from './smartdata.plugins.js';
import { DbCollection } from './smartdata.classes.dbcollection'
import { SmartdataCollection } from './smartdata.classes.collection.js';
import { EasyStore } from './smartdata.classes.easystore.js';
export type TConnectionStatus = 'disconnected' | 'connected' | 'failed'
import { logger } from './smartdata.logging.js';
export class Db {
dbUrl: string
db: plugins.mongodb.Db
status: TConnectionStatus
collections = new Objectmap<DbCollection<any>>()
/**
* interface - indicates the connection status of the db
*/
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
constructor(dbUrl: string) {
this.dbUrl = dbUrl
}
export class SmartdataDb {
smartdataOptions: plugins.tsclass.database.IMongoDescriptor;
mongoDbClient: plugins.mongodb.MongoClient;
mongoDb: plugins.mongodb.Db;
status: TConnectionStatus;
statusConnectedDeferred = plugins.smartpromise.defer();
smartdataCollectionMap = new plugins.lik.ObjectMap<SmartdataCollection<any>>();
// basic connection stuff ----------------------------------------------
constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
this.smartdataOptions = smartdataOptions;
this.status = 'initial';
}
/**
* connects to the database that was specified during instance creation
*/
connect(): plugins.q.Promise<any> {
let done = plugins.q.defer()
plugins.mongodb.MongoClient.connect(this.dbUrl, (err, db) => {
if (err) { console.log(err) }
plugins.assert.equal(null, err)
this.db = db
plugins.beautylog.success(`connected to database at ${this.dbUrl}`)
done.resolve(this.db)
})
return done.promise
}
// easystore
public async createEasyStore(nameIdArg: string) {
const easyStore = new EasyStore(nameIdArg, this);
return easyStore;
}
/**
* closes the connection to the databse
*/
close(): plugins.q.Promise<any> {
let done = plugins.q.defer()
this.db.close()
plugins.beautylog.ok(`disconnected to database at ${this.dbUrl}`)
done.resolve()
return done.promise
}
// basic connection stuff ----------------------------------------------
// advanced communication with the database --------------------------------
/**
* connects to the database that was specified during instance creation
*/
public async init(): Promise<any> {
const finalConnectionUrl = this.smartdataOptions.mongoDbUrl
.replace('<USERNAME>', this.smartdataOptions.mongoDbUser)
.replace('<username>', this.smartdataOptions.mongoDbUser)
.replace('<USER>', this.smartdataOptions.mongoDbUser)
.replace('<user>', this.smartdataOptions.mongoDbUser)
.replace('<PASSWORD>', this.smartdataOptions.mongoDbPass)
.replace('<password>', this.smartdataOptions.mongoDbPass)
.replace('<DBNAME>', this.smartdataOptions.mongoDbName)
.replace('<dbname>', this.smartdataOptions.mongoDbName);
/**
* gets a collection by name: string
*/
getCollectionByName<T>(nameArg: string): plugins.q.Promise<DbCollection<T>> {
let done = plugins.q.defer<DbCollection<any>>()
let resultCollection = this.collections.find((dbCollectionArg) => {
return dbCollectionArg.name === nameArg
})
if (resultCollection !== null) {
done.resolve(resultCollection)
}
return done.promise
};
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(finalConnectionUrl, {
maxPoolSize: 100,
maxIdleTimeMS: 10,
});
this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName);
this.status = 'connected';
this.statusConnectedDeferred.resolve();
console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`);
}
addCollection(dbCollectionArg: DbCollection<any>) {
this.collections.add(dbCollectionArg)
}
/**
* closes the connection to the databse
*/
public async close(): Promise<any> {
await this.mongoDbClient.close();
this.status = 'disconnected';
logger.log('info', `disconnected from database ${this.smartdataOptions.mongoDbName}`);
}
// handle table to class distribution
public addCollection(SmartdataCollectionArg: SmartdataCollection<any>) {
this.smartdataCollectionMap.add(SmartdataCollectionArg);
}
/**
* Gets a collection's name and returns a SmartdataCollection instance
* @param nameArg
* @returns DbTable
*/
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
const resultCollection = await this.smartdataCollectionMap.find(async (dbTableArg) => {
return dbTableArg.collectionName === nameArg;
});
return resultCollection;
}
}

View File

@ -1,76 +0,0 @@
import * as plugins from './smartdata.plugins'
import { Db } from './smartdata.classes.db'
export interface IFindOptions {
limit?: number
}
export function Collection(db: Db) {
return function(constructor){
constructor['dbCollection'] = new DbCollection(constructor.name, db)
}
}
export class DbCollection<T> {
collection: plugins.mongodb.Collection
name: string
constructor(nameArg: string, dbArg: Db) {
this.name = nameArg
this.collection = dbArg.db.collection(nameArg)
}
/**
* adds a validation function that all newly inserted and updated objects have to pass
*/
addObjectValidation(funcArg) { }
/**
* finds an object in the DbCollection
*/
find(docMatchArg: T | any, optionsArg?: IFindOptions): plugins.q.Promise<T[]> {
let done = plugins.q.defer<T[]>()
let findCursor = this.collection.find(docMatchArg)
if (optionsArg) {
if ( optionsArg.limit ) { findCursor = findCursor.limit(1) }
}
findCursor.toArray((err, docs) => {
if (err) { throw err }
done.resolve(docs)
})
return done.promise
}
/**
* inserts object into the DbCollection
*/
insertOne(docArg: T): plugins.q.Promise<void> {
let done = plugins.q.defer<void>()
this.checkDoc(docArg).then(() => {
this.collection.insertOne(docArg)
.then(() => { done.resolve() })
})
return done.promise
}
/**
* inserts many objects at once into the DbCollection
*/
insertMany(docArrayArg: T[]): plugins.q.Promise<void> {
let done = plugins.q.defer<void>()
let checkDocPromiseArray: plugins.q.Promise<void>[] = []
for (let docArg of docArrayArg){
checkDocPromiseArray.push(this.checkDoc(docArg))
}
plugins.q.all(checkDocPromiseArray).then(() => {
this.collection.insertMany(docArrayArg)
.then(() => { done.resolve() })
})
return done.promise
}
private checkDoc(doc: T): plugins.q.Promise<void> {
let done = plugins.q.defer<void>()
done.resolve()
return done.promise
}
}

View File

@ -1,21 +0,0 @@
import * as plugins from './smartdata.plugins'
import { Db } from './smartdata.classes.db'
import { DbCollection } from './smartdata.classes.dbcollection'
export type TDocCreation = 'db' | 'data' | 'mixed'
export class DbDoc<T> {
collection: DbCollection<T>
creationType: TDocCreation
constructor() {
this.collection = this.constructor['dbCollection']
}
save() {
}
saveDeep() {
}
}

View File

@ -0,0 +1,304 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { managed, setDefaultManagerForDoc } from './smartdata.classes.collection.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
@managed()
export class DistributedClass extends SmartDataDbDoc<DistributedClass, DistributedClass> {
// INSTANCE
@unI()
public id: string;
@svDb()
public data: {
status: 'initializing' | 'bidding' | 'settled' | 'stopped';
biddingShortcode?: string;
biddingStartTime?: number;
lastUpdated: number;
elected: boolean;
/**
* used to store request
*/
taskRequests: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest[];
/**
* only used by the leader to convey consultation results
*/
taskRequestResults: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult[];
};
}
/**
* This file implements a distributed coordinator according to the @pushrocks/taskbuffer standard.
* you should not set up this yourself. Instead, there is a factory on the SmartdataDb class
* that will take care of setting this up.
*/
export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distributedCoordination
.AbstractDistributedCoordinator {
public readyPromise: Promise<any>;
public db: SmartdataDb;
private asyncExecutionStack = new plugins.lik.AsyncExecutionStack();
public ownInstance: DistributedClass;
public distributedWatcher: SmartdataDbWatcher<DistributedClass>;
constructor(dbArg: SmartdataDb) {
super();
this.db = dbArg;
setDefaultManagerForDoc(this, DistributedClass);
this.readyPromise = this.db.statusConnectedDeferred.promise;
}
// smartdata specific stuff
public async start() {
await this.init();
}
public async stop() {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (this.distributedWatcher) {
await this.distributedWatcher.close();
}
if (this.ownInstance?.data.elected) {
this.ownInstance.data.elected = false;
}
if (this.ownInstance?.data.status === 'stopped') {
console.log(`stopping a distributed instance that has not been started yet.`);
}
this.ownInstance.data.status = 'stopped';
await this.ownInstance.save();
console.log(`stopped ${this.ownInstance.id}`);
});
}
public id = plugins.smartunique.uni('distributedInstance');
private startHeartbeat = async () => {
while (this.ownInstance.data.status !== 'stopped') {
await this.sendHeartbeat();
await plugins.smartdelay.delayForRandom(5000, 10000);
}
};
public async sendHeartbeat() {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (this.ownInstance.data.status === 'stopped') {
console.log(`aborted sending heartbeat because status is stopped`);
return;
}
await this.ownInstance.updateFromDb();
this.ownInstance.data.lastUpdated = Date.now();
await this.ownInstance.save();
console.log(`sent heartbeat for ${this.ownInstance.id}`);
const allInstances = DistributedClass.getInstances({});
});
if (this.ownInstance.data.status === 'stopped') {
console.log(`aborted sending heartbeat because status is stopped`);
return;
}
const eligibleLeader = await this.getEligibleLeader();
// not awaiting here because we don't want to block the heartbeat
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (!eligibleLeader && this.ownInstance.data.status === 'settled') {
this.checkAndMaybeLead();
}
});
}
private async init() {
await this.readyPromise;
if (!this.ownInstance) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance = new DistributedClass();
this.ownInstance.id = this.id;
this.ownInstance.data = {
elected: false,
lastUpdated: Date.now(),
status: 'initializing',
taskRequests: [],
taskRequestResults: [],
};
await this.ownInstance.save();
});
} else {
console.warn(`distributed instance already initialized`);
}
// lets enable the heartbeat
this.startHeartbeat();
// lets do a leader check
await this.checkAndMaybeLead();
return this.ownInstance;
}
public async getEligibleLeader() {
return this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const allInstances = await DistributedClass.getInstances({});
let leaders = allInstances.filter((instanceArg) => instanceArg.data.elected === true);
const eligibleLeader = leaders.find(
(leader) =>
leader.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ seconds: 20 })
);
return eligibleLeader;
});
}
// --> leader election
public async checkAndMaybeLead() {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance.data.status = 'initializing';
this.ownInstance.save();
});
if (await this.getEligibleLeader()) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
console.log(`${this.ownInstance.id} settled as follower`);
});
return;
} else if (
(await DistributedClass.getInstances({})).find((instanceArg) => {
instanceArg.data.status === 'bidding' &&
instanceArg.data.biddingStartTime <= Date.now() - 4000 &&
instanceArg.data.biddingStartTime >= Date.now() - 30000;
})
) {
console.log('too late to the bidding party... waiting for next round.');
return;
} else {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'bidding';
this.ownInstance.data.biddingStartTime = Date.now();
this.ownInstance.data.biddingShortcode = plugins.smartunique.shortId();
await this.ownInstance.save();
console.log('bidding code stored.');
});
console.log(`bidding for leadership...`);
await plugins.smartdelay.delayFor(
plugins.smarttime.getMilliSecondsFromUnits({ seconds: 5 })
);
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
let biddingInstances = await DistributedClass.getInstances({});
biddingInstances = biddingInstances.filter(
(instanceArg) =>
instanceArg.data.status === 'bidding' &&
instanceArg.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ seconds: 10 })
);
console.log(`found ${biddingInstances.length} bidding instances...`);
this.ownInstance.data.elected = true;
for (const biddingInstance of biddingInstances) {
if (biddingInstance.data.biddingShortcode < this.ownInstance.data.biddingShortcode) {
this.ownInstance.data.elected = false;
}
}
await plugins.smartdelay.delayFor(5000);
console.log(`settling with status elected = ${this.ownInstance.data.elected}`);
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
});
if (this.ownInstance.data.elected) {
this.leadFunction();
}
}
}
/**
* when it has been determined
* that this instance is leading
* the leading is implemented here
*/
public async leadFunction() {
this.distributedWatcher = await DistributedClass.watch({});
const currentTaskRequests: Array<{
taskName: string;
taskExecutionTime: number;
/**
* all instances that requested this task
*/
requestingDistibutedInstanceIds: string[];
responseTimeout: plugins.smartdelay.Timeout<any>;
}> = [];
this.distributedWatcher.changeSubject.subscribe({
next: async (distributedDoc) => {
if (!distributedDoc) {
console.log(`registered deletion of instance...`);
return;
}
console.log(distributedDoc);
console.log(`registered change for ${distributedDoc.id}`);
distributedDoc;
},
});
while (this.ownInstance.data.status !== 'stopped' && this.ownInstance.data.elected) {
const allInstances = await DistributedClass.getInstances({});
for (const instance of allInstances) {
if (instance.data.status === 'stopped') {
await instance.delete();
};
}
await plugins.smartdelay.delayFor(10000);
}
}
// abstract implemented methods
public async fireDistributedTaskRequest(
taskRequestArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult> {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (!this.ownInstance) {
console.error('instance need to be started first...');
return;
}
await this.ownInstance.updateFromDb();
this.ownInstance.data.taskRequests.push(taskRequestArg);
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(10000);
const result = await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
const taskRequestResult = this.ownInstance.data.taskRequestResults.find((resultItem) => {
return resultItem.requestResponseId === taskRequestArg.requestResponseId;
});
return taskRequestResult;
});
if (!result) {
console.warn('no result found for task request...');
return null;
}
return result;
}
public async updateDistributedTaskRequest(
infoBasisArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<void> {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const existingInfoBasis = this.ownInstance.data.taskRequests.find((infoBasisItem) => {
return (
infoBasisItem.taskName === infoBasisArg.taskName &&
infoBasisItem.taskExecutionTime === infoBasisArg.taskExecutionTime
);
});
if (!existingInfoBasis) {
console.warn('trying to update a non existing task request... aborting!');
return;
}
Object.assign(existingInfoBasis, infoBasisArg);
await this.ownInstance.save();
plugins.smartdelay.delayFor(60000).then(() => {
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const indexToRemove = this.ownInstance.data.taskRequests.indexOf(existingInfoBasis);
this.ownInstance.data.taskRequests.splice(indexToRemove, indexToRemove);
await this.ownInstance.save();
});
});
});
}
}

339
ts/smartdata.classes.doc.ts Normal file
View File

@ -0,0 +1,339 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { type IManager, SmartdataCollection } from './smartdata.classes.collection.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
export type TDocCreation = 'db' | 'new' | 'mixed';
export function globalSvDb() {
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
if (!target.globalSaveableProperties) {
target.globalSaveableProperties = [];
}
target.globalSaveableProperties.push(key);
};
}
/**
* saveable - saveable decorator to be used on class properties
*/
export function svDb() {
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
if (!target.saveableProperties) {
target.saveableProperties = [];
}
target.saveableProperties.push(key);
};
}
/**
* unique index - decorator to mark a unique index
*/
export function unI() {
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
console.log(`called unI on >>${target.constructor.name}.${key}<<`);
// mark the index as unique
if (!target.uniqueIndexes) {
target.uniqueIndexes = [];
}
target.uniqueIndexes.push(key);
// and also save it
if (!target.saveableProperties) {
target.saveableProperties = [];
}
target.saveableProperties.push(key);
};
}
export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => {
const convertedFilter: { [key: string]: any } = {};
const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => {
if (typeof filterArg2 === 'object') {
for (const key of Object.keys(filterArg2)) {
if (key.startsWith('$')) {
convertedFilter[keyPathArg2] = filterArg2;
return;
} else if (key.includes('.')) {
throw new Error('keys cannot contain dots');
}
}
for (const key of Object.keys(filterArg2)) {
convertFilterArgument(`${keyPathArg2}.${key}`, filterArg2[key]);
}
} else {
convertedFilter[keyPathArg2] = filterArg2;
}
};
for (const key of Object.keys(filterArg)) {
convertFilterArgument(key, filterArg[key]);
}
return convertedFilter;
};
export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends IManager = any> {
/**
* the collection object an Doc belongs to
*/
public static collection: SmartdataCollection<any>;
public collection: SmartdataCollection<any>;
public static defaultManager;
public static manager;
public manager: TManager;
// STATIC
public static createInstanceFromMongoDbNativeDoc<T>(
this: plugins.tsclass.typeFest.Class<T>,
mongoDbNativeDocArg: any
): T {
const newInstance = new this();
(newInstance as any).creationStatus = 'db';
for (const key of Object.keys(mongoDbNativeDocArg)) {
newInstance[key] = mongoDbNativeDocArg[key];
}
return newInstance;
}
/**
* gets all instances as array
* @param this
* @param filterArg
* @returns
*/
public static async getInstances<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
): Promise<T[]> {
const foundDocs = await (this as any).collection.findAll(convertFilterForMongoDb(filterArg));
const returnArray = [];
for (const foundDoc of foundDocs) {
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
returnArray.push(newInstance);
}
return returnArray;
}
/**
* gets the first matching instance
* @param this
* @param filterArg
* @returns
*/
public static async getInstance<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
): Promise<T> {
const foundDoc = await (this as any).collection.findOne(convertFilterForMongoDb(filterArg));
if (foundDoc) {
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
return newInstance;
} else {
return null;
}
}
/**
* get a unique id prefixed with the class name
*/
public static async getNewId<T = any>(this: plugins.tsclass.typeFest.Class<T>, lengthArg: number = 20) {
return `${(this as any).className}:${plugins.smartunique.shortId(lengthArg)}`;
}
/**
* get cursor
* @returns
*/
public static async getCursor<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
) {
const collection: SmartdataCollection<T> = (this as any).collection;
const cursor: SmartdataDbCursor<T> = await collection.getCursor(
convertFilterForMongoDb(filterArg),
this as any as typeof SmartDataDbDoc
);
return cursor;
}
/**
* watch the collection
* @param this
* @param filterArg
* @param forEachFunction
*/
public static async watch<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
) {
const collection: SmartdataCollection<T> = (this as any).collection;
const watcher: SmartdataDbWatcher<T> = await collection.watch(
convertFilterForMongoDb(filterArg),
this as any
);
return watcher;
}
/**
* run a function for all instances
* @returns
*/
public static async forEach<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
forEachFunction: (itemArg: T) => Promise<any>
) {
const cursor: SmartdataDbCursor<T> = await (this as any).getCursor(filterArg);
await cursor.forEach(forEachFunction);
}
/**
* returns a count of the documents in the collection
*/
public static async getCount<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T> = ({} as any)
) {
const collection: SmartdataCollection<T> = (this as any).collection;
return await collection.getCount(filterArg);
}
// INSTANCE
/**
* how the Doc in memory was created, may prove useful later.
*/
public creationStatus: TDocCreation = 'new';
/**
* updated from db in any case where doc comes from db
*/
@globalSvDb()
_createdAt: string = (new Date()).toISOString();
/**
* will be updated everytime the doc is saved
*/
@globalSvDb()
_updatedAt: string = (new Date()).toISOString();
/**
* an array of saveable properties of ALL doc
*/
public globalSaveableProperties: string[];
/**
* unique indexes
*/
public uniqueIndexes: string[];
/**
* an array of saveable properties of a specific doc
*/
public saveableProperties: string[];
/**
* name
*/
public name: string;
/**
* primary id in the database
*/
public dbDocUniqueId: string;
/**
* class constructor
*/
constructor() {}
/**
* saves this instance but not any connected items
* may lead to data inconsistencies, but is faster
*/
public async save() {
// tslint:disable-next-line: no-this-assignment
const self: any = this;
let dbResult: any;
this._updatedAt = (new Date()).toISOString();
switch (this.creationStatus) {
case 'db':
dbResult = await this.collection.update(self);
break;
case 'new':
dbResult = await this.collection.insert(self);
this.creationStatus = 'db';
break;
default:
console.error('neither new nor in db?');
}
return dbResult;
}
/**
* deletes a document from the database
*/
public async delete() {
await this.collection.delete(this);
}
/**
* also store any referenced objects to DB
* better for data consistency
*/
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
if (!savedMapArg) {
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
}
savedMapArg.add(this);
this.save();
for (const propertyKey of Object.keys(this)) {
const property: any = this[propertyKey];
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
property.saveDeep(savedMapArg);
}
}
}
/**
* updates an object from db
*/
public async updateFromDb() {
const mongoDbNativeDoc = await this.collection.findOne(await this.createIdentifiableObject());
for (const key of Object.keys(mongoDbNativeDoc)) {
this[key] = mongoDbNativeDoc[key];
}
}
/**
* creates a saveable object so the instance can be persisted as json in the database
*/
public async createSavableObject(): Promise<TImplements> {
const saveableObject: unknown = {}; // is not exposed to outside, so any is ok here
const saveableProperties = [
...this.globalSaveableProperties,
...this.saveableProperties
]
for (const propertyNameString of saveableProperties) {
saveableObject[propertyNameString] = this[propertyNameString];
}
return saveableObject as TImplements;
}
/**
* creates an identifiable object for operations that require filtering
*/
public async createIdentifiableObject() {
const identifiableObject: any = {}; // is not exposed to outside, so any is ok here
for (const propertyNameString of this.uniqueIndexes) {
identifiableObject[propertyNameString] = this[propertyNameString];
}
return identifiableObject;
}
}

View File

@ -0,0 +1,119 @@
import * as plugins from './smartdata.plugins.js';
import { Collection } from './smartdata.classes.collection.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
/**
* EasyStore allows the storage of easy objects. It also allows easy sharing of the object between different instances
*/
export class EasyStore<T> {
// instance
public smartdataDbRef: SmartdataDb;
public nameId: string;
private easyStoreClass = (() => {
@Collection(() => this.smartdataDbRef)
class SmartdataEasyStore extends SmartDataDbDoc<SmartdataEasyStore, SmartdataEasyStore> {
@unI()
public nameId: string;
@svDb()
public ephermal: {
activated: boolean;
timeout: number;
};
@svDb()
lastEdit: number;
@svDb()
public data: Partial<T>;
}
return SmartdataEasyStore;
})();
constructor(nameIdArg: string, smnartdataDbRefArg: SmartdataDb) {
this.smartdataDbRef = smnartdataDbRefArg;
this.nameId = nameIdArg;
}
private easyStorePromise: Promise<InstanceType<typeof this.easyStoreClass>>;
private async getEasyStore(): Promise<InstanceType<typeof this.easyStoreClass>> {
if (this.easyStorePromise) {
return this.easyStorePromise;
};
// first run from here
const deferred = plugins.smartpromise.defer<InstanceType<typeof this.easyStoreClass>>();
this.easyStorePromise = deferred.promise;
let easyStore = await this.easyStoreClass.getInstance({
nameId: this.nameId,
});
if (!easyStore) {
easyStore = new this.easyStoreClass();
easyStore.nameId = this.nameId;
easyStore.data = {};
await easyStore.save();
}
deferred.resolve(easyStore);
return this.easyStorePromise;
}
/**
* reads all keyValue pairs at once and returns them
*/
public async readAll() {
const easyStore = await this.getEasyStore();
return easyStore.data;
}
/**
* reads a keyValueFile from disk
*/
public async readKey(keyArg: keyof T) {
const easyStore = await this.getEasyStore();
return easyStore.data[keyArg];
}
/**
* writes a specific key to the keyValueStore
*/
public async writeKey<TKey extends keyof T>(keyArg: TKey, valueArg: T[TKey]) {
const easyStore = await this.getEasyStore();
easyStore.data[keyArg] = valueArg;
await easyStore.save();
}
public async deleteKey(keyArg: keyof T) {
const easyStore = await this.getEasyStore();
delete easyStore.data[keyArg];
await easyStore.save();
}
/**
* writes all keyValue pairs in the object argument
*/
public async writeAll(keyValueObject: Partial<T>) {
const easyStore = await this.getEasyStore();
easyStore.data = { ...easyStore.data, ...keyValueObject };
await easyStore.save();
}
/**
* wipes a key value store from disk
*/
public async wipe() {
const easyStore = await this.getEasyStore();
easyStore.data = {};
await easyStore.save();
}
public async cleanUpEphermal() {
while (
(await this.smartdataDbRef.statusConnectedDeferred.promise) &&
this.smartdataDbRef.status === 'connected'
) {}
}
}

View File

@ -0,0 +1,37 @@
import { SmartDataDbDoc } from './smartdata.classes.doc.js';
import * as plugins from './smartdata.plugins.js';
/**
* a wrapper for the native mongodb cursor. Exposes better
*/
export class SmartdataDbWatcher<T = any> {
// STATIC
public readyDeferred = plugins.smartpromise.defer();
// INSTANCE
private changeStream: plugins.mongodb.ChangeStream<T>;
public changeSubject = new plugins.smartrx.rxjs.Subject<T>();
constructor(
changeStreamArg: plugins.mongodb.ChangeStream<T>,
smartdataDbDocArg: typeof SmartDataDbDoc
) {
this.changeStream = changeStreamArg;
this.changeStream.on('change', async (item: any) => {
if (!item.fullDocument) {
this.changeSubject.next(null);
return;
}
this.changeSubject.next(
smartdataDbDocArg.createInstanceFromMongoDbNativeDoc(item.fullDocument) as any as T
);
});
plugins.smartdelay.delayFor(0).then(() => {
this.readyDeferred.resolve();
});
}
public async close() {
await this.changeStream.close();
}
}

3
ts/smartdata.logging.ts Normal file
View File

@ -0,0 +1,3 @@
import * as plugins from './smartdata.plugins.js';
export const logger = new plugins.smartlog.ConsoleLog();

View File

@ -1,5 +1,29 @@
import 'typings-global'
export import assert = require('assert')
export import beautylog = require('beautylog')
export import mongodb = require('mongodb')
export import q = require('q')
// tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// @pushrocks scope
import * as lik from '@push.rocks/lik';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartlog from '@push.rocks/smartlog';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as smartstring from '@push.rocks/smartstring';
import * as smarttime from '@push.rocks/smarttime';
import * as smartunique from '@push.rocks/smartunique';
import * as taskbuffer from '@push.rocks/taskbuffer';
import * as mongodb from 'mongodb';
export {
lik,
smartdelay,
smartpromise,
smartlog,
smartrx,
mongodb,
smartstring,
smarttime,
smartunique,
taskbuffer,
};

14
tsconfig.json Normal file
View File

@ -0,0 +1,14 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"useDefineForClassFields": false,
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"esModuleInterop": true,
"verbatimModuleSyntax": true
},
"exclude": [
"dist_*/**/*.d.ts"
]
}

View File

@ -1,3 +0,0 @@
{
"extends": "tslint-config-standard"
}