Compare commits

...

197 Commits

Author SHA1 Message Date
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
34 changed files with 8533 additions and 5818 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

View File

@ -1,127 +0,0 @@
# gitzone ci_default
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache:
paths:
- .npmci_cache/
key: '$CI_BUILD_STAGE'
stages:
- security
- test
- release
- metadata
# ====================
# security stage
# ====================
mirror:
stage: security
script:
- npmci git mirror
tags:
- lossless
- docker
- notpriv
audit:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high
tags:
- lossless
- docker
- notpriv
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- priv
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- lossless
- docker
- notpriv
release:
stage: release
script:
- npmci node install stable
- npmci npm publish
only:
- tags
tags:
- lossless
- docker
- notpriv
# ====================
# metadata stage
# ====================
codequality:
stage: metadata
allow_failure: true
script:
- npmci command npm install -g tslint typescript
- npmci npm prepare
- npmci npm install
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install lts
- npmci command npm install -g @gitzone/tsdoc
- npmci npm prepare
- npmci npm install
- npmci command tsdoc
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

24
.vscode/launch.json vendored
View File

@ -2,28 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "current file",
"type": "node",
"command": "npm test",
"name": "Run npm test",
"request": "launch",
"args": [
"${relativeFile}"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
},
{
"name": "test.ts",
"type": "node",
"request": "launch",
"args": [
"test/test.ts"
],
"runtimeArgs": ["-r", "@gitzone/tsrun"],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
"type": "node-terminal"
}
]
}

View File

@ -15,7 +15,7 @@
"properties": {
"projectType": {
"type": "string",
"enum": ["website", "element", "service", "npm"]
"enum": ["website", "element", "service", "npm", "wcc"]
}
}
}

View File

@ -13,10 +13,10 @@
"projectType": "npm",
"module": {
"githost": "gitlab.com",
"gitscope": "pushrocks",
"gitscope": "push.rocks",
"gitrepo": "smartdata",
"shortDescription": "do more with data",
"npmPackagename": "@pushrocks/smartdata",
"description": "do more with data",
"npmPackagename": "@push.rocks/smartdata",
"license": "MIT"
}
}

