Compare commits
276 Commits
Author | SHA1 | Date | |
---|---|---|---|
57d2d56d00 | |||
90751002aa | |||
7606e074a5 | |||
7ec39e397e | |||
21d8d3dc32 | |||
6d456955d8 | |||
d08544c782 | |||
bda9ac8a07 | |||
d27dafba2b | |||
b6594de18c | |||
d9246cbeac | |||
9a5864656e | |||
307f0c7277 | |||
62dc897e73 | |||
552b344914 | |||
5a2cc2406c | |||
73a11370b6 | |||
162265f353 | |||
06776d74c8 | |||
b4cd6b0fe1 | |||
b282f69b35 | |||
203a284c88 | |||
30ae641a9c | |||
cfe733621f | |||
1f76e2478e | |||
7d668bee05 | |||
bef7f68360 | |||
56e9754725 | |||
30d81581cf | |||
5e9db12955 | |||
ad2f422c86 | |||
17ce14bcb9 | |||
32319e6e77 | |||
4cd284eaa9 | |||
00ec2e57c2 | |||
765356ce3d | |||
56b8581d2b | |||
37a9df9086 | |||
090fb668cd | |||
a1c807261c | |||
a2ccf15f69 | |||
84d48f1914 | |||
1e258e5ffb | |||
19d5f553b9 | |||
7a257ea925 | |||
2fa1e89f34 | |||
d6b3896dd3 | |||
49b11b17ce | |||
4ac8a4c0cd | |||
7f9983382a | |||
54f529b0a7 | |||
f542463bf6 | |||
1235ae2eb3 | |||
8166d2f7c2 | |||
7c9f27e02f | |||
842e4b280b | |||
009f3297b2 | |||
2ff3a4e0b7 | |||
0e55cd8876 | |||
eccdf3f00a | |||
c7544133d9 | |||
c7c9acf5bd | |||
c99ec50853 | |||
4dd9557e1d | |||
52b34a6da1 | |||
1bf74fe04d | |||
fdd875ad31 | |||
a7bf0c0298 | |||
59d6336e43 | |||
e0fc81179a | |||
5aa81a56a2 | |||
9ae26177b8 | |||
26ac52d6c5 | |||
fb39463b7d | |||
44acba80c1 | |||
8cf8315577 | |||
9b44b64a50 | |||
699e25201c | |||
2ef9aace68 | |||
cc55a57dfd | |||
b2df512552 | |||
23c62fbd69 | |||
5f70ea0b05 | |||
49a595876a | |||
db38a1ef85 | |||
94854638dd | |||
902fab4cc0 | |||
ed3b19abc5 | |||
5b88da7dce | |||
df273e9efa | |||
fd590e0be3 | |||
ef97b390d4 | |||
cd14eb8bf3 | |||
f48443dcd3 | |||
3f28ff80cb | |||
64005a0b32 | |||
8a77bb3281 | |||
25f50ecf51 | |||
ad87f8147b | |||
da0c9873eb | |||
2fcd3f1550 | |||
f726cf4c5b | |||
c198969fae | |||
be1badeb23 | |||
fe065b966f | |||
811e2490b8 | |||
206ccd40e9 | |||
055298172f | |||
278f3c8169 | |||
0709ba921b | |||
de1f1110b4 | |||
30f4254428 | |||
1c4b03e647 | |||
27cc7651ba | |||
355a2a3f2b | |||
a739582861 | |||
37f9a64735 | |||
83a5170591 | |||
f94363cf31 | |||
df02e5bb71 | |||
38e438c54f | |||
11bc1ac6dc | |||
3431e94ddd | |||
739e040776 | |||
28d57efd9e | |||
f50a61308c | |||
42aa9f9f8a | |||
3f591ff9d8 | |||
7b33347b4c | |||
3f11cbf595 | |||
cf1ec7f9eb | |||
54060deb8f | |||
48cffb5ac2 | |||
8301eb79a2 | |||
ad6366a294 | |||
cb6c03ebfe | |||
551916fe5c | |||
9b3892c1e8 | |||
c1b1af9c5d | |||
d3a3d5be9d | |||
c9a734d879 | |||
856e8e7d1f | |||
7a4d557724 | |||
7cbd0bd99b | |||
e10f6585a5 | |||
5c8dffdd9c | |||
846996eeac | |||
668df09ba0 | |||
03656f4ca0 | |||
c4c612f3a9 | |||
e357f7581c | |||
9697b1e48b | |||
aeb35705d4 | |||
236c8c6551 | |||
1f28db15e7 | |||
86d600e287 | |||
bb81530dac | |||
b9f9b36b87 | |||
df2fadfa01 | |||
8b2beb3485 | |||
144a620f43 | |||
c241247845 | |||
81e39d09e4 | |||
8e51b518b1 | |||
8308d8d03b | |||
97365ddf29 | |||
55d96fa68d | |||
54ec6accdf | |||
fc5d092b25 | |||
dfba057562 | |||
1ad05943b5 | |||
302e51a77f | |||
1330d03af2 | |||
a298e9d190 | |||
e571ef347b | |||
39a4c7ef3f | |||
0d3518d990 | |||
fbdde2268c | |||
8b6c26f45a | |||
97e82ed75a | |||
03baffd9fd | |||
f29ae67580 | |||
e550a8dbd6 | |||
cf6ef25a8d | |||
29c512b0cc | |||
105f69d1c9 | |||
4c375f8465 | |||
9466af6a4a | |||
c5aa747f42 | |||
b5f2474f65 | |||
85f0d99934 | |||
3b2d3d9072 | |||
3ff5c36fdf | |||
a5acc2fe4e | |||
9c81257101 | |||
f7342962f4 | |||
bcd10205d3 | |||
6cab20f32d | |||
5aaa6ad2d6 | |||
635256f2f6 | |||
f799d3efa5 | |||
f74020ba96 | |||
f6d6545ff5 | |||
85a196c8c1 | |||
adb198af01 | |||
6dce9f3ff8 | |||
2f6a4ce3e5 | |||
0984a1ade4 | |||
804701c96a | |||
a3759fa484 | |||
ef38df62be | |||
c17789e92e | |||
0bf2ba554d | |||
5cbf1a222a | |||
f075530838 | |||
efb83853fb | |||
73300ca4d3 | |||
1e946cdceb | |||
608ff93a41 | |||
6211953f14 | |||
99e520b776 | |||
eda8297356 | |||
ffa52a5883 | |||
1e83f0a0ef | |||
0203eabdfd | |||
72894e3ef1 | |||
bbe5f8c6a8 | |||
02af9f5c4b | |||
17de480272 | |||
776fd3ee4e | |||
3272bb7235 | |||
116dfbc3b0 | |||
3f714b1a33 | |||
e58fa57525 | |||
8e7ad5210f | |||
cea6c662ac | |||
6eb43a4b9f | |||
7f89cbeecd | |||
a042a589a0 | |||
75b5f6af08 | |||
e6a36f22ac | |||
246e3486f0 | |||
c1c4a29415 | |||
6448516aa0 | |||
24a7b2dbd3 | |||
afa7c2fe61 | |||
b768896fbe | |||
8d7d9adef1 | |||
ca6d3c60c0 | |||
38c1fcd53a | |||
e433b9e212 | |||
bf3656f792 | |||
7e9fc62ce7 | |||
670a0c63fb | |||
64e76b3ed1 | |||
25803330de | |||
c6cbff1308 | |||
3eeac7b936 | |||
761b742e21 | |||
e56def621c | |||
ab1799f4f2 | |||
9985b849d1 | |||
1800273b25 | |||
e715cc6d37 | |||
f7abc175aa | |||
bbcb3a614e | |||
941d2f0902 | |||
5c78f83a28 | |||
124f117352 | |||
f67da898ae | |||
3c8f70f77e | |||
3674d7d9a5 | |||
fe2a622488 | |||
f092eefbf3 | |||
8d0aa361f9 | |||
692a1223a8 |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal 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
|
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal 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
|
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,4 +1,20 @@
|
||||
node_modules/
|
||||
public/
|
||||
coverage/
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
141
.gitlab-ci.yml
141
.gitlab-ci.yml
@ -1,141 +0,0 @@
|
||||
# gitzone standard
|
||||
image: 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:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
snyk:
|
||||
stage: security
|
||||
script:
|
||||
- npmci command npm install -g snyk
|
||||
- npmci command npm install --ignore-scripts
|
||||
- npmci command snyk test
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# test stage
|
||||
# ====================
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install legacy
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
allow_failure: true
|
||||
|
||||
testLTS:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install lts
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
testSTABLE:
|
||||
stage: test
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm install
|
||||
- npmci npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
release:
|
||||
stage: release
|
||||
script:
|
||||
- npmci node install stable
|
||||
- npmci npm publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
# ====================
|
||||
# metadata stage
|
||||
# ====================
|
||||
codequality:
|
||||
stage: metadata
|
||||
image: docker:stable
|
||||
allow_failure: true
|
||||
services:
|
||||
- docker:stable-dind
|
||||
script:
|
||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
||||
- docker run
|
||||
--env SOURCE_CODE="$PWD"
|
||||
--volume "$PWD":/code
|
||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
||||
artifacts:
|
||||
paths: [codeclimate.json]
|
||||
tags:
|
||||
- docker
|
||||
- priv
|
||||
|
||||
trigger:
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci trigger
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmci
|
||||
stage: metadata
|
||||
script:
|
||||
- npmci command npm install -g npmpage
|
||||
- npmci command npmpage
|
||||
tags:
|
||||
- docker
|
||||
- notpriv
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
allow_failure: true
|
||||
|
||||
windowsCompatibility:
|
||||
image: stefanscherer/node-windows:10-build-tools
|
||||
stage: metadata
|
||||
script:
|
||||
- npm install & npm test
|
||||
coverage: /\d+.?\d+?\%\s*coverage/
|
||||
tags:
|
||||
- windows
|
||||
allow_failure: true
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal 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
26
.vscode/settings.json
vendored
Normal 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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
129
README.md
129
README.md
@ -1,129 +0,0 @@
|
||||
# @pushrocks/smartdata
|
||||
|
||||
do more with data and RethinkDB
|
||||
|
||||
## Availabililty
|
||||
|
||||
[](https://www.npmjs.com/package/smartdata)
|
||||
[](https://GitLab.com/pushrocks/smartdata)
|
||||
[](https://github.com/pushrocks/smartdata)
|
||||
[](https://pushrocks.gitlab.io/smartdata/)
|
||||
|
||||
## Status for master
|
||||
|
||||
[](https://GitLab.com/pushrocks/smartdata/commits/master)
|
||||
[](https://GitLab.com/pushrocks/smartdata/commits/master)
|
||||
[](https://www.npmjs.com/package/smartdata)
|
||||
[](https://david-dm.org/pushrocks/smartdata)
|
||||
[](https://www.bithound.io/github/pushrocks/smartdata/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/smartdata)
|
||||
[](https://snyk.io/test/npm/smartdata)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](http://standardjs.com/)
|
||||
|
||||
## Usage
|
||||
|
||||
Use TypeScript for best in class instellisense.
|
||||
|
||||
smartdata is an ODM that adheres to TypeScript practices and uses classes to organize data.
|
||||
It uses RethinkDB 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 the NoSQL idea we didn't find a matching solution.
|
||||
This is why we started smartdata.
|
||||
|
||||
How RethinkDB's terms map to the ones of smartdata:
|
||||
|
||||
| MongoDb term | smartdata class |
|
||||
| -------------- | ------------------------------|
|
||||
| Database | smartdata.SmartdataDb |
|
||||
| Collection | smartdata.SmartdataCollection |
|
||||
| Document | smartdata.SmartadataDoc |
|
||||
|
||||
### class Db
|
||||
|
||||
represents a Database. Naturally it has .connect() etc. methods on it.
|
||||
|
||||
```typescript
|
||||
import * as smartdata from 'smartdata';
|
||||
|
||||
const smartdataDb = new smartdata.SmartdataDb({
|
||||
mongoDbUrl: '//someurl',
|
||||
mongoDbName: 'myDatabase',
|
||||
mongoDbPass: 'mypassword'
|
||||
});
|
||||
|
||||
smartdataDb.connect();
|
||||
```
|
||||
|
||||
### 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...
|
||||
|
||||
@smartdata.Collection(smartdataDb)
|
||||
class MyObject extends smartdata.DbDoc<MyObject> {
|
||||
// read the next block about DbDoc
|
||||
@smartdata.svDb() property1: string; // @smartdata.svDb() marks the property for db save
|
||||
property2: number; // this one is not marked, so it won't be save upon calling this.save()
|
||||
constructor(optionsArg: { property1: string; property2: number }) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
// start to instantiate instances of classes from scratch or database
|
||||
|
||||
let localObject = new MyObject({
|
||||
property1: 'hi',
|
||||
property2: 2
|
||||
});
|
||||
localObject.save(); // saves the object to the database
|
||||
|
||||
// start retrieving instances
|
||||
|
||||
MyObject.getInstance<MyObject>({
|
||||
property: 'hi'
|
||||
}); // outputs a new instance of MyObject with the values from db assigned
|
||||
```
|
||||
|
||||
### class DbDoc
|
||||
|
||||
represents a individual document in a collection
|
||||
and thereby is ideally suited to extend the class you want to actually store.
|
||||
|
||||
**sStore** instances of classes to Db:
|
||||
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
|
||||
|
||||
**Get** a new class instance from a Doc in the DB:
|
||||
DbDoc exposes a static method that allows you specify a filter to retrieve a cloned class of the one you used to that doc at some point later in time. Yes, let that sink in a minute :)
|
||||
|
||||
So you can just call `.getInstance({ /* filter props here */ })`.
|
||||
|
||||
## 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.
|
||||
|
||||
For further information read the linked docs at the top of this README.
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||
|
||||
[](https://push.rocks)
|
@ -5,14 +5,32 @@
|
||||
"dockerSock": false
|
||||
},
|
||||
"npmci": {
|
||||
"npmGlobalTools": [
|
||||
"@gitzone/npmts"
|
||||
],
|
||||
"npmAccessLevel": "public"
|
||||
"npmGlobalTools": [],
|
||||
"npmAccessLevel": "public",
|
||||
"npmRegistryUrl": "registry.npmjs.org"
|
||||
},
|
||||
"npmts": {
|
||||
"testConfig": {
|
||||
"parallel": false
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
2008
package-lock.json
generated
2008
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
82
package.json
82
package.json
@ -1,43 +1,71 @@
|
||||
{
|
||||
"name": "@pushrocks/smartdata",
|
||||
"version": "3.1.3",
|
||||
"name": "@push.rocks/smartdata",
|
||||
"version": "5.2.6",
|
||||
"private": false,
|
||||
"description": "do more with data",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"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": "tsrun test/test.ts",
|
||||
"testLocal": "(npmdocker)",
|
||||
"build": "(npmts)"
|
||||
"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": {
|
||||
"@pushrocks/lik": "^3.0.4",
|
||||
"@pushrocks/smartlog": "^2.0.9",
|
||||
"@pushrocks/smartpromise": "^2.0.5",
|
||||
"@pushrocks/smartstring": "^3.0.5",
|
||||
"@types/lodash": "^4.14.119",
|
||||
"@types/mongodb": "^3.1.18",
|
||||
"lodash": "^4.17.11",
|
||||
"mongodb": "^3.1.10",
|
||||
"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/tsrun": "^1.1.17",
|
||||
"@pushrocks/qenv": "^3.0.2",
|
||||
"@pushrocks/tapbundle": "^3.0.7",
|
||||
"@types/node": "^10.12.18",
|
||||
"@types/shortid": "0.0.29",
|
||||
"mongodb-memory-server": "^2.9.1",
|
||||
"shortid": "^2.2.14"
|
||||
}
|
||||
"@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
6881
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
0
readme.hints.md
Normal file
0
readme.hints.md
Normal file
115
readme.md
Normal file
115
readme.md
Normal 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.
|
112
test/test.distributedcoordinator.ts
Normal file
112
test/test.distributedcoordinator.ts
Normal 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
55
test/test.easystore.ts
Normal 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();
|
223
test/test.ts
223
test/test.ts
@ -1,26 +1,39 @@
|
||||
import { tap, expect } from '@pushrocks/tapbundle';
|
||||
import * as smartpromise from '@pushrocks/smartpromise';
|
||||
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';
|
||||
|
||||
let testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
|
||||
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 '../ts/index';
|
||||
import { smartstring } from '../ts/smartdata.plugins';
|
||||
import * as shortid from 'shortid';
|
||||
import * as smartdata from '../ts/index.js';
|
||||
|
||||
// =======================================
|
||||
// Connecting to the database server
|
||||
// =======================================
|
||||
|
||||
let testDb = new smartdata.SmartdataDb({
|
||||
mongoDbName: process.env.MONGO_DBNAME,
|
||||
mongoDbUrl: process.env.MONGO_URL,
|
||||
mongoDbPass: process.env.MONGO_PASS
|
||||
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.test('should establish a connection to the rethink Db cluster', async () => {
|
||||
await testDb.connect();
|
||||
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();
|
||||
});
|
||||
|
||||
// =======================================
|
||||
@ -31,11 +44,27 @@ tap.test('should establish a connection to the rethink Db cluster', async () =>
|
||||
// Collections
|
||||
// ------
|
||||
|
||||
@smartdata.Collection(testDb)
|
||||
class Car extends smartdata.SmartDataDbDoc<Car> {
|
||||
@smartdata.unI() index: string = shortid();
|
||||
@smartdata.svDb() color: string;
|
||||
@smartdata.svDb() brand: string;
|
||||
@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;
|
||||
@ -43,23 +72,165 @@ class Car extends smartdata.SmartDataDbDoc<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();
|
||||
|
||||
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('expect to get instance of Car', async () => {
|
||||
let myCars = await Car.getInstances<Car>({
|
||||
brand: 'Volvo'
|
||||
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);
|
||||
})
|
||||
|
||||
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;
|
||||
});
|
||||
expect(myCars[0].color).to.equal('red');
|
||||
});
|
||||
|
||||
// =======================================
|
||||
// close the database connection
|
||||
// =======================================
|
||||
tap.test('should close the database connection', async tools => {
|
||||
await testDb.close();
|
||||
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);
|
||||
});
|
||||
|
||||
tap.start({throwOnError: true});
|
||||
tap.start({ throwOnError: true });
|
||||
|
95
test/test.typescript.ts
Normal file
95
test/test.typescript.ts
Normal 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
74
test/test.watch.ts
Normal 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
8
ts/00_commitinfo_data.ts
Normal 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.'
|
||||
}
|
17
ts/index.ts
17
ts/index.ts
@ -1,3 +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';
|
||||
|
||||
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 };
|
||||
|
5
ts/smartadata.convenience.ts
Normal file
5
ts/smartadata.convenience.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import * as plugins from './smartdata.plugins.js';
|
||||
|
||||
export const getNewUniqueId = async (prefixArg?: string) => {
|
||||
return plugins.smartunique.uni(prefixArg);
|
||||
};
|
@ -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,61 +16,153 @@ 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 db
|
||||
* @param dbArg
|
||||
*/
|
||||
export function Collection(db: SmartdataDb) {
|
||||
return function(constructor) {
|
||||
constructor['smartdataCollection'] = new SmartdataCollection(constructor, db);
|
||||
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
|
||||
*/
|
||||
mongoDbCollection: plugins.mongodb.Collection;
|
||||
objectValidation: IDocValidationFunc<T> = null;
|
||||
collectionName: string;
|
||||
smartdataDb: SmartdataDb;
|
||||
uniqueIndexes: string[] = [];
|
||||
public mongoDbCollection: plugins.mongodb.Collection;
|
||||
public objectValidation: IDocValidationFunc<T> = null;
|
||||
public collectionName: string;
|
||||
public smartdataDb: SmartdataDb;
|
||||
public uniqueIndexes: string[] = [];
|
||||
|
||||
constructor(collectedClassArg: T & SmartDataDbDoc<T>, 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
|
||||
*/
|
||||
async init() {
|
||||
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 => {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* mark unique index
|
||||
*/
|
||||
markUniqueIndexes(keyArrayArg: string[] = []) {
|
||||
for(let key of keyArrayArg) {
|
||||
if(!this.uniqueIndexes.includes(key)) {
|
||||
public markUniqueIndexes(keyArrayArg: string[] = []) {
|
||||
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);
|
||||
@ -78,28 +173,72 @@ export class SmartdataCollection<T> {
|
||||
/**
|
||||
* adds a validation function that all newly inserted and updated objects have to pass
|
||||
*/
|
||||
addDocValidation(funcArg: IDocValidationFunc<T>) {
|
||||
public addDocValidation(funcArg: IDocValidationFunc<T>) {
|
||||
this.objectValidation = funcArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* finds an object in the DbCollection
|
||||
*/
|
||||
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
|
||||
*/
|
||||
async insert(dbDocArg: T & SmartDataDbDoc<T>): Promise<any> {
|
||||
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();
|
||||
console.log(saveableObject);
|
||||
const result = await this.mongoDbCollection.insertOne(saveableObject);
|
||||
return result;
|
||||
}
|
||||
@ -107,11 +246,36 @@ export class SmartdataCollection<T> {
|
||||
/**
|
||||
* inserts object into the DbCollection
|
||||
*/
|
||||
async update(dbDocArg: T & SmartDataDbDoc<T>): Promise<any> {
|
||||
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();
|
||||
this.mongoDbCollection.updateOne(saveableObject.dbDocUniqueId, saveableObject);
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -119,7 +283,7 @@ export class SmartdataCollection<T> {
|
||||
* if this.objectValidation is not set it passes.
|
||||
*/
|
||||
private checkDoc(docArg: T): Promise<void> {
|
||||
let done = plugins.smartq.defer<void>();
|
||||
const done = plugins.smartpromise.defer<void>();
|
||||
let validationResult = true;
|
||||
if (this.objectValidation) {
|
||||
validationResult = this.objectValidation(docArg);
|
||||
|
19
ts/smartdata.classes.collectionfactory.ts
Normal file
19
ts/smartdata.classes.collectionfactory.ts
Normal 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];
|
||||
};
|
||||
}
|
46
ts/smartdata.classes.cursor.ts
Normal file
46
ts/smartdata.classes.cursor.ts
Normal 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();
|
||||
}
|
||||
}
|
@ -1,83 +1,72 @@
|
||||
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.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
|
||||
*/
|
||||
async connect(): Promise<any> {
|
||||
let finalConnectionUrl = this.smartdataOptions.mongoDbUrl;
|
||||
if (this.smartdataOptions.mongoDbPass) {
|
||||
finalConnectionUrl = mongoHelpers.addPassword(
|
||||
this.smartdataOptions.mongoDbUrl,
|
||||
this.smartdataOptions.mongoDbPass
|
||||
);
|
||||
}
|
||||
console.log(finalConnectionUrl);
|
||||
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(
|
||||
finalConnectionUrl,
|
||||
{
|
||||
useNewUrlParser: true
|
||||
}
|
||||
);
|
||||
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);
|
||||
|
||||
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}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* closes the connection to the databse
|
||||
*/
|
||||
async close(): Promise<any> {
|
||||
public async close(): Promise<any> {
|
||||
await this.mongoDbClient.close();
|
||||
this.status = 'disconnected';
|
||||
plugins.smartlog
|
||||
.defaultLogger
|
||||
.log('info', `disconnected from database ${this.smartdataOptions.mongoDbName}`);
|
||||
logger.log('info', `disconnected from database ${this.smartdataOptions.mongoDbName}`);
|
||||
}
|
||||
|
||||
// handle table to class distribution
|
||||
|
||||
addTable(SmartdataCollectionArg: SmartdataCollection<any>) {
|
||||
public addCollection(SmartdataCollectionArg: SmartdataCollection<any>) {
|
||||
this.smartdataCollectionMap.add(SmartdataCollectionArg);
|
||||
}
|
||||
|
||||
@ -86,8 +75,8 @@ export class SmartdataDb {
|
||||
* @param nameArg
|
||||
* @returns DbTable
|
||||
*/
|
||||
async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
|
||||
let resultCollection = this.smartdataCollectionMap.find(dbTableArg => {
|
||||
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
|
||||
const resultCollection = await this.smartdataCollectionMap.find(async (dbTableArg) => {
|
||||
return dbTableArg.collectionName === nameArg;
|
||||
});
|
||||
return resultCollection;
|
||||
|
304
ts/smartdata.classes.distributedcoordinator.ts
Normal file
304
ts/smartdata.classes.distributedcoordinator.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -1,18 +1,28 @@
|
||||
import * as plugins from './smartdata.plugins';
|
||||
import * as plugins from './smartdata.plugins.js';
|
||||
|
||||
import { Objectmap } from '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';
|
||||
|
||||
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<any>, key: string) => {
|
||||
console.log(`called svDb() on ${key}`);
|
||||
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
||||
if (!target.saveableProperties) {
|
||||
target.saveableProperties = [];
|
||||
}
|
||||
@ -24,8 +34,8 @@ export function svDb() {
|
||||
* unique index - decorator to mark a unique index
|
||||
*/
|
||||
export function unI() {
|
||||
return (target: SmartDataDbDoc<any>, key: string) => {
|
||||
console.log('called 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) {
|
||||
@ -39,113 +49,291 @@ export function unI() {
|
||||
}
|
||||
target.saveableProperties.push(key);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class SmartDataDbDoc<T> {
|
||||
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
|
||||
*/
|
||||
collection: SmartdataCollection<T>;
|
||||
public static collection: SmartdataCollection<any>;
|
||||
public collection: SmartdataCollection<any>;
|
||||
public static defaultManager;
|
||||
public static manager;
|
||||
public manager: TManager;
|
||||
|
||||
/**
|
||||
* how the Doc in memory was created, may prove useful later.
|
||||
*/
|
||||
creationStatus: TDocCreation = 'new';
|
||||
|
||||
/**
|
||||
* unique indexes
|
||||
*/
|
||||
uniqueIndexes: string[];
|
||||
|
||||
/**
|
||||
* an array of saveable properties of a doc
|
||||
*/
|
||||
saveableProperties: string[];
|
||||
|
||||
/**
|
||||
* name
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* primary id in the database
|
||||
*/
|
||||
dbDocUniqueId: string;
|
||||
|
||||
/**
|
||||
* class constructor
|
||||
*/
|
||||
constructor() {
|
||||
this.name = this.constructor['name'];
|
||||
this.collection = this.constructor['smartdataCollection'];
|
||||
// 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;
|
||||
}
|
||||
|
||||
static async getInstances<T>(filterArg): Promise<T[]> {
|
||||
let self: any = this; // fool typesystem
|
||||
let referenceMongoDBCollection: SmartdataCollection<T> = self.smartdataCollection;
|
||||
const foundDocs = await referenceMongoDBCollection.find(filterArg);
|
||||
/**
|
||||
* 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 (let item of foundDocs) {
|
||||
let newInstance = new this();
|
||||
for (let key in item) {
|
||||
if (key !== 'id') {
|
||||
newInstance[key] = item[key];
|
||||
}
|
||||
}
|
||||
for (const foundDoc of foundDocs) {
|
||||
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
|
||||
returnArray.push(newInstance);
|
||||
}
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
static async getInstance<T>(filterArg): Promise<T> {
|
||||
let result = await this.getInstances<T>(filterArg);
|
||||
if (result && result.length > 0) {
|
||||
return result[0];
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
async save() {
|
||||
let self: any = this;
|
||||
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':
|
||||
await this.collection.update(self);
|
||||
dbResult = await this.collection.update(self);
|
||||
break;
|
||||
case 'new':
|
||||
let 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() {
|
||||
await this.collection.delete(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* also store any referenced objects to DB
|
||||
* better for data consistency
|
||||
*/
|
||||
saveDeep(savedMapArg: Objectmap<SmartDataDbDoc<any>> = null) {
|
||||
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
|
||||
if (!savedMapArg) {
|
||||
savedMapArg = new Objectmap<SmartDataDbDoc<any>>();
|
||||
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
|
||||
}
|
||||
savedMapArg.add(this);
|
||||
this.save();
|
||||
for (let propertyKey in this) {
|
||||
let property: any = this[propertyKey];
|
||||
for (const propertyKey of Object.keys(this)) {
|
||||
const property: any = this[propertyKey];
|
||||
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
|
||||
property.saveDeep(savedMapArg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createSavableObject() {
|
||||
let saveableObject: any = {}; // is not exposed to outside, so any is ok here
|
||||
for (let propertyNameString of this.saveableProperties) {
|
||||
/**
|
||||
* 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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
119
ts/smartdata.classes.easystore.ts
Normal file
119
ts/smartdata.classes.easystore.ts
Normal 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'
|
||||
) {}
|
||||
}
|
||||
}
|
37
ts/smartdata.classes.watcher.ts
Normal file
37
ts/smartdata.classes.watcher.ts
Normal 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
3
ts/smartdata.logging.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import * as plugins from './smartdata.plugins.js';
|
||||
|
||||
export const logger = new plugins.smartlog.ConsoleLog();
|
@ -1,3 +0,0 @@
|
||||
export const addPassword = (mongoUrlArg: string, passwordArg: string): string => {
|
||||
return mongoUrlArg.replace('<PASSWORD>', passwordArg);
|
||||
};
|
@ -1,8 +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';
|
||||
// tsclass scope
|
||||
import * as tsclass from '@tsclass/tsclass';
|
||||
|
||||
export { assert, smartlog, lodash, smartq, mongodb, smartstring };
|
||||
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,
|
||||
};
|
||||
|
@ -1,7 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"target": "es2017",
|
||||
"module": "commonjs"
|
||||
}
|
||||
}
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user