Compare commits
135 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
22
.gitignore
vendored
22
.gitignore
vendored
@ -1,4 +1,20 @@
|
|||||||
node_modules/
|
|
||||||
public/
|
|
||||||
coverage/
|
|
||||||
.nogit/
|
.nogit/
|
||||||
|
|
||||||
|
# artifacts
|
||||||
|
coverage/
|
||||||
|
public/
|
||||||
|
pages/
|
||||||
|
|
||||||
|
# installs
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# caches
|
||||||
|
.yarn/
|
||||||
|
.cache/
|
||||||
|
.rpt2_cache
|
||||||
|
|
||||||
|
# builds
|
||||||
|
dist/
|
||||||
|
dist_*/
|
||||||
|
|
||||||
|
# custom
|
150
.gitlab-ci.yml
150
.gitlab-ci.yml
@ -1,16 +1,16 @@
|
|||||||
# gitzone standard
|
# gitzone ci_default
|
||||||
image: hosttoday/ht-docker-node:npmci
|
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
paths:
|
paths:
|
||||||
- .npmci_cache/
|
- .npmci_cache/
|
||||||
key: "$CI_BUILD_STAGE"
|
key: '$CI_BUILD_STAGE'
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- security
|
- security
|
||||||
- test
|
- test
|
||||||
- release
|
- release
|
||||||
- metadata
|
- metadata
|
||||||
|
|
||||||
# ====================
|
# ====================
|
||||||
# security stage
|
# security stage
|
||||||
@ -18,108 +18,114 @@ stages:
|
|||||||
mirror:
|
mirror:
|
||||||
stage: security
|
stage: security
|
||||||
script:
|
script:
|
||||||
- npmci git mirror
|
- npmci git mirror
|
||||||
|
only:
|
||||||
|
- tags
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- lossless
|
||||||
- notpriv
|
- docker
|
||||||
|
- notpriv
|
||||||
|
|
||||||
snyk:
|
auditProductionDependencies:
|
||||||
|
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||||
stage: security
|
stage: security
|
||||||
script:
|
script:
|
||||||
- npmci command npm install -g snyk
|
- npmci npm prepare
|
||||||
- npmci command npm install --ignore-scripts
|
- npmci command npm install --production --ignore-scripts
|
||||||
- npmci command snyk test
|
- npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
- npmci command npm audit --audit-level=high --only=prod --production
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
- notpriv
|
|
||||||
|
auditDevDependencies:
|
||||||
|
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||||
|
stage: security
|
||||||
|
script:
|
||||||
|
- npmci npm prepare
|
||||||
|
- npmci command npm install --ignore-scripts
|
||||||
|
- npmci command npm config set registry https://registry.npmjs.org
|
||||||
|
- npmci command npm audit --audit-level=high --only=dev
|
||||||
|
tags:
|
||||||
|
- docker
|
||||||
|
allow_failure: true
|
||||||
|
|
||||||
# ====================
|
# ====================
|
||||||
# test stage
|
# test stage
|
||||||
# ====================
|
# ====================
|
||||||
testLEGACY:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci node install legacy
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
testLTS:
|
testStable:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- npmci node install lts
|
- npmci npm prepare
|
||||||
- npmci npm install
|
- npmci node install stable
|
||||||
- npmci npm test
|
- npmci npm install
|
||||||
|
- npmci npm test
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
coverage: /\d+.?\d+?\%\s*coverage/
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
- notpriv
|
|
||||||
|
testBuild:
|
||||||
testSTABLE:
|
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- npmci node install stable
|
- npmci npm prepare
|
||||||
- npmci npm install
|
- npmci node install stable
|
||||||
- npmci npm test
|
- npmci npm install
|
||||||
|
- npmci command npm run build
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
coverage: /\d+.?\d+?\%\s*coverage/
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- docker
|
||||||
- notpriv
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
stage: release
|
stage: release
|
||||||
script:
|
script:
|
||||||
- npmci node install stable
|
- npmci node install stable
|
||||||
- npmci npm publish
|
- npmci npm publish
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- lossless
|
||||||
- notpriv
|
- docker
|
||||||
|
- notpriv
|
||||||
|
|
||||||
# ====================
|
# ====================
|
||||||
# metadata stage
|
# metadata stage
|
||||||
# ====================
|
# ====================
|
||||||
codequality:
|
codequality:
|
||||||
stage: metadata
|
stage: metadata
|
||||||
image: docker:stable
|
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
services:
|
only:
|
||||||
- docker:stable-dind
|
- tags
|
||||||
script:
|
script:
|
||||||
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
|
- npmci command npm install -g tslint typescript
|
||||||
- docker run
|
- npmci npm prepare
|
||||||
--env SOURCE_CODE="$PWD"
|
- npmci npm install
|
||||||
--volume "$PWD":/code
|
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
||||||
--volume /var/run/docker.sock:/var/run/docker.sock
|
|
||||||
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
|
|
||||||
artifacts:
|
|
||||||
paths: [codeclimate.json]
|
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- lossless
|
||||||
- priv
|
- docker
|
||||||
|
- priv
|
||||||
|
|
||||||
trigger:
|
trigger:
|
||||||
stage: metadata
|
stage: metadata
|
||||||
script:
|
script:
|
||||||
- npmci trigger
|
- npmci trigger
|
||||||
only:
|
only:
|
||||||
- tags
|
- tags
|
||||||
tags:
|
tags:
|
||||||
- docker
|
- lossless
|
||||||
- notpriv
|
- docker
|
||||||
|
- notpriv
|
||||||
|
|
||||||
pages:
|
pages:
|
||||||
image: hosttoday/ht-docker-node:npmci
|
|
||||||
stage: metadata
|
stage: metadata
|
||||||
script:
|
script:
|
||||||
- npmci command npm install -g npmpage
|
- npmci node install lts
|
||||||
- npmci command npmpage
|
- npmci command npm install -g @gitzone/tsdoc
|
||||||
|
- npmci npm prepare
|
||||||
|
- npmci npm install
|
||||||
|
- npmci command tsdoc
|
||||||
tags:
|
tags:
|
||||||
|
- lossless
|
||||||
- docker
|
- docker
|
||||||
- notpriv
|
- notpriv
|
||||||
only:
|
only:
|
||||||
@ -127,15 +133,5 @@ pages:
|
|||||||
artifacts:
|
artifacts:
|
||||||
expire_in: 1 week
|
expire_in: 1 week
|
||||||
paths:
|
paths:
|
||||||
- public
|
- public
|
||||||
allow_failure: true
|
|
||||||
|
|
||||||
windowsCompatibility:
|
|
||||||
image: stefanscherer/node-windows:10-build-tools
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npm install & npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- windows
|
|
||||||
allow_failure: true
|
allow_failure: true
|
||||||
|
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm test",
|
||||||
|
"name": "Run npm test",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": ["/npmextra.json"],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"npmci": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for npmci"
|
||||||
|
},
|
||||||
|
"gitzone": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for gitzone",
|
||||||
|
"properties": {
|
||||||
|
"projectType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
129
README.md
129
README.md
@ -1,129 +0,0 @@
|
|||||||
# @pushrocks/smartdata
|
|
||||||
|
|
||||||
do more with data and RethinkDB
|
|
||||||
|
|
||||||
## Availabililty
|
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/smartdata)
|
|
||||||
[](https://GitLab.com/pushrocks/smartdata)
|
|
||||||
[](https://github.com/pushrocks/smartdata)
|
|
||||||
[](https://pushrocks.gitlab.io/smartdata/)
|
|
||||||
|
|
||||||
## Status for master
|
|
||||||
|
|
||||||
[](https://GitLab.com/pushrocks/smartdata/commits/master)
|
|
||||||
[](https://GitLab.com/pushrocks/smartdata/commits/master)
|
|
||||||
[](https://www.npmjs.com/package/smartdata)
|
|
||||||
[](https://david-dm.org/pushrocks/smartdata)
|
|
||||||
[](https://www.bithound.io/github/pushrocks/smartdata/master/dependencies/npm)
|
|
||||||
[](https://www.bithound.io/github/pushrocks/smartdata)
|
|
||||||
[](https://snyk.io/test/npm/smartdata)
|
|
||||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
|
||||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
|
||||||
[](http://standardjs.com/)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Use TypeScript for best in class instellisense.
|
|
||||||
|
|
||||||
smartdata is an ODM that adheres to TypeScript practices and uses classes to organize data.
|
|
||||||
It uses RethinkDB as persistent storage.
|
|
||||||
|
|
||||||
## Intention
|
|
||||||
|
|
||||||
There are many ODMs out there, however when we searched for an ODM that uses TypeScript,
|
|
||||||
acts smart while still embracing the NoSQL idea we didn't find a matching solution.
|
|
||||||
This is why we started smartdata.
|
|
||||||
|
|
||||||
How RethinkDB's terms map to the ones of smartdata:
|
|
||||||
|
|
||||||
| MongoDb term | smartdata class |
|
|
||||||
| -------------- | ------------------------------|
|
|
||||||
| Database | smartdata.SmartdataDb |
|
|
||||||
| Collection | smartdata.SmartdataCollection |
|
|
||||||
| Document | smartdata.SmartadataDoc |
|
|
||||||
|
|
||||||
### class Db
|
|
||||||
|
|
||||||
represents a Database. Naturally it has .connect() etc. methods on it.
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
import * as smartdata from 'smartdata';
|
|
||||||
|
|
||||||
const smartdataDb = new smartdata.SmartdataDb({
|
|
||||||
mongoDbUrl: '//someurl',
|
|
||||||
mongoDbName: 'myDatabase',
|
|
||||||
mongoDbPass: 'mypassword'
|
|
||||||
});
|
|
||||||
|
|
||||||
smartdataDb.connect();
|
|
||||||
```
|
|
||||||
|
|
||||||
### class DbCollection
|
|
||||||
|
|
||||||
represents a collection of objects.
|
|
||||||
A collection is defined by the object class (that is extending smartdata.dbdoc) it respresents
|
|
||||||
|
|
||||||
So to get to get access to a specific collection you document
|
|
||||||
|
|
||||||
```typescript
|
|
||||||
// continues from the block before...
|
|
||||||
|
|
||||||
@smartdata.Collection(smartdataDb)
|
|
||||||
class MyObject extends smartdata.DbDoc<MyObject> {
|
|
||||||
// read the next block about DbDoc
|
|
||||||
@smartdata.svDb() property1: string; // @smartdata.svDb() marks the property for db save
|
|
||||||
property2: number; // this one is not marked, so it won't be save upon calling this.save()
|
|
||||||
constructor(optionsArg: { property1: string; property2: number }) {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start to instantiate instances of classes from scratch or database
|
|
||||||
|
|
||||||
let localObject = new MyObject({
|
|
||||||
property1: 'hi',
|
|
||||||
property2: 2
|
|
||||||
});
|
|
||||||
localObject.save(); // saves the object to the database
|
|
||||||
|
|
||||||
// start retrieving instances
|
|
||||||
|
|
||||||
MyObject.getInstance<MyObject>({
|
|
||||||
property: 'hi'
|
|
||||||
}); // outputs a new instance of MyObject with the values from db assigned
|
|
||||||
```
|
|
||||||
|
|
||||||
### class DbDoc
|
|
||||||
|
|
||||||
represents a individual document in a collection
|
|
||||||
and thereby is ideally suited to extend the class you want to actually store.
|
|
||||||
|
|
||||||
**sStore** instances of classes to Db:
|
|
||||||
DbDoc extends your class with the following methods:
|
|
||||||
|
|
||||||
- `.save()` will save (or update) the object you call it on only. Any referenced non-savable objects will not get stored.
|
|
||||||
- `.saveDeep()` does the same like `.save()`.
|
|
||||||
In addition it will look for properties that reference an object
|
|
||||||
that extends DbDoc as well and call .saveDeep() on them as well.
|
|
||||||
Loops are prevented
|
|
||||||
|
|
||||||
**Get** a new class instance from a Doc in the DB:
|
|
||||||
DbDoc exposes a static method that allows you specify a filter to retrieve a cloned class of the one you used to that doc at some point later in time. Yes, let that sink in a minute :)
|
|
||||||
|
|
||||||
So you can just call `.getInstance({ /* filter props here */ })`.
|
|
||||||
|
|
||||||
## TypeScript
|
|
||||||
|
|
||||||
How does TypeScript play into this?
|
|
||||||
Since you define your classes in TypeScript and types flow through smartdata in a generic way
|
|
||||||
you should get all the Intellisense and type checking you love when using smartdata.
|
|
||||||
smartdata itself also bundles typings.
|
|
||||||
So you don't need to install any additional types when importing smartdata.
|
|
||||||
|
|
||||||
For further information read the linked docs at the top of this README.
|
|
||||||
|
|
||||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
|
||||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
|
||||||
|
|
||||||
[](https://push.rocks)
|
|
@ -1,29 +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,14 +5,19 @@
|
|||||||
"dockerSock": false
|
"dockerSock": false
|
||||||
},
|
},
|
||||||
"npmci": {
|
"npmci": {
|
||||||
"npmGlobalTools": [
|
"npmGlobalTools": [],
|
||||||
"@gitzone/npmts"
|
"npmAccessLevel": "public",
|
||||||
],
|
"npmRegistryUrl": "registry.npmjs.org"
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
},
|
||||||
"npmts": {
|
"gitzone": {
|
||||||
"testConfig": {
|
"projectType": "npm",
|
||||||
"parallel": false
|
"module": {
|
||||||
|
"githost": "gitlab.com",
|
||||||
|
"gitscope": "pushrocks",
|
||||||
|
"gitrepo": "smartdata",
|
||||||
|
"shortDescription": "do more with data",
|
||||||
|
"npmPackagename": "@pushrocks/smartdata",
|
||||||
|
"license": "MIT"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
26489
package-lock.json
generated
26489
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
62
package.json
62
package.json
@ -1,14 +1,14 @@
|
|||||||
{
|
{
|
||||||
"name": "@pushrocks/smartdata",
|
"name": "@pushrocks/smartdata",
|
||||||
"version": "3.1.1",
|
"version": "4.0.13",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "do more with data",
|
"description": "do more with data",
|
||||||
"main": "dist/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "tsrun test/test.ts",
|
"test": "(tstest test/)",
|
||||||
"testLocal": "(npmdocker)",
|
"testLocal": "(npmdocker)",
|
||||||
"build": "(npmts)"
|
"build": "(tsbuild --web)"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -21,23 +21,43 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
|
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pushrocks/smartlog": "^1.0.6",
|
"@pushrocks/lik": "^4.0.20",
|
||||||
"@pushrocks/smartpromise": "^2.0.5",
|
"@pushrocks/smartlog": "^2.0.39",
|
||||||
"@types/lodash": "^4.14.110",
|
"@pushrocks/smartpromise": "^3.1.5",
|
||||||
"@types/mongodb": "^3.1.1",
|
"@pushrocks/smartstring": "^3.0.24",
|
||||||
"lik": "^2.0.5",
|
"@pushrocks/smartunique": "^3.0.3",
|
||||||
"lodash": "^4.17.10",
|
"@tsclass/tsclass": "^3.0.33",
|
||||||
"mongodb": "^3.1.1",
|
"@types/lodash": "^4.14.169",
|
||||||
"runtime-type-checks": "0.0.4",
|
"@types/mongodb": "^3.6.12",
|
||||||
"smartstring": "^2.0.28"
|
"lodash": "^4.17.21",
|
||||||
|
"mongodb": "^3.6.6",
|
||||||
|
"runtime-type-checks": "0.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@gitzone/tsrun": "^1.1.2",
|
"@gitzone/tsbuild": "^2.1.25",
|
||||||
"@types/node": "^10.5.2",
|
"@gitzone/tstest": "^1.0.54",
|
||||||
|
"@pushrocks/qenv": "^4.0.10",
|
||||||
|
"@pushrocks/tapbundle": "^3.2.14",
|
||||||
|
"@types/mongodb-memory-server": "^2.3.0",
|
||||||
|
"@types/node": "^15.3.0",
|
||||||
"@types/shortid": "0.0.29",
|
"@types/shortid": "0.0.29",
|
||||||
"mongodb-memory-server": "^1.9.0",
|
"mongodb-memory-server": "^6.9.6",
|
||||||
"qenv": "^1.1.7",
|
"tslint": "^6.1.3",
|
||||||
"shortid": "^2.2.11",
|
"tslint-config-prettier": "^1.18.0"
|
||||||
"tapbundle": "^2.0.2"
|
},
|
||||||
}
|
"files": [
|
||||||
|
"ts/**/*",
|
||||||
|
"ts_web/**/*",
|
||||||
|
"dist/**/*",
|
||||||
|
"dist_*/**/*",
|
||||||
|
"dist_ts/**/*",
|
||||||
|
"dist_ts_web/**/*",
|
||||||
|
"assets/**/*",
|
||||||
|
"cli.js",
|
||||||
|
"npmextra.json",
|
||||||
|
"readme.md"
|
||||||
|
],
|
||||||
|
"browserslist": [
|
||||||
|
"last 1 chrome versions"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
2
qenv.yml
2
qenv.yml
@ -1,4 +1,4 @@
|
|||||||
vars:
|
required:
|
||||||
- MONGO_URL
|
- MONGO_URL
|
||||||
- MONGO_DBNAME
|
- MONGO_DBNAME
|
||||||
- MONGO_PASS
|
- MONGO_PASS
|
||||||
|
156
readme.md
Normal file
156
readme.md
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
# @pushrocks/smartdata
|
||||||
|
do more with data
|
||||||
|
|
||||||
|
## Availabililty and Links
|
||||||
|
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartdata)
|
||||||
|
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartdata)
|
||||||
|
* [github.com (source mirror)](https://github.com/pushrocks/smartdata)
|
||||||
|
* [docs (typedoc)](https://pushrocks.gitlab.io/smartdata/)
|
||||||
|
|
||||||
|
## Status for master
|
||||||
|
|
||||||
|
Status Category | Status Badge
|
||||||
|
-- | --
|
||||||
|
GitLab Pipelines | [](https://lossless.cloud)
|
||||||
|
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||||
|
npm | [](https://lossless.cloud)
|
||||||
|
Snyk | [](https://lossless.cloud)
|
||||||
|
TypeScript Support | [](https://lossless.cloud)
|
||||||
|
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||||
|
Code Style | [](https://lossless.cloud)
|
||||||
|
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||||
|
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||||
|
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||||
|
Platform support | [](https://lossless.cloud) [](https://lossless.cloud)
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Use TypeScript for best in class instellisense.
|
||||||
|
|
||||||
|
smartdata is an ODM that adheres to TypeScript practices and uses classes to organize data.
|
||||||
|
It uses RethinkDB as persistent storage.
|
||||||
|
|
||||||
|
## Intention
|
||||||
|
|
||||||
|
There are many ODMs out there, however when we searched for an ODM that uses TypeScript,
|
||||||
|
acts smart while still embracing the NoSQL idea we didn't find a matching solution.
|
||||||
|
This is why we started smartdata.
|
||||||
|
|
||||||
|
How RethinkDB's terms map to the ones of smartdata:
|
||||||
|
|
||||||
|
| MongoDb term | smartdata class |
|
||||||
|
| ------------ | ----------------------------- |
|
||||||
|
| Database | smartdata.SmartdataDb |
|
||||||
|
| Collection | smartdata.SmartdataCollection |
|
||||||
|
| Document | smartdata.SmartadataDoc |
|
||||||
|
|
||||||
|
### class Db
|
||||||
|
|
||||||
|
represents a Database. Naturally it has .connect() etc. methods on it.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Assuming toplevel await
|
||||||
|
import * as smartdata from 'smartdata';
|
||||||
|
|
||||||
|
const smartdataDb = new smartdata.SmartdataDb({
|
||||||
|
mongoDbUrl: '//someurl',
|
||||||
|
mongoDbName: 'myDatabase',
|
||||||
|
mongoDbPass: 'mypassword',
|
||||||
|
});
|
||||||
|
|
||||||
|
await smartdataDb.connect();
|
||||||
|
```
|
||||||
|
|
||||||
|
### class DbCollection
|
||||||
|
|
||||||
|
represents a collection of objects.
|
||||||
|
A collection is defined by the object class (that is extending smartdata.dbdoc) it respresents
|
||||||
|
|
||||||
|
So to get to get access to a specific collection you document
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Assuming toplevel await
|
||||||
|
// continues from the block before...
|
||||||
|
|
||||||
|
@smartdata.Collection(smartdataDb)
|
||||||
|
class MyObject extends smartdata.DbDoc<MyObject /* ,[an optional interface to implement] */> {
|
||||||
|
// read the next block about DbDoc
|
||||||
|
@smartdata.svDb()
|
||||||
|
property1: string; // @smartdata.svDb() marks the property for db save
|
||||||
|
|
||||||
|
property2: number; // this one is not marked, so it won't be save upon calling this.save()
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
super(); // the super call is important ;) But you probably know that.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// start to instantiate instances of classes from scratch or database
|
||||||
|
|
||||||
|
const localObject = new MyObject({
|
||||||
|
property1: 'hi',
|
||||||
|
property2: {
|
||||||
|
deep: 3
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await localObject.save(); // saves the object to the database
|
||||||
|
|
||||||
|
// start retrieving instances
|
||||||
|
|
||||||
|
// .getInstance is staticly inheritied, yet fully typed static function to get instances with fully typed filters
|
||||||
|
const myInstance = await MyObject.getInstance({
|
||||||
|
property1: 'hi',
|
||||||
|
property2: {
|
||||||
|
deep: {
|
||||||
|
$gt: 2
|
||||||
|
} as any
|
||||||
|
}
|
||||||
|
}); // outputs a new instance of MyObject with the values from db assigned
|
||||||
|
```
|
||||||
|
|
||||||
|
### class DbDoc
|
||||||
|
|
||||||
|
represents a individual document in a collection
|
||||||
|
and thereby is ideally suited to extend the class you want to actually store.
|
||||||
|
|
||||||
|
### CRUD operations
|
||||||
|
|
||||||
|
smartdata supports full CRUD operations
|
||||||
|
|
||||||
|
**Store** or **Update** instances of classes to MongoDB:
|
||||||
|
DbDoc extends your class with the following methods:
|
||||||
|
|
||||||
|
- async `.save()` will save (or update) the object you call it on only. Any referenced non-savable objects will not get stored.
|
||||||
|
- async `.saveDeep()` does the same like `.save()`.
|
||||||
|
In addition it will look for properties that reference an object
|
||||||
|
that extends DbDoc as well and call .saveDeep() on them as well.
|
||||||
|
Loops are prevented
|
||||||
|
|
||||||
|
**Get** a new class instance from MongoDB:
|
||||||
|
DbDoc exposes a static method that allows you specify a filter to retrieve a cloned class of the one you used to that doc at some point later in time:
|
||||||
|
|
||||||
|
- static async `.getInstance({ /* filter props here */ })` gets you an instance that has the data of the first matched document as properties.
|
||||||
|
- static async `getInstances({ /* filter props here */ })` get you an array instances (one instance for every matched document).
|
||||||
|
|
||||||
|
**Delete** instances from MongoDb:
|
||||||
|
smartdata extends your class with a method to easily delete the doucment from DB:
|
||||||
|
|
||||||
|
- async `.delete()`will delete the document from 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. You don't need to install any additional types for smartdata.
|
||||||
|
|
||||||
|
## Contribution
|
||||||
|
|
||||||
|
We are always happy for code contributions. If you are not the code contributing type that is ok. Still, maintaining Open Source repositories takes considerable time and thought. If you like the quality of what we do and our modules are useful to you we would appreciate a little monthly contribution: You can [contribute one time](https://lossless.link/contribute-onetime) or [contribute monthly](https://lossless.link/contribute). :)
|
||||||
|
|
||||||
|
For further information read the linked docs at the top of this readme.
|
||||||
|
|
||||||
|
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||||
|
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
||||||
|
|
||||||
|
[](https://maintainedby.lossless.com)
|
208
test/test.ts
208
test/test.ts
@ -1,26 +1,52 @@
|
|||||||
import { tap, expect } from 'tapbundle';
|
import { tap, expect } from '@pushrocks/tapbundle';
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
import { Qenv } from '@pushrocks/qenv';
|
||||||
import { Qenv } from 'qenv';
|
|
||||||
|
|
||||||
let testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
|
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
|
||||||
|
|
||||||
|
console.log(process.memoryUsage());
|
||||||
|
|
||||||
// the tested module
|
// the tested module
|
||||||
import * as smartdata from '../ts';
|
import * as smartdata from '../ts/index';
|
||||||
import { smartstring } from '../ts/smartdata.plugins';
|
|
||||||
import * as shortid from 'shortid';
|
import * as mongoPlugin from 'mongodb-memory-server';
|
||||||
|
import { smartunique } from '../ts/smartdata.plugins';
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// Connecting to the database server
|
// Connecting to the database server
|
||||||
// =======================================
|
// =======================================
|
||||||
|
|
||||||
let testDb = new smartdata.SmartdataDb({
|
let testDb: smartdata.SmartdataDb;
|
||||||
mongoDbName: process.env.MONGO_DBNAME,
|
let smartdataOptions: smartdata.IMongoDescriptor;
|
||||||
mongoDbUrl: process.env.MONGO_URL,
|
let mongod: mongoPlugin.MongoMemoryServer;
|
||||||
mongoDbPass: process.env.MONGO_PASS
|
|
||||||
|
const totalCars = 2000;
|
||||||
|
|
||||||
|
tap.skip.test('should create a testinstance as database', async () => {
|
||||||
|
mongod = new mongoPlugin.MongoMemoryServer({});
|
||||||
|
console.log('created mongod instance');
|
||||||
|
await mongod._startUpInstance().catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
console.log('mongod started');
|
||||||
|
smartdataOptions = {
|
||||||
|
mongoDbName: await mongod.getDbName(),
|
||||||
|
mongoDbPass: '',
|
||||||
|
mongoDbUrl: await mongod.getUri(),
|
||||||
|
};
|
||||||
|
console.log(smartdataOptions);
|
||||||
|
testDb = new smartdata.SmartdataDb(smartdataOptions);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should establish a connection to the rethink Db cluster', async () => {
|
tap.test('should connect to atlas', async (tools) => {
|
||||||
await testDb.connect();
|
const databaseName = `test-smartdata-${smartunique.shortId()}`;
|
||||||
|
testDb = new smartdata.SmartdataDb({
|
||||||
|
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
|
||||||
|
mongoDbName: databaseName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should establish a connection to mongod', async () => {
|
||||||
|
await testDb.init();
|
||||||
});
|
});
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
@ -31,11 +57,24 @@ tap.test('should establish a connection to the rethink Db cluster', async () =>
|
|||||||
// Collections
|
// Collections
|
||||||
// ------
|
// ------
|
||||||
|
|
||||||
@smartdata.Collection(testDb)
|
@smartdata.Collection(() => {
|
||||||
class Car extends smartdata.SmartDataDbDoc<Car> {
|
return testDb;
|
||||||
@smartdata.unI() index: string = shortid();
|
})
|
||||||
@smartdata.svDb() color: string;
|
class Car extends smartdata.SmartDataDbDoc<Car, Car> {
|
||||||
@smartdata.svDb() brand: string;
|
@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) {
|
constructor(colorArg: string, brandArg: string) {
|
||||||
super();
|
super();
|
||||||
this.color = colorArg;
|
this.color = colorArg;
|
||||||
@ -46,20 +85,139 @@ class Car extends smartdata.SmartDataDbDoc<Car> {
|
|||||||
tap.test('should save the car to the db', async () => {
|
tap.test('should save the car to the db', async () => {
|
||||||
const myCar = new Car('red', 'Volvo');
|
const myCar = new Car('red', 'Volvo');
|
||||||
await myCar.save();
|
await myCar.save();
|
||||||
|
|
||||||
|
const myCar2 = new Car('red', 'Volvo');
|
||||||
|
await myCar2.save();
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
process.memoryUsage();
|
||||||
|
do {
|
||||||
|
const myCar3 = new Car('red', 'Renault');
|
||||||
|
await myCar3.save();
|
||||||
|
counter++;
|
||||||
|
if (counter % 100 === 0) {
|
||||||
|
console.log(
|
||||||
|
`Filled database with ${counter} of ${totalCars} Cars and memory usage ${
|
||||||
|
process.memoryUsage().rss / 1e6
|
||||||
|
} MB`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} while (counter < totalCars);
|
||||||
|
console.log(process.memoryUsage());
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('expect to get instance of Car', async () => {
|
tap.test('expect to get instance of Car with shallow match', async () => {
|
||||||
let myCars = await Car.getInstances<Car>({
|
const totalQueryCycles = totalCars / 4;
|
||||||
brand: 'Volvo'
|
let counter = 0;
|
||||||
});
|
do {
|
||||||
expect(myCars[0].color).to.equal('red');
|
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).to.equal('yes');
|
||||||
|
expect(myCars[0].brand).to.equal('Renault');
|
||||||
|
counter++;
|
||||||
|
} while (counter < totalQueryCycles);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tap.test('expect to get instance of Car with deep match', async () => {
|
||||||
|
const totalQueryCycles = totalCars / 4;
|
||||||
|
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).to.equal('yes');
|
||||||
|
expect(myCars2[0].brand).to.equal('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).to.equal('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).to.equal('blue');
|
||||||
|
for (const myCar of myCars) {
|
||||||
|
await myCar.delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
const myCar2 = await Car.getInstance<Car>({
|
||||||
|
brand: 'Volvo',
|
||||||
|
});
|
||||||
|
expect(myCar2.color).to.equal('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 myTruck = await Truck.getInstance({ color: 'blue' });
|
||||||
|
myTruck.id = 'foo';
|
||||||
|
await myTruck.save();
|
||||||
|
const myTruck2 = await Truck.getInstance({ color: 'blue' });
|
||||||
|
console.log(myTruck2);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should ', async () => {})
|
||||||
|
|
||||||
// =======================================
|
// =======================================
|
||||||
// close the database connection
|
// close the database connection
|
||||||
// =======================================
|
// =======================================
|
||||||
tap.test('should close the database connection', async tools => {
|
tap.test('should close the database connection', async (tools) => {
|
||||||
|
await testDb.mongoDb.dropDatabase();
|
||||||
await testDb.close();
|
await testDb.close();
|
||||||
|
try {
|
||||||
|
await mongod.stop();
|
||||||
|
} catch (e) {}
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start({throwOnError: true});
|
tap.start({ throwOnError: true });
|
||||||
|
110
test/test.typescript.ts
Normal file
110
test/test.typescript.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
import { tap, expect } from '@pushrocks/tapbundle';
|
||||||
|
import { Qenv } from '@pushrocks/qenv';
|
||||||
|
|
||||||
|
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
|
||||||
|
|
||||||
|
console.log(process.memoryUsage());
|
||||||
|
|
||||||
|
// the tested module
|
||||||
|
import * as smartdata from '../ts/index';
|
||||||
|
|
||||||
|
import * as mongoPlugin from 'mongodb-memory-server';
|
||||||
|
import { smartunique } from '../ts/smartdata.plugins';
|
||||||
|
|
||||||
|
// =======================================
|
||||||
|
// Connecting to the database server
|
||||||
|
// =======================================
|
||||||
|
|
||||||
|
let testDb: smartdata.SmartdataDb;
|
||||||
|
let smartdataOptions: smartdata.IMongoDescriptor;
|
||||||
|
let mongod: mongoPlugin.MongoMemoryServer;
|
||||||
|
|
||||||
|
const totalCars = 2000;
|
||||||
|
|
||||||
|
tap.skip.test('should create a testinstance as database', async () => {
|
||||||
|
mongod = new mongoPlugin.MongoMemoryServer({});
|
||||||
|
console.log('created mongod instance');
|
||||||
|
await mongod._startUpInstance().catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
console.log('mongod started');
|
||||||
|
smartdataOptions = {
|
||||||
|
mongoDbName: await mongod.getDbName(),
|
||||||
|
mongoDbPass: '',
|
||||||
|
mongoDbUrl: await mongod.getUri(),
|
||||||
|
};
|
||||||
|
console.log(smartdataOptions);
|
||||||
|
testDb = new smartdata.SmartdataDb(smartdataOptions);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should connect to atlas', async (tools) => {
|
||||||
|
const databaseName = `test-smartdata-${smartunique.shortId()}`;
|
||||||
|
testDb = new smartdata.SmartdataDb({
|
||||||
|
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
|
||||||
|
mongoDbName: databaseName,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should establish a connection to mongod', async () => {
|
||||||
|
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('should close the database connection', async (tools) => {
|
||||||
|
await testDb.mongoDb.dropDatabase();
|
||||||
|
await testDb.close();
|
||||||
|
try {
|
||||||
|
await mongod.stop();
|
||||||
|
} catch (e) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.start({ throwOnError: true });
|
@ -1,3 +1,5 @@
|
|||||||
export * from './smartdata.classes.db';
|
export * from './smartdata.classes.db';
|
||||||
export * from './smartdata.classes.collection';
|
export * from './smartdata.classes.collection';
|
||||||
export * from './smartdata.classes.doc';
|
export * from './smartdata.classes.doc';
|
||||||
|
|
||||||
|
export { IMongoDescriptor } from './interfaces';
|
||||||
|
1
ts/interfaces/index.ts
Normal file
1
ts/interfaces/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './mongodescriptor';
|
22
ts/interfaces/mongodescriptor.ts
Normal file
22
ts/interfaces/mongodescriptor.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export interface IMongoDescriptor {
|
||||||
|
/**
|
||||||
|
* the URL to connect to
|
||||||
|
*/
|
||||||
|
mongoDbUrl: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the db to use for the project
|
||||||
|
*/
|
||||||
|
mongoDbName?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a username to use to connect to the database
|
||||||
|
*/
|
||||||
|
|
||||||
|
mongoDbUser?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* an optional password that will be replace <PASSWORD> in the connection string
|
||||||
|
*/
|
||||||
|
mongoDbPass?: string;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import * as plugins from './smartdata.plugins';
|
import * as plugins from './smartdata.plugins';
|
||||||
import { SmartdataDb } from './smartdata.classes.db';
|
import { SmartdataDb } from './smartdata.classes.db';
|
||||||
import { SmartDataDbDoc } from './smartdata.classes.doc';
|
import { SmartDataDbDoc } from './smartdata.classes.doc';
|
||||||
|
import { CollectionFactory } from './smartdata.classes.collectionfactory';
|
||||||
|
|
||||||
export interface IFindOptions {
|
export interface IFindOptions {
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@ -13,61 +14,141 @@ export interface IDocValidationFunc<T> {
|
|||||||
(doc: T): boolean;
|
(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
|
* This is a decorator that will tell the decorated class what dbTable to use
|
||||||
* @param db
|
* @param dbArg
|
||||||
*/
|
*/
|
||||||
export function Collection(db: SmartdataDb) {
|
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
|
||||||
return function(constructor) {
|
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
|
||||||
constructor['smartdataCollection'] = new SmartdataCollection(constructor, db);
|
return class extends constructor {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 Manager<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
|
||||||
|
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
|
||||||
|
return class extends constructor {
|
||||||
|
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['db']) {
|
||||||
|
manager = (managerArg as TManager);
|
||||||
|
} else {
|
||||||
|
manager = (managerArg as TDelayed<TManager>)();
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
public get manager() {
|
||||||
|
let manager: TManager;
|
||||||
|
if (managerArg['db']) {
|
||||||
|
manager = (managerArg as TManager);
|
||||||
|
} else {
|
||||||
|
manager = (managerArg as TDelayed<TManager>)();
|
||||||
|
}
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// tslint:disable-next-line: max-classes-per-file
|
||||||
export class SmartdataCollection<T> {
|
export class SmartdataCollection<T> {
|
||||||
/**
|
/**
|
||||||
* the collection that is used
|
* the collection that is used
|
||||||
*/
|
*/
|
||||||
mongoDbCollection: plugins.mongodb.Collection;
|
public mongoDbCollection: plugins.mongodb.Collection;
|
||||||
objectValidation: IDocValidationFunc<T> = null;
|
public objectValidation: IDocValidationFunc<T> = null;
|
||||||
collectionName: string;
|
public collectionName: string;
|
||||||
smartdataDb: SmartdataDb;
|
public smartdataDb: SmartdataDb;
|
||||||
uniqueIndexes: string[] = [];
|
public uniqueIndexes: string[] = [];
|
||||||
|
|
||||||
constructor(collectedClassArg: T & SmartDataDbDoc<T>, smartDataDbArg: SmartdataDb) {
|
constructor(classNameArg: string, smartDataDbArg: SmartdataDb) {
|
||||||
// tell the collection where it belongs
|
// tell the collection where it belongs
|
||||||
this.collectionName = collectedClassArg.name;
|
this.collectionName = classNameArg;
|
||||||
this.smartdataDb = smartDataDbArg;
|
this.smartdataDb = smartDataDbArg;
|
||||||
|
|
||||||
// tell the db class about it (important since Db uses different systems under the hood)
|
// tell the db class about it (important since Db uses different systems under the hood)
|
||||||
this.smartdataDb.addTable(this);
|
this.smartdataDb.addCollection(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
|
* makes sure a collection exists within MongoDb that maps to the SmartdataCollection
|
||||||
*/
|
*/
|
||||||
async init() {
|
public async init() {
|
||||||
if (!this.mongoDbCollection) {
|
if (!this.mongoDbCollection) {
|
||||||
// connect this instance to a MongoDB collection
|
// connect this instance to a MongoDB collection
|
||||||
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
|
const availableMongoDbCollections = await this.smartdataDb.mongoDb.collections();
|
||||||
const wantedCollection = availableMongoDbCollections.find(collection => {
|
const wantedCollection = availableMongoDbCollections.find((collection) => {
|
||||||
return collection.collectionName === this.collectionName;
|
return collection.collectionName === this.collectionName;
|
||||||
});
|
});
|
||||||
if (!wantedCollection) {
|
if (!wantedCollection) {
|
||||||
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
|
await this.smartdataDb.mongoDb.createCollection(this.collectionName);
|
||||||
|
console.log(`Successfully initiated Collection ${this.collectionName}`);
|
||||||
}
|
}
|
||||||
this.mongoDbCollection = await this.smartdataDb.mongoDb.collection(this.collectionName);
|
this.mongoDbCollection = this.smartdataDb.mongoDb.collection(this.collectionName);
|
||||||
console.log(`Successfully initiated Collection ${this.collectionName}`)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* mark unique index
|
* mark unique index
|
||||||
*/
|
*/
|
||||||
markUniqueIndexes(keyArrayArg: string[] = []) {
|
public markUniqueIndexes(keyArrayArg: string[] = []) {
|
||||||
for(let key of keyArrayArg) {
|
for (const key of keyArrayArg) {
|
||||||
if(!this.uniqueIndexes.includes(key)) {
|
if (!this.uniqueIndexes.includes(key)) {
|
||||||
this.mongoDbCollection.createIndex(key, {
|
this.mongoDbCollection.createIndex(key, {
|
||||||
unique: true
|
unique: true,
|
||||||
});
|
});
|
||||||
// make sure we only call this once and not for every doc we create
|
// make sure we only call this once and not for every doc we create
|
||||||
this.uniqueIndexes.push(key);
|
this.uniqueIndexes.push(key);
|
||||||
@ -78,14 +159,14 @@ export class SmartdataCollection<T> {
|
|||||||
/**
|
/**
|
||||||
* adds a validation function that all newly inserted and updated objects have to pass
|
* adds a validation function that all newly inserted and updated objects have to pass
|
||||||
*/
|
*/
|
||||||
addDocValidation(funcArg: IDocValidationFunc<T>) {
|
public addDocValidation(funcArg: IDocValidationFunc<T>) {
|
||||||
this.objectValidation = funcArg;
|
this.objectValidation = funcArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* finds an object in the DbCollection
|
* finds an object in the DbCollection
|
||||||
*/
|
*/
|
||||||
async find(filterObject: any): Promise<any> {
|
public async find(filterObject: any): Promise<any> {
|
||||||
await this.init();
|
await this.init();
|
||||||
const result = await this.mongoDbCollection.find(filterObject).toArray();
|
const result = await this.mongoDbCollection.find(filterObject).toArray();
|
||||||
return result;
|
return result;
|
||||||
@ -94,12 +175,11 @@ export class SmartdataCollection<T> {
|
|||||||
/**
|
/**
|
||||||
* create an object in the database
|
* create an object in the database
|
||||||
*/
|
*/
|
||||||
async insert(dbDocArg: T & SmartDataDbDoc<T>): Promise<any> {
|
public async insert(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
await this.init();
|
await this.init();
|
||||||
await this.checkDoc(dbDocArg);
|
await this.checkDoc(dbDocArg);
|
||||||
this.markUniqueIndexes(dbDocArg.uniqueIndexes);
|
this.markUniqueIndexes(dbDocArg.uniqueIndexes);
|
||||||
const saveableObject = await dbDocArg.createSavableObject();
|
const saveableObject = await dbDocArg.createSavableObject();
|
||||||
console.log(saveableObject);
|
|
||||||
const result = await this.mongoDbCollection.insertOne(saveableObject);
|
const result = await this.mongoDbCollection.insertOne(saveableObject);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -107,11 +187,33 @@ export class SmartdataCollection<T> {
|
|||||||
/**
|
/**
|
||||||
* inserts object into the DbCollection
|
* inserts object into the DbCollection
|
||||||
*/
|
*/
|
||||||
async update(dbDocArg: T & SmartDataDbDoc<T>): Promise<any> {
|
public async update(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
await this.init();
|
await this.init();
|
||||||
await this.checkDoc(dbDocArg);
|
await this.checkDoc(dbDocArg);
|
||||||
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
||||||
const saveableObject = await dbDocArg.createSavableObject();
|
const saveableObject = await dbDocArg.createSavableObject();
|
||||||
this.mongoDbCollection.updateOne(saveableObject.dbDocUniqueId, saveableObject);
|
const updateableObject: any = {};
|
||||||
|
for (const key of Object.keys(saveableObject)) {
|
||||||
|
if (identifiableObject[key]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
updateableObject[key] = saveableObject[key];
|
||||||
|
}
|
||||||
|
const result = await this.mongoDbCollection.updateOne(
|
||||||
|
identifiableObject,
|
||||||
|
{ $set: updateableObject },
|
||||||
|
{ upsert: true }
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(dbDocArg: T & SmartDataDbDoc<T, unknown>): Promise<any> {
|
||||||
|
await this.init();
|
||||||
|
await this.checkDoc(dbDocArg);
|
||||||
|
const identifiableObject = await dbDocArg.createIdentifiableObject();
|
||||||
|
await this.mongoDbCollection.deleteOne(identifiableObject, {
|
||||||
|
w: 1,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -119,7 +221,7 @@ export class SmartdataCollection<T> {
|
|||||||
* if this.objectValidation is not set it passes.
|
* if this.objectValidation is not set it passes.
|
||||||
*/
|
*/
|
||||||
private checkDoc(docArg: T): Promise<void> {
|
private checkDoc(docArg: T): Promise<void> {
|
||||||
let done = plugins.smartq.defer<void>();
|
const done = plugins.smartq.defer<void>();
|
||||||
let validationResult = true;
|
let validationResult = true;
|
||||||
if (this.objectValidation) {
|
if (this.objectValidation) {
|
||||||
validationResult = this.objectValidation(docArg);
|
validationResult = this.objectValidation(docArg);
|
||||||
|
22
ts/smartdata.classes.collectionfactory.ts
Normal file
22
ts/smartdata.classes.collectionfactory.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import * as plugins from './smartdata.plugins';
|
||||||
|
import { SmartdataCollection } from './smartdata.classes.collection';
|
||||||
|
import { SmartdataDb } from './smartdata.classes.db';
|
||||||
|
|
||||||
|
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];
|
||||||
|
};
|
||||||
|
}
|
@ -1,40 +1,24 @@
|
|||||||
import * as plugins from './smartdata.plugins';
|
import * as plugins from './smartdata.plugins';
|
||||||
import { Objectmap } from 'lik';
|
import { ObjectMap } from '@pushrocks/lik';
|
||||||
|
|
||||||
import { SmartdataCollection } from './smartdata.classes.collection';
|
import { SmartdataCollection } from './smartdata.classes.collection';
|
||||||
|
|
||||||
import * as mongoHelpers from './smartdata.mongohelpers';
|
import { logger } from './smartdata.logging';
|
||||||
|
import { IMongoDescriptor } from './interfaces';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* interface - indicates the connection status of the db
|
* interface - indicates the connection status of the db
|
||||||
*/
|
*/
|
||||||
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
|
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
|
||||||
|
|
||||||
export interface ISmartdataOptions {
|
|
||||||
/**
|
|
||||||
* the URL to connect to
|
|
||||||
*/
|
|
||||||
mongoDbUrl: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the db to use for the project
|
|
||||||
*/
|
|
||||||
mongoDbName: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* an optional password that will be replace <PASSWORD> in the connection string
|
|
||||||
*/
|
|
||||||
mongoDbPass?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SmartdataDb {
|
export class SmartdataDb {
|
||||||
smartdataOptions: ISmartdataOptions;
|
smartdataOptions: IMongoDescriptor;
|
||||||
mongoDbClient: plugins.mongodb.MongoClient;
|
mongoDbClient: plugins.mongodb.MongoClient;
|
||||||
mongoDb: plugins.mongodb.Db;
|
mongoDb: plugins.mongodb.Db;
|
||||||
status: TConnectionStatus;
|
status: TConnectionStatus;
|
||||||
smartdataCollectionMap = new Objectmap<SmartdataCollection<any>>();
|
smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>();
|
||||||
|
|
||||||
constructor(smartdataOptions: ISmartdataOptions) {
|
constructor(smartdataOptions: IMongoDescriptor) {
|
||||||
this.smartdataOptions = smartdataOptions;
|
this.smartdataOptions = smartdataOptions;
|
||||||
this.status = 'initial';
|
this.status = 'initial';
|
||||||
}
|
}
|
||||||
@ -44,21 +28,23 @@ export class SmartdataDb {
|
|||||||
/**
|
/**
|
||||||
* connects to the database that was specified during instance creation
|
* connects to the database that was specified during instance creation
|
||||||
*/
|
*/
|
||||||
async connect(): Promise<any> {
|
public async init(): Promise<any> {
|
||||||
let finalConnectionUrl = this.smartdataOptions.mongoDbUrl;
|
const finalConnectionUrl = this.smartdataOptions.mongoDbUrl
|
||||||
if (this.smartdataOptions.mongoDbPass) {
|
.replace('<USERNAME>', this.smartdataOptions.mongoDbUser)
|
||||||
finalConnectionUrl = mongoHelpers.addPassword(
|
.replace('<username>', this.smartdataOptions.mongoDbUser)
|
||||||
this.smartdataOptions.mongoDbUrl,
|
.replace('<USER>', this.smartdataOptions.mongoDbUser)
|
||||||
this.smartdataOptions.mongoDbPass
|
.replace('<user>', this.smartdataOptions.mongoDbUser)
|
||||||
);
|
.replace('<PASSWORD>', this.smartdataOptions.mongoDbPass)
|
||||||
}
|
.replace('<password>', this.smartdataOptions.mongoDbPass)
|
||||||
console.log(finalConnectionUrl);
|
.replace('<DBNAME>', this.smartdataOptions.mongoDbName)
|
||||||
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(
|
.replace('<dbname>', this.smartdataOptions.mongoDbName);
|
||||||
finalConnectionUrl,
|
|
||||||
{
|
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(finalConnectionUrl, {
|
||||||
useNewUrlParser: true
|
useNewUrlParser: true,
|
||||||
}
|
useUnifiedTopology: true,
|
||||||
);
|
maxPoolSize: 100,
|
||||||
|
maxIdleTimeMS: 10,
|
||||||
|
});
|
||||||
this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName);
|
this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName);
|
||||||
this.status = 'connected';
|
this.status = 'connected';
|
||||||
console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`);
|
console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`);
|
||||||
@ -67,17 +53,15 @@ export class SmartdataDb {
|
|||||||
/**
|
/**
|
||||||
* closes the connection to the databse
|
* closes the connection to the databse
|
||||||
*/
|
*/
|
||||||
async close(): Promise<any> {
|
public async close(): Promise<any> {
|
||||||
await this.mongoDbClient.close();
|
await this.mongoDbClient.close();
|
||||||
this.status = 'disconnected';
|
this.status = 'disconnected';
|
||||||
plugins.smartlog
|
logger.log('info', `disconnected from database ${this.smartdataOptions.mongoDbName}`);
|
||||||
.getDefaultLogger()
|
|
||||||
.info(`disconnected from database ${this.smartdataOptions.mongoDbName}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handle table to class distribution
|
// handle table to class distribution
|
||||||
|
|
||||||
addTable(SmartdataCollectionArg: SmartdataCollection<any>) {
|
public addCollection(SmartdataCollectionArg: SmartdataCollection<any>) {
|
||||||
this.smartdataCollectionMap.add(SmartdataCollectionArg);
|
this.smartdataCollectionMap.add(SmartdataCollectionArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,8 +70,8 @@ export class SmartdataDb {
|
|||||||
* @param nameArg
|
* @param nameArg
|
||||||
* @returns DbTable
|
* @returns DbTable
|
||||||
*/
|
*/
|
||||||
async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
|
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
|
||||||
let resultCollection = this.smartdataCollectionMap.find(dbTableArg => {
|
const resultCollection = this.smartdataCollectionMap.find((dbTableArg) => {
|
||||||
return dbTableArg.collectionName === nameArg;
|
return dbTableArg.collectionName === nameArg;
|
||||||
});
|
});
|
||||||
return resultCollection;
|
return resultCollection;
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import * as plugins from './smartdata.plugins';
|
import * as plugins from './smartdata.plugins';
|
||||||
|
|
||||||
import { Objectmap } from 'lik';
|
import { ObjectMap } from '@pushrocks/lik';
|
||||||
|
|
||||||
import { SmartdataDb } from './smartdata.classes.db';
|
import { SmartdataDb } from './smartdata.classes.db';
|
||||||
import { SmartdataCollection } from './smartdata.classes.collection';
|
import { IManager, SmartdataCollection } from './smartdata.classes.collection';
|
||||||
|
|
||||||
export type TDocCreation = 'db' | 'new' | 'mixed';
|
export type TDocCreation = 'db' | 'new' | 'mixed';
|
||||||
|
|
||||||
@ -11,8 +11,8 @@ export type TDocCreation = 'db' | 'new' | 'mixed';
|
|||||||
* saveable - saveable decorator to be used on class properties
|
* saveable - saveable decorator to be used on class properties
|
||||||
*/
|
*/
|
||||||
export function svDb() {
|
export function svDb() {
|
||||||
return (target: SmartDataDbDoc<any>, key: string) => {
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
console.log(`called svDb() on ${key}`);
|
console.log(`called svDb() on >${target.constructor.name}.${key}<`);
|
||||||
if (!target.saveableProperties) {
|
if (!target.saveableProperties) {
|
||||||
target.saveableProperties = [];
|
target.saveableProperties = [];
|
||||||
}
|
}
|
||||||
@ -24,8 +24,8 @@ export function svDb() {
|
|||||||
* unique index - decorator to mark a unique index
|
* unique index - decorator to mark a unique index
|
||||||
*/
|
*/
|
||||||
export function unI() {
|
export function unI() {
|
||||||
return (target: SmartDataDbDoc<any>, key: string) => {
|
return (target: SmartDataDbDoc<unknown, unknown>, key: string) => {
|
||||||
console.log('called unI');
|
console.log(`called unI on >>${target.constructor.name}.${key}<<`);
|
||||||
|
|
||||||
// mark the index as unique
|
// mark the index as unique
|
||||||
if (!target.uniqueIndexes) {
|
if (!target.uniqueIndexes) {
|
||||||
@ -39,66 +39,91 @@ export function unI() {
|
|||||||
}
|
}
|
||||||
target.saveableProperties.push(key);
|
target.saveableProperties.push(key);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class SmartDataDbDoc<T> {
|
export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends IManager = any> {
|
||||||
/**
|
/**
|
||||||
* the collection object an Doc belongs to
|
* the collection object an Doc belongs to
|
||||||
*/
|
*/
|
||||||
collection: SmartdataCollection<T>;
|
public static collection: SmartdataCollection<any>;
|
||||||
|
public collection: SmartdataCollection<any>;
|
||||||
|
public static defaultManager;
|
||||||
|
public static manager;
|
||||||
|
public manager: TManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* how the Doc in memory was created, may prove useful later.
|
* how the Doc in memory was created, may prove useful later.
|
||||||
*/
|
*/
|
||||||
creationStatus: TDocCreation = 'new';
|
public creationStatus: TDocCreation = 'new';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unique indexes
|
* unique indexes
|
||||||
*/
|
*/
|
||||||
uniqueIndexes: string[];
|
public uniqueIndexes: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* an array of saveable properties of a doc
|
* an array of saveable properties of a doc
|
||||||
*/
|
*/
|
||||||
saveableProperties: string[];
|
public saveableProperties: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* name
|
* name
|
||||||
*/
|
*/
|
||||||
name: string;
|
public name: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* primary id in the database
|
* primary id in the database
|
||||||
*/
|
*/
|
||||||
dbDocUniqueId: string;
|
public dbDocUniqueId: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* class constructor
|
* class constructor
|
||||||
*/
|
*/
|
||||||
constructor() {
|
constructor() {}
|
||||||
this.name = this.constructor['name'];
|
|
||||||
this.collection = this.constructor['smartdataCollection'];
|
|
||||||
}
|
|
||||||
|
|
||||||
static async getInstances<T>(filterArg): Promise<T[]> {
|
public static async getInstances<T>(
|
||||||
let self: any = this; // fool typesystem
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
let referenceMongoDBCollection: SmartdataCollection<T> = self.smartdataCollection;
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
||||||
const foundDocs = await referenceMongoDBCollection.find(filterArg);
|
): Promise<T[]> {
|
||||||
const returnArray = [];
|
const convertedFilter: any = {};
|
||||||
for (let item of foundDocs) {
|
const convertFilterArgument = (keyPathArg: string, filterArg2: any) => {
|
||||||
let newInstance = new this();
|
if (typeof filterArg2 === 'object') {
|
||||||
for (let key in item) {
|
for (const key of Object.keys(filterArg2)) {
|
||||||
if (key !== 'id') {
|
if (key.startsWith('$')) {
|
||||||
newInstance[key] = item[key];
|
convertedFilter[keyPathArg] = filterArg2;
|
||||||
|
return;
|
||||||
|
} else if (key.includes('.')) {
|
||||||
|
throw new Error('keys cannot contain dots');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
for (const key of Object.keys(filterArg2)) {
|
||||||
|
convertFilterArgument(`${keyPathArg}.${key}`, filterArg2[key]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
convertedFilter[keyPathArg] = filterArg2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const key of Object.keys(filterArg)) {
|
||||||
|
convertFilterArgument(key, filterArg[key]);
|
||||||
|
}
|
||||||
|
const foundDocs = await (this as any).collection.find(convertedFilter);
|
||||||
|
const returnArray = [];
|
||||||
|
for (const item of foundDocs) {
|
||||||
|
const newInstance = new this();
|
||||||
|
(newInstance as any).creationStatus = 'db';
|
||||||
|
for (const key of Object.keys(item)) {
|
||||||
|
newInstance[key] = item[key];
|
||||||
}
|
}
|
||||||
returnArray.push(newInstance);
|
returnArray.push(newInstance);
|
||||||
}
|
}
|
||||||
return returnArray;
|
return returnArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async getInstance<T>(filterArg): Promise<T> {
|
public static async getInstance<T>(
|
||||||
let result = await this.getInstances<T>(filterArg);
|
this: plugins.tsclass.typeFest.Class<T>,
|
||||||
|
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
|
||||||
|
): Promise<T> {
|
||||||
|
const result = await (this as any).getInstances(filterArg);
|
||||||
if (result && result.length > 0) {
|
if (result && result.length > 0) {
|
||||||
return result[0];
|
return result[0];
|
||||||
}
|
}
|
||||||
@ -108,44 +133,68 @@ export class SmartDataDbDoc<T> {
|
|||||||
* saves this instance but not any connected items
|
* saves this instance but not any connected items
|
||||||
* may lead to data inconsistencies, but is faster
|
* may lead to data inconsistencies, but is faster
|
||||||
*/
|
*/
|
||||||
async save() {
|
public async save() {
|
||||||
let self: any = this;
|
// tslint:disable-next-line: no-this-assignment
|
||||||
|
const self: any = this;
|
||||||
|
let dbResult: any;
|
||||||
switch (this.creationStatus) {
|
switch (this.creationStatus) {
|
||||||
case 'db':
|
case 'db':
|
||||||
await this.collection.update(self);
|
dbResult = await this.collection.update(self);
|
||||||
break;
|
break;
|
||||||
case 'new':
|
case 'new':
|
||||||
let writeResult = await this.collection.insert(self);
|
dbResult = await this.collection.insert(self);
|
||||||
this.creationStatus = 'db';
|
this.creationStatus = 'db';
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
console.error('neither new nor in db?');
|
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
|
* also store any referenced objects to DB
|
||||||
* better for data consistency
|
* better for data consistency
|
||||||
*/
|
*/
|
||||||
saveDeep(savedMapArg: Objectmap<SmartDataDbDoc<any>> = null) {
|
public saveDeep(savedMapArg: ObjectMap<SmartDataDbDoc<any, any>> = null) {
|
||||||
if (!savedMapArg) {
|
if (!savedMapArg) {
|
||||||
savedMapArg = new Objectmap<SmartDataDbDoc<any>>();
|
savedMapArg = new ObjectMap<SmartDataDbDoc<any, any>>();
|
||||||
}
|
}
|
||||||
savedMapArg.add(this);
|
savedMapArg.add(this);
|
||||||
this.save();
|
this.save();
|
||||||
for (let propertyKey in this) {
|
for (const propertyKey of Object.keys(this)) {
|
||||||
let property: any = this[propertyKey];
|
const property: any = this[propertyKey];
|
||||||
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
|
if (property instanceof SmartDataDbDoc && !savedMapArg.checkForObject(property)) {
|
||||||
property.saveDeep(savedMapArg);
|
property.saveDeep(savedMapArg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSavableObject() {
|
/**
|
||||||
let saveableObject: any = {}; // is not exposed to outside, so any is ok here
|
* creates a saveable object so the instance can be persisted as json in the database
|
||||||
for (let propertyNameString of this.saveableProperties) {
|
*/
|
||||||
|
public async createSavableObject(): Promise<TImplements> {
|
||||||
|
const saveableObject: unknown = {}; // is not exposed to outside, so any is ok here
|
||||||
|
for (const propertyNameString of this.saveableProperties) {
|
||||||
saveableObject[propertyNameString] = this[propertyNameString];
|
saveableObject[propertyNameString] = this[propertyNameString];
|
||||||
}
|
}
|
||||||
return saveableObject;
|
return saveableObject as TImplements;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* creates an identifiable object for operations that require filtering
|
||||||
|
*/
|
||||||
|
public async createIdentifiableObject() {
|
||||||
|
const identifiableObject: any = {}; // is not exposed to outside, so any is ok here
|
||||||
|
for (const propertyNameString of this.uniqueIndexes) {
|
||||||
|
identifiableObject[propertyNameString] = this[propertyNameString];
|
||||||
|
}
|
||||||
|
return identifiableObject;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
ts/smartdata.logging.ts
Normal file
3
ts/smartdata.logging.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import * as plugins from './smartdata.plugins';
|
||||||
|
|
||||||
|
export const logger = new plugins.smartlog.ConsoleLog();
|
@ -1,3 +0,0 @@
|
|||||||
export const addPassword = (mongoUrlArg: string, passwordArg: string): string => {
|
|
||||||
return mongoUrlArg.replace('<PASSWORD>', passwordArg);
|
|
||||||
};
|
|
@ -1,8 +1,14 @@
|
|||||||
import * as assert from 'assert';
|
// tsclass scope
|
||||||
|
import * as tsclass from '@tsclass/tsclass';
|
||||||
|
|
||||||
|
export { tsclass };
|
||||||
|
|
||||||
|
// @pushrocks scope
|
||||||
import * as smartlog from '@pushrocks/smartlog';
|
import * as smartlog from '@pushrocks/smartlog';
|
||||||
import * as lodash from 'lodash';
|
import * as lodash from 'lodash';
|
||||||
import * as mongodb from 'mongodb';
|
import * as mongodb from 'mongodb';
|
||||||
import * as smartq from 'smartq';
|
import * as smartq from '@pushrocks/smartpromise';
|
||||||
import * as smartstring from 'smartstring';
|
import * as smartstring from '@pushrocks/smartstring';
|
||||||
|
import * as smartunique from '@pushrocks/smartunique';
|
||||||
|
|
||||||
export { assert, smartlog, lodash, smartq, mongodb, smartstring };
|
export { smartlog, lodash, smartq, mongodb, smartstring, smartunique };
|
||||||
|
17
tslint.json
Normal file
17
tslint.json
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"extends": ["tslint:latest", "tslint-config-prettier"],
|
||||||
|
"rules": {
|
||||||
|
"semicolon": [true, "always"],
|
||||||
|
"no-console": false,
|
||||||
|
"ordered-imports": false,
|
||||||
|
"object-literal-sort-keys": false,
|
||||||
|
"member-ordering": {
|
||||||
|
"options":{
|
||||||
|
"order": [
|
||||||
|
"static-method"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultSeverity": "warning"
|
||||||
|
}
|
Reference in New Issue
Block a user