5405
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,15 @@
{
"name": "@pushrocks/smartdata",
"version": "3.1.29",
"name": "@push.rocks/smartdata",
"version": "5.0.41",
"private": false,
"description": "do more with data",
"main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts",
"type": "module",
"scripts": {
"test": "(tstest test/)",
"testLocal": "(npmdocker)",
"build": "(tsbuild --web)"
"test": "tstest test/",
"build": "tsbuild --web --allowimplicitany",
"buildDocs": "tsdoc"
},
"repository": {
"type": "git",
@ -21,28 +22,26 @@
},
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
"dependencies": {
"@pushrocks/lik": "^4.0.13",
"@pushrocks/smartlog": "^2.0.35",
"@pushrocks/smartpromise": "^3.0.6",
"@pushrocks/smartstring": "^3.0.18",
"@pushrocks/smartunique": "^3.0.3",
"@types/lodash": "^4.14.155",
"@types/mongodb": "^3.5.20",
"lodash": "^4.17.15",
"mongodb": "^3.5.8",
"runtime-type-checks": "0.0.4"
"@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": {
"@gitzone/tsbuild": "^2.1.24",
"@gitzone/tstest": "^1.0.33",
"@pushrocks/qenv": "^4.0.10",
"@pushrocks/tapbundle": "^3.2.1",
"@types/mongodb-memory-server": "^2.3.0",
"@types/node": "^14.0.13",
"@types/shortid": "0.0.29",
"mongodb-memory-server": "^6.6.1",
"tslint": "^6.1.2",
"tslint-config-prettier": "^1.18.0"
"@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/**/*",
@ -55,5 +54,8 @@
"cli.js",
"npmextra.json",
"readme.md"
],
"browserslist": [
"last 1 chrome versions"
]
}

6881
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1 @@
required:
- MONGO_URL
- MONGO_DBNAME
- MONGO_PASS

View File

@ -1,27 +1,26 @@
# @pushrocks/smartdata
# @push.rocks/smartdata
do more with data
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartdata)
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartdata)
* [github.com (source mirror)](https://github.com/pushrocks/smartdata)
* [docs (typedoc)](https://pushrocks.gitlab.io/smartdata/)
* [npmjs.org (npm package)](https://www.npmjs.com/package/@push.rocks/smartdata)
* [gitlab.com (source)](https://gitlab.com/push.rocks/smartdata)
* [github.com (source mirror)](https://github.com/push.rocks/smartdata)
* [docs (typedoc)](https://push.rocks.gitlab.io/smartdata/)
## Status for master
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartdata/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartdata/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartdata)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartdata)](https://lossless.cloud)
GitLab Pipelines | [![pipeline status](https://gitlab.com/push.rocks/smartdata/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/push.rocks/smartdata/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@push.rocks/smartdata)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/push.rocks/smartdata)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@push.rocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@push.rocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@push.rocks/smartdata)](https://lossless.cloud)
## Usage
@ -49,15 +48,16 @@ How RethinkDB's terms map to the ones of smartdata:
represents a Database. Naturally it has .connect() etc. methods on it.
```typescript
// Assuming toplevel await
import * as smartdata from 'smartdata';
const smartdataDb = new smartdata.SmartdataDb({
mongoDbUrl: '//someurl',
mongoDbName: 'myDatabase',
mongoDbPass: 'mypassword'
mongoDbPass: 'mypassword',
});
smartdataDb.connect();
await smartdataDb.connect();
```
### class DbCollection
@ -68,10 +68,11 @@ A collection is defined by the object class (that is extending smartdata.dbdoc)
So to get to get access to a specific collection you document
```typescript
// Assuming toplevel await
// continues from the block before...
@smartdata.Collection(smartdataDb)
class MyObject extends smartdata.DbDoc<MyObject> {
class MyObject extends smartdata.DbDoc<MyObject /* ,[an optional interface to implement] */> {
// read the next block about DbDoc
@smartdata.svDb()
property1: string; // @smartdata.svDb() marks the property for db save
@ -87,14 +88,22 @@ class MyObject extends smartdata.DbDoc<MyObject> {
const localObject = new MyObject({
property1: 'hi',
property2: 2
property2: {
deep: 3,
},
});
localObject.save(); // saves the object to the database
await localObject.save(); // saves the object to the database
// start retrieving instances
MyObject.getInstance<MyObject>({
property: 'hi'
// .getInstance is staticly inheritied, yet fully typed static function to get instances with fully typed filters
const myInstance = await MyObject.getInstance({
property1: 'hi',
property2: {
deep: {
$gt: 2,
} as any,
},
}); // outputs a new instance of MyObject with the values from db assigned
```
@ -134,14 +143,12 @@ Since you define your classes in TypeScript and types flow through smartdata in
you should get all the Intellisense and type checking you love when using smartdata.
smartdata itself also bundles typings. You don't need to install any additional types for smartdata.
## Contribution
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
For further information read the linked docs at the top of this readme.
> MIT licensed | **&copy;** [Lossless GmbH](https://lossless.gmbh)
## Legal
> MIT licensed | **&copy;** [Task Venture Capital GmbH](https://task.vc)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

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/testdata/`);
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,35 +1,38 @@
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
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 mongodb from 'mongodb';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
// the tested module
import * as smartdata from '../ts/index';
import { smartstring } from '../ts/smartdata.plugins';
import * as smartunique from '@pushrocks/smartunique';
console.log(process.memoryUsage());
import * as mongoPlugin from 'mongodb-memory-server';
// the tested module
import * as smartdata from '../ts/index.js';
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
let smartdataOptions: smartdata.ISmartdataOptions;
let mongod: mongoPlugin.MongoMemoryServer;
const totalCars = 2000;
tap.test('should create a testinstance as database', async () => {
mongod = new mongoPlugin.MongoMemoryServer();
smartdataOptions = {
mongoDbName: await mongod.getDbName(),
mongoDbPass: '',
mongoDbUrl: await mongod.getConnectionString()
};
console.log(smartdataOptions);
testDb = new smartdata.SmartdataDb(smartdataOptions);
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.test('should establish a connection to the rethink Db cluster', async () => {
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();
});
@ -54,6 +57,14 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
@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;
@ -61,44 +72,106 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
}
}
tap.test('should save the car to the db', async () => {
tap.test('should create a new id', async () => {
const newid = await Car.getNewId();
console.log(newid);
})
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();
const myCar3 = new Car('red', 'Renault');
await myCar3.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', async () => {
const myCars = await Car.getInstances<Car>({
brand: 'Volvo'
});
expect(myCars[0].color).to.equal('red');
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'
brand: 'Volvo',
});
expect(myCar.color).to.equal('red');
expect(myCar.color).toEqual('red');
myCar.color = 'blue';
await myCar.save();
});
tap.test('should be able to delete an instance of car', async () => {
const myCar = await Car.getInstance<Car>({
brand: 'Volvo'
const myCars = await Car.getInstances({
brand: 'Volvo',
color: 'blue',
});
expect(myCar.color).to.equal('blue');
await myCar.delete();
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'
brand: 'Volvo',
});
expect(myCar2.color).to.equal('red');
expect(myCar2.color).toEqual('red');
});
// tslint:disable-next-line: max-classes-per-file
@ -125,19 +198,31 @@ class Truck extends smartdata.SmartDataDbDoc<Car, Car> {
tap.test('should store a new Truck', async () => {
const truck = new Truck('blue', 'MAN');
await truck.save();
const myTruck = await Truck.getInstance<Truck>({ color: 'blue' });
myTruck.id = 'foo';
await myTruck.save();
const myTruck2 = await Truck.getInstance<Truck>({ color: 'blue' });
console.log(myTruck2);
const myTruck2 = await Truck.getInstance({ color: 'blue' });
myTruck2.color = 'red';
await myTruck2.save();
const myTruck3 = await Truck.getInstance({ color: 'blue' });
console.log(myTruck3);
});
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('should close the database connection', async tools => {
tap.test('close', async () => {
await testDb.mongoDb.dropDatabase();
await testDb.close();
await mongod.stop();
if (smartmongoInstance) {
await smartmongoInstance.stop();
}
});
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.0.41',
description: 'do more with data'
}

View File

@ -1,5 +1,14 @@
export * from './smartdata.classes.db';
export * from './smartdata.classes.collection';
export * from './smartdata.classes.doc';
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 { IMongoDescriptor } from './interfaces';
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

@ -1 +0,0 @@
export * from './mongodescriptor';

View File

@ -1,5 +0,0 @@
export interface IMongoDescriptor {
mongoDbName: string;
mongoDbUrl: string;
mongoDbPass: string;
}

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

@ -1,6 +1,9 @@
import * as plugins from './smartdata.plugins';
import { SmartdataDb } from './smartdata.classes.db';
import { SmartDataDbDoc } from './smartdata.classes.doc';
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;
@ -13,25 +16,108 @@ export interface IDocValidationFunc<T> {
(doc: T): boolean;
}
export type TDelayedDbCreation = () => SmartdataDb;
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 | TDelayedDbCreation) {
return function(constructor) {
if (dbArg instanceof SmartdataDb) {
// tslint:disable-next-line: no-string-literal
constructor['smartdataCollection'] = new SmartdataCollection(constructor, dbArg);
} else {
constructor['smartdataDelayedCollection'] = () => {
return new SmartdataCollection(constructor, 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
@ -42,13 +128,13 @@ export class SmartdataCollection<T> {
public smartdataDb: SmartdataDb;
public uniqueIndexes: string[] = [];
constructor(collectedClassArg: T & SmartDataDbDoc<T, unknown>, smartDataDbArg: SmartdataDb) {
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
// tell the collection where it belongs
this.collectionName = collectedClassArg.name;
this.collectionName = classNameArg;
this.smartdataDb = smartDataDbArg;
// tell the db class about it (important since Db uses different systems under the hood)
this.smartdataDb.addTable(this);
this.smartdataDb.addCollection(this);
}
/**
@ -58,14 +144,14 @@ export class SmartdataCollection<T> {
if (!this.mongoDbCollection) {
// connect this instance to a MongoDB collection
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
const wantedCollection = availableMongoDbCollections.find(collection => {
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 = await this.smartdataDb.mongoDb.collection(this.collectionName);
// console.log(`Successfully initiated Collection ${this.collectionName}`);
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
}
}
@ -76,7 +162,7 @@ export class SmartdataCollection<T> {
for (const key of keyArrayArg) {
if (!this.uniqueIndexes.includes(key)) {
this.mongoDbCollection.createIndex(key, {
unique: true
unique: true,
});
// make sure we only call this once and not for every doc we create
this.uniqueIndexes.push(key);
@ -94,12 +180,57 @@ export class SmartdataCollection<T> {
/**
* finds an object in the DbCollection
*/
public async find(filterObject: any): Promise<any> {
public async findOne(filterObject: any): Promise<any> {
await this.init();
const result = await this.mongoDbCollection.find(filterObject).toArray();
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
*/
@ -127,20 +258,19 @@ export class SmartdataCollection<T> {
}
updateableObject[key] = saveableObject[key];
}
this.mongoDbCollection.updateOne(
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, {
w: 1
});
await this.mongoDbCollection.deleteOne(identifiableObject);
}
/**
@ -148,7 +278,7 @@ export class SmartdataCollection<T> {
* if this.objectValidation is not set it passes.
*/
private checkDoc(docArg: T): Promise<void> {
const done = plugins.smartq.defer<void>();
const done = plugins.smartpromise.defer<void>();
let validationResult = true;
if (this.objectValidation) {
validationResult = this.objectValidation(docArg);

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,57 @@
import * as plugins from './smartdata.plugins';
import { ObjectMap } from '@pushrocks/lik';
import * as plugins from './smartdata.plugins.js';
import { SmartdataCollection } from './smartdata.classes.collection';
import { SmartdataCollection } from './smartdata.classes.collection.js';
import { EasyStore } from './smartdata.classes.easystore.js';
import * as mongoHelpers from './smartdata.mongohelpers';
import { logger } from './smartdata.logging';
import { logger } from './smartdata.logging.js';
/**
* interface - indicates the connection status of the db
*/
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
export interface ISmartdataOptions {
/**
* the URL to connect to
*/
mongoDbUrl: string;
/**
* the db to use for the project
*/
mongoDbName: string;
/**
* an optional password that will be replace <PASSWORD> in the connection string
*/
mongoDbPass?: string;
}
export class SmartdataDb {
smartdataOptions: ISmartdataOptions;
smartdataOptions: plugins.tsclass.database.IMongoDescriptor;
mongoDbClient: plugins.mongodb.MongoClient;
mongoDb: plugins.mongodb.Db;
status: TConnectionStatus;
smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>();
statusConnectedDeferred = plugins.smartpromise.defer();
smartdataCollectionMap = new plugins.lik.ObjectMap<SmartdataCollection<any>>();
constructor(smartdataOptions: ISmartdataOptions) {
constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
this.smartdataOptions = smartdataOptions;
this.status = 'initial';
}
// easystore
public async createEasyStore(nameIdArg: string) {
const easyStore = new EasyStore(nameIdArg, this);
return easyStore;
}
// basic connection stuff ----------------------------------------------
/**
* connects to the database that was specified during instance creation
*/
public async init(): Promise<any> {
let finalConnectionUrl = this.smartdataOptions.mongoDbUrl;
if (this.smartdataOptions.mongoDbPass) {
finalConnectionUrl = mongoHelpers.addPassword(
this.smartdataOptions.mongoDbUrl,
this.smartdataOptions.mongoDbPass
);
}
console.log(`connection Url: ${finalConnectionUrl}`);
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);
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(finalConnectionUrl, {
useNewUrlParser: true,
useUnifiedTopology: true
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}`);
}
@ -74,7 +66,7 @@ export class SmartdataDb {
// handle table to class distribution
public addTable(SmartdataCollectionArg: SmartdataCollection<any>) {
public addCollection(SmartdataCollectionArg: SmartdataCollection<any>) {
this.smartdataCollectionMap.add(SmartdataCollectionArg);
}
@ -84,7 +76,7 @@ export class SmartdataDb {
* @returns DbTable
*/
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
const resultCollection = this.smartdataCollectionMap.find(dbTableArg => {
const resultCollection = await this.smartdataCollectionMap.find(async (dbTableArg) => {
return dbTableArg.collectionName === nameArg;
});
return resultCollection;

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();
});
});
});
}
}

View File

@ -1,9 +1,9 @@
import * as plugins from './smartdata.plugins';
import * as plugins from './smartdata.plugins.js';
import { ObjectMap } from '@pushrocks/lik';
import { SmartdataDb } from './smartdata.classes.db';
import { SmartdataCollection } from './smartdata.classes.collection';
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';
@ -12,7 +12,7 @@ export type TDocCreation = 'db' | 'new' | 'mixed';
*/
export function svDb() {
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
console.log(`called svDb() on ${key}`);
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
if (!target.saveableProperties) {
target.saveableProperties = [];
}
@ -25,7 +25,7 @@ export function svDb() {
*/
export function unI() {
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
console.log('called unI');
console.log(`called unI on >>${target.constructor.name}.${key}<<`);
// mark the index as unique
if (!target.uniqueIndexes) {
@ -41,11 +41,147 @@ export function unI() {
};
}
export class SmartDataDbDoc<T, TImplements> {
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 collection: SmartdataCollection<T>;
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);
}
// INSTANCE
/**
* how the Doc in memory was created, may prove useful later.
@ -75,48 +211,7 @@ export class SmartDataDbDoc<T, TImplements> {
/**
* class constructor
*/
constructor() {
this.name = this.constructor['name'];
if (this.constructor['smartdataCollection']) {
// tslint:disable-next-line: no-string-literal
this.collection = this.constructor['smartdataCollection'];
// tslint:disable-next-line: no-string-literal
} else if (typeof this.constructor['smartdataDelayedCollection'] === 'function') {
// tslint:disable-next-line: no-string-literal
this.collection = this.constructor['smartdataDelayedCollection']();
} else {
console.error('Could not determine collection for DbDoc');
}
}
public static async getInstances<T>(filterArg): Promise<T[]> {
const self: any = this; // fool typesystem
let referenceMongoDBCollection: SmartdataCollection<T>;
if (self.smartdataCollection) {
referenceMongoDBCollection = self.smartdataCollection;
} else if (self.smartdataDelayedCollection) {
referenceMongoDBCollection = self.smartdataDelayedCollection();
}
const foundDocs = await referenceMongoDBCollection.find(filterArg);
const returnArray = [];
for (const item of foundDocs) {
const newInstance = new this();
newInstance.creationStatus = 'db';
for (const key of Object.keys(item)) {
newInstance[key] = item[key];
}
returnArray.push(newInstance);
}
return returnArray;
}
public static async getInstance<T>(filterArg): Promise<T> {
const result = await this.getInstances<T>(filterArg);
if (result && result.length > 0) {
return result[0];
}
}
constructor() {}
/**
* saves this instance but not any connected items
@ -125,34 +220,35 @@ export class SmartDataDbDoc<T, TImplements> {
public async save() {
// tslint:disable-next-line: no-this-assignment
const self: any = this;
let dbResult: any;
switch (this.creationStatus) {
case 'db':
await this.collection.update(self);
dbResult = await this.collection.update(self);
break;
case 'new':
const writeResult = await this.collection.insert(self);
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() {
const self: any = this;
await this.collection.delete(self);
await this.collection.delete(this);
}
/**
* also store any referenced objects to DB
* better for data consistency
*/
public saveDeep(savedMapArg: ObjectMap<SmartDataDbDoc<any, any>> = null) {
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
if (!savedMapArg) {
savedMapArg = new ObjectMap<SmartDataDbDoc<any, any>>();
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
}
savedMapArg.add(this);
this.save();
@ -164,6 +260,16 @@ export class SmartDataDbDoc<T, TImplements> {
}
}
/**
* 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
*/

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();
}
}

View File

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

View File

@ -1,7 +0,0 @@
export const addUsername = (mongoUrlArg: string, usernameArg: string): string => {
return mongoUrlArg.replace('<USERNAME>', usernameArg);
};
export const addPassword = (mongoUrlArg: string, passwordArg: string): string => {
return mongoUrlArg.replace('<PASSWORD>', passwordArg);
};

View File

@ -1,9 +1,29 @@
import * as assert from 'assert';
import * as smartlog from '@pushrocks/smartlog';
import * as lodash from 'lodash';
import * as mongodb from 'mongodb';
import * as smartq from '@pushrocks/smartpromise';
import * as smartstring from '@pushrocks/smartstring';
import * as smartunique from '@pushrocks/smartunique';
// tsclass scope
import * as tsclass from '@tsclass/tsclass';
export { assert, smartlog, lodash, smartq, mongodb, smartstring, smartunique };
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,
};

View File

@ -1,7 +1,9 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"target": "es2017",
"module": "commonjs"
"useDefineForClassFields": false,
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "nodenext"
}
}
}

View File

@ -1,17 +0,0 @@
{
"extends": ["tslint:latest", "tslint-config-prettier"],
"rules": {
"semicolon": [true, "always"],
"no-console": false,
"ordered-imports": false,
"object-literal-sort-keys": false,
"member-ordering": {
"options":{
"order": [
"static-method"
]
}
}
},
"defaultSeverity": "warning"
}