Compare commits
347 Commits
Author | SHA1 | Date | |
---|---|---|---|
0834ec5c91 | |||
6a2a708ea1 | |||
1d977986f1 | |||
e325b42906 | |||
1a359d355a | |||
b5a9449d5e | |||
558f83a3d9 | |||
76ae454221 | |||
90cfc4644d | |||
0be279e5f5 | |||
9755522bba | |||
de8736e99e | |||
c430627a21 | |||
0bfebaf5b9 | |||
4733982d03 | |||
368dc27607 | |||
938b25c925 | |||
ab251858ba | |||
24371ccf78 | |||
ed1eecbab8 | |||
0d2dcec3e2 | |||
9426a21a2a | |||
4fac974fc9 | |||
cad2decf59 | |||
0f61bdc455 | |||
408b2cce4a | |||
7a08700451 | |||
ebaf3e685c | |||
c8d51a30d8 | |||
d957e911de | |||
fee936c75f | |||
ac867401de | |||
c066464526 | |||
0105aa2a18 | |||
4c2477c269 | |||
ea0d2bb251 | |||
b3e30a8711 | |||
64621dd38f | |||
117c257a27 | |||
b30522c505 | |||
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 | |||
ae7d101ab9 | |||
3825b15a65 | |||
3fc41678d2 | |||
39f1b7ada1 | |||
f763de2403 | |||
cba1d031b6 | |||
d31e6a5931 | |||
46514a2743 | |||
1cf02157c4 | |||
72efa14da8 | |||
a73c2084c0 | |||
5ed557a21a | |||
2b6c30ff55 | |||
12ef599333 | |||
191ea5d3c6 | |||
bdf33ed1ca | |||
1c76905dcd | |||
4d88cea69d | |||
8b4f7169ff | |||
2a1c45608f | |||
85a6444263 | |||
766ae1d1ff | |||
7630882312 | |||
4aec47f207 | |||
c2e3a1ae6e | |||
3d8f8646b1 | |||
aca9817c56 | |||
7e341f2b50 | |||
d2ca108ef8 | |||
e2d12f8c9c | |||
cfcd9ab386 |
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: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{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 @ship.zone/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: code.foss.global/host.today/ht-docker-node:npmci
|
||||||
|
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@/${{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 @ship.zone/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 @ship.zone/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 @ship.zone/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 @ship.zone/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 @git.zone/tsdoc
|
||||||
|
npmci command tsdoc
|
||||||
|
continue-on-error: true
|
21
.gitignore
vendored
21
.gitignore
vendored
@ -1,4 +1,19 @@
|
|||||||
node_modules/
|
|
||||||
public/
|
|
||||||
coverage/
|
|
||||||
.nogit/
|
.nogit/
|
||||||
|
|
||||||
|
# artifacts
|
||||||
|
coverage/
|
||||||
|
public/
|
||||||
|
|
||||||
|
# installs
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.yarn/
|
||||||
|
.cache/
|
||||||
|
.rpt2_cache
|
||||||
|
|
||||||
|
# builds
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
|
||||||
|
#------# custom
|
@ -1,58 +0,0 @@
|
|||||||
# gitzone standard
|
|
||||||
image: hosttoday/ht-docker-node:npmci
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .yarn/
|
|
||||||
key: "$CI_BUILD_STAGE"
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- test
|
|
||||||
- release
|
|
||||||
- trigger
|
|
||||||
- pages
|
|
||||||
|
|
||||||
testSTABLE:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci npm publish
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
stage: trigger
|
|
||||||
script:
|
|
||||||
- npmci trigger
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
|
|
||||||
pages:
|
|
||||||
image: hosttoday/ht-docker-node:npmci
|
|
||||||
stage: pages
|
|
||||||
script:
|
|
||||||
- npmci command yarn global add npmpage
|
|
||||||
- npmci command npmpage
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 week
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
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"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
114
README.md
114
README.md
@ -1,114 +0,0 @@
|
|||||||
# 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:
|
|
||||||
|
|
||||||
RethinkDB term | smartdata class
|
|
||||||
--- | ---
|
|
||||||
Database | smartdata.Db
|
|
||||||
Table | smartdata.DbTable
|
|
||||||
Document | smartdata.DbDoc
|
|
||||||
|
|
||||||
### class Db
|
|
||||||
represents a Database. Naturally it has .connect() etc. methods on it.
|
|
||||||
```javascript
|
|
||||||
import * as smartdata from 'smartdata'
|
|
||||||
|
|
||||||
let myRethinkDb1 = new smartdata.Db({
|
|
||||||
// rethinkDb connection options here
|
|
||||||
})
|
|
||||||
|
|
||||||
myDb1.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
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// continues from the block before...
|
|
||||||
|
|
||||||
@Collection(myRethinkDb1)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let myCollection = myRethinkDb1.getCollectionByName<myObject>(myObject)
|
|
||||||
|
|
||||||
// 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
|
|
||||||
```
|
|
||||||
|
|
||||||
> Alert: You NEVER instantiate a collection.
|
|
||||||
This is done for you!!!
|
|
||||||
|
|
||||||
### class DbDoc
|
|
||||||
represents a individual document in a collection
|
|
||||||
and thereby is ideally suited to extend the class you want to actually store.
|
|
||||||
|
|
||||||
DbDoc extends your class with the following methods:
|
|
||||||
|
|
||||||
* `.save()` will save (or update) the object you call it on only. Any referenced non-savable objects will not get stored.
|
|
||||||
* `.saveDeep()` does the same like `.save()`.
|
|
||||||
In addition it will look for properties that reference an object
|
|
||||||
that extends DbDoc as well and call .saveDeep() on them as well.
|
|
||||||
Loops are prevented
|
|
||||||
|
|
||||||
So now we can **store** instances of classes to Db...
|
|
||||||
How do we **get** a new class instance from a Doc in the DB?
|
|
||||||
|
|
||||||
## TypeScript
|
|
||||||
How does TypeScript play into this?
|
|
||||||
Since you define your classes in TypeScript and types flow through smartdata in a generic way
|
|
||||||
you should get all the Intellisense and type checking you love when using smartdata.
|
|
||||||
smartdata itself also bundles typings.
|
|
||||||
So you don't need to install any additional types when importing smartdata.
|
|
||||||
|
|
||||||
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)
|
|
270
changelog.md
Normal file
270
changelog.md
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-04-18 - 5.9.2 - fix(documentation)
|
||||||
|
Update search API documentation to replace deprecated searchWithLucene examples with the unified search(query) API and clarify its behavior.
|
||||||
|
|
||||||
|
- Replaced 'searchWithLucene' examples with 'search(query)' in the README.
|
||||||
|
- Updated explanation to detail field-specific exact match, partial word regex search, multi-word literal matching, and handling of empty queries.
|
||||||
|
- Clarified guidelines for creating MongoDB text indexes on searchable fields for optimized search performance.
|
||||||
|
|
||||||
|
## 2025-04-18 - 5.9.1 - fix(search)
|
||||||
|
Refactor search tests to use unified search API and update text index type casting
|
||||||
|
|
||||||
|
- Replaced all calls from searchWithLucene with search in test/search tests
|
||||||
|
- Updated text index specification in the collection class to use proper type casting
|
||||||
|
|
||||||
|
## 2025-04-18 - 5.9.0 - feat(collections/search)
|
||||||
|
Improve text index creation and search fallback mechanisms in collections and document search methods
|
||||||
|
|
||||||
|
- Auto-create a compound text index on all searchable fields in SmartdataCollection with a one-time flag to prevent duplicate index creation.
|
||||||
|
- Refine the search method in SmartDataDbDoc to support exact field matches and safe regex fallback for non-Lucene queries.
|
||||||
|
|
||||||
|
## 2025-04-17 - 5.8.4 - fix(core)
|
||||||
|
Update commit metadata with no functional code changes
|
||||||
|
|
||||||
|
- Commit info and documentation refreshed
|
||||||
|
- No code or test changes detected in the diff
|
||||||
|
|
||||||
|
## 2025-04-17 - 5.8.3 - fix(readme)
|
||||||
|
Improve readme documentation on data models and connection management
|
||||||
|
|
||||||
|
- Clarify that data models use @Collection, @unI, @svDb, @index, and @searchable decorators
|
||||||
|
- Document that ObjectId and Buffer fields are stored as BSON types natively without extra decorators
|
||||||
|
- Update connection management section to use 'db.close()' instead of 'db.disconnect()'
|
||||||
|
- Revise license section to reference the MIT License without including additional legal details
|
||||||
|
|
||||||
|
## 2025-04-14 - 5.8.2 - fix(classes.doc.ts)
|
||||||
|
Ensure collection initialization before creating a cursor in getCursorExtended
|
||||||
|
|
||||||
|
- Added 'await collection.init()' to guarantee that the MongoDB collection is initialized before using the cursor
|
||||||
|
- Prevents potential runtime errors when accessing collection.mongoDbCollection
|
||||||
|
|
||||||
|
## 2025-04-14 - 5.8.1 - fix(cursor, doc)
|
||||||
|
Add explicit return types and casts to SmartdataDbCursor methods and update getCursorExtended signature in SmartDataDbDoc.
|
||||||
|
|
||||||
|
- Specify Promise<T> as return type for next() in SmartdataDbCursor and cast return value to T.
|
||||||
|
- Specify Promise<T[]> as return type for toArray() in SmartdataDbCursor and cast return value to T[].
|
||||||
|
- Update getCursorExtended to return Promise<SmartdataDbCursor<T>> for clearer type safety.
|
||||||
|
|
||||||
|
## 2025-04-14 - 5.8.0 - feat(cursor)
|
||||||
|
Add toArray method to SmartdataDbCursor to convert raw MongoDB documents into initialized class instances
|
||||||
|
|
||||||
|
- Introduced asynchronous toArray method in SmartdataDbCursor which retrieves all documents from the MongoDB cursor
|
||||||
|
- Maps each native document to a SmartDataDbDoc instance using createInstanceFromMongoDbNativeDoc for consistent API usage
|
||||||
|
|
||||||
|
## 2025-04-14 - 5.7.0 - feat(SmartDataDbDoc)
|
||||||
|
Add extended cursor method getCursorExtended for flexible cursor modifications
|
||||||
|
|
||||||
|
- Introduces getCursorExtended in classes.doc.ts to allow modifier functions for MongoDB cursors
|
||||||
|
- Wraps the modified cursor with SmartdataDbCursor for improved API consistency
|
||||||
|
- Enhances querying capabilities by enabling customized cursor transformations
|
||||||
|
|
||||||
|
## 2025-04-07 - 5.6.0 - feat(indexing)
|
||||||
|
Add support for regular index creation in documents and collections
|
||||||
|
|
||||||
|
- Implement new index decorator in classes.doc.ts to mark properties with regular indexing options
|
||||||
|
- Update SmartdataCollection to create regular indexes if defined on a document during insert
|
||||||
|
- Enhance document structure to store and utilize regular index configurations
|
||||||
|
|
||||||
|
## 2025-04-06 - 5.5.1 - fix(ci & formatting)
|
||||||
|
Minor fixes: update CI workflow image and npmci package references, adjust package.json and readme URLs, and apply consistent code formatting.
|
||||||
|
|
||||||
|
- Update image and repo URL in Gitea workflows from GitLab to code.foss.global
|
||||||
|
- Replace '@shipzone/npmci' with '@ship.zone/npmci' throughout CI scripts
|
||||||
|
- Adjust homepage and bugs URL in package.json and readme
|
||||||
|
- Apply trailing commas and consistent formatting in TypeScript source files
|
||||||
|
- Minor update to .gitignore custom section label
|
||||||
|
|
||||||
|
## 2025-04-06 - 5.5.0 - feat(search)
|
||||||
|
Enhance search functionality with robust Lucene query transformation and reliable fallback mechanisms
|
||||||
|
|
||||||
|
- Improve Lucene adapter to properly structure $or queries for term, phrase, wildcard, and fuzzy search
|
||||||
|
- Implement and document a robust searchWithLucene method with fallback to in-memory filtering
|
||||||
|
- Update readme and tests with extensive examples for @searchable fields and Lucene-based queries
|
||||||
|
|
||||||
|
## 2025-04-06 - 5.4.0 - feat(core)
|
||||||
|
Refactor file structure and update dependency versions
|
||||||
|
|
||||||
|
- Renamed files and modules from 'smartdata.classes.*' to 'classes.*' and adjusted corresponding import paths.
|
||||||
|
- Updated dependency versions: '@push.rocks/smartmongo' to ^2.0.11, '@tsclass/tsclass' to ^8.2.0, and 'mongodb' to ^6.15.0.
|
||||||
|
- Renamed dev dependency packages from '@gitzone/...' to '@git.zone/...' and updated '@push.rocks/tapbundle' and '@types/node'.
|
||||||
|
- Fixed YAML workflow command: replaced 'pnpm install -g @gitzone/tsdoc' with 'pnpm install -g @git.zone/tsdoc'.
|
||||||
|
- Added package manager configuration and pnpm-workspace.yaml for built dependencies.
|
||||||
|
|
||||||
|
## 2025-03-10 - 5.3.0 - feat(docs)
|
||||||
|
Enhance documentation with updated installation instructions and comprehensive usage examples covering advanced features such as deep queries, automatic indexing, and distributed coordination.
|
||||||
|
|
||||||
|
- Added pnpm installation command
|
||||||
|
- Updated User model example to include ObjectId, Binary, and custom serialization
|
||||||
|
- Expanded CRUD operations examples with cursor methods and deep query support
|
||||||
|
- Enhanced sections on EasyStore, real-time data watching with RxJS integration, and managed collections
|
||||||
|
- Included detailed examples for transactions, deep object queries, and document lifecycle hooks
|
||||||
|
|
||||||
|
## 2025-02-03 - 5.2.12 - fix(documentation)
|
||||||
|
Remove license badge from README
|
||||||
|
|
||||||
|
- Removed the license badge from the README file, ensuring compliance with branding guidelines.
|
||||||
|
|
||||||
|
## 2025-02-03 - 5.2.11 - fix(documentation)
|
||||||
|
Updated project documentation for accuracy and added advanced feature details
|
||||||
|
|
||||||
|
- Added details for EasyStore, Distributed Coordination, and Real-time Data Watching features.
|
||||||
|
- Updated database connection setup instructions to include user authentication.
|
||||||
|
- Re-organized advanced usage section to showcase additional features separately.
|
||||||
|
|
||||||
|
## 2024-09-05 - 5.2.10 - fix(smartdata.classes.doc)
|
||||||
|
Fix issue with array handling in convertFilterForMongoDb function
|
||||||
|
|
||||||
|
- Corrected the logic to properly handle array filters in the convertFilterForMongoDb function to avoid incorrect assignments.
|
||||||
|
|
||||||
|
## 2024-09-05 - 5.2.9 - fix(smartdata.classes.doc)
|
||||||
|
Fixed issue with convertFilterForMongoDb to handle array operators.
|
||||||
|
|
||||||
|
- Updated the convertFilterForMongoDb function in smartdata.classes.doc.ts to properly handle array operators like $in and $all.
|
||||||
|
|
||||||
|
## 2024-09-05 - 5.2.8 - fix(smartdata.classes.doc)
|
||||||
|
Fix key handling in convertFilterForMongoDb function
|
||||||
|
|
||||||
|
- Fixed an issue in convertFilterForMongoDb that allowed keys with dots which could cause errors.
|
||||||
|
|
||||||
|
## 2024-09-05 - 5.2.7 - fix(core)
|
||||||
|
Fixed issue with handling filter keys containing dots in smartdata.classes.doc.ts
|
||||||
|
|
||||||
|
- Fixed an error in the convertFilterForMongoDb function which previously threw an error when keys contained dots.
|
||||||
|
|
||||||
|
## 2024-06-18 - 5.2.6 - Chore
|
||||||
|
Maintenance Release
|
||||||
|
|
||||||
|
- Release version 5.2.6
|
||||||
|
|
||||||
|
## 2024-05-31 - 5.2.2 - Bug Fixes
|
||||||
|
Fixes and Maintenance
|
||||||
|
|
||||||
|
- Fixed issue where `_createdAt` and `_updatedAt` registered saveableProperties for all document types
|
||||||
|
|
||||||
|
## 2024-04-15 - 5.1.2 - New Feature
|
||||||
|
Enhancements and Bug Fixes
|
||||||
|
|
||||||
|
- Added static `.getCount({})` method to `SmartDataDbDoc`
|
||||||
|
- Changed fields `_createdAt` and `_updatedAt` to ISO format
|
||||||
|
|
||||||
|
## 2024-04-14 - 5.0.43 - New Feature
|
||||||
|
New Feature Addition
|
||||||
|
|
||||||
|
- Added default `_createdAt` and `_updatedAt` fields, fixes #1
|
||||||
|
|
||||||
|
## 2024-03-30 - 5.0.41 - Bug Fixes
|
||||||
|
Improvements and Fixes
|
||||||
|
|
||||||
|
- Improved `tsconfig.json` for ES Module use
|
||||||
|
|
||||||
|
## 2023-07-10 - 5.0.20 - Chore
|
||||||
|
Organizational Changes
|
||||||
|
|
||||||
|
- Switched to new org scheme
|
||||||
|
|
||||||
|
## 2023-07-21 - 5.0.21 to 5.0.26 - Fixes
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2023-07-21 - 5.0.20 - Chore
|
||||||
|
Organizational Changes
|
||||||
|
|
||||||
|
- Switch to the new org scheme
|
||||||
|
|
||||||
|
## 2023-06-25 - 5.0.14 to 5.0.19 - Fixes
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2022-05-17 - 5.0.0 - Major Update
|
||||||
|
Breaking Changes
|
||||||
|
|
||||||
|
- Switched to ESM
|
||||||
|
|
||||||
|
## 2022-05-18 - 5.0.2 - Bug Fixes
|
||||||
|
Bug Fixes
|
||||||
|
|
||||||
|
- The `watcher.changeSubject` now emits the correct type into observer functions
|
||||||
|
|
||||||
|
## 2022-05-17 - 5.0.1 - Chore
|
||||||
|
Testing Improvements
|
||||||
|
|
||||||
|
- Tests now use `@pushrocks/smartmongo` backed by `wiredTiger`
|
||||||
|
|
||||||
|
## 2022-05-17 to 2022-11-08 - 5.0.8 to 5.0.10
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2021-11-12 - 4.0.17 to 4.0.20
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2021-09-17 - 4.0.10 to 4.0.16
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2021-06-09 - 4.0.1 to 4.0.9
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2021-06-06 - 4.0.0 - Major Update
|
||||||
|
Major Release
|
||||||
|
|
||||||
|
- Maintenance and core updates
|
||||||
|
|
||||||
|
## 2021-05-17 - 3.1.56 - Chore
|
||||||
|
Maintenance Release
|
||||||
|
|
||||||
|
- Release version 3.1.56
|
||||||
|
|
||||||
|
## 2020-09-09 - 3.1.44 to 3.1.52
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2020-06-12 - 3.1.26 to 3.1.28
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2020-02-18 - 3.1.23 to 3.1.25
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2019-09-11 - 3.1.20 to 3.1.22
|
||||||
|
Multiple Fix Releases
|
||||||
|
|
||||||
|
- Various core updates and bug fixes
|
||||||
|
|
||||||
|
## 2018-07-10 - 3.0.5 - New Feature
|
||||||
|
Added Feature
|
||||||
|
|
||||||
|
- Added custom unique indexes to `SmartdataDoc`
|
||||||
|
|
||||||
|
## 2018-07-08 - 3.0.1 - Chore
|
||||||
|
Dependencies Update
|
||||||
|
|
||||||
|
- Updated mongodb dependencies
|
||||||
|
|
||||||
|
## 2018-07-08 - 3.0.0 - Major Update
|
||||||
|
Refactor and Cleanup
|
||||||
|
|
||||||
|
- Cleaned project structure
|
||||||
|
|
||||||
|
## 2018-01-16 - 2.0.7 - Breaking Change
|
||||||
|
Big Changes
|
||||||
|
|
||||||
|
- Switched to `@pushrocks` scope and moved from `rethinkdb` to `mongodb`
|
||||||
|
|
||||||
|
## 2018-01-12 - 2.0.0 - Major Release
|
||||||
|
Core Updates
|
||||||
|
|
||||||
|
- Updated CI configurations
|
||||||
|
|
3
dist/index.d.ts
vendored
3
dist/index.d.ts
vendored
@ -1,3 +0,0 @@
|
|||||||
export * from './smartdata.classes.db';
|
|
||||||
export * from './smartdata.classes.dbcollection';
|
|
||||||
export * from './smartdata.classes.dbdoc';
|
|
9
dist/index.js
vendored
9
dist/index.js
vendored
@ -1,9 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
function __export(m) {
|
|
||||||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
|
||||||
}
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
__export(require("./smartdata.classes.db"));
|
|
||||||
__export(require("./smartdata.classes.dbcollection"));
|
|
||||||
__export(require("./smartdata.classes.dbdoc"));
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQUFBLDRDQUFzQztBQUN0QyxzREFBZ0Q7QUFDaEQsK0NBQXlDIn0=
|
|
35
dist/smartdata.classes.db.d.ts
vendored
35
dist/smartdata.classes.db.d.ts
vendored
@ -1,35 +0,0 @@
|
|||||||
import * as plugins from './smartdata.plugins';
|
|
||||||
import { Objectmap } from 'lik';
|
|
||||||
import { DbTable } from './smartdata.classes.dbcollection';
|
|
||||||
import { ConnectionOptions } from 'rethinkdb';
|
|
||||||
/**
|
|
||||||
* interface - indicates the connection status of the db
|
|
||||||
*/
|
|
||||||
export declare type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
|
|
||||||
export declare class Db {
|
|
||||||
dbName: string;
|
|
||||||
connectionOptions: plugins.rethinkDb.ConnectionOptions;
|
|
||||||
dbConnection: plugins.rethinkDb.Connection;
|
|
||||||
status: TConnectionStatus;
|
|
||||||
dbTablesMap: Objectmap<DbTable<any>>;
|
|
||||||
constructor(connectionOptionsArg: ConnectionOptions);
|
|
||||||
/**
|
|
||||||
* supply additional SSl options
|
|
||||||
*/
|
|
||||||
setSsl(certificateStringArg: string, formatArg: 'base64' | 'clearText'): void;
|
|
||||||
/**
|
|
||||||
* connects to the database that was specified during instance creation
|
|
||||||
*/
|
|
||||||
connect(): Promise<any>;
|
|
||||||
/**
|
|
||||||
* closes the connection to the databse
|
|
||||||
*/
|
|
||||||
close(): Promise<any>;
|
|
||||||
/**
|
|
||||||
* Gets a table's name and returns smartdata's DbTable class
|
|
||||||
* @param nameArg
|
|
||||||
* @returns DbTable
|
|
||||||
*/
|
|
||||||
getDbTableByName<T>(nameArg: string): Promise<DbTable<T>>;
|
|
||||||
addTable(dbCollectionArg: DbTable<any>): void;
|
|
||||||
}
|
|
76
dist/smartdata.classes.db.js
vendored
76
dist/smartdata.classes.db.js
vendored
@ -1,76 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const plugins = require("./smartdata.plugins");
|
|
||||||
const lik_1 = require("lik");
|
|
||||||
class Db {
|
|
||||||
constructor(connectionOptionsArg) {
|
|
||||||
this.dbTablesMap = new lik_1.Objectmap();
|
|
||||||
this.dbName = connectionOptionsArg.db;
|
|
||||||
this.connectionOptions = connectionOptionsArg;
|
|
||||||
this.status = 'initial';
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* supply additional SSl options
|
|
||||||
*/
|
|
||||||
setSsl(certificateStringArg, formatArg) {
|
|
||||||
let certificateString;
|
|
||||||
if (formatArg = 'base64') {
|
|
||||||
certificateString = plugins.smartstring.base64.decode(certificateStringArg);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
certificateString = certificateStringArg;
|
|
||||||
}
|
|
||||||
this.connectionOptions['ssl'] = {
|
|
||||||
ca: Buffer.from(certificateString)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// basic connection stuff ----------------------------------------------
|
|
||||||
/**
|
|
||||||
* connects to the database that was specified during instance creation
|
|
||||||
*/
|
|
||||||
connect() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
this.dbConnection = yield plugins.rethinkDb.connect(this.connectionOptions);
|
|
||||||
this.dbConnection.use(this.dbName);
|
|
||||||
this.status = 'connected';
|
|
||||||
plugins.beautylog.ok(`Connected to database ${this.dbName}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* closes the connection to the databse
|
|
||||||
*/
|
|
||||||
close() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
yield this.dbConnection.close();
|
|
||||||
this.status = 'disconnected';
|
|
||||||
plugins.beautylog.ok(`disconnected from database ${this.dbName}`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// handle table to class distribution
|
|
||||||
/**
|
|
||||||
* Gets a table's name and returns smartdata's DbTable class
|
|
||||||
* @param nameArg
|
|
||||||
* @returns DbTable
|
|
||||||
*/
|
|
||||||
getDbTableByName(nameArg) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
let resultCollection = this.dbTablesMap.find((dbCollectionArg) => {
|
|
||||||
return dbCollectionArg.tableName === nameArg;
|
|
||||||
});
|
|
||||||
return resultCollection;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
addTable(dbCollectionArg) {
|
|
||||||
this.dbTablesMap.add(dbCollectionArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.Db = Db;
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEuY2xhc3Nlcy5kYi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBQUEsK0NBQThDO0FBQzlDLDZCQUErQjtBQVcvQjtJQU9FLFlBQVksb0JBQXVDO1FBRm5ELGdCQUFXLEdBQUcsSUFBSSxlQUFTLEVBQWdCLENBQUE7UUFHekMsSUFBSSxDQUFDLE1BQU0sR0FBRyxvQkFBb0IsQ0FBQyxFQUFFLENBQUE7UUFDckMsSUFBSSxDQUFDLGlCQUFpQixHQUFHLG9CQUFvQixDQUFBO1FBQzdDLElBQUksQ0FBQyxNQUFNLEdBQUcsU0FBUyxDQUFBO0lBQ3pCLENBQUM7SUFFRDs7T0FFRztJQUNILE1BQU0sQ0FBRSxvQkFBNEIsRUFBRSxTQUFpQztRQUNyRSxJQUFJLGlCQUF5QixDQUFBO1FBQzdCLEVBQUUsQ0FBQSxDQUFDLFNBQVMsR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDO1lBQ3hCLGlCQUFpQixHQUFHLE9BQU8sQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFBO1FBQzdFLENBQUM7UUFBQyxJQUFJLENBQUMsQ0FBQztZQUNOLGlCQUFpQixHQUFHLG9CQUFvQixDQUFBO1FBQzFDLENBQUM7UUFDRCxJQUFJLENBQUMsaUJBQWlCLENBQUMsS0FBSyxDQUFDLEdBQUc7WUFDOUIsRUFBRSxFQUFFLE1BQU0sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUM7U0FDbkMsQ0FBQTtJQUNILENBQUM7SUFFRCx3RUFBd0U7SUFFeEU7O09BRUc7SUFDRyxPQUFPOztZQUNYLElBQUksQ0FBQyxZQUFZLEdBQUcsTUFBTSxPQUFPLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQTtZQUMzRSxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUE7WUFDbEMsSUFBSSxDQUFDLE1BQU0sR0FBRyxXQUFXLENBQUE7WUFDekIsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMseUJBQXlCLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFBO1FBQzlELENBQUM7S0FBQTtJQUVEOztPQUVHO0lBQ0csS0FBSzs7WUFDVCxNQUFNLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUE7WUFDL0IsSUFBSSxDQUFDLE1BQU0sR0FBRyxjQUFjLENBQUE7WUFDNUIsT0FBTyxDQUFDLFNBQVMsQ0FBQyxFQUFFLENBQUMsOEJBQThCLElBQUksQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFBO1FBQ25FLENBQUM7S0FBQTtJQUVELHFDQUFxQztJQUVyQzs7OztPQUlHO0lBQ0csZ0JBQWdCLENBQUksT0FBZTs7WUFDdkMsSUFBSSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLGVBQWUsRUFBRSxFQUFFO2dCQUMvRCxNQUFNLENBQUMsZUFBZSxDQUFDLFNBQVMsS0FBSyxPQUFPLENBQUE7WUFDOUMsQ0FBQyxDQUFDLENBQUE7WUFDRixNQUFNLENBQUMsZ0JBQWdCLENBQUE7UUFDekIsQ0FBQztLQUFBO0lBRUQsUUFBUSxDQUFFLGVBQTZCO1FBQ3JDLElBQUksQ0FBQyxXQUFXLENBQUMsR0FBRyxDQUFDLGVBQWUsQ0FBQyxDQUFBO0lBQ3ZDLENBQUM7Q0FDRjtBQWxFRCxnQkFrRUMifQ==
|
|
45
dist/smartdata.classes.dbcollection.d.ts
vendored
45
dist/smartdata.classes.dbcollection.d.ts
vendored
@ -1,45 +0,0 @@
|
|||||||
import * as plugins from './smartdata.plugins';
|
|
||||||
import { Db } from './smartdata.classes.db';
|
|
||||||
import { DbDoc } from './smartdata.classes.dbdoc';
|
|
||||||
import { WriteResult, Cursor } from 'rethinkdb';
|
|
||||||
export interface IFindOptions {
|
|
||||||
limit?: number;
|
|
||||||
}
|
|
||||||
export interface IDocValidation<T> {
|
|
||||||
(doc: T): boolean;
|
|
||||||
}
|
|
||||||
export declare function Collection(db: Db): (constructor: any) => void;
|
|
||||||
export declare class DbTable<T> {
|
|
||||||
/**
|
|
||||||
* the collection that is used, defaults to mongodb collection,
|
|
||||||
* can be nedb datastore (sub api of mongodb)
|
|
||||||
*/
|
|
||||||
table: plugins.rethinkDb.Table;
|
|
||||||
objectValidation: IDocValidation<T>;
|
|
||||||
tableName: string;
|
|
||||||
db: Db;
|
|
||||||
constructor(collectedClassArg: T & DbDoc<T>, dbArg: Db);
|
|
||||||
init(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* adds a validation function that all newly inserted and updated objects have to pass
|
|
||||||
*/
|
|
||||||
addDocValidation(funcArg: IDocValidation<T>): void;
|
|
||||||
/**
|
|
||||||
* finds an object in the DbCollection
|
|
||||||
*/
|
|
||||||
find(): Promise<Cursor>;
|
|
||||||
/**
|
|
||||||
* create an object in the database
|
|
||||||
*/
|
|
||||||
insert(dbDocArg: T & DbDoc<T>): Promise<WriteResult>;
|
|
||||||
/**
|
|
||||||
* inserts object into the DbCollection
|
|
||||||
*/
|
|
||||||
update(dbDocArg: T & DbDoc<T>): Promise<WriteResult>;
|
|
||||||
/**
|
|
||||||
* checks a Doc for constraints
|
|
||||||
* if this.objectValidation is not set it passes.
|
|
||||||
*/
|
|
||||||
private checkDoc(docArg);
|
|
||||||
extractKey(writeResult: WriteResult): void;
|
|
||||||
}
|
|
103
dist/smartdata.classes.dbcollection.js
vendored
103
dist/smartdata.classes.dbcollection.js
vendored
@ -1,103 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const plugins = require("./smartdata.plugins");
|
|
||||||
function Collection(db) {
|
|
||||||
return function (constructor) {
|
|
||||||
constructor['dbCollection'] = new DbTable(constructor, db);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exports.Collection = Collection;
|
|
||||||
class DbTable {
|
|
||||||
constructor(collectedClassArg, dbArg) {
|
|
||||||
this.objectValidation = null;
|
|
||||||
// tell the collection where it belongs
|
|
||||||
this.tableName = collectedClassArg.name;
|
|
||||||
this.db = dbArg;
|
|
||||||
// tell the db class about it (important since Db uses different systems under the hood)
|
|
||||||
this.db.addTable(this);
|
|
||||||
}
|
|
||||||
init() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
if (!this.table) {
|
|
||||||
// connect this instance to a RethinkDB table
|
|
||||||
const availableTables = yield plugins.rethinkDb
|
|
||||||
.db(this.db.dbName)
|
|
||||||
.tableList()
|
|
||||||
.run(this.db.dbConnection);
|
|
||||||
if (availableTables.indexOf(this.tableName)) {
|
|
||||||
yield plugins.rethinkDb
|
|
||||||
.db(this.db.dbName)
|
|
||||||
.tableCreate(this.tableName)
|
|
||||||
.run(this.db.dbConnection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.table = plugins.rethinkDb.table(this.tableName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* adds a validation function that all newly inserted and updated objects have to pass
|
|
||||||
*/
|
|
||||||
addDocValidation(funcArg) {
|
|
||||||
this.objectValidation = funcArg;
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* finds an object in the DbCollection
|
|
||||||
*/
|
|
||||||
find() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
yield this.init();
|
|
||||||
return yield plugins.rethinkDb.table(this.tableName).filter({}).run(this.db.dbConnection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* create an object in the database
|
|
||||||
*/
|
|
||||||
insert(dbDocArg) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
yield this.init();
|
|
||||||
yield this.checkDoc(dbDocArg);
|
|
||||||
return yield plugins.rethinkDb.table(this.tableName).insert(dbDocArg.createSavableObject()).run(this.db.dbConnection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* inserts object into the DbCollection
|
|
||||||
*/
|
|
||||||
update(dbDocArg) {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
yield this.init();
|
|
||||||
yield this.checkDoc(dbDocArg);
|
|
||||||
console.log(this.tableName, dbDocArg.createSavableObject());
|
|
||||||
return yield plugins.rethinkDb.table(this.tableName).update(dbDocArg.createSavableObject()).run(this.db.dbConnection);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* checks a Doc for constraints
|
|
||||||
* if this.objectValidation is not set it passes.
|
|
||||||
*/
|
|
||||||
checkDoc(docArg) {
|
|
||||||
let done = plugins.smartq.defer();
|
|
||||||
let validationResult = true;
|
|
||||||
if (this.objectValidation) {
|
|
||||||
validationResult = this.objectValidation(docArg);
|
|
||||||
}
|
|
||||||
if (validationResult) {
|
|
||||||
done.resolve();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
done.reject('validation of object did not pass');
|
|
||||||
}
|
|
||||||
return done.promise;
|
|
||||||
}
|
|
||||||
extractKey(writeResult) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.DbTable = DbTable;
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGJjb2xsZWN0aW9uLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvc21hcnRkYXRhLmNsYXNzZXMuZGJjb2xsZWN0aW9uLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7QUFBQSwrQ0FBOEM7QUFlOUMsb0JBQTRCLEVBQU07SUFDaEMsTUFBTSxDQUFDLFVBQVUsV0FBVztRQUMxQixXQUFXLENBQUUsY0FBYyxDQUFFLEdBQUcsSUFBSSxPQUFPLENBQUMsV0FBVyxFQUFFLEVBQUUsQ0FBQyxDQUFBO0lBQzlELENBQUMsQ0FBQTtBQUNILENBQUM7QUFKRCxnQ0FJQztBQUVEO0lBVUUsWUFBYSxpQkFBK0IsRUFBRSxLQUFTO1FBSnZELHFCQUFnQixHQUFzQixJQUFJLENBQUE7UUFLeEMsdUNBQXVDO1FBQ3ZDLElBQUksQ0FBQyxTQUFTLEdBQUcsaUJBQWlCLENBQUMsSUFBSSxDQUFBO1FBQ3ZDLElBQUksQ0FBQyxFQUFFLEdBQUcsS0FBSyxDQUFBO1FBRWYsd0ZBQXdGO1FBQ3hGLElBQUksQ0FBQyxFQUFFLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFBO0lBQ3hCLENBQUM7SUFFSyxJQUFJOztZQUNSLEVBQUUsQ0FBQSxDQUFDLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLENBQUM7Z0JBQ2YsNkNBQTZDO2dCQUM3QyxNQUFNLGVBQWUsR0FBRyxNQUFNLE9BQU8sQ0FBQyxTQUFTO3FCQUM1QyxFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUM7cUJBQ2xCLFNBQVMsRUFBRTtxQkFDWCxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsQ0FBQTtnQkFDNUIsRUFBRSxDQUFBLENBQUMsZUFBZSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQyxDQUFDO29CQUMzQyxNQUFNLE9BQU8sQ0FBQyxTQUFTO3lCQUN0QixFQUFFLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUM7eUJBQ2xCLFdBQVcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDO3lCQUMzQixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsQ0FBQTtnQkFDNUIsQ0FBQztZQUNILENBQUM7WUFDRCxJQUFJLENBQUMsS0FBSyxHQUFHLE9BQU8sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUN0RCxDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNILGdCQUFnQixDQUFFLE9BQTBCO1FBQzFDLElBQUksQ0FBQyxnQkFBZ0IsR0FBRyxPQUFPLENBQUE7SUFDakMsQ0FBQztJQUVEOztPQUVHO0lBQ0csSUFBSTs7WUFDUixNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUNqQixNQUFNLENBQUMsTUFBTSxPQUFPLENBQUMsU0FBUyxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsTUFBTSxDQUFDLEVBRTNELENBQUMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQyxZQUFZLENBQUMsQ0FBQTtRQUM5QixDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNHLE1BQU0sQ0FBRSxRQUFzQjs7WUFDbEMsTUFBTSxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUE7WUFDakIsTUFBTSxJQUFJLENBQUMsUUFBUSxDQUFDLFFBQVEsQ0FBQyxDQUFBO1lBQzdCLE1BQU0sQ0FBQyxNQUFNLE9BQU8sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxNQUFNLENBQ3pELFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxDQUMvQixDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDLFlBQVksQ0FBQyxDQUFBO1FBQzdCLENBQUM7S0FBQTtJQUVEOztPQUVHO0lBQ0csTUFBTSxDQUFFLFFBQXNCOztZQUNsQyxNQUFNLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUNqQixNQUFNLElBQUksQ0FBQyxRQUFRLENBQUMsUUFBUSxDQUFDLENBQUE7WUFDN0IsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxFQUFFLFFBQVEsQ0FBQyxtQkFBbUIsRUFBRSxDQUFDLENBQUE7WUFDM0QsTUFBTSxDQUFDLE1BQU0sT0FBTyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFDLE1BQU0sQ0FDekQsUUFBUSxDQUFDLG1CQUFtQixFQUFFLENBQy9CLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDN0IsQ0FBQztLQUFBO0lBRUQ7OztPQUdHO0lBQ0ssUUFBUSxDQUFFLE1BQVM7UUFDekIsSUFBSSxJQUFJLEdBQUcsT0FBTyxDQUFDLE1BQU0sQ0FBQyxLQUFLLEVBQVEsQ0FBQTtRQUN2QyxJQUFJLGdCQUFnQixHQUFHLElBQUksQ0FBQTtRQUMzQixFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO1lBQzFCLGdCQUFnQixHQUFHLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsQ0FBQTtRQUNsRCxDQUFDO1FBQ0QsRUFBRSxDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDO1lBQ3JCLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQTtRQUNoQixDQUFDO1FBQUMsSUFBSSxDQUFDLENBQUM7WUFDTixJQUFJLENBQUMsTUFBTSxDQUFDLG1DQUFtQyxDQUFDLENBQUE7UUFDbEQsQ0FBQztRQUNELE1BQU0sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFBO0lBQ3JCLENBQUM7SUFFRCxVQUFVLENBQUUsV0FBd0I7SUFFcEMsQ0FBQztDQUNGO0FBakdELDBCQWlHQyJ9
|
|
44
dist/smartdata.classes.dbdoc.d.ts
vendored
44
dist/smartdata.classes.dbdoc.d.ts
vendored
@ -1,44 +0,0 @@
|
|||||||
import { Objectmap } from 'lik';
|
|
||||||
import { DbTable } from './smartdata.classes.dbcollection';
|
|
||||||
export declare type TDocCreation = 'db' | 'new' | 'mixed';
|
|
||||||
/**
|
|
||||||
* saveable - saveable decorator to be used on class properties
|
|
||||||
*/
|
|
||||||
export declare function svDb(): (target: DbDoc<any>, key: string) => void;
|
|
||||||
export declare class DbDoc<T> {
|
|
||||||
/**
|
|
||||||
* the collection object an Doc belongs to
|
|
||||||
*/
|
|
||||||
collection: DbTable<T>;
|
|
||||||
/**
|
|
||||||
* how the Doc in memory was created, may prove useful later.
|
|
||||||
*/
|
|
||||||
creationStatus: TDocCreation;
|
|
||||||
/**
|
|
||||||
* an array of saveable properties of a doc
|
|
||||||
*/
|
|
||||||
saveableProperties: string[];
|
|
||||||
/**
|
|
||||||
* name
|
|
||||||
*/
|
|
||||||
name: string;
|
|
||||||
/**
|
|
||||||
* primary id in the database
|
|
||||||
*/
|
|
||||||
dbId: string;
|
|
||||||
/**
|
|
||||||
* class constructor
|
|
||||||
*/
|
|
||||||
constructor();
|
|
||||||
/**
|
|
||||||
* saves this instance but not any connected items
|
|
||||||
* may lead to data inconsistencies, but is faster
|
|
||||||
*/
|
|
||||||
save(): Promise<void>;
|
|
||||||
/**
|
|
||||||
* also store any referenced objects to DB
|
|
||||||
* better for data consistency
|
|
||||||
*/
|
|
||||||
saveDeep(savedMapArg?: Objectmap<DbDoc<any>>): void;
|
|
||||||
createSavableObject(): any;
|
|
||||||
}
|
|
83
dist/smartdata.classes.dbdoc.js
vendored
83
dist/smartdata.classes.dbdoc.js
vendored
@ -1,83 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
||||||
return new (P || (P = Promise))(function (resolve, reject) {
|
|
||||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
||||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
||||||
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
|
|
||||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
||||||
});
|
|
||||||
};
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const lik_1 = require("lik");
|
|
||||||
/**
|
|
||||||
* saveable - saveable decorator to be used on class properties
|
|
||||||
*/
|
|
||||||
function svDb() {
|
|
||||||
return (target, key) => {
|
|
||||||
console.log('called sva');
|
|
||||||
if (!target.saveableProperties) {
|
|
||||||
target.saveableProperties = [];
|
|
||||||
}
|
|
||||||
target.saveableProperties.push(key);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
exports.svDb = svDb;
|
|
||||||
class DbDoc {
|
|
||||||
/**
|
|
||||||
* class constructor
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
/**
|
|
||||||
* how the Doc in memory was created, may prove useful later.
|
|
||||||
*/
|
|
||||||
this.creationStatus = 'new';
|
|
||||||
this.name = this.constructor['name'];
|
|
||||||
this.collection = this.constructor['dbCollection'];
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* saves this instance but not any connected items
|
|
||||||
* may lead to data inconsistencies, but is faster
|
|
||||||
*/
|
|
||||||
save() {
|
|
||||||
return __awaiter(this, void 0, void 0, function* () {
|
|
||||||
let self = this;
|
|
||||||
switch (this.creationStatus) {
|
|
||||||
case 'db':
|
|
||||||
yield this.collection.update(self);
|
|
||||||
break;
|
|
||||||
case 'new':
|
|
||||||
let writeResult = yield this.collection.insert(self);
|
|
||||||
this.creationStatus = 'db';
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('neither new nor in db?');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* also store any referenced objects to DB
|
|
||||||
* better for data consistency
|
|
||||||
*/
|
|
||||||
saveDeep(savedMapArg = null) {
|
|
||||||
if (!savedMapArg) {
|
|
||||||
savedMapArg = new lik_1.Objectmap();
|
|
||||||
}
|
|
||||||
savedMapArg.add(this);
|
|
||||||
this.save();
|
|
||||||
for (let propertyKey in this) {
|
|
||||||
let property = this[propertyKey];
|
|
||||||
if (property instanceof DbDoc && !savedMapArg.checkForObject(property)) {
|
|
||||||
property.saveDeep(savedMapArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
createSavableObject() {
|
|
||||||
let saveableObject = {}; // is not exposed to outside, so any is ok here
|
|
||||||
for (let propertyNameString of this.saveableProperties) {
|
|
||||||
saveableObject[propertyNameString] = this[propertyNameString];
|
|
||||||
}
|
|
||||||
return saveableObject;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.DbDoc = DbDoc;
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLmNsYXNzZXMuZGJkb2MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEuY2xhc3Nlcy5kYmRvYy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7O0FBRUEsNkJBQStCO0FBTy9COztHQUVHO0FBQ0g7SUFDRSxNQUFNLENBQUMsQ0FBQyxNQUFrQixFQUFFLEdBQVcsRUFBRSxFQUFFO1FBQ3pDLE9BQU8sQ0FBQyxHQUFHLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDekIsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDO1lBQUMsTUFBTSxDQUFDLGtCQUFrQixHQUFHLEVBQUUsQ0FBQTtRQUFDLENBQUM7UUFDbEUsTUFBTSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQTtJQUNyQyxDQUFDLENBQUE7QUFDSCxDQUFDO0FBTkQsb0JBTUM7QUFFRDtJQTJCRTs7T0FFRztJQUNIO1FBdkJBOztXQUVHO1FBQ0gsbUJBQWMsR0FBaUIsS0FBSyxDQUFBO1FBcUJsQyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQyxXQUFXLENBQUMsTUFBTSxDQUFDLENBQUE7UUFDcEMsSUFBSSxDQUFDLFVBQVUsR0FBRyxJQUFJLENBQUMsV0FBVyxDQUFFLGNBQWMsQ0FBRSxDQUFBO0lBQ3RELENBQUM7SUFFRDs7O09BR0c7SUFDRyxJQUFJOztZQUNSLElBQUksSUFBSSxHQUFRLElBQUksQ0FBQTtZQUNwQixNQUFNLENBQUMsQ0FBQyxJQUFJLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztnQkFDNUIsS0FBSyxJQUFJO29CQUNQLE1BQU0sSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUE7b0JBQ2xDLEtBQUssQ0FBQTtnQkFDUCxLQUFLLEtBQUs7b0JBQ1IsSUFBSSxXQUFXLEdBQUcsTUFBTSxJQUFJLENBQUMsVUFBVSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsQ0FBQTtvQkFDcEQsSUFBSSxDQUFDLGNBQWMsR0FBRyxJQUFJLENBQUE7b0JBQzFCLEtBQUssQ0FBQztnQkFDUjtvQkFDRSxPQUFPLENBQUMsS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUE7WUFDM0MsQ0FBQztRQUNILENBQUM7S0FBQTtJQUVEOzs7T0FHRztJQUNILFFBQVEsQ0FBRSxjQUFxQyxJQUFJO1FBQ2pELEVBQUUsQ0FBQyxDQUFDLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztZQUNqQixXQUFXLEdBQUcsSUFBSSxlQUFTLEVBQWMsQ0FBQTtRQUMzQyxDQUFDO1FBQ0QsV0FBVyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQTtRQUNyQixJQUFJLENBQUMsSUFBSSxFQUFFLENBQUE7UUFDWCxHQUFHLENBQUMsQ0FBQyxJQUFJLFdBQVcsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQzdCLElBQUksUUFBUSxHQUFRLElBQUksQ0FBRSxXQUFXLENBQUUsQ0FBQTtZQUN2QyxFQUFFLENBQUMsQ0FBQyxRQUFRLFlBQVksS0FBSyxJQUFJLENBQUMsV0FBVyxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDLENBQUM7Z0JBQ3ZFLFFBQVEsQ0FBQyxRQUFRLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDaEMsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQsbUJBQW1CO1FBQ2pCLElBQUksY0FBYyxHQUFRLEVBQUUsQ0FBQSxDQUFDLCtDQUErQztRQUM1RSxHQUFHLENBQUMsQ0FBQyxJQUFJLGtCQUFrQixJQUFJLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxDQUFDLENBQUM7WUFDdkQsY0FBYyxDQUFFLGtCQUFrQixDQUFFLEdBQUcsSUFBSSxDQUFFLGtCQUFrQixDQUFFLENBQUE7UUFDbkUsQ0FBQztRQUNELE1BQU0sQ0FBQyxjQUFjLENBQUE7SUFDdkIsQ0FBQztDQUNGO0FBL0VELHNCQStFQyJ9
|
|
7
dist/smartdata.plugins.d.ts
vendored
7
dist/smartdata.plugins.d.ts
vendored
@ -1,7 +0,0 @@
|
|||||||
import * as assert from 'assert';
|
|
||||||
import * as beautylog from 'beautylog';
|
|
||||||
import * as lodash from 'lodash';
|
|
||||||
import * as rethinkDb from 'rethinkdb';
|
|
||||||
import * as smartq from 'smartq';
|
|
||||||
import * as smartstring from 'smartstring';
|
|
||||||
export { assert, beautylog, lodash, smartq, rethinkDb, smartstring };
|
|
15
dist/smartdata.plugins.js
vendored
15
dist/smartdata.plugins.js
vendored
@ -1,15 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const assert = require("assert");
|
|
||||||
exports.assert = assert;
|
|
||||||
const beautylog = require("beautylog");
|
|
||||||
exports.beautylog = beautylog;
|
|
||||||
const lodash = require("lodash");
|
|
||||||
exports.lodash = lodash;
|
|
||||||
const rethinkDb = require("rethinkdb");
|
|
||||||
exports.rethinkDb = rethinkDb;
|
|
||||||
const smartq = require("smartq");
|
|
||||||
exports.smartq = smartq;
|
|
||||||
const smartstring = require("smartstring");
|
|
||||||
exports.smartstring = smartstring;
|
|
||||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRkYXRhLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydGRhdGEucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBLGlDQUFnQztBQVE1Qix3QkFBTTtBQVBWLHVDQUFzQztBQVFsQyw4QkFBUztBQVBiLGlDQUFnQztBQVE1Qix3QkFBTTtBQVBWLHVDQUFzQztBQVNsQyw4QkFBUztBQVJiLGlDQUFnQztBQU81Qix3QkFBTTtBQU5WLDJDQUEwQztBQVF0QyxrQ0FBVyJ9
|
|
@ -1,26 +0,0 @@
|
|||||||
# smartdata
|
|
||||||
do more with data
|
|
||||||
|
|
||||||
## 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://nodejs.org/dist/latest-v6.x/docs/api/)
|
|
||||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
|
||||||
[](http://standardjs.com/)
|
|
||||||
|
|
||||||
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,13 +5,32 @@
|
|||||||
"dockerSock": false
|
"dockerSock": false
|
||||||
},
|
},
|
||||||
"npmci": {
|
"npmci": {
|
||||||
"globalNpmTools": [
|
"npmGlobalTools": [],
|
||||||
"npmts"
|
"npmAccessLevel": "public",
|
||||||
]
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
},
|
},
|
||||||
"npmts": {
|
"gitzone": {
|
||||||
"testConfig": {
|
"projectType": "npm",
|
||||||
"parallel": false
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
223
package-lock.json
generated
223
package-lock.json
generated
@ -1,223 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "smartdata",
|
|
||||||
"version": "2.0.1",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"@types/lodash": {
|
|
||||||
"version": "4.14.92",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.92.tgz",
|
|
||||||
"integrity": "sha512-cdvY1fyUGYgG7/i07a/sR5PnD6+Z+ljUrD0CNVf0qj645VvEdLNtZPjvCp4siPy3rQ/KRXMfUATIqi3+9x57Sw=="
|
|
||||||
},
|
|
||||||
"@types/minimatch": {
|
|
||||||
"version": "3.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
|
|
||||||
"integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA=="
|
|
||||||
},
|
|
||||||
"balanced-match": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
|
|
||||||
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
|
|
||||||
},
|
|
||||||
"brace-expansion": {
|
|
||||||
"version": "1.1.8",
|
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz",
|
|
||||||
"integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=",
|
|
||||||
"requires": {
|
|
||||||
"balanced-match": "1.0.0",
|
|
||||||
"concat-map": "0.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"concat-map": {
|
|
||||||
"version": "0.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
|
||||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
|
||||||
},
|
|
||||||
"conventional-commit-types": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz",
|
|
||||||
"integrity": "sha1-XblXOdbCEqy+e29lahG5QLqmiUY=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"cz-conventional-changelog": {
|
|
||||||
"version": "2.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz",
|
|
||||||
"integrity": "sha1-L0vHOQ4yROTfKT5ro1Hkx0Cnx2Q=",
|
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
|
||||||
"conventional-commit-types": "2.2.0",
|
|
||||||
"lodash.map": "4.6.0",
|
|
||||||
"longest": "1.0.1",
|
|
||||||
"right-pad": "1.0.1",
|
|
||||||
"word-wrap": "1.2.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"define-properties": {
|
|
||||||
"version": "1.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
|
|
||||||
"integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
|
|
||||||
"requires": {
|
|
||||||
"foreach": "2.0.5",
|
|
||||||
"object-keys": "1.0.11"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es-abstract": {
|
|
||||||
"version": "1.10.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz",
|
|
||||||
"integrity": "sha512-/uh/DhdqIOSkAWifU+8nG78vlQxdLckUdI/sPgy0VhuXi2qJ7T8czBmqIYtLQVpCIFYafChnsRsB5pyb1JdmCQ==",
|
|
||||||
"requires": {
|
|
||||||
"es-to-primitive": "1.1.1",
|
|
||||||
"function-bind": "1.1.1",
|
|
||||||
"has": "1.0.1",
|
|
||||||
"is-callable": "1.1.3",
|
|
||||||
"is-regex": "1.0.4"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es-to-primitive": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
|
|
||||||
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
|
|
||||||
"requires": {
|
|
||||||
"is-callable": "1.1.3",
|
|
||||||
"is-date-object": "1.0.1",
|
|
||||||
"is-symbol": "1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"foreach": {
|
|
||||||
"version": "2.0.5",
|
|
||||||
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
|
|
||||||
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
|
|
||||||
},
|
|
||||||
"function-bind": {
|
|
||||||
"version": "1.1.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
|
|
||||||
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
|
|
||||||
},
|
|
||||||
"has": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=",
|
|
||||||
"requires": {
|
|
||||||
"function-bind": "1.1.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-callable": {
|
|
||||||
"version": "1.1.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
|
|
||||||
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
|
|
||||||
},
|
|
||||||
"is-date-object": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY="
|
|
||||||
},
|
|
||||||
"is-regex": {
|
|
||||||
"version": "1.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
|
|
||||||
"integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
|
|
||||||
"requires": {
|
|
||||||
"has": "1.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"is-symbol": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI="
|
|
||||||
},
|
|
||||||
"lik": {
|
|
||||||
"version": "2.0.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/lik/-/lik-2.0.2.tgz",
|
|
||||||
"integrity": "sha512-xdNrvQqVLMNkU25Z6tNrJXyOhn0hugZvWZCIPb315M5J4fK2OzgpLacp68J5bYWydRWX4L3Tl/i69BNWHsvcXg==",
|
|
||||||
"requires": {
|
|
||||||
"@types/lodash": "4.14.92",
|
|
||||||
"@types/minimatch": "3.0.3",
|
|
||||||
"lodash": "4.17.4",
|
|
||||||
"minimatch": "3.0.4",
|
|
||||||
"smartq": "1.1.6",
|
|
||||||
"symbol-tree": "3.2.2",
|
|
||||||
"typings-global": "1.0.28"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"lodash": {
|
|
||||||
"version": "4.17.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz",
|
|
||||||
"integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4="
|
|
||||||
},
|
|
||||||
"smartq": {
|
|
||||||
"version": "1.1.6",
|
|
||||||
"resolved": "https://registry.npmjs.org/smartq/-/smartq-1.1.6.tgz",
|
|
||||||
"integrity": "sha512-W7vTj4kSqN8kHVq+Q6BJTr/UGc36TnO0pzKNU8B4cr7TXG4N98eyubWaaCHPSjCUqDgmUPPru929WXzetai97A==",
|
|
||||||
"requires": {
|
|
||||||
"typings-global": "1.0.28",
|
|
||||||
"util.promisify": "1.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"lodash.map": {
|
|
||||||
"version": "4.6.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz",
|
|
||||||
"integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"longest": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"minimatch": {
|
|
||||||
"version": "3.0.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
|
||||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
|
||||||
"requires": {
|
|
||||||
"brace-expansion": "1.1.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"object-keys": {
|
|
||||||
"version": "1.0.11",
|
|
||||||
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz",
|
|
||||||
"integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0="
|
|
||||||
},
|
|
||||||
"object.getownpropertydescriptors": {
|
|
||||||
"version": "2.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
|
|
||||||
"integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "1.1.2",
|
|
||||||
"es-abstract": "1.10.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"right-pad": {
|
|
||||||
"version": "1.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/right-pad/-/right-pad-1.0.1.tgz",
|
|
||||||
"integrity": "sha1-jKCMLLtbVedNr6lr9/0aJ9VoyNA=",
|
|
||||||
"dev": true
|
|
||||||
},
|
|
||||||
"symbol-tree": {
|
|
||||||
"version": "3.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz",
|
|
||||||
"integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY="
|
|
||||||
},
|
|
||||||
"typings-global": {
|
|
||||||
"version": "1.0.28",
|
|
||||||
"resolved": "https://registry.npmjs.org/typings-global/-/typings-global-1.0.28.tgz",
|
|
||||||
"integrity": "sha512-6VOwJWEY2971HOMHu/7sURzUXiD4/LiMJPsMAOqkHHAtS3MVpLFE5gzTiHilsH9KY5VE1mBQirWIgWFsDuo90A=="
|
|
||||||
},
|
|
||||||
"util.promisify": {
|
|
||||||
"version": "1.0.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz",
|
|
||||||
"integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==",
|
|
||||||
"requires": {
|
|
||||||
"define-properties": "1.1.2",
|
|
||||||
"object.getownpropertydescriptors": "2.0.3"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"word-wrap": {
|
|
||||||
"version": "1.2.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
|
|
||||||
"integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
88
package.json
88
package.json
@ -1,45 +1,75 @@
|
|||||||
{
|
{
|
||||||
"name": "smartdata",
|
"name": "@push.rocks/smartdata",
|
||||||
"version": "2.0.1",
|
"version": "5.9.2",
|
||||||
"description": "do more with data",
|
"private": false,
|
||||||
"main": "dist/index.js",
|
"description": "An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.",
|
||||||
"typings": "dist/index.d.ts",
|
"main": "dist_ts/index.js",
|
||||||
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(npmts)",
|
"test": "tstest test/",
|
||||||
"testLocal": "(npmdocker)"
|
"build": "tsbuild --web --allowimplicitany",
|
||||||
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@gitlab.com/pushrocks/smartdata.git"
|
"url": "https://code.foss.global/push.rocks/smartdata.git"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/pushrocks/smartdata/issues"
|
"url": "https://code.foss.global/push.rocks/smartdata/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
|
"homepage": "https://code.foss.global/push.rocks/smartdata#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/lodash": "^4.14.92",
|
"@push.rocks/lik": "^6.0.14",
|
||||||
"@types/rethinkdb": "^2.3.8",
|
"@push.rocks/smartdelay": "^3.0.1",
|
||||||
"beautylog": "^6.1.10",
|
"@push.rocks/smartlog": "^3.0.2",
|
||||||
"lik": "^2.0.2",
|
"@push.rocks/smartmongo": "^2.0.11",
|
||||||
"lodash": "^4.17.4",
|
"@push.rocks/smartpromise": "^4.0.2",
|
||||||
"rethinkdb": "^2.3.3",
|
"@push.rocks/smartrx": "^3.0.7",
|
||||||
"runtime-type-checks": "0.0.4",
|
"@push.rocks/smartstring": "^4.0.15",
|
||||||
"smartq": "^1.1.6",
|
"@push.rocks/smarttime": "^4.0.6",
|
||||||
"smartstring": "^2.0.28"
|
"@push.rocks/smartunique": "^3.0.8",
|
||||||
|
"@push.rocks/taskbuffer": "^3.1.7",
|
||||||
|
"@tsclass/tsclass": "^8.2.0",
|
||||||
|
"mongodb": "^6.15.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^8.5.7",
|
"@git.zone/tsbuild": "^2.3.2",
|
||||||
"@types/shelljs": "^0.7.4",
|
"@git.zone/tsrun": "^1.2.44",
|
||||||
"cz-conventional-changelog": "^2.1.0",
|
"@git.zone/tstest": "^1.0.77",
|
||||||
"qenv": "^1.1.7",
|
"@push.rocks/qenv": "^6.0.5",
|
||||||
"shelljs": "^0.7.8",
|
"@push.rocks/tapbundle": "^5.6.2",
|
||||||
"tapbundle": "^1.1.1"
|
"@types/node": "^22.14.0"
|
||||||
},
|
},
|
||||||
"config": {
|
"files": [
|
||||||
"commitizen": {
|
"ts/**/*",
|
||||||
"path": "./node_modules/cz-conventional-changelog"
|
"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"
|
||||||
|
],
|
||||||
|
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6",
|
||||||
|
"pnpm": {
|
||||||
|
"overrides": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
9922
pnpm-lock.yaml
generated
Normal file
9922
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
4
pnpm-workspace.yaml
Normal file
4
pnpm-workspace.yaml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
onlyBuiltDependencies:
|
||||||
|
- esbuild
|
||||||
|
- mongodb-memory-server
|
||||||
|
- puppeteer
|
8
qenv.yml
8
qenv.yml
@ -1,7 +1 @@
|
|||||||
vars:
|
required:
|
||||||
- RDB_DB
|
|
||||||
- RDB_HOST
|
|
||||||
- RDB_USER
|
|
||||||
- RDB_PASS
|
|
||||||
- RDB_PORT
|
|
||||||
- RDB_CERT
|
|
||||||
|
0
readme.hints.md
Normal file
0
readme.hints.md
Normal file
592
readme.md
Normal file
592
readme.md
Normal file
@ -0,0 +1,592 @@
|
|||||||
|
# @push.rocks/smartdata
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/@push.rocks/smartdata)
|
||||||
|
|
||||||
|
A powerful TypeScript-first MongoDB wrapper that provides advanced features for distributed systems, real-time data synchronization, and easy data management.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Type-Safe MongoDB Integration**: Full TypeScript support with decorators for schema definition
|
||||||
|
- **Document Management**: Type-safe CRUD operations with automatic timestamp tracking
|
||||||
|
- **EasyStore**: Simple key-value storage with automatic persistence and sharing between instances
|
||||||
|
- **Distributed Coordination**: Built-in support for leader election and distributed task management
|
||||||
|
- **Real-time Data Sync**: Watchers for real-time data changes with RxJS integration
|
||||||
|
- **Connection Management**: Automatic connection handling with connection pooling
|
||||||
|
- **Collection Management**: Type-safe collection operations with automatic indexing
|
||||||
|
- **Deep Query Type Safety**: Fully type-safe queries for nested object properties with `DeepQuery<T>`
|
||||||
|
- **MongoDB Compatibility**: Support for all MongoDB query operators and advanced features
|
||||||
|
- **Enhanced Cursors**: Chainable, type-safe cursor API with memory efficient data processing
|
||||||
|
- **Type Conversion**: Automatic handling of MongoDB types like ObjectId and Binary data
|
||||||
|
- **Serialization Hooks**: Custom serialization and deserialization of document properties
|
||||||
|
- **Powerful Search Capabilities**: Unified `search(query)` API supporting field:value exact matches, multi-field regex searches, case-insensitive matching, and automatic escaping to prevent regex injection
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js >= 16.x
|
||||||
|
- MongoDB >= 4.4
|
||||||
|
- TypeScript >= 4.x (for development)
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
To install `@push.rocks/smartdata`, use npm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @push.rocks/smartdata --save
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with pnpm:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add @push.rocks/smartdata
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
### Setting Up and Connecting to the Database
|
||||||
|
|
||||||
|
Before interacting with the database, you need to set up and establish a connection. The `SmartdataDb` class handles connection pooling and automatic reconnection.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { SmartdataDb } from '@push.rocks/smartdata';
|
||||||
|
|
||||||
|
// Create a new instance of SmartdataDb with MongoDB connection details
|
||||||
|
const db = new SmartdataDb({
|
||||||
|
mongoDbUrl: 'mongodb://<USERNAME>:<PASSWORD>@localhost:27017/<DBNAME>',
|
||||||
|
mongoDbName: 'your-database-name',
|
||||||
|
mongoDbUser: 'your-username',
|
||||||
|
mongoDbPass: 'your-password',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize and connect to the database
|
||||||
|
// This sets up a connection pool with max 100 connections
|
||||||
|
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`, `@svDb`, `@index`, and `@searchable` to define your data models. Fields of type `ObjectId` or `Buffer` decorated with `@svDb()` will be stored as BSON ObjectId and Binary, respectively; no separate `@oid()` or `@bin()` decorators are required.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import {
|
||||||
|
SmartDataDbDoc,
|
||||||
|
Collection,
|
||||||
|
unI,
|
||||||
|
svDb,
|
||||||
|
index,
|
||||||
|
searchable,
|
||||||
|
} from '@push.rocks/smartdata';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
@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()
|
||||||
|
@searchable() // Mark 'username' as searchable
|
||||||
|
public username: string; // Mark 'username' to be saved in DB
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@searchable() // Mark 'email' as searchable
|
||||||
|
@index() // Create a regular index for this field
|
||||||
|
public email: string; // Mark 'email' to be saved in DB
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public organizationId: ObjectId; // Stored as BSON ObjectId
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public profilePicture: Buffer; // Stored as BSON Binary
|
||||||
|
|
||||||
|
@svDb({
|
||||||
|
serialize: (data) => JSON.stringify(data), // Custom serialization
|
||||||
|
deserialize: (data) => JSON.parse(data), // Custom deserialization
|
||||||
|
})
|
||||||
|
public preferences: Record<string, any>;
|
||||||
|
|
||||||
|
constructor(username: string, email: string) {
|
||||||
|
super();
|
||||||
|
this.username = username;
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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' });
|
||||||
|
|
||||||
|
// Using a cursor for large collections
|
||||||
|
const cursor = await User.getCursor({ active: true });
|
||||||
|
|
||||||
|
// Process documents one at a time (memory efficient)
|
||||||
|
await cursor.forEach(async (user, index) => {
|
||||||
|
// Process each user with its position
|
||||||
|
console.log(`Processing user ${index}: ${user.username}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Chain cursor methods like in the MongoDB native driver
|
||||||
|
const paginatedCursor = await User.getCursor({ active: true })
|
||||||
|
.limit(10) // Limit results
|
||||||
|
.skip(20) // Skip first 20 results
|
||||||
|
.sort({ createdAt: -1 }); // Sort by creation date descending
|
||||||
|
|
||||||
|
// Convert cursor to array (when you know the result set is small)
|
||||||
|
const userArray = await paginatedCursor.toArray();
|
||||||
|
|
||||||
|
// Other cursor operations
|
||||||
|
const nextUser = await cursor.next(); // Get the next document
|
||||||
|
const hasMoreUsers = await cursor.hasNext(); // Check if more documents exist
|
||||||
|
const count = await cursor.count(); // Get the count of documents in the cursor
|
||||||
|
|
||||||
|
// Always close cursors when done with them
|
||||||
|
await cursor.close();
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Update
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Assuming 'user' is an instance of User
|
||||||
|
user.email = 'newEmail@example.com';
|
||||||
|
await user.save(); // Update the user in the database
|
||||||
|
|
||||||
|
// Upsert operations (insert if not exists, update if exists)
|
||||||
|
const upsertedUser = await User.upsert(
|
||||||
|
{ id: 'user-123' }, // Query to find the user
|
||||||
|
{
|
||||||
|
// Fields to update or insert
|
||||||
|
username: 'newUsername',
|
||||||
|
email: 'newEmail@example.com',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Delete
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Assuming 'user' is an instance of User
|
||||||
|
await user.delete(); // Delete the user from the database
|
||||||
|
```
|
||||||
|
|
||||||
|
## Advanced Features
|
||||||
|
|
||||||
|
### Search Functionality
|
||||||
|
|
||||||
|
SmartData provides powerful search capabilities with a Lucene-like query syntax and robust fallback mechanisms:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Define a model with searchable fields
|
||||||
|
@Collection(() => db)
|
||||||
|
class Product extends SmartDataDbDoc<Product, Product> {
|
||||||
|
@unI()
|
||||||
|
public id: string = 'product-id';
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@searchable() // Mark this field as searchable
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@searchable() // Mark this field as searchable
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@searchable() // Mark this field as searchable
|
||||||
|
public category: string;
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public price: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all fields marked as searchable for a class
|
||||||
|
const searchableFields = getSearchableFields('Product'); // ['name', 'description', 'category']
|
||||||
|
|
||||||
|
// Basic search across all searchable fields
|
||||||
|
const iphoneProducts = await Product.search('iPhone');
|
||||||
|
|
||||||
|
// Field-specific exact match
|
||||||
|
const electronicsProducts = await Product.search('category:Electronics');
|
||||||
|
|
||||||
|
// Partial word search (regex across all fields)
|
||||||
|
const laptopResults = await Product.search('laptop');
|
||||||
|
|
||||||
|
// Multi-word literal search
|
||||||
|
const paperwhite = await Product.search('Kindle Paperwhite');
|
||||||
|
|
||||||
|
// Empty query returns all documents
|
||||||
|
const allProducts = await Product.search('');
|
||||||
|
```
|
||||||
|
|
||||||
|
The search functionality includes:
|
||||||
|
|
||||||
|
- `@searchable()` decorator for marking fields as searchable
|
||||||
|
- `getSearchableFields()` to list searchable fields for a model
|
||||||
|
- `search(query: string)` method supporting:
|
||||||
|
- Field-specific exact matches (`field:value`)
|
||||||
|
- Case-insensitive partial matches across all searchable fields
|
||||||
|
- Multi-word literal matching
|
||||||
|
- Empty queries returning all documents
|
||||||
|
- Automatic escaping of special characters to prevent regex injection
|
||||||
|
|
||||||
|
### EasyStore
|
||||||
|
|
||||||
|
EasyStore provides a simple key-value storage system with automatic persistence:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create an EasyStore instance with a specific type
|
||||||
|
interface ConfigStore {
|
||||||
|
apiKey: string;
|
||||||
|
settings: {
|
||||||
|
theme: string;
|
||||||
|
notifications: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a type-safe EasyStore
|
||||||
|
const store = await db.createEasyStore<ConfigStore>('app-config');
|
||||||
|
|
||||||
|
// Write and read data with full type safety
|
||||||
|
await store.writeKey('apiKey', 'secret-api-key-123');
|
||||||
|
await store.writeKey('settings', { theme: 'dark', notifications: true });
|
||||||
|
|
||||||
|
const apiKey = await store.readKey('apiKey'); // Type: string
|
||||||
|
const settings = await store.readKey('settings'); // Type: { theme: string, notifications: boolean }
|
||||||
|
|
||||||
|
// Check if a key exists
|
||||||
|
const hasKey = await store.hasKey('apiKey'); // true
|
||||||
|
|
||||||
|
// Delete a key
|
||||||
|
await store.deleteKey('apiKey');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Distributed Coordination
|
||||||
|
|
||||||
|
Built-in support for distributed systems with leader election:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create a distributed coordinator
|
||||||
|
const coordinator = new SmartdataDistributedCoordinator(db);
|
||||||
|
|
||||||
|
// Start coordination
|
||||||
|
await coordinator.start();
|
||||||
|
|
||||||
|
// Handle leadership changes
|
||||||
|
coordinator.on('leadershipChange', (isLeader) => {
|
||||||
|
if (isLeader) {
|
||||||
|
// This instance is now the leader
|
||||||
|
// Run leader-specific tasks
|
||||||
|
startPeriodicJobs();
|
||||||
|
} else {
|
||||||
|
// This instance is no longer the leader
|
||||||
|
stopPeriodicJobs();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Access leadership status anytime
|
||||||
|
if (coordinator.isLeader) {
|
||||||
|
// Run leader-only operations
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute a task only on the leader
|
||||||
|
await coordinator.executeIfLeader(async () => {
|
||||||
|
// This code only runs on the leader instance
|
||||||
|
await runImportantTask();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop coordination when shutting down
|
||||||
|
await coordinator.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Real-time Data Watching
|
||||||
|
|
||||||
|
Watch for changes in your collections with RxJS integration using MongoDB Change Streams:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Create a watcher for a specific collection with a query filter
|
||||||
|
const watcher = await User.watch(
|
||||||
|
{
|
||||||
|
active: true, // Only watch for changes to active users
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fullDocument: true, // Include the full document in change notifications
|
||||||
|
bufferTimeMs: 100, // Buffer changes for 100ms to reduce notification frequency
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Subscribe to changes using RxJS
|
||||||
|
watcher.changeSubject.subscribe((change) => {
|
||||||
|
console.log('Change operation:', change.operationType); // 'insert', 'update', 'delete', etc.
|
||||||
|
console.log('Document changed:', change.docInstance); // The full document instance
|
||||||
|
|
||||||
|
// Handle different types of changes
|
||||||
|
if (change.operationType === 'insert') {
|
||||||
|
console.log('New user created:', change.docInstance.username);
|
||||||
|
} else if (change.operationType === 'update') {
|
||||||
|
console.log('User updated:', change.docInstance.username);
|
||||||
|
} else if (change.operationType === 'delete') {
|
||||||
|
console.log('User deleted');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manual observation with event emitter pattern
|
||||||
|
watcher.on('change', (change) => {
|
||||||
|
console.log('Document changed:', change);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Stop watching when no longer needed
|
||||||
|
await watcher.stop();
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managed Collections
|
||||||
|
|
||||||
|
For more complex data models that require additional context:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Collection(() => db)
|
||||||
|
class ManagedDoc extends SmartDataDbDoc<ManagedDoc, ManagedDoc> {
|
||||||
|
@unI()
|
||||||
|
public id: string = 'unique-id';
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public data: string;
|
||||||
|
|
||||||
|
@managed()
|
||||||
|
public manager: YourCustomManager;
|
||||||
|
|
||||||
|
// The manager can provide additional functionality
|
||||||
|
async specialOperation() {
|
||||||
|
return this.manager.doSomethingSpecial(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Indexing
|
||||||
|
|
||||||
|
Define indexes directly in your model class:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Collection(() => db)
|
||||||
|
class Product extends SmartDataDbDoc<Product, Product> {
|
||||||
|
@unI() // Unique index
|
||||||
|
public id: string = 'product-id';
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@index() // Regular index for faster queries
|
||||||
|
public category: string;
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
@index({ sparse: true }) // Sparse index with options
|
||||||
|
public optionalField?: string;
|
||||||
|
|
||||||
|
// Compound indexes can be defined in the collection decorator
|
||||||
|
@Collection(() => db, {
|
||||||
|
indexMap: {
|
||||||
|
compoundIndex: {
|
||||||
|
fields: { category: 1, name: 1 },
|
||||||
|
options: { background: true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transaction Support
|
||||||
|
|
||||||
|
Use MongoDB transactions for atomic operations:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const session = await db.startSession();
|
||||||
|
try {
|
||||||
|
await session.withTransaction(async () => {
|
||||||
|
const user = await User.getInstance({ id: 'user-id' }, { session });
|
||||||
|
user.balance -= 100;
|
||||||
|
await user.save({ session });
|
||||||
|
|
||||||
|
const recipient = await User.getInstance({ id: 'recipient-id' }, { session });
|
||||||
|
recipient.balance += 100;
|
||||||
|
await user.save({ session });
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
await session.endSession();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deep Object Queries
|
||||||
|
|
||||||
|
SmartData provides fully type-safe deep property queries with the `DeepQuery` type:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// If your document has nested objects
|
||||||
|
class UserProfile extends SmartDataDbDoc<UserProfile, UserProfile> {
|
||||||
|
@unI()
|
||||||
|
public id: string = 'profile-id';
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public user: {
|
||||||
|
details: {
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
address: {
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Type-safe string literals for dot notation
|
||||||
|
const usersInUSA = await UserProfile.getInstances({
|
||||||
|
'user.details.address.country': 'USA',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fully typed deep queries with the DeepQuery type
|
||||||
|
import { DeepQuery } from '@push.rocks/smartdata';
|
||||||
|
|
||||||
|
const typedQuery: DeepQuery<UserProfile> = {
|
||||||
|
id: 'profile-id',
|
||||||
|
'user.details.firstName': 'John',
|
||||||
|
'user.details.address.country': 'USA',
|
||||||
|
};
|
||||||
|
|
||||||
|
// TypeScript will error if paths are incorrect
|
||||||
|
const results = await UserProfile.getInstances(typedQuery);
|
||||||
|
|
||||||
|
// MongoDB query operators are supported
|
||||||
|
const operatorQuery: DeepQuery<UserProfile> = {
|
||||||
|
'user.details.address.country': 'USA',
|
||||||
|
'user.details.address.city': { $in: ['New York', 'Los Angeles'] },
|
||||||
|
};
|
||||||
|
|
||||||
|
const filteredResults = await UserProfile.getInstances(operatorQuery);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Document Lifecycle Hooks
|
||||||
|
|
||||||
|
Implement custom logic at different stages of a document's lifecycle:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
@Collection(() => db)
|
||||||
|
class Order extends SmartDataDbDoc<Order, Order> {
|
||||||
|
@unI()
|
||||||
|
public id: string = 'order-id';
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public total: number;
|
||||||
|
|
||||||
|
@svDb()
|
||||||
|
public items: string[];
|
||||||
|
|
||||||
|
// Called before saving the document
|
||||||
|
async beforeSave() {
|
||||||
|
// Calculate total based on items
|
||||||
|
this.total = await calculateTotal(this.items);
|
||||||
|
|
||||||
|
// Validate the document
|
||||||
|
if (this.items.length === 0) {
|
||||||
|
throw new Error('Order must have at least one item');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called after the document is saved
|
||||||
|
async afterSave() {
|
||||||
|
// Notify other systems about the saved order
|
||||||
|
await notifyExternalSystems(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called before deleting the document
|
||||||
|
async beforeDelete() {
|
||||||
|
// Check if order can be deleted
|
||||||
|
const canDelete = await checkOrderDeletable(this.id);
|
||||||
|
if (!canDelete) {
|
||||||
|
throw new Error('Order cannot be deleted');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### Connection Management
|
||||||
|
|
||||||
|
- Always call `db.init()` before using any database features
|
||||||
|
- Use `db.close()` when shutting down your application
|
||||||
|
- Set appropriate connection pool sizes based on your application's needs
|
||||||
|
|
||||||
|
### Document Design
|
||||||
|
|
||||||
|
- Use appropriate decorators (`@svDb`, `@unI`, `@index`, `@searchable`) to optimize database operations
|
||||||
|
- Implement type-safe models by properly extending `SmartDataDbDoc`
|
||||||
|
- Consider using interfaces to define document structures separately from implementation
|
||||||
|
- Mark fields that need to be searched with the `@searchable()` decorator
|
||||||
|
|
||||||
|
### Search Optimization
|
||||||
|
|
||||||
|
- (Optional) Create MongoDB text indexes on searchable fields to speed up full-text search
|
||||||
|
- Use `search(query)` for all search operations (field:value, partial matches, multi-word)
|
||||||
|
- Prefer field-specific exact matches when possible for optimal performance
|
||||||
|
- Avoid unnecessary complexity in query strings to keep regex searches efficient
|
||||||
|
|
||||||
|
### Performance Optimization
|
||||||
|
|
||||||
|
- Use cursors for large datasets instead of loading all documents into memory
|
||||||
|
- Create appropriate indexes for frequent query patterns
|
||||||
|
- Use projections to limit the fields returned when you don't need the entire document
|
||||||
|
|
||||||
|
### Distributed Systems
|
||||||
|
|
||||||
|
- Implement proper error handling for leader election events
|
||||||
|
- Ensure all instances have synchronized clocks when using time-based coordination
|
||||||
|
- Use the distributed coordinator's task management features for coordinated operations
|
||||||
|
|
||||||
|
### Type Safety
|
||||||
|
|
||||||
|
- Take advantage of the `DeepQuery<T>` type for fully type-safe queries
|
||||||
|
- Define proper types for your document models to enhance IDE auto-completion
|
||||||
|
- Use generic type parameters to specify exact document types when working with collections
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
We welcome contributions to @push.rocks/smartdata! Here's how you can help:
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch (`git checkout -b feature/amazing-feature`)
|
||||||
|
3. Commit your changes (`git commit -m 'Add amazing feature'`)
|
||||||
|
4. Push to the branch (`git push origin feature/amazing-feature`)
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
Please make sure to update tests as appropriate and follow our coding standards.
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This repository is licensed under the MIT License. For details, see [MIT License](https://opensource.org/licenses/MIT).
|
||||||
|
|
||||||
|
**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.
|
122
test/test.distributedcoordinator.ts
Normal file
122
test/test.distributedcoordinator.ts
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
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/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/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();
|
233
test/test.search.ts
Normal file
233
test/test.search.ts
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import * as smartmongo from '@push.rocks/smartmongo';
|
||||||
|
import { smartunique } from '../ts/plugins.js';
|
||||||
|
|
||||||
|
// Import the smartdata library
|
||||||
|
import * as smartdata from '../ts/index.js';
|
||||||
|
import { searchable, getSearchableFields } from '../ts/classes.doc.js';
|
||||||
|
|
||||||
|
// Set up database connection
|
||||||
|
let smartmongoInstance: smartmongo.SmartMongo;
|
||||||
|
let testDb: smartdata.SmartdataDb;
|
||||||
|
|
||||||
|
// Define a test class with searchable fields using the standard SmartDataDbDoc
|
||||||
|
@smartdata.Collection(() => testDb)
|
||||||
|
class Product extends smartdata.SmartDataDbDoc<Product, Product> {
|
||||||
|
@smartdata.unI()
|
||||||
|
public id: string = smartunique.shortId();
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
@searchable()
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
@searchable()
|
||||||
|
public description: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
@searchable()
|
||||||
|
public category: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
public price: number;
|
||||||
|
|
||||||
|
constructor(nameArg: string, descriptionArg: string, categoryArg: string, priceArg: number) {
|
||||||
|
super();
|
||||||
|
this.name = nameArg;
|
||||||
|
this.description = descriptionArg;
|
||||||
|
this.category = categoryArg;
|
||||||
|
this.price = priceArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tap.test('should create a test database instance', async () => {
|
||||||
|
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
|
||||||
|
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
|
||||||
|
await testDb.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should create test products with searchable fields', async () => {
|
||||||
|
// Create several products with different fields to search
|
||||||
|
const products = [
|
||||||
|
new Product('iPhone 12', 'Latest iPhone with A14 Bionic chip', 'Electronics', 999),
|
||||||
|
new Product('MacBook Pro', 'Powerful laptop for professionals', 'Electronics', 1999),
|
||||||
|
new Product('AirPods', 'Wireless earbuds with noise cancellation', 'Electronics', 249),
|
||||||
|
new Product('Galaxy S21', 'Samsung flagship phone with great camera', 'Electronics', 899),
|
||||||
|
new Product('Kindle Paperwhite', 'E-reader with built-in light', 'Books', 129),
|
||||||
|
new Product('Harry Potter', 'Fantasy book series about wizards', 'Books', 49),
|
||||||
|
new Product('Coffee Maker', 'Automatic drip coffee machine', 'Kitchen', 89),
|
||||||
|
new Product('Blender', 'High-speed blender for smoothies', 'Kitchen', 129),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Save all products to the database
|
||||||
|
for (const product of products) {
|
||||||
|
await product.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify that we can get all products
|
||||||
|
const allProducts = await Product.getInstances({});
|
||||||
|
expect(allProducts.length).toEqual(products.length);
|
||||||
|
console.log(`Successfully created and saved ${allProducts.length} products`);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should retrieve searchable fields for a class', async () => {
|
||||||
|
// Use the getSearchableFields function to verify our searchable fields
|
||||||
|
const searchableFields = getSearchableFields('Product');
|
||||||
|
console.log('Searchable fields:', searchableFields);
|
||||||
|
|
||||||
|
expect(searchableFields.length).toEqual(3);
|
||||||
|
expect(searchableFields).toContain('name');
|
||||||
|
expect(searchableFields).toContain('description');
|
||||||
|
expect(searchableFields).toContain('category');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search products by exact field match', async () => {
|
||||||
|
// Basic field exact match search
|
||||||
|
const electronicsProducts = await Product.getInstances({ category: 'Electronics' });
|
||||||
|
console.log(`Found ${electronicsProducts.length} products in Electronics category`);
|
||||||
|
|
||||||
|
expect(electronicsProducts.length).toEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search products by basic search method', async () => {
|
||||||
|
// Using the basic search method with simple Lucene query
|
||||||
|
try {
|
||||||
|
const iPhoneResults = await Product.search('iPhone');
|
||||||
|
console.log(`Found ${iPhoneResults.length} products matching 'iPhone' using basic search`);
|
||||||
|
|
||||||
|
expect(iPhoneResults.length).toEqual(1);
|
||||||
|
expect(iPhoneResults[0].name).toEqual('iPhone 12');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Basic search error:', error.message);
|
||||||
|
// If basic search fails, we'll demonstrate the enhanced approach in later tests
|
||||||
|
console.log('Will test with enhanced searchWithLucene method next');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search products with search method', async () => {
|
||||||
|
// Using the robust searchWithLucene method
|
||||||
|
const wirelessResults = await Product.search('wireless');
|
||||||
|
console.log(
|
||||||
|
`Found ${wirelessResults.length} products matching 'wireless' using search`,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(wirelessResults.length).toEqual(1);
|
||||||
|
expect(wirelessResults[0].name).toEqual('AirPods');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search products by category with search', async () => {
|
||||||
|
// Using field-specific search with searchWithLucene
|
||||||
|
const kitchenResults = await Product.search('category:Kitchen');
|
||||||
|
console.log(`Found ${kitchenResults.length} products in Kitchen category using search`);
|
||||||
|
|
||||||
|
expect(kitchenResults.length).toEqual(2);
|
||||||
|
expect(kitchenResults[0].category).toEqual('Kitchen');
|
||||||
|
expect(kitchenResults[1].category).toEqual('Kitchen');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search products with partial word matches', async () => {
|
||||||
|
// Testing partial word matches
|
||||||
|
const proResults = await Product.search('Pro');
|
||||||
|
console.log(`Found ${proResults.length} products matching 'Pro'`);
|
||||||
|
|
||||||
|
// Should match both "MacBook Pro" and "professionals" in description
|
||||||
|
expect(proResults.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search across multiple searchable fields', async () => {
|
||||||
|
// Test searching across all searchable fields
|
||||||
|
const bookResults = await Product.search('book');
|
||||||
|
console.log(`Found ${bookResults.length} products matching 'book' across all fields`);
|
||||||
|
|
||||||
|
// Should match "MacBook" in name and "Books" in category
|
||||||
|
expect(bookResults.length).toBeGreaterThan(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should handle case insensitive searches', async () => {
|
||||||
|
// Test case insensitivity
|
||||||
|
const electronicsResults = await Product.search('electronics');
|
||||||
|
const ElectronicsResults = await Product.search('Electronics');
|
||||||
|
|
||||||
|
console.log(`Found ${electronicsResults.length} products matching lowercase 'electronics'`);
|
||||||
|
console.log(`Found ${ElectronicsResults.length} products matching capitalized 'Electronics'`);
|
||||||
|
|
||||||
|
// Both searches should return the same results
|
||||||
|
expect(electronicsResults.length).toEqual(ElectronicsResults.length);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should demonstrate search fallback mechanisms', async () => {
|
||||||
|
console.log('\n====== FALLBACK MECHANISM DEMONSTRATION ======');
|
||||||
|
console.log('If MongoDB query fails, searchWithLucene will:');
|
||||||
|
console.log('1. Try using basic MongoDB filters');
|
||||||
|
console.log('2. Fall back to field-specific searches');
|
||||||
|
console.log('3. As last resort, perform in-memory filtering');
|
||||||
|
console.log('This ensures robust search even with complex queries');
|
||||||
|
console.log('==============================================\n');
|
||||||
|
|
||||||
|
// Use a simpler term that should be found in descriptions
|
||||||
|
// Avoid using "OR" operator which requires a text index
|
||||||
|
const results = await Product.search('high');
|
||||||
|
console.log(`Found ${results.length} products matching 'high'`);
|
||||||
|
|
||||||
|
// "High-speed blender" contains "high"
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Try another fallback example that won't need $text
|
||||||
|
const powerfulResults = await Product.search('powerful');
|
||||||
|
console.log(`Found ${powerfulResults.length} products matching 'powerful'`);
|
||||||
|
|
||||||
|
// "Powerful laptop for professionals" contains "powerful"
|
||||||
|
expect(powerfulResults.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should explain the advantages of the integrated approach', async () => {
|
||||||
|
console.log('\n====== INTEGRATED SEARCH APPROACH BENEFITS ======');
|
||||||
|
console.log('1. No separate class hierarchy - keeps code simple');
|
||||||
|
console.log('2. Enhanced convertFilterForMongoDb handles MongoDB operators');
|
||||||
|
console.log('3. Robust fallback mechanisms ensure searches always work');
|
||||||
|
console.log('4. searchWithLucene provides powerful search capabilities');
|
||||||
|
console.log('5. Backwards compatible with existing code');
|
||||||
|
console.log('================================================\n');
|
||||||
|
|
||||||
|
expect(true).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Additional robustness tests
|
||||||
|
tap.test('should search exact name using field:value', async () => {
|
||||||
|
const nameResults = await Product.search('name:AirPods');
|
||||||
|
expect(nameResults.length).toEqual(1);
|
||||||
|
expect(nameResults[0].name).toEqual('AirPods');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should throw when searching non-searchable field', async () => {
|
||||||
|
let error: Error;
|
||||||
|
try {
|
||||||
|
await Product.search('price:129');
|
||||||
|
} catch (err) {
|
||||||
|
error = err as Error;
|
||||||
|
}
|
||||||
|
expect(error).toBeTruthy();
|
||||||
|
expect(error.message).toMatch(/not searchable/);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('empty query should return all products', async () => {
|
||||||
|
const allResults = await Product.search('');
|
||||||
|
expect(allResults.length).toEqual(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should search multi-word term across fields', async () => {
|
||||||
|
const termResults = await Product.search('iPhone 12');
|
||||||
|
expect(termResults.length).toEqual(1);
|
||||||
|
expect(termResults[0].name).toEqual('iPhone 12');
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('close database connection', async () => {
|
||||||
|
await testDb.mongoDb.dropDatabase();
|
||||||
|
await testDb.close();
|
||||||
|
if (smartmongoInstance) {
|
||||||
|
await smartmongoInstance.stopAndDumpToDir(`.nogit/dbdump/test.search.ts`);
|
||||||
|
}
|
||||||
|
setTimeout(() => process.exit(), 2000);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start({ throwOnError: true });
|
243
test/test.ts
243
test/test.ts
@ -1,30 +1,40 @@
|
|||||||
import { tap, expect } from 'tapbundle'
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import * as smartq from 'smartq'
|
import { Qenv } from '@push.rocks/qenv';
|
||||||
import { Qenv } from 'qenv'
|
import * as smartmongo from '@push.rocks/smartmongo';
|
||||||
|
import { smartunique } from '../ts/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
|
// the tested module
|
||||||
import * as smartdata from '../ts/index'
|
import * as smartdata from '../ts/index.js';
|
||||||
import { smartstring } from '../ts/smartdata.plugins';
|
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// Connecting to the database server
|
// Connecting to the database server
|
||||||
// =======================================
|
// =======================================
|
||||||
|
|
||||||
let testDb = new smartdata.Db({
|
let smartmongoInstance: smartmongo.SmartMongo;
|
||||||
db: process.env.RDB_DB,
|
let testDb: smartdata.SmartdataDb;
|
||||||
host: process.env.RDB_HOST,
|
|
||||||
user: process.env.RDB_USER,
|
|
||||||
password: process.env.RDB_PASS,
|
|
||||||
port: parseInt(process.env.RDB_PORT)
|
|
||||||
})
|
|
||||||
testDb.setSsl(process.env.RDB_CERT, 'base64')
|
|
||||||
|
|
||||||
tap.test('should establish a connection to the rethink Db cluster', async () => {
|
const totalCars = 2000;
|
||||||
|
|
||||||
await testDb.connect()
|
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
|
// The actual tests
|
||||||
@ -34,28 +44,193 @@ tap.test('should establish a connection to the rethink Db cluster', async () =>
|
|||||||
// Collections
|
// Collections
|
||||||
// ------
|
// ------
|
||||||
|
|
||||||
@smartdata.Collection(testDb)
|
@smartdata.Collection(() => {
|
||||||
class Car extends smartdata.DbDoc<Car> {
|
return testDb;
|
||||||
@smartdata.svDb() color: string
|
})
|
||||||
@smartdata.svDb() brand: string
|
class Car extends smartdata.SmartDataDbDoc<Car, Car> {
|
||||||
constructor (colorArg: string, brandArg: string) {
|
@smartdata.unI()
|
||||||
super()
|
public index: string = smartunique.shortId();
|
||||||
this.color = colorArg
|
|
||||||
this.brand = brandArg
|
@smartdata.svDb()
|
||||||
|
public color: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
public brand: string;
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
public testBuffer = Buffer.from('hello');
|
||||||
|
|
||||||
|
@smartdata.svDb()
|
||||||
|
deepData = {
|
||||||
|
sodeep: 'yes',
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(colorArg: string, brandArg: string) {
|
||||||
|
super();
|
||||||
|
this.color = colorArg;
|
||||||
|
this.brand = brandArg;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
tap.test('should save the car to the db', async () => {
|
tap.test('should create a new id', async () => {
|
||||||
const myCar = new Car('red','Volvo')
|
const newid = await Car.getNewId();
|
||||||
await myCar.save()
|
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('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;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// close the database connection
|
// close the database connection
|
||||||
// =======================================
|
// =======================================
|
||||||
tap.test('should close the database connection', async (tools) => {
|
tap.test('close', async () => {
|
||||||
await testDb.close()
|
if (smartmongoInstance) {
|
||||||
})
|
await smartmongoInstance.stopAndDumpToDir('./.nogit/dbdump/test.ts');
|
||||||
|
} else {
|
||||||
|
await testDb.mongoDb.dropDatabase();
|
||||||
|
await testDb.close();
|
||||||
|
}
|
||||||
|
setTimeout(() => process.exit(), 2000);
|
||||||
|
});
|
||||||
|
|
||||||
tap.start()
|
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/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/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 @push.rocks/commitinfo
|
||||||
|
*/
|
||||||
|
export const commitinfo = {
|
||||||
|
name: '@push.rocks/smartdata',
|
||||||
|
version: '5.9.2',
|
||||||
|
description: 'An advanced library for NoSQL data organization and manipulation using TypeScript with support for MongoDB, data validation, collections, and custom data types.'
|
||||||
|
}
|
335
ts/classes.collection.ts
Normal file
335
ts/classes.collection.ts
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { SmartdataDb } from './classes.db.js';
|
||||||
|
import { SmartdataDbCursor } from './classes.cursor.js';
|
||||||
|
import { SmartDataDbDoc, type IIndexOptions, getSearchableFields } from './classes.doc.js';
|
||||||
|
import { SmartdataDbWatcher } from './classes.watcher.js';
|
||||||
|
import { CollectionFactory } from './classes.collectionfactory.js';
|
||||||
|
|
||||||
|
export interface IFindOptions {
|
||||||
|
limit?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export interface IDocValidationFunc<T> {
|
||||||
|
(doc: T): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TDelayed<TDelayedArg> = () => TDelayedArg;
|
||||||
|
|
||||||
|
const collectionFactory = new CollectionFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a decorator that will tell the decorated class what dbTable to use
|
||||||
|
* @param dbArg
|
||||||
|
*/
|
||||||
|
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
||||||
|
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
|
||||||
|
const decoratedClass = class extends constructor {
|
||||||
|
public static className = constructor.name;
|
||||||
|
public static get collection() {
|
||||||
|
if (!(dbArg instanceof SmartdataDb)) {
|
||||||
|
dbArg = dbArg();
|
||||||
|
}
|
||||||
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
||||||
|
}
|
||||||
|
public get collection() {
|
||||||
|
if (!(dbArg instanceof SmartdataDb)) {
|
||||||
|
dbArg = dbArg();
|
||||||
|
}
|
||||||
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return decoratedClass;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IManager {
|
||||||
|
db: SmartdataDb;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const setDefaultManagerForDoc = <T,>(managerArg: IManager, dbDocArg: T): T => {
|
||||||
|
(dbDocArg as any).prototype.defaultManager = managerArg;
|
||||||
|
return dbDocArg;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a decorator that will tell the decorated class what dbTable to use
|
||||||
|
* @param dbArg
|
||||||
|
*/
|
||||||
|
export function managed<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
|
||||||
|
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
|
||||||
|
const decoratedClass = class extends constructor {
|
||||||
|
public static className = constructor.name;
|
||||||
|
public static get collection() {
|
||||||
|
let dbArg: SmartdataDb;
|
||||||
|
if (!managerArg) {
|
||||||
|
dbArg = this.prototype.defaultManager.db;
|
||||||
|
} else if (managerArg['db']) {
|
||||||
|
dbArg = (managerArg as TManager).db;
|
||||||
|
} else {
|
||||||
|
dbArg = (managerArg as TDelayed<TManager>)().db;
|
||||||
|
}
|
||||||
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
||||||
|
}
|
||||||
|
public get collection() {
|
||||||
|
let dbArg: SmartdataDb;
|
||||||
|
if (!managerArg) {
|
||||||
|
//console.log(this.defaultManager.db);
|
||||||
|
//process.exit(0)
|
||||||
|
dbArg = this.defaultManager.db;
|
||||||
|
} else if (managerArg['db']) {
|
||||||
|
dbArg = (managerArg as TManager).db;
|
||||||
|
} else {
|
||||||
|
dbArg = (managerArg as TDelayed<TManager>)().db;
|
||||||
|
}
|
||||||
|
return collectionFactory.getCollection(constructor.name, dbArg);
|
||||||
|
}
|
||||||
|
public static get manager() {
|
||||||
|
let manager: TManager;
|
||||||
|
if (!managerArg) {
|
||||||
|
manager = this.prototype.defaultManager;
|
||||||
|
} else if (managerArg['db']) {
|
||||||
|
manager = managerArg as TManager;
|
||||||
|
} else {
|
||||||
|
manager = (managerArg as TDelayed<TManager>)();
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
public get manager() {
|
||||||
|
let manager: TManager;
|
||||||
|
if (!managerArg) {
|
||||||
|
manager = this.defaultManager;
|
||||||
|
} else if (managerArg['db']) {
|
||||||
|
manager = managerArg as TManager;
|
||||||
|
} else {
|
||||||
|
manager = (managerArg as TDelayed<TManager>)();
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return decoratedClass;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dpecrecated use @managed instead
|
||||||
|
*/
|
||||||
|
export const Manager = managed;
|
||||||
|
|
||||||
|
export class SmartdataCollection<T> {
|
||||||
|
/**
|
||||||
|
* the collection that is used
|
||||||
|
*/
|
||||||
|
public mongoDbCollection: plugins.mongodb.Collection;
|
||||||
|
public objectValidation: IDocValidationFunc<T> = null;
|
||||||
|
public collectionName: string;
|
||||||
|
public smartdataDb: SmartdataDb;
|
||||||
|
public uniqueIndexes: string[] = [];
|
||||||
|
public regularIndexes: Array<{field: string, options: IIndexOptions}> = [];
|
||||||
|
// flag to ensure text index is created only once
|
||||||
|
private textIndexCreated: boolean = false;
|
||||||
|
|
||||||
|
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
|
||||||
|
// tell the collection where it belongs
|
||||||
|
this.collectionName = classNameArg;
|
||||||
|
this.smartdataDb = smartDataDbArg;
|
||||||
|
|
||||||
|
// tell the db class about it (important since Db uses different systems under the hood)
|
||||||
|
this.smartdataDb.addCollection(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
|
||||||
|
*/
|
||||||
|
public async init() {
|
||||||
|
if (!this.mongoDbCollection) {
|
||||||
|
// connect this instance to a MongoDB collection
|
||||||
|
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
|
||||||
|
const wantedCollection = availableMongoDbCollections.find((collection) => {
|
||||||
|
return collection.collectionName === this.collectionName;
|
||||||
|
});
|
||||||
|
if (!wantedCollection) {
|
||||||
|
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
|
||||||
|
console.log(`Successfully initiated Collection ${this.collectionName}`);
|
||||||
|
}
|
||||||
|
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
|
||||||
|
// Auto-create a compound text index on all searchable fields
|
||||||
|
const searchableFields = getSearchableFields(this.collectionName);
|
||||||
|
if (searchableFields.length > 0 && !this.textIndexCreated) {
|
||||||
|
// Build a compound text index spec
|
||||||
|
const indexSpec: Record<string, 'text'> = {};
|
||||||
|
searchableFields.forEach(f => { indexSpec[f] = 'text'; });
|
||||||
|
// Cast to any to satisfy TypeScript IndexSpecification typing
|
||||||
|
await this.mongoDbCollection.createIndex(indexSpec as any, { name: 'smartdata_text_index' });
|
||||||
|
this.textIndexCreated = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mark unique index
|
||||||
|
*/
|
||||||
|
public markUniqueIndexes(keyArrayArg: string[] = []) {
|
||||||
|
for (const key of keyArrayArg) {
|
||||||
|
if (!this.uniqueIndexes.includes(key)) {
|
||||||
|
this.mongoDbCollection.createIndex(key, {
|
||||||
|
unique: true,
|
||||||
|
});
|
||||||
|
// make sure we only call this once and not for every doc we create
|
||||||
|
this.uniqueIndexes.push(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates regular indexes for the collection
|
||||||
|
*/
|
||||||
|
public createRegularIndexes(indexesArg: Array<{field: string, options: IIndexOptions}> = []) {
|
||||||
|
for (const indexDef of indexesArg) {
|
||||||
|
// Check if we've already created this index
|
||||||
|
const indexKey = indexDef.field;
|
||||||
|
if (!this.regularIndexes.some(i => i.field === indexKey)) {
|
||||||
|
this.mongoDbCollection.createIndex(
|
||||||
|
{ [indexDef.field]: 1 }, // Simple single-field index
|
||||||
|
indexDef.options
|
||||||
|
);
|
||||||
|
// Track that we've created this index
|
||||||
|
this.regularIndexes.push(indexDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds a validation function that all newly inserted and updated objects have to pass
|
||||||
|
*/
|
||||||
|
public addDocValidation(funcArg: IDocValidationFunc<T>) {
|
||||||
|
this.objectValidation = funcArg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds an object in the DbCollection
|
||||||
|
*/
|
||||||
|
public async findOne(filterObject: any): Promise<any> {
|
||||||
|
await this.init();
|
||||||
|
const cursor = this.mongoDbCollection.find(filterObject);
|
||||||
|
const result = await cursor.next();
|
||||||
|
cursor.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCursor(
|
||||||
|
filterObjectArg: any,
|
||||||
|
dbDocArg: typeof SmartDataDbDoc,
|
||||||
|
): Promise<SmartdataDbCursor<any>> {
|
||||||
|
await this.init();
|
||||||
|
const cursor = this.mongoDbCollection.find(filterObjectArg);
|
||||||
|
return new SmartdataDbCursor(cursor, dbDocArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds an object in the DbCollection
|
||||||
|
*/
|
||||||
|
public async findAll(filterObject: any): Promise<any[]> {
|
||||||
|
await this.init();
|
||||||
|
const cursor = this.mongoDbCollection.find(filterObject);
|
||||||
|
const result = await cursor.toArray();
|
||||||
|
cursor.close();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* watches the collection while applying a filter
|
||||||
|
*/
|
||||||
|
public async watch(
|
||||||
|
filterObject: any,
|
||||||
|
smartdataDbDocArg: typeof SmartDataDbDoc,
|
||||||
|
): Promise<SmartdataDbWatcher> {
|
||||||
|
await this.init();
|
||||||
|
const changeStream = this.mongoDbCollection.watch(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
$match: filterObject,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
{
|
||||||
|
fullDocument: 'updateLookup',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
|
||||||
|
await smartdataWatcher.readyDeferred.promise;
|
||||||
|
return smartdataWatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create an object in the database
|
||||||
|
*/
|
||||||
|
public async insert(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
|
await this.init();
|
||||||
|
await this.checkDoc(dbDocArg);
|
||||||
|
this.markUniqueIndexes(dbDocArg.uniqueIndexes);
|
||||||
|
|
||||||
|
// Create regular indexes if available
|
||||||
|
if (dbDocArg.regularIndexes && dbDocArg.regularIndexes.length > 0) {
|
||||||
|
this.createRegularIndexes(dbDocArg.regularIndexes);
|
||||||
|
}
|
||||||
|
|
||||||
|
const saveableObject = await dbDocArg.createSavableObject();
|
||||||
|
const result = await this.mongoDbCollection.insertOne(saveableObject);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* inserts object into the DbCollection
|
||||||
|
*/
|
||||||
|
public async update(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
|
await this.init();
|
||||||
|
await this.checkDoc(dbDocArg);
|
||||||
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
||||||
|
const saveableObject = await dbDocArg.createSavableObject();
|
||||||
|
const updateableObject: any = {};
|
||||||
|
for (const key of Object.keys(saveableObject)) {
|
||||||
|
if (identifiableObject[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
updateableObject[key] = saveableObject[key];
|
||||||
|
}
|
||||||
|
const result = await this.mongoDbCollection.updateOne(
|
||||||
|
identifiableObject,
|
||||||
|
{ $set: updateableObject },
|
||||||
|
{ upsert: true },
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
|
await this.init();
|
||||||
|
await this.checkDoc(dbDocArg);
|
||||||
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
||||||
|
await this.mongoDbCollection.deleteOne(identifiableObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getCount(filterObject: any) {
|
||||||
|
await this.init();
|
||||||
|
return this.mongoDbCollection.countDocuments(filterObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks a Doc for constraints
|
||||||
|
* if this.objectValidation is not set it passes.
|
||||||
|
*/
|
||||||
|
private checkDoc(docArg: T): Promise<void> {
|
||||||
|
const done = plugins.smartpromise.defer<void>();
|
||||||
|
let validationResult = true;
|
||||||
|
if (this.objectValidation) {
|
||||||
|
validationResult = this.objectValidation(docArg);
|
||||||
|
}
|
||||||
|
if (validationResult) {
|
||||||
|
done.resolve();
|
||||||
|
} else {
|
||||||
|
done.reject('validation of object did not pass');
|
||||||
|
}
|
||||||
|
return done.promise;
|
||||||
|
}
|
||||||
|
}
|
19
ts/classes.collectionfactory.ts
Normal file
19
ts/classes.collectionfactory.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { SmartdataCollection } from './classes.collection.js';
|
||||||
|
import { SmartdataDb } from './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];
|
||||||
|
};
|
||||||
|
}
|
5
ts/classes.convenience.ts
Normal file
5
ts/classes.convenience.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export const getNewUniqueId = async (prefixArg?: string) => {
|
||||||
|
return plugins.smartunique.uni(prefixArg);
|
||||||
|
};
|
51
ts/classes.cursor.ts
Normal file
51
ts/classes.cursor.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { SmartDataDbDoc } from './classes.doc.js';
|
||||||
|
import * as plugins from './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): Promise<T> {
|
||||||
|
const result = this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(
|
||||||
|
await this.mongodbCursor.next(),
|
||||||
|
);
|
||||||
|
if (!result && closeAtEnd) {
|
||||||
|
await this.close();
|
||||||
|
}
|
||||||
|
return result as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 toArray(): Promise<T[]> {
|
||||||
|
const result = await this.mongodbCursor.toArray();
|
||||||
|
return result.map((itemArg) => this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(itemArg)) as T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
public async close() {
|
||||||
|
await this.mongodbCursor.close();
|
||||||
|
}
|
||||||
|
}
|
84
ts/classes.db.ts
Normal file
84
ts/classes.db.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
import { SmartdataCollection } from './classes.collection.js';
|
||||||
|
import { EasyStore } from './classes.easystore.js';
|
||||||
|
|
||||||
|
import { logger } from './logging.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* interface - indicates the connection status of the db
|
||||||
|
*/
|
||||||
|
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
|
||||||
|
|
||||||
|
export class SmartdataDb {
|
||||||
|
smartdataOptions: plugins.tsclass.database.IMongoDescriptor;
|
||||||
|
mongoDbClient: plugins.mongodb.MongoClient;
|
||||||
|
mongoDb: plugins.mongodb.Db;
|
||||||
|
status: TConnectionStatus;
|
||||||
|
statusConnectedDeferred = plugins.smartpromise.defer();
|
||||||
|
smartdataCollectionMap = new plugins.lik.ObjectMap<SmartdataCollection<any>>();
|
||||||
|
|
||||||
|
constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
|
||||||
|
this.smartdataOptions = smartdataOptions;
|
||||||
|
this.status = 'initial';
|
||||||
|
}
|
||||||
|
|
||||||
|
// easystore
|
||||||
|
public async createEasyStore(nameIdArg: string) {
|
||||||
|
const easyStore = new EasyStore(nameIdArg, this);
|
||||||
|
return easyStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
// basic connection stuff ----------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* connects to the database that was specified during instance creation
|
||||||
|
*/
|
||||||
|
public async init(): Promise<any> {
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
public async close(): Promise<any> {
|
||||||
|
await this.mongoDbClient.close();
|
||||||
|
this.status = 'disconnected';
|
||||||
|
logger.log('info', `disconnected from database ${this.smartdataOptions.mongoDbName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle table to class distribution
|
||||||
|
|
||||||
|
public addCollection(SmartdataCollectionArg: SmartdataCollection<any>) {
|
||||||
|
this.smartdataCollectionMap.add(SmartdataCollectionArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a collection's name and returns a SmartdataCollection instance
|
||||||
|
* @param nameArg
|
||||||
|
* @returns DbTable
|
||||||
|
*/
|
||||||
|
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
|
||||||
|
const resultCollection = await this.smartdataCollectionMap.find(async (dbTableArg) => {
|
||||||
|
return dbTableArg.collectionName === nameArg;
|
||||||
|
});
|
||||||
|
return resultCollection;
|
||||||
|
}
|
||||||
|
}
|
302
ts/classes.distributedcoordinator.ts
Normal file
302
ts/classes.distributedcoordinator.ts
Normal file
@ -0,0 +1,302 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { SmartdataDb } from './classes.db.js';
|
||||||
|
import { managed, setDefaultManagerForDoc } from './classes.collection.js';
|
||||||
|
import { SmartDataDbDoc, svDb, unI } from './classes.doc.js';
|
||||||
|
import { SmartdataDbWatcher } from './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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
550
ts/classes.doc.ts
Normal file
550
ts/classes.doc.ts
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
import { SmartdataDb } from './classes.db.js';
|
||||||
|
import { SmartdataDbCursor } from './classes.cursor.js';
|
||||||
|
import { type IManager, SmartdataCollection } from './classes.collection.js';
|
||||||
|
import { SmartdataDbWatcher } from './classes.watcher.js';
|
||||||
|
import { SmartdataLuceneAdapter } from './classes.lucene.adapter.js';
|
||||||
|
|
||||||
|
export type TDocCreation = 'db' | 'new' | 'mixed';
|
||||||
|
|
||||||
|
// Set of searchable fields for each class
|
||||||
|
const searchableFieldsMap = new Map<string, Set<string>>();
|
||||||
|
|
||||||
|
export function globalSvDb() {
|
||||||
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
|
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
||||||
|
if (!target.globalSaveableProperties) {
|
||||||
|
target.globalSaveableProperties = [];
|
||||||
|
}
|
||||||
|
target.globalSaveableProperties.push(key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* saveable - saveable decorator to be used on class properties
|
||||||
|
*/
|
||||||
|
export function svDb() {
|
||||||
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
|
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
||||||
|
if (!target.saveableProperties) {
|
||||||
|
target.saveableProperties = [];
|
||||||
|
}
|
||||||
|
target.saveableProperties.push(key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* searchable - marks a property as searchable with Lucene query syntax
|
||||||
|
*/
|
||||||
|
export function searchable() {
|
||||||
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
|
console.log(`called searchable() on >${target.constructor.name}.${key}<`);
|
||||||
|
|
||||||
|
// Initialize the set for this class if it doesn't exist
|
||||||
|
const className = target.constructor.name;
|
||||||
|
if (!searchableFieldsMap.has(className)) {
|
||||||
|
searchableFieldsMap.set(className, new Set<string>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the property to the searchable fields set
|
||||||
|
searchableFieldsMap.get(className).add(key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get searchable fields for a class
|
||||||
|
*/
|
||||||
|
export function getSearchableFields(className: string): string[] {
|
||||||
|
if (!searchableFieldsMap.has(className)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return Array.from(searchableFieldsMap.get(className));
|
||||||
|
}
|
||||||
|
// Escape user input for safe use in MongoDB regular expressions
|
||||||
|
function escapeForRegex(input: string): string {
|
||||||
|
return input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* unique index - decorator to mark a unique index
|
||||||
|
*/
|
||||||
|
export function unI() {
|
||||||
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
|
console.log(`called unI on >>${target.constructor.name}.${key}<<`);
|
||||||
|
|
||||||
|
// mark the index as unique
|
||||||
|
if (!target.uniqueIndexes) {
|
||||||
|
target.uniqueIndexes = [];
|
||||||
|
}
|
||||||
|
target.uniqueIndexes.push(key);
|
||||||
|
|
||||||
|
// and also save it
|
||||||
|
if (!target.saveableProperties) {
|
||||||
|
target.saveableProperties = [];
|
||||||
|
}
|
||||||
|
target.saveableProperties.push(key);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for MongoDB indexes
|
||||||
|
*/
|
||||||
|
export interface IIndexOptions {
|
||||||
|
background?: boolean;
|
||||||
|
unique?: boolean;
|
||||||
|
sparse?: boolean;
|
||||||
|
expireAfterSeconds?: number;
|
||||||
|
[key: string]: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* index - decorator to mark a field for regular indexing
|
||||||
|
*/
|
||||||
|
export function index(options?: IIndexOptions) {
|
||||||
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
|
console.log(`called index() on >${target.constructor.name}.${key}<`);
|
||||||
|
|
||||||
|
// Initialize regular indexes array if it doesn't exist
|
||||||
|
if (!target.regularIndexes) {
|
||||||
|
target.regularIndexes = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add this field to regularIndexes with its options
|
||||||
|
target.regularIndexes.push({
|
||||||
|
field: key,
|
||||||
|
options: options || {}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also ensure it's marked as saveable
|
||||||
|
if (!target.saveableProperties) {
|
||||||
|
target.saveableProperties = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!target.saveableProperties.includes(key)) {
|
||||||
|
target.saveableProperties.push(key);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => {
|
||||||
|
// Special case: detect MongoDB operators and pass them through directly
|
||||||
|
const topLevelOperators = ['$and', '$or', '$nor', '$not', '$text', '$where', '$regex'];
|
||||||
|
for (const key of Object.keys(filterArg)) {
|
||||||
|
if (topLevelOperators.includes(key)) {
|
||||||
|
return filterArg; // Return the filter as-is for MongoDB operators
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Original conversion logic for non-MongoDB query objects
|
||||||
|
const convertedFilter: { [key: string]: any } = {};
|
||||||
|
|
||||||
|
const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => {
|
||||||
|
if (Array.isArray(filterArg2)) {
|
||||||
|
// Directly assign arrays (they might be using operators like $in or $all)
|
||||||
|
convertFilterArgument(keyPathArg2, filterArg2[0]);
|
||||||
|
} else if (typeof filterArg2 === 'object' && filterArg2 !== null) {
|
||||||
|
for (const key of Object.keys(filterArg2)) {
|
||||||
|
if (key.startsWith('$')) {
|
||||||
|
convertedFilter[keyPathArg2] = filterArg2;
|
||||||
|
return;
|
||||||
|
} else if (key.includes('.')) {
|
||||||
|
throw new Error('keys cannot contain dots');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(filterArg2)) {
|
||||||
|
convertFilterArgument(`${keyPathArg2}.${key}`, filterArg2[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
convertedFilter[keyPathArg2] = filterArg2;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const key of Object.keys(filterArg)) {
|
||||||
|
convertFilterArgument(key, filterArg[key]);
|
||||||
|
}
|
||||||
|
return convertedFilter;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends IManager = any> {
|
||||||
|
/**
|
||||||
|
* the collection object an Doc belongs to
|
||||||
|
*/
|
||||||
|
public static collection: SmartdataCollection<any>;
|
||||||
|
public collection: SmartdataCollection<any>;
|
||||||
|
public static defaultManager;
|
||||||
|
public static manager;
|
||||||
|
public manager: TManager;
|
||||||
|
|
||||||
|
// STATIC
|
||||||
|
public static createInstanceFromMongoDbNativeDoc<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
mongoDbNativeDocArg: any,
|
||||||
|
): T {
|
||||||
|
const newInstance = new this();
|
||||||
|
(newInstance as any).creationStatus = 'db';
|
||||||
|
for (const key of Object.keys(mongoDbNativeDocArg)) {
|
||||||
|
newInstance[key] = mongoDbNativeDocArg[key];
|
||||||
|
}
|
||||||
|
return newInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets all instances as array
|
||||||
|
* @param this
|
||||||
|
* @param filterArg
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static async getInstances<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
||||||
|
): Promise<T[]> {
|
||||||
|
const foundDocs = await (this as any).collection.findAll(convertFilterForMongoDb(filterArg));
|
||||||
|
const returnArray = [];
|
||||||
|
for (const foundDoc of foundDocs) {
|
||||||
|
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
|
||||||
|
returnArray.push(newInstance);
|
||||||
|
}
|
||||||
|
return returnArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets the first matching instance
|
||||||
|
* @param this
|
||||||
|
* @param filterArg
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static async getInstance<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
||||||
|
): Promise<T> {
|
||||||
|
const foundDoc = await (this as any).collection.findOne(convertFilterForMongoDb(filterArg));
|
||||||
|
if (foundDoc) {
|
||||||
|
const newInstance: T = (this as any).createInstanceFromMongoDbNativeDoc(foundDoc);
|
||||||
|
return newInstance;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get a unique id prefixed with the class name
|
||||||
|
*/
|
||||||
|
public static async getNewId<T = any>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
lengthArg: number = 20,
|
||||||
|
) {
|
||||||
|
return `${(this as any).className}:${plugins.smartunique.shortId(lengthArg)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get cursor
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
public static async getCursor<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
||||||
|
) {
|
||||||
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
||||||
|
const cursor: SmartdataDbCursor<T> = await collection.getCursor(
|
||||||
|
convertFilterForMongoDb(filterArg),
|
||||||
|
this as any as typeof SmartDataDbDoc,
|
||||||
|
);
|
||||||
|
return cursor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async getCursorExtended<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
|
||||||
|
modifierFunction = (cursorArg: plugins.mongodb.FindCursor<plugins.mongodb.WithId<plugins.mongodb.BSON.Document>>) => cursorArg,
|
||||||
|
): Promise<SmartdataDbCursor<T>> {
|
||||||
|
const collection: SmartdataCollection<T> = (this as any).collection;
|
||||||
|
await collection.init();
|
||||||
|
let cursor: plugins.mongodb.FindCursor<any> = collection.mongoDbCollection.find(
|
||||||
|
convertFilterForMongoDb(filterArg),
|
||||||
|
);
|
||||||
|
cursor = modifierFunction(cursor);
|
||||||
|
return new SmartdataDbCursor<T>(cursor, this as any as typeof SmartDataDbDoc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a MongoDB filter from a Lucene query string
|
||||||
|
* @param luceneQuery Lucene query string
|
||||||
|
* @returns MongoDB query object
|
||||||
|
*/
|
||||||
|
public static createSearchFilter<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
luceneQuery: string,
|
||||||
|
): any {
|
||||||
|
const className = (this as any).className || this.name;
|
||||||
|
const searchableFields = getSearchableFields(className);
|
||||||
|
|
||||||
|
if (searchableFields.length === 0) {
|
||||||
|
throw new Error(`No searchable fields defined for class ${className}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const adapter = new SmartdataLuceneAdapter(searchableFields);
|
||||||
|
return adapter.convert(luceneQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search documents by text or field:value syntax, with safe regex fallback
|
||||||
|
* @param query A search term or field:value expression
|
||||||
|
* @returns Array of matching documents
|
||||||
|
*/
|
||||||
|
public static async search<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
query: string,
|
||||||
|
): Promise<T[]> {
|
||||||
|
const className = (this as any).className || this.name;
|
||||||
|
const searchableFields = getSearchableFields(className);
|
||||||
|
if (searchableFields.length === 0) {
|
||||||
|
throw new Error(`No searchable fields defined for class ${className}`);
|
||||||
|
}
|
||||||
|
// field:value exact match (case-sensitive for non-regex fields)
|
||||||
|
const fv = query.match(/^(\w+):(.+)$/);
|
||||||
|
if (fv) {
|
||||||
|
const field = fv[1];
|
||||||
|
const value = fv[2];
|
||||||
|
if (!searchableFields.includes(field)) {
|
||||||
|
throw new Error(`Field '${field}' is not searchable for class ${className}`);
|
||||||
|
}
|
||||||
|
return await (this as any).getInstances({ [field]: value });
|
||||||
|
}
|
||||||
|
// safe regex across all searchable fields (case-insensitive)
|
||||||
|
const escaped = escapeForRegex(query);
|
||||||
|
const orConditions = searchableFields.map((field) => ({
|
||||||
|
[field]: { $regex: escaped, $options: 'i' },
|
||||||
|
}));
|
||||||
|
return await (this as any).getInstances({ $or: orConditions });
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search by text across all searchable fields (fallback method)
|
||||||
|
* @param searchText The text to search for in all searchable fields
|
||||||
|
* @returns Array of matching documents
|
||||||
|
*/
|
||||||
|
private static async searchByTextAcrossFields<T>(
|
||||||
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
searchText: string,
|
||||||
|
): Promise<T[]> {
|
||||||
|
try {
|
||||||
|
const className = (this as any).className || this.name;
|
||||||
|
const searchableFields = getSearchableFields(className);
|
||||||
|
|
||||||
|
// Fallback to direct filter if we have searchable fields
|
||||||
|
if (searchableFields.length > 0) {
|
||||||
|
// Create a simple $or query with regex for each field
|
||||||
|
const orConditions = searchableFields.map((field) => ({
|
||||||
|
[field]: { $regex: searchText, $options: 'i' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const filter = { $or: orConditions };
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try with MongoDB filter first
|
||||||
|
return await (this as any).getInstances(filter);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('MongoDB filter failed, falling back to in-memory search');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last resort: get all and filter in memory
|
||||||
|
const allDocs = await (this as any).getInstances({});
|
||||||
|
const lowerSearchText = searchText.toLowerCase();
|
||||||
|
|
||||||
|
return allDocs.filter((doc: any) => {
|
||||||
|
for (const field of searchableFields) {
|
||||||
|
const value = doc[field];
|
||||||
|
if (value && typeof value === 'string' && value.toLowerCase().includes(lowerSearchText)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in searchByTextAcrossFields: ${error.message}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* regular indexes with their options
|
||||||
|
*/
|
||||||
|
public regularIndexes: Array<{field: string, options: IIndexOptions}> = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an array of saveable properties of a specific doc
|
||||||
|
*/
|
||||||
|
public saveableProperties: string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* name
|
||||||
|
*/
|
||||||
|
public name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* primary id in the database
|
||||||
|
*/
|
||||||
|
public dbDocUniqueId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class constructor
|
||||||
|
*/
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* saves this instance but not any connected items
|
||||||
|
* may lead to data inconsistencies, but is faster
|
||||||
|
*/
|
||||||
|
public async save() {
|
||||||
|
// tslint:disable-next-line: no-this-assignment
|
||||||
|
const self: any = this;
|
||||||
|
let dbResult: any;
|
||||||
|
|
||||||
|
this._updatedAt = new Date().toISOString();
|
||||||
|
|
||||||
|
switch (this.creationStatus) {
|
||||||
|
case 'db':
|
||||||
|
dbResult = await this.collection.update(self);
|
||||||
|
break;
|
||||||
|
case 'new':
|
||||||
|
dbResult = await this.collection.insert(self);
|
||||||
|
this.creationStatus = 'db';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.error('neither new nor in db?');
|
||||||
|
}
|
||||||
|
return dbResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* deletes a document from the database
|
||||||
|
*/
|
||||||
|
public async delete() {
|
||||||
|
await this.collection.delete(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* also store any referenced objects to DB
|
||||||
|
* better for data consistency
|
||||||
|
*/
|
||||||
|
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
|
||||||
|
if (!savedMapArg) {
|
||||||
|
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
|
||||||
|
}
|
||||||
|
savedMapArg.add(this);
|
||||||
|
this.save();
|
||||||
|
for (const propertyKey of Object.keys(this)) {
|
||||||
|
const property: any = this[propertyKey];
|
||||||
|
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
|
||||||
|
property.saveDeep(savedMapArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* updates an object from db
|
||||||
|
*/
|
||||||
|
public async updateFromDb() {
|
||||||
|
const mongoDbNativeDoc = await this.collection.findOne(await this.createIdentifiableObject());
|
||||||
|
for (const key of Object.keys(mongoDbNativeDoc)) {
|
||||||
|
this[key] = mongoDbNativeDoc[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates a saveable object so the instance can be persisted as json in the database
|
||||||
|
*/
|
||||||
|
public async createSavableObject(): Promise<TImplements> {
|
||||||
|
const saveableObject: unknown = {}; // is not exposed to outside, so any is ok here
|
||||||
|
const saveableProperties = [...this.globalSaveableProperties, ...this.saveableProperties];
|
||||||
|
for (const propertyNameString of saveableProperties) {
|
||||||
|
saveableObject[propertyNameString] = this[propertyNameString];
|
||||||
|
}
|
||||||
|
return saveableObject as TImplements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates an identifiable object for operations that require filtering
|
||||||
|
*/
|
||||||
|
public async createIdentifiableObject() {
|
||||||
|
const identifiableObject: any = {}; // is not exposed to outside, so any is ok here
|
||||||
|
for (const propertyNameString of this.uniqueIndexes) {
|
||||||
|
identifiableObject[propertyNameString] = this[propertyNameString];
|
||||||
|
}
|
||||||
|
return identifiableObject;
|
||||||
|
}
|
||||||
|
}
|
119
ts/classes.easystore.ts
Normal file
119
ts/classes.easystore.ts
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
import { Collection } from './classes.collection.js';
|
||||||
|
import { SmartdataDb } from './classes.db.js';
|
||||||
|
import { SmartDataDbDoc, svDb, unI } from './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'
|
||||||
|
) {}
|
||||||
|
}
|
||||||
|
}
|
760
ts/classes.lucene.adapter.ts
Normal file
760
ts/classes.lucene.adapter.ts
Normal file
@ -0,0 +1,760 @@
|
|||||||
|
/**
|
||||||
|
* Lucene to MongoDB query adapter for SmartData
|
||||||
|
*/
|
||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
// Types
|
||||||
|
type NodeType =
|
||||||
|
| 'TERM'
|
||||||
|
| 'PHRASE'
|
||||||
|
| 'FIELD'
|
||||||
|
| 'AND'
|
||||||
|
| 'OR'
|
||||||
|
| 'NOT'
|
||||||
|
| 'RANGE'
|
||||||
|
| 'WILDCARD'
|
||||||
|
| 'FUZZY'
|
||||||
|
| 'GROUP';
|
||||||
|
|
||||||
|
interface QueryNode {
|
||||||
|
type: NodeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TermNode extends QueryNode {
|
||||||
|
type: 'TERM';
|
||||||
|
value: string;
|
||||||
|
boost?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface PhraseNode extends QueryNode {
|
||||||
|
type: 'PHRASE';
|
||||||
|
value: string;
|
||||||
|
proximity?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FieldNode extends QueryNode {
|
||||||
|
type: 'FIELD';
|
||||||
|
field: string;
|
||||||
|
value: AnyQueryNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BooleanNode extends QueryNode {
|
||||||
|
type: 'AND' | 'OR' | 'NOT';
|
||||||
|
left: AnyQueryNode;
|
||||||
|
right: AnyQueryNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RangeNode extends QueryNode {
|
||||||
|
type: 'RANGE';
|
||||||
|
field: string;
|
||||||
|
lower: string;
|
||||||
|
upper: string;
|
||||||
|
includeLower: boolean;
|
||||||
|
includeUpper: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WildcardNode extends QueryNode {
|
||||||
|
type: 'WILDCARD';
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FuzzyNode extends QueryNode {
|
||||||
|
type: 'FUZZY';
|
||||||
|
value: string;
|
||||||
|
maxEdits: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GroupNode extends QueryNode {
|
||||||
|
type: 'GROUP';
|
||||||
|
value: AnyQueryNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AnyQueryNode =
|
||||||
|
| TermNode
|
||||||
|
| PhraseNode
|
||||||
|
| FieldNode
|
||||||
|
| BooleanNode
|
||||||
|
| RangeNode
|
||||||
|
| WildcardNode
|
||||||
|
| FuzzyNode
|
||||||
|
| GroupNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lucene query parser
|
||||||
|
*/
|
||||||
|
export class LuceneParser {
|
||||||
|
private pos: number = 0;
|
||||||
|
private input: string = '';
|
||||||
|
private tokens: string[] = [];
|
||||||
|
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a Lucene query string into an AST
|
||||||
|
*/
|
||||||
|
parse(query: string): AnyQueryNode {
|
||||||
|
this.input = query.trim();
|
||||||
|
this.pos = 0;
|
||||||
|
this.tokens = this.tokenize(this.input);
|
||||||
|
|
||||||
|
return this.parseQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tokenize the input string into tokens
|
||||||
|
*/
|
||||||
|
private tokenize(input: string): string[] {
|
||||||
|
const specialChars = /[()\[\]{}"~^:]/;
|
||||||
|
const operators = /AND|OR|NOT|TO/;
|
||||||
|
|
||||||
|
let tokens: string[] = [];
|
||||||
|
let current = '';
|
||||||
|
let inQuote = false;
|
||||||
|
|
||||||
|
for (let i = 0; i < input.length; i++) {
|
||||||
|
const char = input[i];
|
||||||
|
|
||||||
|
// Handle quoted strings
|
||||||
|
if (char === '"') {
|
||||||
|
if (inQuote) {
|
||||||
|
tokens.push(current + char);
|
||||||
|
current = '';
|
||||||
|
inQuote = false;
|
||||||
|
} else {
|
||||||
|
if (current) tokens.push(current);
|
||||||
|
current = char;
|
||||||
|
inQuote = true;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inQuote) {
|
||||||
|
current += char;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle whitespace
|
||||||
|
if (char === ' ' || char === '\t' || char === '\n') {
|
||||||
|
if (current) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special characters
|
||||||
|
if (specialChars.test(char)) {
|
||||||
|
if (current) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
tokens.push(char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
current += char;
|
||||||
|
|
||||||
|
// Check if current is an operator
|
||||||
|
if (operators.test(current) && (i + 1 === input.length || /\s/.test(input[i + 1]))) {
|
||||||
|
tokens.push(current);
|
||||||
|
current = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current) tokens.push(current);
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse the main query expression
|
||||||
|
*/
|
||||||
|
private parseQuery(): AnyQueryNode {
|
||||||
|
const left = this.parseBooleanOperand();
|
||||||
|
|
||||||
|
if (this.pos < this.tokens.length) {
|
||||||
|
const token = this.tokens[this.pos];
|
||||||
|
|
||||||
|
if (token === 'AND' || token === 'OR') {
|
||||||
|
this.pos++;
|
||||||
|
const right = this.parseQuery();
|
||||||
|
return {
|
||||||
|
type: token as 'AND' | 'OR',
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
} else if (token === 'NOT' || token === '-') {
|
||||||
|
this.pos++;
|
||||||
|
const right = this.parseQuery();
|
||||||
|
return {
|
||||||
|
type: 'NOT',
|
||||||
|
left,
|
||||||
|
right,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse boolean operands (terms, phrases, fields, groups)
|
||||||
|
*/
|
||||||
|
private parseBooleanOperand(): AnyQueryNode {
|
||||||
|
if (this.pos >= this.tokens.length) {
|
||||||
|
throw new Error('Unexpected end of input');
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this.tokens[this.pos];
|
||||||
|
|
||||||
|
// Handle grouping with parentheses
|
||||||
|
if (token === '(') {
|
||||||
|
this.pos++;
|
||||||
|
const group = this.parseQuery();
|
||||||
|
|
||||||
|
if (this.pos < this.tokens.length && this.tokens[this.pos] === ')') {
|
||||||
|
this.pos++;
|
||||||
|
return { type: 'GROUP', value: group } as GroupNode;
|
||||||
|
} else {
|
||||||
|
throw new Error('Unclosed group');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fields (field:value)
|
||||||
|
if (this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1] === ':') {
|
||||||
|
const field = token;
|
||||||
|
this.pos += 2; // Skip field and colon
|
||||||
|
|
||||||
|
if (this.pos < this.tokens.length) {
|
||||||
|
const value = this.parseBooleanOperand();
|
||||||
|
return { type: 'FIELD', field, value } as FieldNode;
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected value after field');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle range queries
|
||||||
|
if (token === '[' || token === '{') {
|
||||||
|
return this.parseRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle phrases ("term term")
|
||||||
|
if (token.startsWith('"') && token.endsWith('"')) {
|
||||||
|
const phrase = token.slice(1, -1);
|
||||||
|
this.pos++;
|
||||||
|
|
||||||
|
// Check for proximity operator
|
||||||
|
let proximity: number | undefined;
|
||||||
|
if (this.pos < this.tokens.length && this.tokens[this.pos] === '~') {
|
||||||
|
this.pos++;
|
||||||
|
if (this.pos < this.tokens.length && /^\d+$/.test(this.tokens[this.pos])) {
|
||||||
|
proximity = parseInt(this.tokens[this.pos], 10);
|
||||||
|
this.pos++;
|
||||||
|
} else {
|
||||||
|
throw new Error('Expected number after proximity operator');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'PHRASE', value: phrase, proximity } as PhraseNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle wildcards
|
||||||
|
if (token.includes('*') || token.includes('?')) {
|
||||||
|
this.pos++;
|
||||||
|
return { type: 'WILDCARD', value: token } as WildcardNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fuzzy searches
|
||||||
|
if (this.pos + 1 < this.tokens.length && this.tokens[this.pos + 1] === '~') {
|
||||||
|
const term = token;
|
||||||
|
this.pos += 2; // Skip term and tilde
|
||||||
|
|
||||||
|
let maxEdits = 2; // Default
|
||||||
|
if (this.pos < this.tokens.length && /^\d+$/.test(this.tokens[this.pos])) {
|
||||||
|
maxEdits = parseInt(this.tokens[this.pos], 10);
|
||||||
|
this.pos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { type: 'FUZZY', value: term, maxEdits } as FuzzyNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple term
|
||||||
|
this.pos++;
|
||||||
|
return { type: 'TERM', value: token } as TermNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse range queries
|
||||||
|
*/
|
||||||
|
private parseRange(): RangeNode {
|
||||||
|
const includeLower = this.tokens[this.pos] === '[';
|
||||||
|
const includeUpper = this.tokens[this.pos + 4] === ']';
|
||||||
|
|
||||||
|
this.pos++; // Skip open bracket
|
||||||
|
|
||||||
|
if (this.pos + 4 >= this.tokens.length) {
|
||||||
|
throw new Error('Invalid range query syntax');
|
||||||
|
}
|
||||||
|
|
||||||
|
const lower = this.tokens[this.pos];
|
||||||
|
this.pos++;
|
||||||
|
|
||||||
|
if (this.tokens[this.pos] !== 'TO') {
|
||||||
|
throw new Error('Expected TO in range query');
|
||||||
|
}
|
||||||
|
this.pos++;
|
||||||
|
|
||||||
|
const upper = this.tokens[this.pos];
|
||||||
|
this.pos++;
|
||||||
|
|
||||||
|
if (this.tokens[this.pos] !== (includeLower ? ']' : '}')) {
|
||||||
|
throw new Error('Invalid range query closing bracket');
|
||||||
|
}
|
||||||
|
this.pos++;
|
||||||
|
|
||||||
|
// For simplicity, assuming the field is handled separately
|
||||||
|
return {
|
||||||
|
type: 'RANGE',
|
||||||
|
field: '', // This will be filled by the field node
|
||||||
|
lower,
|
||||||
|
upper,
|
||||||
|
includeLower,
|
||||||
|
includeUpper,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transformer for Lucene AST to MongoDB query
|
||||||
|
* FIXED VERSION - proper MongoDB query structure
|
||||||
|
*/
|
||||||
|
export class LuceneToMongoTransformer {
|
||||||
|
constructor() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a Lucene AST node to a MongoDB query
|
||||||
|
*/
|
||||||
|
transform(node: AnyQueryNode, searchFields?: string[]): any {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'TERM':
|
||||||
|
return this.transformTerm(node, searchFields);
|
||||||
|
case 'PHRASE':
|
||||||
|
return this.transformPhrase(node, searchFields);
|
||||||
|
case 'FIELD':
|
||||||
|
return this.transformField(node);
|
||||||
|
case 'AND':
|
||||||
|
return this.transformAnd(node);
|
||||||
|
case 'OR':
|
||||||
|
return this.transformOr(node);
|
||||||
|
case 'NOT':
|
||||||
|
return this.transformNot(node);
|
||||||
|
case 'RANGE':
|
||||||
|
return this.transformRange(node);
|
||||||
|
case 'WILDCARD':
|
||||||
|
return this.transformWildcard(node, searchFields);
|
||||||
|
case 'FUZZY':
|
||||||
|
return this.transformFuzzy(node, searchFields);
|
||||||
|
case 'GROUP':
|
||||||
|
return this.transform(node.value, searchFields);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported node type: ${(node as any).type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a term to MongoDB query
|
||||||
|
* FIXED: properly structured $or query for multiple fields
|
||||||
|
*/
|
||||||
|
private transformTerm(node: TermNode, searchFields?: string[]): any {
|
||||||
|
// If specific fields are provided, search across those fields
|
||||||
|
if (searchFields && searchFields.length > 0) {
|
||||||
|
// Create an $or query to search across multiple fields
|
||||||
|
const orConditions = searchFields.map((field) => ({
|
||||||
|
[field]: { $regex: node.value, $options: 'i' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { $or: orConditions };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, use text search (requires a text index on desired fields)
|
||||||
|
return { $text: { $search: node.value } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a phrase to MongoDB query
|
||||||
|
* FIXED: properly structured $or query for multiple fields
|
||||||
|
*/
|
||||||
|
private transformPhrase(node: PhraseNode, searchFields?: string[]): any {
|
||||||
|
// If specific fields are provided, search phrase across those fields
|
||||||
|
if (searchFields && searchFields.length > 0) {
|
||||||
|
const orConditions = searchFields.map((field) => ({
|
||||||
|
[field]: { $regex: `${node.value.replace(/\s+/g, '\\s+')}`, $options: 'i' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { $or: orConditions };
|
||||||
|
}
|
||||||
|
|
||||||
|
// For phrases, we use a regex to ensure exact matches
|
||||||
|
return { $text: { $search: `"${node.value}"` } };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform a field query to MongoDB query
|
||||||
|
*/
|
||||||
|
private transformField(node: FieldNode): any {
|
||||||
|
// Handle special case for range queries on fields
|
||||||
|
if (node.value.type === 'RANGE') {
|
||||||
|
const rangeNode = node.value as RangeNode;
|
||||||
|
rangeNode.field = node.field;
|
||||||
|
return this.transformRange(rangeNode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special case for wildcards on fields
|
||||||
|
if (node.value.type === 'WILDCARD') {
|
||||||
|
return {
|
||||||
|
[node.field]: {
|
||||||
|
$regex: this.luceneWildcardToRegex((node.value as WildcardNode).value),
|
||||||
|
$options: 'i',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle special case for fuzzy searches on fields
|
||||||
|
if (node.value.type === 'FUZZY') {
|
||||||
|
return {
|
||||||
|
[node.field]: {
|
||||||
|
$regex: this.createFuzzyRegex((node.value as FuzzyNode).value),
|
||||||
|
$options: 'i',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for exact term matches on fields
|
||||||
|
if (node.value.type === 'TERM') {
|
||||||
|
return { [node.field]: { $regex: (node.value as TermNode).value, $options: 'i' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Special case for phrase matches on fields
|
||||||
|
if (node.value.type === 'PHRASE') {
|
||||||
|
return {
|
||||||
|
[node.field]: {
|
||||||
|
$regex: `${(node.value as PhraseNode).value.replace(/\s+/g, '\\s+')}`,
|
||||||
|
$options: 'i',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other cases, we'll transform the value and apply it to the field
|
||||||
|
const transformedValue = this.transform(node.value);
|
||||||
|
|
||||||
|
// If the transformed value uses $text, we need to adapt it for the field
|
||||||
|
if (transformedValue.$text) {
|
||||||
|
return { [node.field]: { $regex: transformedValue.$text.$search, $options: 'i' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle $or and $and cases
|
||||||
|
if (transformedValue.$or || transformedValue.$and) {
|
||||||
|
// This is a bit complex - we need to restructure the query to apply the field
|
||||||
|
// For now, simplify by just using a regex on the field
|
||||||
|
const term = this.extractTermFromBooleanQuery(transformedValue);
|
||||||
|
if (term) {
|
||||||
|
return { [node.field]: { $regex: term, $options: 'i' } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { [node.field]: transformedValue };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract a term from a boolean query (simplification)
|
||||||
|
*/
|
||||||
|
private extractTermFromBooleanQuery(query: any): string | null {
|
||||||
|
if (query.$or && Array.isArray(query.$or) && query.$or.length > 0) {
|
||||||
|
const firstClause = query.$or[0];
|
||||||
|
for (const field in firstClause) {
|
||||||
|
if (firstClause[field].$regex) {
|
||||||
|
return firstClause[field].$regex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.$and && Array.isArray(query.$and) && query.$and.length > 0) {
|
||||||
|
const firstClause = query.$and[0];
|
||||||
|
for (const field in firstClause) {
|
||||||
|
if (firstClause[field].$regex) {
|
||||||
|
return firstClause[field].$regex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform AND operator to MongoDB query
|
||||||
|
* FIXED: $and must be an array
|
||||||
|
*/
|
||||||
|
private transformAnd(node: BooleanNode): any {
|
||||||
|
return { $and: [this.transform(node.left), this.transform(node.right)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform OR operator to MongoDB query
|
||||||
|
* FIXED: $or must be an array
|
||||||
|
*/
|
||||||
|
private transformOr(node: BooleanNode): any {
|
||||||
|
return { $or: [this.transform(node.left), this.transform(node.right)] };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform NOT operator to MongoDB query
|
||||||
|
* FIXED: $and must be an array and $not usage
|
||||||
|
*/
|
||||||
|
private transformNot(node: BooleanNode): any {
|
||||||
|
const leftQuery = this.transform(node.left);
|
||||||
|
const rightQuery = this.transform(node.right);
|
||||||
|
|
||||||
|
// Create a query that includes left but excludes right
|
||||||
|
if (rightQuery.$text) {
|
||||||
|
// For text searches, we need a different approach
|
||||||
|
// We'll use a negated regex instead
|
||||||
|
const searchTerm = rightQuery.$text.$search.replace(/"/g, '');
|
||||||
|
|
||||||
|
// Determine the fields to apply the negation to
|
||||||
|
const notConditions = [];
|
||||||
|
|
||||||
|
for (const field in leftQuery) {
|
||||||
|
if (field !== '$or' && field !== '$and') {
|
||||||
|
notConditions.push({
|
||||||
|
[field]: { $not: { $regex: searchTerm, $options: 'i' } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If left query has $or or $and, we need to handle it differently
|
||||||
|
if (leftQuery.$or) {
|
||||||
|
return {
|
||||||
|
$and: [leftQuery, { $nor: [{ $or: notConditions }] }],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// Simple case - just add $not to each field
|
||||||
|
return {
|
||||||
|
$and: [leftQuery, { $and: notConditions }],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For other queries, we can use $not directly
|
||||||
|
// We need to handle different structures based on the rightQuery
|
||||||
|
let notQuery = {};
|
||||||
|
|
||||||
|
if (rightQuery.$or) {
|
||||||
|
notQuery = { $nor: rightQuery.$or };
|
||||||
|
} else if (rightQuery.$and) {
|
||||||
|
// Convert $and to $nor
|
||||||
|
notQuery = { $nor: rightQuery.$and };
|
||||||
|
} else {
|
||||||
|
// Simple field condition
|
||||||
|
for (const field in rightQuery) {
|
||||||
|
notQuery[field] = { $not: rightQuery[field] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { $and: [leftQuery, notQuery] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform range query to MongoDB query
|
||||||
|
*/
|
||||||
|
private transformRange(node: RangeNode): any {
|
||||||
|
const range: any = {};
|
||||||
|
|
||||||
|
if (node.lower !== '*') {
|
||||||
|
range[node.includeLower ? '$gte' : '$gt'] = this.parseValue(node.lower);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (node.upper !== '*') {
|
||||||
|
range[node.includeUpper ? '$lte' : '$lt'] = this.parseValue(node.upper);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { [node.field]: range };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform wildcard query to MongoDB query
|
||||||
|
* FIXED: properly structured for multiple fields
|
||||||
|
*/
|
||||||
|
private transformWildcard(node: WildcardNode, searchFields?: string[]): any {
|
||||||
|
// Convert Lucene wildcards to MongoDB regex
|
||||||
|
const regex = this.luceneWildcardToRegex(node.value);
|
||||||
|
|
||||||
|
// If specific fields are provided, search wildcard across those fields
|
||||||
|
if (searchFields && searchFields.length > 0) {
|
||||||
|
const orConditions = searchFields.map((field) => ({
|
||||||
|
[field]: { $regex: regex, $options: 'i' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { $or: orConditions };
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, apply to the default field
|
||||||
|
return { $regex: regex, $options: 'i' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transform fuzzy query to MongoDB query
|
||||||
|
* FIXED: properly structured for multiple fields
|
||||||
|
*/
|
||||||
|
private transformFuzzy(node: FuzzyNode, searchFields?: string[]): any {
|
||||||
|
// MongoDB doesn't have built-in fuzzy search
|
||||||
|
// This is a very basic approach using regex
|
||||||
|
const regex = this.createFuzzyRegex(node.value);
|
||||||
|
|
||||||
|
// If specific fields are provided, search fuzzy term across those fields
|
||||||
|
if (searchFields && searchFields.length > 0) {
|
||||||
|
const orConditions = searchFields.map((field) => ({
|
||||||
|
[field]: { $regex: regex, $options: 'i' },
|
||||||
|
}));
|
||||||
|
|
||||||
|
return { $or: orConditions };
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, apply to the default field
|
||||||
|
return { $regex: regex, $options: 'i' };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Lucene wildcards to MongoDB regex patterns
|
||||||
|
*/
|
||||||
|
private luceneWildcardToRegex(wildcardPattern: string): string {
|
||||||
|
// Replace Lucene wildcards with regex equivalents
|
||||||
|
// * => .*
|
||||||
|
// ? => .
|
||||||
|
// Also escape regex special chars
|
||||||
|
return wildcardPattern
|
||||||
|
.replace(/([.+^${}()|\\])/g, '\\$1') // Escape regex special chars
|
||||||
|
.replace(/\*/g, '.*')
|
||||||
|
.replace(/\?/g, '.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a simplified fuzzy search regex
|
||||||
|
*/
|
||||||
|
private createFuzzyRegex(term: string): string {
|
||||||
|
// For a very simple approach, we allow some characters to be optional
|
||||||
|
let regex = '';
|
||||||
|
for (let i = 0; i < term.length; i++) {
|
||||||
|
// Make every other character optional (simplified fuzzy)
|
||||||
|
if (i % 2 === 1) {
|
||||||
|
regex += term[i] + '?';
|
||||||
|
} else {
|
||||||
|
regex += term[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return regex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse string values to appropriate types (numbers, dates, etc.)
|
||||||
|
*/
|
||||||
|
private parseValue(value: string): any {
|
||||||
|
// Try to parse as number
|
||||||
|
if (/^-?\d+$/.test(value)) {
|
||||||
|
return parseInt(value, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/^-?\d+\.\d+$/.test(value)) {
|
||||||
|
return parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to parse as date (simplified)
|
||||||
|
const date = new Date(value);
|
||||||
|
if (!isNaN(date.getTime())) {
|
||||||
|
return date;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default to string
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main adapter class
|
||||||
|
*/
|
||||||
|
export class SmartdataLuceneAdapter {
|
||||||
|
private parser: LuceneParser;
|
||||||
|
private transformer: LuceneToMongoTransformer;
|
||||||
|
private defaultSearchFields: string[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param defaultSearchFields - Optional array of field names to search across when no field is specified
|
||||||
|
*/
|
||||||
|
constructor(defaultSearchFields?: string[]) {
|
||||||
|
this.parser = new LuceneParser();
|
||||||
|
this.transformer = new LuceneToMongoTransformer();
|
||||||
|
if (defaultSearchFields) {
|
||||||
|
this.defaultSearchFields = defaultSearchFields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a Lucene query string to a MongoDB query object
|
||||||
|
* @param luceneQuery - The Lucene query string to convert
|
||||||
|
* @param searchFields - Optional array of field names to search across (overrides defaultSearchFields)
|
||||||
|
*/
|
||||||
|
convert(luceneQuery: string, searchFields?: string[]): any {
|
||||||
|
try {
|
||||||
|
// For simple single term queries, create a simpler query structure
|
||||||
|
if (
|
||||||
|
!luceneQuery.includes(':') &&
|
||||||
|
!luceneQuery.includes(' AND ') &&
|
||||||
|
!luceneQuery.includes(' OR ') &&
|
||||||
|
!luceneQuery.includes(' NOT ') &&
|
||||||
|
!luceneQuery.includes('(') &&
|
||||||
|
!luceneQuery.includes('[')
|
||||||
|
) {
|
||||||
|
// This is a simple term, use a more direct approach
|
||||||
|
const fieldsToSearch = searchFields || this.defaultSearchFields;
|
||||||
|
|
||||||
|
if (fieldsToSearch && fieldsToSearch.length > 0) {
|
||||||
|
return {
|
||||||
|
$or: fieldsToSearch.map((field) => ({
|
||||||
|
[field]: { $regex: luceneQuery, $options: 'i' },
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For more complex queries, use the full parser
|
||||||
|
// Parse the Lucene query into an AST
|
||||||
|
const ast = this.parser.parse(luceneQuery);
|
||||||
|
|
||||||
|
// Use provided searchFields, fall back to defaultSearchFields
|
||||||
|
const fieldsToSearch = searchFields || this.defaultSearchFields;
|
||||||
|
|
||||||
|
// Transform the AST to a MongoDB query
|
||||||
|
return this.transformWithFields(ast, fieldsToSearch);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to convert Lucene query "${luceneQuery}":`, error);
|
||||||
|
throw new Error(`Failed to convert Lucene query: ${error}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to transform the AST with field information
|
||||||
|
*/
|
||||||
|
private transformWithFields(node: AnyQueryNode, searchFields: string[]): any {
|
||||||
|
// Special case for term nodes without a specific field
|
||||||
|
if (
|
||||||
|
node.type === 'TERM' ||
|
||||||
|
node.type === 'PHRASE' ||
|
||||||
|
node.type === 'WILDCARD' ||
|
||||||
|
node.type === 'FUZZY'
|
||||||
|
) {
|
||||||
|
return this.transformer.transform(node, searchFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
// For other node types, use the standard transformation
|
||||||
|
return this.transformer.transform(node);
|
||||||
|
}
|
||||||
|
}
|
37
ts/classes.watcher.ts
Normal file
37
ts/classes.watcher.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { SmartDataDbDoc } from './classes.doc.js';
|
||||||
|
import * as plugins from './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();
|
||||||
|
}
|
||||||
|
}
|
17
ts/index.ts
17
ts/index.ts
@ -1,3 +1,14 @@
|
|||||||
export * from './smartdata.classes.db'
|
export * from './classes.db.js';
|
||||||
export * from './smartdata.classes.dbcollection'
|
export * from './classes.collection.js';
|
||||||
export * from './smartdata.classes.dbdoc'
|
export * from './classes.doc.js';
|
||||||
|
export * from './classes.easystore.js';
|
||||||
|
export * from './classes.cursor.js';
|
||||||
|
|
||||||
|
import * as convenience from './classes.convenience.js';
|
||||||
|
|
||||||
|
export { convenience };
|
||||||
|
|
||||||
|
// to be removed with the next breaking update
|
||||||
|
import type * as plugins from './plugins.js';
|
||||||
|
type IMongoDescriptor = plugins.tsclass.database.IMongoDescriptor;
|
||||||
|
export type { IMongoDescriptor };
|
||||||
|
3
ts/logging.ts
Normal file
3
ts/logging.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as plugins from './plugins.js';
|
||||||
|
|
||||||
|
export const logger = new plugins.smartlog.ConsoleLog();
|
29
ts/plugins.ts
Normal file
29
ts/plugins.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
// tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export { tsclass };
|
||||||
|
|
||||||
|
// @pushrocks scope
|
||||||
|
import * as lik from '@push.rocks/lik';
|
||||||
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
|
import * as smartlog from '@push.rocks/smartlog';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
|
import * as smartstring from '@push.rocks/smartstring';
|
||||||
|
import * as smarttime from '@push.rocks/smarttime';
|
||||||
|
import * as smartunique from '@push.rocks/smartunique';
|
||||||
|
import * as taskbuffer from '@push.rocks/taskbuffer';
|
||||||
|
import * as mongodb from 'mongodb';
|
||||||
|
|
||||||
|
export {
|
||||||
|
lik,
|
||||||
|
smartdelay,
|
||||||
|
smartpromise,
|
||||||
|
smartlog,
|
||||||
|
smartrx,
|
||||||
|
mongodb,
|
||||||
|
smartstring,
|
||||||
|
smarttime,
|
||||||
|
smartunique,
|
||||||
|
taskbuffer,
|
||||||
|
};
|
@ -1,79 +0,0 @@
|
|||||||
import * as plugins from './smartdata.plugins'
|
|
||||||
import { Objectmap } from 'lik'
|
|
||||||
|
|
||||||
import { DbTable } from './smartdata.classes.dbcollection'
|
|
||||||
|
|
||||||
import { Connection as dbConnection, ConnectionOptions } from 'rethinkdb'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* interface - indicates the connection status of the db
|
|
||||||
*/
|
|
||||||
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed'
|
|
||||||
|
|
||||||
export class Db {
|
|
||||||
dbName: string
|
|
||||||
connectionOptions: plugins.rethinkDb.ConnectionOptions
|
|
||||||
dbConnection: plugins.rethinkDb.Connection
|
|
||||||
status: TConnectionStatus
|
|
||||||
dbTablesMap = new Objectmap<DbTable<any>>()
|
|
||||||
|
|
||||||
constructor(connectionOptionsArg: ConnectionOptions) {
|
|
||||||
this.dbName = connectionOptionsArg.db
|
|
||||||
this.connectionOptions = connectionOptionsArg
|
|
||||||
this.status = 'initial'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* supply additional SSl options
|
|
||||||
*/
|
|
||||||
setSsl (certificateStringArg: string, formatArg: 'base64' | 'clearText') {
|
|
||||||
let certificateString: string
|
|
||||||
if(formatArg = 'base64') {
|
|
||||||
certificateString = plugins.smartstring.base64.decode(certificateStringArg)
|
|
||||||
} else {
|
|
||||||
certificateString = certificateStringArg
|
|
||||||
}
|
|
||||||
this.connectionOptions['ssl'] = {
|
|
||||||
ca: Buffer.from(certificateString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// basic connection stuff ----------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* connects to the database that was specified during instance creation
|
|
||||||
*/
|
|
||||||
async connect (): Promise<any> {
|
|
||||||
this.dbConnection = await plugins.rethinkDb.connect(this.connectionOptions)
|
|
||||||
this.dbConnection.use(this.dbName)
|
|
||||||
this.status = 'connected'
|
|
||||||
plugins.beautylog.ok(`Connected to database ${this.dbName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* closes the connection to the databse
|
|
||||||
*/
|
|
||||||
async close (): Promise<any> {
|
|
||||||
await this.dbConnection.close()
|
|
||||||
this.status = 'disconnected'
|
|
||||||
plugins.beautylog.ok(`disconnected from database ${this.dbName}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle table to class distribution
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets a table's name and returns smartdata's DbTable class
|
|
||||||
* @param nameArg
|
|
||||||
* @returns DbTable
|
|
||||||
*/
|
|
||||||
async getDbTableByName<T>(nameArg: string): Promise<DbTable<T>> {
|
|
||||||
let resultCollection = this.dbTablesMap.find((dbCollectionArg) => {
|
|
||||||
return dbCollectionArg.tableName === nameArg
|
|
||||||
})
|
|
||||||
return resultCollection
|
|
||||||
}
|
|
||||||
|
|
||||||
addTable (dbCollectionArg: DbTable<any>) {
|
|
||||||
this.dbTablesMap.add(dbCollectionArg)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,119 +0,0 @@
|
|||||||
import * as plugins from './smartdata.plugins'
|
|
||||||
import { Db } from './smartdata.classes.db'
|
|
||||||
import { DbDoc } from './smartdata.classes.dbdoc'
|
|
||||||
|
|
||||||
// RethinkDb Interfaces
|
|
||||||
import { WriteResult, Cursor } from 'rethinkdb'
|
|
||||||
|
|
||||||
export interface IFindOptions {
|
|
||||||
limit?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IDocValidation<T> {
|
|
||||||
(doc: T): boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function Collection (db: Db) {
|
|
||||||
return function (constructor) {
|
|
||||||
constructor[ 'dbCollection' ] = new DbTable(constructor, db)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DbTable<T> {
|
|
||||||
/**
|
|
||||||
* the collection that is used, defaults to mongodb collection,
|
|
||||||
* can be nedb datastore (sub api of mongodb)
|
|
||||||
*/
|
|
||||||
table: plugins.rethinkDb.Table
|
|
||||||
objectValidation: IDocValidation<T> = null
|
|
||||||
tableName: string
|
|
||||||
db: Db
|
|
||||||
|
|
||||||
constructor (collectedClassArg: T & DbDoc<T>, dbArg: Db) {
|
|
||||||
// tell the collection where it belongs
|
|
||||||
this.tableName = collectedClassArg.name
|
|
||||||
this.db = dbArg
|
|
||||||
|
|
||||||
// tell the db class about it (important since Db uses different systems under the hood)
|
|
||||||
this.db.addTable(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
if(!this.table) {
|
|
||||||
// connect this instance to a RethinkDB table
|
|
||||||
const availableTables = await plugins.rethinkDb
|
|
||||||
.db(this.db.dbName)
|
|
||||||
.tableList()
|
|
||||||
.run(this.db.dbConnection)
|
|
||||||
if(availableTables.indexOf(this.tableName)) {
|
|
||||||
await plugins.rethinkDb
|
|
||||||
.db(this.db.dbName)
|
|
||||||
.tableCreate(this.tableName)
|
|
||||||
.run(this.db.dbConnection)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.table = plugins.rethinkDb.table(this.tableName)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* adds a validation function that all newly inserted and updated objects have to pass
|
|
||||||
*/
|
|
||||||
addDocValidation (funcArg: IDocValidation<T>) {
|
|
||||||
this.objectValidation = funcArg
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* finds an object in the DbCollection
|
|
||||||
*/
|
|
||||||
async find (): Promise<Cursor> {
|
|
||||||
await this.init()
|
|
||||||
return await plugins.rethinkDb.table(this.tableName).filter({
|
|
||||||
/* TODO: */
|
|
||||||
}).run(this.db.dbConnection)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* create an object in the database
|
|
||||||
*/
|
|
||||||
async insert (dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
|
||||||
await this.init()
|
|
||||||
await this.checkDoc(dbDocArg)
|
|
||||||
return await plugins.rethinkDb.table(this.tableName).insert(
|
|
||||||
dbDocArg.createSavableObject()
|
|
||||||
).run(this.db.dbConnection)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* inserts object into the DbCollection
|
|
||||||
*/
|
|
||||||
async update (dbDocArg: T & DbDoc<T>): Promise<WriteResult> {
|
|
||||||
await this.init()
|
|
||||||
await this.checkDoc(dbDocArg)
|
|
||||||
console.log(this.tableName, dbDocArg.createSavableObject())
|
|
||||||
return await plugins.rethinkDb.table(this.tableName).update(
|
|
||||||
dbDocArg.createSavableObject()
|
|
||||||
).run(this.db.dbConnection)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checks a Doc for constraints
|
|
||||||
* if this.objectValidation is not set it passes.
|
|
||||||
*/
|
|
||||||
private checkDoc (docArg: T): Promise<void> {
|
|
||||||
let done = plugins.smartq.defer<void>()
|
|
||||||
let validationResult = true
|
|
||||||
if (this.objectValidation) {
|
|
||||||
validationResult = this.objectValidation(docArg)
|
|
||||||
}
|
|
||||||
if (validationResult) {
|
|
||||||
done.resolve()
|
|
||||||
} else {
|
|
||||||
done.reject('validation of object did not pass')
|
|
||||||
}
|
|
||||||
return done.promise
|
|
||||||
}
|
|
||||||
|
|
||||||
extractKey (writeResult: WriteResult) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,100 +0,0 @@
|
|||||||
import * as plugins from './smartdata.plugins'
|
|
||||||
|
|
||||||
import { Objectmap } from 'lik'
|
|
||||||
|
|
||||||
import { Db } from './smartdata.classes.db'
|
|
||||||
import { DbTable } from './smartdata.classes.dbcollection'
|
|
||||||
|
|
||||||
export type TDocCreation = 'db' | 'new' | 'mixed'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* saveable - saveable decorator to be used on class properties
|
|
||||||
*/
|
|
||||||
export function svDb() {
|
|
||||||
return (target: DbDoc<any>, key: string) => {
|
|
||||||
console.log('called sva')
|
|
||||||
if (!target.saveableProperties) { target.saveableProperties = [] }
|
|
||||||
target.saveableProperties.push(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DbDoc<T> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the collection object an Doc belongs to
|
|
||||||
*/
|
|
||||||
collection: DbTable<T>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* how the Doc in memory was created, may prove useful later.
|
|
||||||
*/
|
|
||||||
creationStatus: TDocCreation = 'new'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* an array of saveable properties of a doc
|
|
||||||
*/
|
|
||||||
saveableProperties: string[]
|
|
||||||
|
|
||||||
/**
|
|
||||||
* name
|
|
||||||
*/
|
|
||||||
name: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* primary id in the database
|
|
||||||
*/
|
|
||||||
dbId: string
|
|
||||||
|
|
||||||
/**
|
|
||||||
* class constructor
|
|
||||||
*/
|
|
||||||
constructor () {
|
|
||||||
this.name = this.constructor['name']
|
|
||||||
this.collection = this.constructor[ 'dbCollection' ]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* saves this instance but not any connected items
|
|
||||||
* may lead to data inconsistencies, but is faster
|
|
||||||
*/
|
|
||||||
async save () {
|
|
||||||
let self: any = this
|
|
||||||
switch (this.creationStatus) {
|
|
||||||
case 'db':
|
|
||||||
await this.collection.update(self)
|
|
||||||
break
|
|
||||||
case 'new':
|
|
||||||
let writeResult = await this.collection.insert(self)
|
|
||||||
this.creationStatus = 'db'
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
console.error('neither new nor in db?')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* also store any referenced objects to DB
|
|
||||||
* better for data consistency
|
|
||||||
*/
|
|
||||||
saveDeep (savedMapArg: Objectmap<DbDoc<any>> = null) {
|
|
||||||
if (!savedMapArg) {
|
|
||||||
savedMapArg = new Objectmap<DbDoc<any>>()
|
|
||||||
}
|
|
||||||
savedMapArg.add(this)
|
|
||||||
this.save()
|
|
||||||
for (let propertyKey in this) {
|
|
||||||
let property: any = this[ propertyKey ]
|
|
||||||
if (property instanceof DbDoc && !savedMapArg.checkForObject(property)) {
|
|
||||||
property.saveDeep(savedMapArg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
createSavableObject () {
|
|
||||||
let saveableObject: any = {} // is not exposed to outside, so any is ok here
|
|
||||||
for (let propertyNameString of this.saveableProperties) {
|
|
||||||
saveableObject[ propertyNameString ] = this[ propertyNameString ]
|
|
||||||
}
|
|
||||||
return saveableObject
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,15 +0,0 @@
|
|||||||
import * as assert from 'assert'
|
|
||||||
import * as beautylog from 'beautylog'
|
|
||||||
import * as lodash from 'lodash'
|
|
||||||
import * as rethinkDb from 'rethinkdb'
|
|
||||||
import * as smartq from 'smartq'
|
|
||||||
import * as smartstring from 'smartstring'
|
|
||||||
|
|
||||||
export {
|
|
||||||
assert,
|
|
||||||
beautylog,
|
|
||||||
lodash,
|
|
||||||
smartq,
|
|
||||||
rethinkDb,
|
|
||||||
smartstring
|
|
||||||
}
|
|
@ -1,8 +1,16 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"lib": [
|
"useDefineForClassFields": false,
|
||||||
"es2015"
|
"target": "ES2022",
|
||||||
]
|
"module": "NodeNext",
|
||||||
}
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {}
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist_*/**/*.d.ts"
|
||||||
|
]
|
||||||
}
|
}
|
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "tslint-config-standard"
|
|
||||||
}
|
|
788
yarn.lock
788
yarn.lock
@ -1,788 +0,0 @@
|
|||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
"@types/code@^4.0.3":
|
|
||||||
version "4.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/code/-/code-4.0.3.tgz#9c4de39f86eb3eba070146d2dab7dbc3f8eac35f"
|
|
||||||
|
|
||||||
"@types/events@*":
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-1.1.0.tgz#93b1be91f63c184450385272c47b6496fd028e02"
|
|
||||||
|
|
||||||
"@types/fs-extra@4.x.x":
|
|
||||||
version "4.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-4.0.7.tgz#02533262386b5a6b9a49797dc82feffdf269140a"
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/glob@*":
|
|
||||||
version "5.0.34"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-5.0.34.tgz#ee626c9be3da877d717911c6101eee0a9871bbf4"
|
|
||||||
dependencies:
|
|
||||||
"@types/events" "*"
|
|
||||||
"@types/minimatch" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/lodash@^4.14.55", "@types/lodash@^4.14.74", "@types/lodash@^4.14.92":
|
|
||||||
version "4.14.92"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.92.tgz#6e3cb0b71a1e12180a47a42a744e856c3ae99a57"
|
|
||||||
|
|
||||||
"@types/minimatch@*", "@types/minimatch@3.x.x":
|
|
||||||
version "3.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
|
|
||||||
|
|
||||||
"@types/node@*", "@types/node@^8.0.33", "@types/node@^8.5.7":
|
|
||||||
version "8.5.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-8.5.7.tgz#9c498c35af354dcfbca3790fb2e81129e93cf0e2"
|
|
||||||
|
|
||||||
"@types/rethinkdb@^2.3.8":
|
|
||||||
version "2.3.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/rethinkdb/-/rethinkdb-2.3.8.tgz#961f78f0e731668631891bd1199722bb4a2258a8"
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/shelljs@^0.7.4":
|
|
||||||
version "0.7.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/shelljs/-/shelljs-0.7.7.tgz#1f7bfa28947661afea06365db9b1135bbc903ec4"
|
|
||||||
dependencies:
|
|
||||||
"@types/glob" "*"
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
"@types/vinyl@^2.0.1":
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/@types/vinyl/-/vinyl-2.0.2.tgz#4f3b8dae8f5828d3800ef709b0cff488ee852de3"
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "*"
|
|
||||||
|
|
||||||
ansi-256-colors@^1.1.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/ansi-256-colors/-/ansi-256-colors-1.1.0.tgz#910de50efcc7c09e3d82f2f87abd6b700c18818a"
|
|
||||||
|
|
||||||
ansi-regex@^2.0.0:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
|
||||||
|
|
||||||
ansi-styles@^2.2.1:
|
|
||||||
version "2.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
|
||||||
|
|
||||||
argparse@^1.0.7:
|
|
||||||
version "1.0.9"
|
|
||||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86"
|
|
||||||
dependencies:
|
|
||||||
sprintf-js "~1.0.2"
|
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
|
||||||
|
|
||||||
beautycolor@^1.0.7:
|
|
||||||
version "1.0.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/beautycolor/-/beautycolor-1.0.11.tgz#71c5568d5a7ed5c144d3a54f753ad1b08862aea5"
|
|
||||||
dependencies:
|
|
||||||
ansi-256-colors "^1.1.0"
|
|
||||||
typings-global "^1.0.14"
|
|
||||||
|
|
||||||
beautylog@^6.1.10:
|
|
||||||
version "6.1.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/beautylog/-/beautylog-6.1.10.tgz#9c27e566937684cb689f9372d98cfa5415d50b72"
|
|
||||||
dependencies:
|
|
||||||
"@types/lodash" "^4.14.55"
|
|
||||||
beautycolor "^1.0.7"
|
|
||||||
figlet "^1.2.0"
|
|
||||||
lodash "^4.17.4"
|
|
||||||
ora "^1.1.0"
|
|
||||||
smartenv "^2.0.0"
|
|
||||||
smartq "^1.1.1"
|
|
||||||
typings-global "^1.0.14"
|
|
||||||
|
|
||||||
bindings@^1.2.1:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7"
|
|
||||||
|
|
||||||
"bluebird@>= 2.3.2 < 3":
|
|
||||||
version "2.11.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1"
|
|
||||||
|
|
||||||
brace-expansion@^1.1.7:
|
|
||||||
version "1.1.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292"
|
|
||||||
dependencies:
|
|
||||||
balanced-match "^1.0.0"
|
|
||||||
concat-map "0.0.1"
|
|
||||||
|
|
||||||
chalk@^1.0.0, chalk@^1.1.1:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
|
|
||||||
dependencies:
|
|
||||||
ansi-styles "^2.2.1"
|
|
||||||
escape-string-regexp "^1.0.2"
|
|
||||||
has-ansi "^2.0.0"
|
|
||||||
strip-ansi "^3.0.0"
|
|
||||||
supports-color "^2.0.0"
|
|
||||||
|
|
||||||
cli-cursor@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
|
||||||
dependencies:
|
|
||||||
restore-cursor "^2.0.0"
|
|
||||||
|
|
||||||
cli-spinners@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-1.1.0.tgz#f1847b168844d917a671eb9d147e3df497c90d06"
|
|
||||||
|
|
||||||
clone-buffer@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58"
|
|
||||||
|
|
||||||
clone-stats@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680"
|
|
||||||
|
|
||||||
clone@^2.1.1:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.1.tgz#d217d1e961118e3ac9a4b8bba3285553bf647cdb"
|
|
||||||
|
|
||||||
cloneable-readable@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cloneable-readable/-/cloneable-readable-1.0.0.tgz#a6290d413f217a61232f95e458ff38418cfb0117"
|
|
||||||
dependencies:
|
|
||||||
inherits "^2.0.1"
|
|
||||||
process-nextick-args "^1.0.6"
|
|
||||||
through2 "^2.0.1"
|
|
||||||
|
|
||||||
code@^5.1.0:
|
|
||||||
version "5.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/code/-/code-5.1.2.tgz#e3310c2078ca7dc0b49b9c39a8b0a7b06bd75efe"
|
|
||||||
dependencies:
|
|
||||||
hoek "5.x.x"
|
|
||||||
|
|
||||||
concat-map@0.0.1:
|
|
||||||
version "0.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
|
||||||
|
|
||||||
conventional-commit-types@^2.0.0:
|
|
||||||
version "2.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/conventional-commit-types/-/conventional-commit-types-2.2.0.tgz#5db95739d6c212acbe7b6f656a11b940baa68946"
|
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
|
||||||
|
|
||||||
crypto-random-string@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-1.0.0.tgz#a230f64f568310e1498009940790ec99545bca7e"
|
|
||||||
|
|
||||||
cz-conventional-changelog@^2.1.0:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/cz-conventional-changelog/-/cz-conventional-changelog-2.1.0.tgz#2f4bc7390e3244e4df293e6ba351e4c740a7c764"
|
|
||||||
dependencies:
|
|
||||||
conventional-commit-types "^2.0.0"
|
|
||||||
lodash.map "^4.5.1"
|
|
||||||
longest "^1.0.1"
|
|
||||||
right-pad "^1.0.1"
|
|
||||||
word-wrap "^1.0.3"
|
|
||||||
|
|
||||||
define-properties@^1.1.2:
|
|
||||||
version "1.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
|
|
||||||
dependencies:
|
|
||||||
foreach "^2.0.5"
|
|
||||||
object-keys "^1.0.8"
|
|
||||||
|
|
||||||
early@^2.1.1:
|
|
||||||
version "2.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/early/-/early-2.1.1.tgz#841e23254ea5dc54d8afaeee82f5ab65c00ee23c"
|
|
||||||
dependencies:
|
|
||||||
beautycolor "^1.0.7"
|
|
||||||
smartq "^1.1.1"
|
|
||||||
typings-global "^1.0.16"
|
|
||||||
|
|
||||||
es-abstract@^1.5.1:
|
|
||||||
version "1.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.10.0.tgz#1ecb36c197842a00d8ee4c2dfd8646bb97d60864"
|
|
||||||
dependencies:
|
|
||||||
es-to-primitive "^1.1.1"
|
|
||||||
function-bind "^1.1.1"
|
|
||||||
has "^1.0.1"
|
|
||||||
is-callable "^1.1.3"
|
|
||||||
is-regex "^1.0.4"
|
|
||||||
|
|
||||||
es-to-primitive@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
|
|
||||||
dependencies:
|
|
||||||
is-callable "^1.1.1"
|
|
||||||
is-date-object "^1.0.1"
|
|
||||||
is-symbol "^1.0.1"
|
|
||||||
|
|
||||||
es6-error@^4.0.2:
|
|
||||||
version "4.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d"
|
|
||||||
|
|
||||||
escape-string-regexp@^1.0.2:
|
|
||||||
version "1.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
|
||||||
|
|
||||||
esprima@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804"
|
|
||||||
|
|
||||||
figlet@^1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/figlet/-/figlet-1.2.0.tgz#6c46537378fab649146b5a6143dda019b430b410"
|
|
||||||
|
|
||||||
first-chunk-stream@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz#1bdecdb8e083c0664b91945581577a43a9f31d70"
|
|
||||||
dependencies:
|
|
||||||
readable-stream "^2.0.2"
|
|
||||||
|
|
||||||
foreach@^2.0.5:
|
|
||||||
version "2.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
|
||||||
|
|
||||||
fs-extra@^4.0.2:
|
|
||||||
version "4.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94"
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.1.2"
|
|
||||||
jsonfile "^4.0.0"
|
|
||||||
universalify "^0.1.0"
|
|
||||||
|
|
||||||
fs.realpath@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
|
|
||||||
|
|
||||||
function-bind@^1.0.2, function-bind@^1.1.1:
|
|
||||||
version "1.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
|
||||||
|
|
||||||
glob@^7.0.0, glob@^7.1.2:
|
|
||||||
version "7.1.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15"
|
|
||||||
dependencies:
|
|
||||||
fs.realpath "^1.0.0"
|
|
||||||
inflight "^1.0.4"
|
|
||||||
inherits "2"
|
|
||||||
minimatch "^3.0.4"
|
|
||||||
once "^1.3.0"
|
|
||||||
path-is-absolute "^1.0.0"
|
|
||||||
|
|
||||||
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
|
|
||||||
version "4.1.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
|
||||||
|
|
||||||
has-ansi@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91"
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^2.0.0"
|
|
||||||
|
|
||||||
has@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28"
|
|
||||||
dependencies:
|
|
||||||
function-bind "^1.0.2"
|
|
||||||
|
|
||||||
hoek@5.x.x:
|
|
||||||
version "5.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.2.tgz#d2f2c95d36fe7189cf8aa8c237abc1950eca1378"
|
|
||||||
|
|
||||||
home@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/home/-/home-1.0.1.tgz#96a423ceb49b98378ff5ef3ceae059a557f9dd35"
|
|
||||||
dependencies:
|
|
||||||
os-homedir "^1.0.1"
|
|
||||||
|
|
||||||
inflight@^1.0.4:
|
|
||||||
version "1.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
|
|
||||||
dependencies:
|
|
||||||
once "^1.3.0"
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
inherits@2, inherits@^2.0.1, inherits@~2.0.3:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
|
||||||
|
|
||||||
interpret@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.1.0.tgz#7ed1b1410c6a0e0f78cf95d3b8440c63f78b8614"
|
|
||||||
|
|
||||||
is-buffer@^1.1.5:
|
|
||||||
version "1.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
|
||||||
|
|
||||||
is-callable@^1.1.1, is-callable@^1.1.3:
|
|
||||||
version "1.1.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.3.tgz#86eb75392805ddc33af71c92a0eedf74ee7604b2"
|
|
||||||
|
|
||||||
is-date-object@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
|
|
||||||
|
|
||||||
is-number@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195"
|
|
||||||
dependencies:
|
|
||||||
kind-of "^3.0.2"
|
|
||||||
|
|
||||||
is-regex@^1.0.4:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
|
|
||||||
dependencies:
|
|
||||||
has "^1.0.1"
|
|
||||||
|
|
||||||
is-symbol@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
|
|
||||||
|
|
||||||
is-utf8@^0.2.0, is-utf8@^0.2.1:
|
|
||||||
version "0.2.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72"
|
|
||||||
|
|
||||||
isarray@~1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
|
||||||
|
|
||||||
js-base64@^2.3.2:
|
|
||||||
version "2.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.4.0.tgz#9e566fee624751a1d720c966cd6226d29d4025aa"
|
|
||||||
|
|
||||||
js-yaml@^3.10.0:
|
|
||||||
version "3.10.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.10.0.tgz#2e78441646bd4682e963f22b6e92823c309c62dc"
|
|
||||||
dependencies:
|
|
||||||
argparse "^1.0.7"
|
|
||||||
esprima "^4.0.0"
|
|
||||||
|
|
||||||
jsonfile@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
|
|
||||||
optionalDependencies:
|
|
||||||
graceful-fs "^4.1.6"
|
|
||||||
|
|
||||||
kind-of@^3.0.2:
|
|
||||||
version "3.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
|
|
||||||
dependencies:
|
|
||||||
is-buffer "^1.1.5"
|
|
||||||
|
|
||||||
kind-of@^4.0.0:
|
|
||||||
version "4.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57"
|
|
||||||
dependencies:
|
|
||||||
is-buffer "^1.1.5"
|
|
||||||
|
|
||||||
leakage@^0.3.0:
|
|
||||||
version "0.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/leakage/-/leakage-0.3.0.tgz#15d698abdc76bbc6439601f4f3020e77e2d50c39"
|
|
||||||
dependencies:
|
|
||||||
es6-error "^4.0.2"
|
|
||||||
left-pad "^1.1.3"
|
|
||||||
memwatch-next "^0.3.0"
|
|
||||||
minimist "^1.2.0"
|
|
||||||
pretty-bytes "^4.0.2"
|
|
||||||
|
|
||||||
left-pad@^1.1.3:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.2.0.tgz#d30a73c6b8201d8f7d8e7956ba9616087a68e0ee"
|
|
||||||
|
|
||||||
lik@^2.0.2:
|
|
||||||
version "2.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/lik/-/lik-2.0.2.tgz#da4e67458ab81fac9e62848e8e76dc1efe1c646f"
|
|
||||||
dependencies:
|
|
||||||
"@types/lodash" "^4.14.74"
|
|
||||||
"@types/minimatch" "3.x.x"
|
|
||||||
lodash "^4.17.4"
|
|
||||||
minimatch "^3.0.4"
|
|
||||||
smartq "^1.1.6"
|
|
||||||
symbol-tree "^3.2.2"
|
|
||||||
typings-global "^1.0.20"
|
|
||||||
|
|
||||||
lodash.map@^4.5.1:
|
|
||||||
version "4.6.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash.map/-/lodash.map-4.6.0.tgz#771ec7839e3473d9c4cde28b19394c3562f4f6d3"
|
|
||||||
|
|
||||||
lodash@^4.17.4:
|
|
||||||
version "4.17.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
|
|
||||||
|
|
||||||
log-symbols@^1.0.2:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18"
|
|
||||||
dependencies:
|
|
||||||
chalk "^1.0.0"
|
|
||||||
|
|
||||||
longest@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097"
|
|
||||||
|
|
||||||
memwatch-next@^0.3.0:
|
|
||||||
version "0.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/memwatch-next/-/memwatch-next-0.3.0.tgz#2111050f9a906e0aa2d72a4ec0f0089c78726f8f"
|
|
||||||
dependencies:
|
|
||||||
bindings "^1.2.1"
|
|
||||||
nan "^2.3.2"
|
|
||||||
|
|
||||||
mimic-fn@^1.0.0:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18"
|
|
||||||
|
|
||||||
minimatch@^3.0.4:
|
|
||||||
version "3.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
|
|
||||||
dependencies:
|
|
||||||
brace-expansion "^1.1.7"
|
|
||||||
|
|
||||||
minimist@^1.2.0:
|
|
||||||
version "1.2.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
|
||||||
|
|
||||||
nan@^2.3.2:
|
|
||||||
version "2.8.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a"
|
|
||||||
|
|
||||||
normalize-newline@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/normalize-newline/-/normalize-newline-3.0.0.tgz#1cbea804aba436001f83938ab21ec039d69ae9d3"
|
|
||||||
|
|
||||||
object-keys@^1.0.8:
|
|
||||||
version "1.0.11"
|
|
||||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d"
|
|
||||||
|
|
||||||
object.getownpropertydescriptors@^2.0.3:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16"
|
|
||||||
dependencies:
|
|
||||||
define-properties "^1.1.2"
|
|
||||||
es-abstract "^1.5.1"
|
|
||||||
|
|
||||||
once@^1.3.0:
|
|
||||||
version "1.4.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
|
||||||
dependencies:
|
|
||||||
wrappy "1"
|
|
||||||
|
|
||||||
onetime@^2.0.0:
|
|
||||||
version "2.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4"
|
|
||||||
dependencies:
|
|
||||||
mimic-fn "^1.0.0"
|
|
||||||
|
|
||||||
ora@^1.1.0:
|
|
||||||
version "1.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/ora/-/ora-1.3.0.tgz#80078dd2b92a934af66a3ad72a5b910694ede51a"
|
|
||||||
dependencies:
|
|
||||||
chalk "^1.1.1"
|
|
||||||
cli-cursor "^2.1.0"
|
|
||||||
cli-spinners "^1.0.0"
|
|
||||||
log-symbols "^1.0.2"
|
|
||||||
|
|
||||||
os-homedir@^1.0.1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3"
|
|
||||||
|
|
||||||
path-is-absolute@^1.0.0:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
|
|
||||||
|
|
||||||
path-parse@^1.0.5:
|
|
||||||
version "1.0.5"
|
|
||||||
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1"
|
|
||||||
|
|
||||||
pify@^2.3.0:
|
|
||||||
version "2.3.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
|
||||||
|
|
||||||
pretty-bytes@^4.0.2:
|
|
||||||
version "4.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-4.0.2.tgz#b2bf82e7350d65c6c33aa95aaa5a4f6327f61cd9"
|
|
||||||
|
|
||||||
process-nextick-args@^1.0.6, process-nextick-args@~1.0.6:
|
|
||||||
version "1.0.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3"
|
|
||||||
|
|
||||||
qenv@^1.1.7:
|
|
||||||
version "1.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/qenv/-/qenv-1.1.7.tgz#d03f8bf8fe37494cf08d0919fe765dca84d9afae"
|
|
||||||
dependencies:
|
|
||||||
lodash "^4.17.4"
|
|
||||||
smartfile "^4.2.11"
|
|
||||||
typings-global "^1.0.16"
|
|
||||||
|
|
||||||
randomatic@^1.1.7:
|
|
||||||
version "1.1.7"
|
|
||||||
resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c"
|
|
||||||
dependencies:
|
|
||||||
is-number "^3.0.0"
|
|
||||||
kind-of "^4.0.0"
|
|
||||||
|
|
||||||
readable-stream@^2.0.2, readable-stream@^2.1.5:
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c"
|
|
||||||
dependencies:
|
|
||||||
core-util-is "~1.0.0"
|
|
||||||
inherits "~2.0.3"
|
|
||||||
isarray "~1.0.0"
|
|
||||||
process-nextick-args "~1.0.6"
|
|
||||||
safe-buffer "~5.1.1"
|
|
||||||
string_decoder "~1.0.3"
|
|
||||||
util-deprecate "~1.0.1"
|
|
||||||
|
|
||||||
rechoir@^0.6.2:
|
|
||||||
version "0.6.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384"
|
|
||||||
dependencies:
|
|
||||||
resolve "^1.1.6"
|
|
||||||
|
|
||||||
reflect-metadata@^0.1.2:
|
|
||||||
version "0.1.10"
|
|
||||||
resolved "https://registry.yarnpkg.com/reflect-metadata/-/reflect-metadata-0.1.10.tgz#b4f83704416acad89988c9b15635d47e03b9344a"
|
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
|
||||||
version "1.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
|
||||||
|
|
||||||
replace-ext@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.0.tgz#de63128373fcbf7c3ccfa4de5a480c45a67958eb"
|
|
||||||
|
|
||||||
require-reload@0.2.2:
|
|
||||||
version "0.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/require-reload/-/require-reload-0.2.2.tgz#29a7591846caf91b6e8a3cda991683f95f8d7d42"
|
|
||||||
|
|
||||||
resolve@^1.1.6:
|
|
||||||
version "1.5.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.5.0.tgz#1f09acce796c9a762579f31b2c1cc4c3cddf9f36"
|
|
||||||
dependencies:
|
|
||||||
path-parse "^1.0.5"
|
|
||||||
|
|
||||||
restore-cursor@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
|
|
||||||
dependencies:
|
|
||||||
onetime "^2.0.0"
|
|
||||||
signal-exit "^3.0.2"
|
|
||||||
|
|
||||||
rethinkdb@^2.3.3:
|
|
||||||
version "2.3.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/rethinkdb/-/rethinkdb-2.3.3.tgz#3dc6586e22fa1dabee0d254e64bd0e379fad2f72"
|
|
||||||
dependencies:
|
|
||||||
bluebird ">= 2.3.2 < 3"
|
|
||||||
|
|
||||||
right-pad@^1.0.1:
|
|
||||||
version "1.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/right-pad/-/right-pad-1.0.1.tgz#8ca08c2cbb5b55e74dafa96bf7fd1a27d568c8d0"
|
|
||||||
|
|
||||||
runtime-type-checks@0.0.4:
|
|
||||||
version "0.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/runtime-type-checks/-/runtime-type-checks-0.0.4.tgz#5682baf2ffe53f955fe3e065b40a0a09943845c8"
|
|
||||||
dependencies:
|
|
||||||
reflect-metadata "^0.1.2"
|
|
||||||
|
|
||||||
safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
|
||||||
version "5.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
|
||||||
|
|
||||||
shelljs@^0.7.8:
|
|
||||||
version "0.7.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.8.tgz#decbcf874b0d1e5fb72e14b164a9683048e9acb3"
|
|
||||||
dependencies:
|
|
||||||
glob "^7.0.0"
|
|
||||||
interpret "^1.0.0"
|
|
||||||
rechoir "^0.6.2"
|
|
||||||
|
|
||||||
signal-exit@^3.0.2:
|
|
||||||
version "3.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
|
||||||
|
|
||||||
smartchai@^1.0.3:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartchai/-/smartchai-1.0.8.tgz#a074836f4ddd4b98c50f1e7ae9e8e8ad9f6f1902"
|
|
||||||
dependencies:
|
|
||||||
"@types/code" "^4.0.3"
|
|
||||||
code "^5.1.0"
|
|
||||||
typings-global "^1.0.20"
|
|
||||||
|
|
||||||
smartdelay@^1.0.3:
|
|
||||||
version "1.0.4"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartdelay/-/smartdelay-1.0.4.tgz#791c1a4ee6770494064c10b1d2d2b8e6f3105b82"
|
|
||||||
dependencies:
|
|
||||||
smartq "^1.1.1"
|
|
||||||
typings-global "^1.0.16"
|
|
||||||
|
|
||||||
smartenv@^2.0.0:
|
|
||||||
version "2.0.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartenv/-/smartenv-2.0.6.tgz#b38c679b0c151b9af548f68c3a072c29d1417e8d"
|
|
||||||
dependencies:
|
|
||||||
lodash "^4.17.4"
|
|
||||||
smartq "^1.1.1"
|
|
||||||
typings-global "^1.0.14"
|
|
||||||
|
|
||||||
smartfile@^4.2.11:
|
|
||||||
version "4.2.26"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartfile/-/smartfile-4.2.26.tgz#800f08b1089e153b7fd8e0ba165da465a071d407"
|
|
||||||
dependencies:
|
|
||||||
"@types/fs-extra" "4.x.x"
|
|
||||||
"@types/vinyl" "^2.0.1"
|
|
||||||
fs-extra "^4.0.2"
|
|
||||||
glob "^7.1.2"
|
|
||||||
js-yaml "^3.10.0"
|
|
||||||
require-reload "0.2.2"
|
|
||||||
smartpath "^3.2.8"
|
|
||||||
smartq "^1.1.6"
|
|
||||||
smartrequest "^1.0.6"
|
|
||||||
typings-global "^1.0.20"
|
|
||||||
vinyl-file "^3.0.0"
|
|
||||||
|
|
||||||
smartpath@^3.2.8:
|
|
||||||
version "3.2.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartpath/-/smartpath-3.2.8.tgz#4834bd3a8bae2295baacadba23c87a501952f940"
|
|
||||||
dependencies:
|
|
||||||
home "^1.0.1"
|
|
||||||
typings-global "^1.0.14"
|
|
||||||
|
|
||||||
smartq@^1.1.1, smartq@^1.1.6:
|
|
||||||
version "1.1.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartq/-/smartq-1.1.6.tgz#0c1ff4336d95e95b4f1fdd8ccd7e2c5a323b8412"
|
|
||||||
dependencies:
|
|
||||||
typings-global "^1.0.19"
|
|
||||||
util.promisify "^1.0.0"
|
|
||||||
|
|
||||||
smartrequest@^1.0.6:
|
|
||||||
version "1.0.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartrequest/-/smartrequest-1.0.8.tgz#9af18dde34efa7d43b4ecfc92ccb157a98eda3b1"
|
|
||||||
dependencies:
|
|
||||||
smartq "^1.1.1"
|
|
||||||
|
|
||||||
smartstring@^2.0.28:
|
|
||||||
version "2.0.28"
|
|
||||||
resolved "https://registry.yarnpkg.com/smartstring/-/smartstring-2.0.28.tgz#384b808dc3780eff0031ce7925c1b1668abbcad5"
|
|
||||||
dependencies:
|
|
||||||
crypto-random-string "^1.0.0"
|
|
||||||
js-base64 "^2.3.2"
|
|
||||||
normalize-newline "^3.0.0"
|
|
||||||
randomatic "^1.1.7"
|
|
||||||
strip-indent "^2.0.0"
|
|
||||||
typings-global "^1.0.20"
|
|
||||||
|
|
||||||
sprintf-js@~1.0.2:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
|
|
||||||
|
|
||||||
string_decoder@~1.0.3:
|
|
||||||
version "1.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
|
||||||
dependencies:
|
|
||||||
safe-buffer "~5.1.0"
|
|
||||||
|
|
||||||
strip-ansi@^3.0.0:
|
|
||||||
version "3.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
|
|
||||||
dependencies:
|
|
||||||
ansi-regex "^2.0.0"
|
|
||||||
|
|
||||||
strip-bom-buf@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz#1cb45aaf57530f4caf86c7f75179d2c9a51dd572"
|
|
||||||
dependencies:
|
|
||||||
is-utf8 "^0.2.1"
|
|
||||||
|
|
||||||
strip-bom-stream@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz#f87db5ef2613f6968aa545abfe1ec728b6a829ca"
|
|
||||||
dependencies:
|
|
||||||
first-chunk-stream "^2.0.0"
|
|
||||||
strip-bom "^2.0.0"
|
|
||||||
|
|
||||||
strip-bom@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e"
|
|
||||||
dependencies:
|
|
||||||
is-utf8 "^0.2.0"
|
|
||||||
|
|
||||||
strip-indent@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-2.0.0.tgz#5ef8db295d01e6ed6cbf7aab96998d7822527b68"
|
|
||||||
|
|
||||||
supports-color@^2.0.0:
|
|
||||||
version "2.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"
|
|
||||||
|
|
||||||
symbol-tree@^3.2.2:
|
|
||||||
version "3.2.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6"
|
|
||||||
|
|
||||||
tapbundle@^1.1.1:
|
|
||||||
version "1.1.8"
|
|
||||||
resolved "https://registry.yarnpkg.com/tapbundle/-/tapbundle-1.1.8.tgz#e08aee0e100a830d8a26a583a85d37ce53312e02"
|
|
||||||
dependencies:
|
|
||||||
"@types/node" "^8.0.33"
|
|
||||||
early "^2.1.1"
|
|
||||||
leakage "^0.3.0"
|
|
||||||
smartchai "^1.0.3"
|
|
||||||
smartdelay "^1.0.3"
|
|
||||||
smartq "^1.1.1"
|
|
||||||
typings-global "^1.0.19"
|
|
||||||
|
|
||||||
through2@^2.0.1:
|
|
||||||
version "2.0.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be"
|
|
||||||
dependencies:
|
|
||||||
readable-stream "^2.1.5"
|
|
||||||
xtend "~4.0.1"
|
|
||||||
|
|
||||||
typings-global@^1.0.14, typings-global@^1.0.16, typings-global@^1.0.19, typings-global@^1.0.20:
|
|
||||||
version "1.0.28"
|
|
||||||
resolved "https://registry.yarnpkg.com/typings-global/-/typings-global-1.0.28.tgz#e28cc965476564cbc00e438739e0aa0735d323d4"
|
|
||||||
|
|
||||||
universalify@^0.1.0:
|
|
||||||
version "0.1.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7"
|
|
||||||
|
|
||||||
util-deprecate@~1.0.1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
|
||||||
|
|
||||||
util.promisify@^1.0.0:
|
|
||||||
version "1.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030"
|
|
||||||
dependencies:
|
|
||||||
define-properties "^1.1.2"
|
|
||||||
object.getownpropertydescriptors "^2.0.3"
|
|
||||||
|
|
||||||
vinyl-file@^3.0.0:
|
|
||||||
version "3.0.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/vinyl-file/-/vinyl-file-3.0.0.tgz#b104d9e4409ffa325faadd520642d0a3b488b365"
|
|
||||||
dependencies:
|
|
||||||
graceful-fs "^4.1.2"
|
|
||||||
pify "^2.3.0"
|
|
||||||
strip-bom-buf "^1.0.0"
|
|
||||||
strip-bom-stream "^2.0.0"
|
|
||||||
vinyl "^2.0.1"
|
|
||||||
|
|
||||||
vinyl@^2.0.1:
|
|
||||||
version "2.1.0"
|
|
||||||
resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-2.1.0.tgz#021f9c2cf951d6b939943c89eb5ee5add4fd924c"
|
|
||||||
dependencies:
|
|
||||||
clone "^2.1.1"
|
|
||||||
clone-buffer "^1.0.0"
|
|
||||||
clone-stats "^1.0.0"
|
|
||||||
cloneable-readable "^1.0.0"
|
|
||||||
remove-trailing-separator "^1.0.1"
|
|
||||||
replace-ext "^1.0.0"
|
|
||||||
|
|
||||||
word-wrap@^1.0.3:
|
|
||||||
version "1.2.3"
|
|
||||||
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
|
|
||||||
|
|
||||||
wrappy@1:
|
|
||||||
version "1.0.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
|
||||||
|
|
||||||
xtend@~4.0.1:
|
|
||||||
version "4.0.1"
|
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
|
Loading…
x
Reference in New Issue
Block a user