Compare commits

..

93 Commits

Author SHA1 Message Date
fb39463b7d 5.0.19 2023-06-25 01:53:17 +02:00
44acba80c1 fix(core): update 2023-06-25 01:53:17 +02:00
8cf8315577 5.0.18 2023-06-25 01:51:44 +02:00
9b44b64a50 fix(core): update 2023-06-25 01:51:43 +02:00
699e25201c 5.0.17 2023-06-25 01:30:14 +02:00
2ef9aace68 fix(core): update 2023-06-25 01:30:14 +02:00
cc55a57dfd 5.0.16 2023-06-25 01:29:40 +02:00
b2df512552 fix(core): update 2023-06-25 01:29:39 +02:00
23c62fbd69 5.0.15 2023-06-25 01:28:30 +02:00
5f70ea0b05 fix(core): update 2023-06-25 01:28:29 +02:00
49a595876a 5.0.14 2023-06-25 01:27:10 +02:00
db38a1ef85 fix(core): update 2023-06-25 01:27:09 +02:00
94854638dd 5.0.13 2023-06-24 23:57:35 +02:00
902fab4cc0 fix(core): update 2023-06-24 23:57:34 +02:00
ed3b19abc5 5.0.12 2023-03-21 20:16:08 +01:00
5b88da7dce fix(core): update 2023-03-21 20:16:07 +01:00
df273e9efa 5.0.11 2023-02-06 11:43:11 +01:00
fd590e0be3 fix(core): update 2023-02-06 11:43:11 +01:00
ef97b390d4 5.0.10 2022-11-08 10:26:21 +01:00
cd14eb8bf3 fix(core): update 2022-11-08 10:26:20 +01:00
f48443dcd3 5.0.9 2022-11-01 18:23:58 +01:00
3f28ff80cb fix(core): update 2022-11-01 18:23:57 +01:00
64005a0b32 5.0.8 2022-09-12 11:37:54 +02:00
8a77bb3281 fix(core): update 2022-09-12 11:37:53 +02:00
25f50ecf51 5.0.7 2022-06-14 22:04:35 +02:00
ad87f8147b fix(core): update 2022-06-14 22:04:34 +02:00
da0c9873eb 5.0.6 2022-06-14 22:02:57 +02:00
2fcd3f1550 fix(core): update 2022-06-14 22:02:57 +02:00
f726cf4c5b 5.0.5 2022-06-05 17:19:12 +02:00
c198969fae fix(core): update 2022-06-05 17:19:12 +02:00
be1badeb23 5.0.4 2022-06-05 17:18:13 +02:00
fe065b966f fix(core): update 2022-06-05 17:18:13 +02:00
811e2490b8 5.0.3 2022-05-19 16:15:28 +02:00
206ccd40e9 fix(watcher.changeSubject): now emits correct type into observer functions 2022-05-19 16:15:28 +02:00
055298172f 5.0.2 2022-05-18 18:17:12 +02:00
278f3c8169 fix(tests): now uses @pushrocks/smartmongo backed by wiredTiger 2022-05-18 18:17:11 +02:00
0709ba921b 5.0.1 2022-05-17 23:54:26 +02:00
de1f1110b4 fix(core): update 2022-05-17 23:54:26 +02:00
30f4254428 5.0.0 2022-05-17 21:30:53 +02:00
1c4b03e647 BREAKING CHANGE(core): switch to esm 2022-05-17 21:30:53 +02:00
27cc7651ba 4.0.29 2022-05-17 21:26:17 +02:00
355a2a3f2b fix(core): update 2022-05-17 21:26:17 +02:00
a739582861 4.0.28 2022-05-17 00:33:44 +02:00
37f9a64735 fix(core): update 2022-05-17 00:33:44 +02:00
83a5170591 4.0.27 2021-11-12 19:32:54 +01:00
f94363cf31 fix(core): update 2021-11-12 19:32:54 +01:00
df02e5bb71 4.0.26 2021-11-12 19:16:11 +01:00
38e438c54f fix(core): update 2021-11-12 19:16:11 +01:00
11bc1ac6dc 4.0.25 2021-11-12 19:03:06 +01:00
3431e94ddd fix(core): update 2021-11-12 19:03:06 +01:00
739e040776 4.0.24 2021-11-12 19:02:29 +01:00
28d57efd9e fix(core): update 2021-11-12 19:02:29 +01:00
f50a61308c 4.0.23 2021-11-12 18:12:59 +01:00
42aa9f9f8a fix(core): update 2021-11-12 18:12:59 +01:00
3f591ff9d8 4.0.22 2021-11-12 18:04:08 +01:00
7b33347b4c fix(core): update 2021-11-12 18:04:08 +01:00
3f11cbf595 4.0.21 2021-11-12 17:32:43 +01:00
cf1ec7f9eb fix(core): update 2021-11-12 17:32:43 +01:00
54060deb8f 4.0.20 2021-11-12 17:22:32 +01:00
48cffb5ac2 fix(core): update 2021-11-12 17:22:31 +01:00
8301eb79a2 4.0.19 2021-11-12 16:36:25 +01:00
ad6366a294 fix(core): update 2021-11-12 16:36:25 +01:00
cb6c03ebfe 4.0.18 2021-11-12 16:23:27 +01:00
551916fe5c fix(core): update 2021-11-12 16:23:26 +01:00
9b3892c1e8 4.0.17 2021-10-18 14:37:17 +02:00
c1b1af9c5d fix(dbdoc manager access working again ): update 2021-10-18 14:37:17 +02:00
d3a3d5be9d 4.0.16 2021-10-16 23:33:22 +02:00
c9a734d879 fix(core): update 2021-10-16 23:33:21 +02:00
856e8e7d1f 4.0.15 2021-10-16 21:17:03 +02:00
7a4d557724 fix(core): update 2021-10-16 21:17:02 +02:00
7cbd0bd99b 4.0.14 2021-10-16 19:54:06 +02:00
e10f6585a5 fix(core): update 2021-10-16 19:54:05 +02:00
5c8dffdd9c 4.0.13 2021-10-02 15:03:35 +02:00
846996eeac fix(core): update 2021-10-02 15:03:35 +02:00
668df09ba0 4.0.12 2021-09-19 17:05:09 +02:00
03656f4ca0 fix(core): update 2021-09-19 17:05:08 +02:00
c4c612f3a9 4.0.11 2021-09-18 00:38:21 +02:00
e357f7581c fix(core): update 2021-09-18 00:38:20 +02:00
9697b1e48b 4.0.10 2021-09-17 22:34:15 +02:00
aeb35705d4 fix(core): update 2021-09-17 22:34:15 +02:00
236c8c6551 4.0.9 2021-09-16 19:49:55 +02:00
1f28db15e7 fix(core): update 2021-09-16 19:49:55 +02:00
86d600e287 4.0.8 2021-06-09 15:38:15 +02:00
bb81530dac fix(core): update 2021-06-09 15:38:14 +02:00
b9f9b36b87 4.0.7 2021-06-09 15:37:50 +02:00
df2fadfa01 fix(core): update 2021-06-09 15:37:49 +02:00
8b2beb3485 4.0.6 2021-06-09 14:55:56 +02:00
144a620f43 fix(core): update 2021-06-09 14:55:55 +02:00
c241247845 4.0.5 2021-06-09 14:32:14 +02:00
81e39d09e4 4.0.4 2021-06-09 14:10:08 +02:00
8e51b518b1 fix(core): update 2021-06-09 14:10:08 +02:00
8308d8d03b 4.0.3 2021-06-09 13:40:23 +02:00
97365ddf29 fix(core): update 2021-06-09 13:40:23 +02:00
29 changed files with 6995 additions and 26631 deletions

View File

@ -0,0 +1,107 @@
name: CI Pipeline
on: push
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
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: 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: Code quality
run: |
npmci command npm install -g typescript
npmci npm prepare
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
npmci command npm run buildDocs
- name: Upload artifacts
uses: actions/upload-artifact@v2
with:
name: public
path: public
retention-days: 7

View File

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

24
.vscode/launch.json vendored
View File

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

View File

@ -15,7 +15,7 @@
"githost": "gitlab.com", "githost": "gitlab.com",
"gitscope": "pushrocks", "gitscope": "pushrocks",
"gitrepo": "smartdata", "gitrepo": "smartdata",
"shortDescription": "do more with data", "description": "do more with data",
"npmPackagename": "@pushrocks/smartdata", "npmPackagename": "@pushrocks/smartdata",
"license": "MIT" "license": "MIT"
} }

26257
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,15 @@
{ {
"name": "@pushrocks/smartdata", "name": "@pushrocks/smartdata",
"version": "4.0.2", "version": "5.0.19",
"private": false, "private": false,
"description": "do more with data", "description": "do more with data",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",
"type": "module",
"scripts": { "scripts": {
"test": "(tstest test/)", "test": "tstest test/",
"testLocal": "(npmdocker)", "build": "tsbuild --web --allowimplicitany",
"build": "(tsbuild --web)" "buildDocs": "tsdoc"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -21,29 +22,27 @@
}, },
"homepage": "https://gitlab.com/pushrocks/smartdata#README", "homepage": "https://gitlab.com/pushrocks/smartdata#README",
"dependencies": { "dependencies": {
"@pushrocks/lik": "^4.0.20", "@pushrocks/lik": "^6.0.2",
"@pushrocks/smartlog": "^2.0.39", "@pushrocks/smartdelay": "^3.0.1",
"@pushrocks/smartpromise": "^3.1.5", "@pushrocks/smartlog": "^3.0.1",
"@pushrocks/smartstring": "^3.0.24", "@pushrocks/smartmongo": "^2.0.7",
"@tsclass/tsclass": "^3.0.33", "@pushrocks/smartpromise": "^4.0.2",
"@types/lodash": "^4.14.169", "@pushrocks/smartrx": "^3.0.2",
"@types/mongodb": "^3.6.12", "@pushrocks/smartstring": "^4.0.2",
"lodash": "^4.17.21", "@pushrocks/smarttime": "^4.0.1",
"mongodb": "^3.6.6", "@pushrocks/smartunique": "^3.0.3",
"runtime-type-checks": "0.0.4" "@pushrocks/taskbuffer": "^3.0.10",
"@tsclass/tsclass": "^4.0.42",
"mongodb": "^5.6.0"
}, },
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.1.25", "@gitzone/tsbuild": "^2.1.66",
"@gitzone/tstest": "^1.0.54", "@gitzone/tsrun": "^1.2.42",
"@pushrocks/qenv": "^4.0.10", "@gitzone/tstest": "^1.0.74",
"@pushrocks/smartunique": "^3.0.3", "@pushrocks/qenv": "^5.0.2",
"@pushrocks/tapbundle": "^3.2.14", "@pushrocks/tapbundle": "^5.0.8",
"@types/mongodb-memory-server": "^2.3.0", "@types/node": "^20.3.1",
"@types/node": "^15.3.0", "@types/shortid": "0.0.29"
"@types/shortid": "0.0.29",
"mongodb-memory-server": "^6.9.6",
"tslint": "^6.1.3",
"tslint-config-prettier": "^1.18.0"
}, },
"files": [ "files": [
"ts/**/*", "ts/**/*",

5854
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](htt
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud) PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud) PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud) BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage ## Usage
@ -90,7 +89,7 @@ class MyObject extends smartdata.DbDoc<MyObject /* ,[an optional interface to im
const localObject = new MyObject({ const localObject = new MyObject({
property1: 'hi', property1: 'hi',
property2: { property2: {
deep: 3 deep: 3,
}, },
}); });
await localObject.save(); // saves the object to the database await localObject.save(); // saves the object to the database
@ -102,9 +101,9 @@ const myInstance = await MyObject.getInstance({
property1: 'hi', property1: 'hi',
property2: { property2: {
deep: { deep: {
$gt: 2 $gt: 2,
} as any } as any,
} },
}); // outputs a new instance of MyObject with the values from db assigned }); // outputs a new instance of MyObject with the values from db assigned
``` ```
@ -150,7 +149,6 @@ We are always happy for code contributions. If you are not the code contributing
For further information read the linked docs at the top of this readme. For further information read the linked docs at the top of this readme.
> MIT licensed | **&copy;** [Lossless GmbH](https://lossless.gmbh) ## Legal
> MIT licensed | **&copy;** [Task Venture Capital GmbH](https://task.vc)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy) | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

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

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

View File

@ -1,51 +1,38 @@
import { tap, expect } from '@pushrocks/tapbundle'; import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv'; import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
import * as mongodb from 'mongodb';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/'); const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage()); console.log(process.memoryUsage());
// the tested module // the tested module
import * as smartdata from '../ts/index'; import * as smartdata from '../ts/index.js';
import * as mongoPlugin from 'mongodb-memory-server';
import { smartunique } from '../ts/smartdata.plugins';
// ======================================= // =======================================
// Connecting to the database server // Connecting to the database server
// ======================================= // =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb; let testDb: smartdata.SmartdataDb;
let smartdataOptions: smartdata.IMongoDescriptor;
let mongod: mongoPlugin.MongoMemoryServer;
const totalCars = 2000; const totalCars = 2000;
tap.skip.test('should create a testinstance as database', async () => { tap.test('should create a testinstance as database', async () => {
mongod = new mongoPlugin.MongoMemoryServer({}); smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
console.log('created mongod instance'); testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await mongod._startUpInstance().catch((err) => { await testDb.init();
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) => { tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`; const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({ testDb = new smartdata.SmartdataDb({
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'), mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName, mongoDbName: databaseName,
}); });
});
tap.test('should establish a connection to mongod', async () => {
await testDb.init(); await testDb.init();
}); });
@ -70,6 +57,9 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
@smartdata.svDb() @smartdata.svDb()
public brand: string; public brand: string;
@smartdata.svDb()
public testBuffer = Buffer.from('hello');
@smartdata.svDb() @smartdata.svDb()
deepData = { deepData = {
sodeep: 'yes', sodeep: 'yes',
@ -82,7 +72,7 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
} }
} }
tap.test('should save the car to the db', async () => { tap.test('should save the car to the db', async (toolsArg) => {
const myCar = new Car('red', 'Volvo'); const myCar = new Car('red', 'Volvo');
await myCar.save(); await myCar.save();
@ -90,6 +80,9 @@ tap.test('should save the car to the db', async () => {
await myCar2.save(); await myCar2.save();
let counter = 0; let counter = 0;
const gottenCarInstance = await Car.getInstance({});
console.log(gottenCarInstance.testBuffer instanceof mongodb.Binary);
process.memoryUsage(); process.memoryUsage();
do { do {
const myCar3 = new Car('red', 'Renault'); const myCar3 = new Car('red', 'Renault');
@ -107,7 +100,7 @@ tap.test('should save the car to the db', async () => {
}); });
tap.test('expect to get instance of Car with shallow match', async () => { tap.test('expect to get instance of Car with shallow match', async () => {
const totalQueryCycles = totalCars / 4; const totalQueryCycles = totalCars / 2;
let counter = 0; let counter = 0;
do { do {
const timeStart = Date.now(); const timeStart = Date.now();
@ -121,20 +114,20 @@ tap.test('expect to get instance of Car with shallow match', async () => {
}ms to query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB` }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].deepData.sodeep).toEqual('yes');
expect(myCars[0].brand).to.equal('Renault'); expect(myCars[0].brand).toEqual('Renault');
counter++; counter++;
} while (counter < totalQueryCycles); } while (counter < totalQueryCycles);
}); });
tap.test('expect to get instance of Car with deep match', async () => { tap.test('expect to get instance of Car with deep match', async () => {
const totalQueryCycles = totalCars / 4; const totalQueryCycles = totalCars / 6;
let counter = 0; let counter = 0;
do { do {
const timeStart = Date.now(); const timeStart = Date.now();
const myCars2 = await Car.getInstances({ const myCars2 = await Car.getInstances({
deepData: { deepData: {
sodeep: 'yes' sodeep: 'yes',
}, },
}); });
if (counter % 10 === 0) { if (counter % 10 === 0) {
@ -144,8 +137,8 @@ tap.test('expect to get instance of Car with deep match', async () => {
}ms to deep query a set of 2000 with memory footprint ${process.memoryUsage().rss / 1e6} MB` }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].deepData.sodeep).toEqual('yes');
expect(myCars2[0].brand).to.equal('Volvo'); expect(myCars2[0].brand).toEqual('Volvo');
counter++; counter++;
} while (counter < totalQueryCycles); } while (counter < totalQueryCycles);
}); });
@ -154,7 +147,7 @@ tap.test('expect to get instance of Car and update it', async () => {
const myCar = await Car.getInstance<Car>({ const myCar = await Car.getInstance<Car>({
brand: 'Volvo', brand: 'Volvo',
}); });
expect(myCar.color).to.equal('red'); expect(myCar.color).toEqual('red');
myCar.color = 'blue'; myCar.color = 'blue';
await myCar.save(); await myCar.save();
}); });
@ -165,7 +158,7 @@ tap.test('should be able to delete an instance of car', async () => {
color: 'blue', color: 'blue',
}); });
console.log(myCars); console.log(myCars);
expect(myCars[0].color).to.equal('blue'); expect(myCars[0].color).toEqual('blue');
for (const myCar of myCars) { for (const myCar of myCars) {
await myCar.delete(); await myCar.delete();
} }
@ -173,7 +166,7 @@ tap.test('should be able to delete an instance of car', async () => {
const myCar2 = await Car.getInstance<Car>({ const myCar2 = await Car.getInstance<Car>({
brand: 'Volvo', brand: 'Volvo',
}); });
expect(myCar2.color).to.equal('red'); expect(myCar2.color).toEqual('red');
}); });
// tslint:disable-next-line: max-classes-per-file // tslint:disable-next-line: max-classes-per-file
@ -200,23 +193,31 @@ class Truck extends smartdata.SmartDataDbDoc<Car, Car> {
tap.test('should store a new Truck', async () => { tap.test('should store a new Truck', async () => {
const truck = new Truck('blue', 'MAN'); const truck = new Truck('blue', 'MAN');
await truck.save(); await truck.save();
const myTruck = await Truck.getInstance({ color: 'blue' });
myTruck.id = 'foo';
await myTruck.save();
const myTruck2 = await Truck.getInstance({ color: 'blue' }); const myTruck2 = await Truck.getInstance({ color: 'blue' });
console.log(myTruck2); myTruck2.color = 'red';
await myTruck2.save();
const myTruck3 = await Truck.getInstance({ color: 'blue' });
console.log(myTruck3);
}); });
tap.test('should ', async () => {}) 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.mongoDb.dropDatabase();
await testDb.close(); await testDb.close();
try { if (smartmongoInstance) {
await mongod.stop(); await smartmongoInstance.stop();
} catch (e) {} }
}); });
tap.start({ throwOnError: true }); tap.start({ throwOnError: true });

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

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

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

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

8
ts/00_commitinfo_data.ts Normal file
View File

@ -0,0 +1,8 @@
/**
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@pushrocks/smartdata',
version: '5.0.19',
description: 'do more with data'
}

View File

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

View File

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

View File

@ -1,22 +0,0 @@
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;
}

View File

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

View File

@ -1,7 +1,9 @@
import * as plugins from './smartdata.plugins'; import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db'; import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartDataDbDoc } from './smartdata.classes.doc'; import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { CollectionFactory } from './smartdata.classes.collectionfactory'; import { SmartDataDbDoc } from './smartdata.classes.doc.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
import { CollectionFactory } from './smartdata.classes.collectionfactory.js';
export interface IFindOptions { export interface IFindOptions {
limit?: number; limit?: number;
@ -22,25 +24,91 @@ 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 dbArg * @param dbArg
*/ */
export function Collection<TManager>(dbArg: SmartdataDb | TDelayed<SmartdataDb>, managerArg?: TDelayed<TManager>) { export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) { return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor { return class extends constructor {
public static get collection() { public static get collection() {
if (!(dbArg instanceof SmartdataDb)) {
dbArg = dbArg();
}
return collectionFactory.getCollection(constructor.name, dbArg); return collectionFactory.getCollection(constructor.name, dbArg);
} }
public get collection() { public get collection() {
return collectionFactory.getCollection(constructor.name, dbArg); if (!(dbArg instanceof SmartdataDb)) {
} dbArg = dbArg();
public get manager() {
if (managerArg) {
return managerArg();
} }
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) {
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;
} }
}; };
}; };
} }
// 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
@ -103,12 +171,57 @@ export class SmartdataCollection<T> {
/** /**
* finds an object in the DbCollection * finds an object in the DbCollection
*/ */
public async find(filterObject: any): Promise<any> { public async findOne(filterObject: any): Promise<any> {
await this.init(); await this.init();
const result = await this.mongoDbCollection.find(filterObject).toArray(); const cursor = this.mongoDbCollection.find(filterObject);
const result = await cursor.next();
cursor.close();
return result; 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 * create an object in the database
*/ */
@ -148,9 +261,7 @@ export class SmartdataCollection<T> {
await this.init(); await this.init();
await this.checkDoc(dbDocArg); await this.checkDoc(dbDocArg);
const identifiableObject = await dbDocArg.createIdentifiableObject(); const identifiableObject = await dbDocArg.createIdentifiableObject();
await this.mongoDbCollection.deleteOne(identifiableObject, { await this.mongoDbCollection.deleteOne(identifiableObject);
w: 1,
});
} }
/** /**

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import * as plugins from './smartdata.plugins'; import * as plugins from './smartdata.plugins.js';
import { ObjectMap } from '@pushrocks/lik'; import { ObjectMap } from '@pushrocks/lik';
import { SmartdataCollection } from './smartdata.classes.collection'; import { SmartdataCollection } from './smartdata.classes.collection.js';
import { EasyStore } from './smartdata.classes.easystore.js';
import { logger } from './smartdata.logging'; import { logger } from './smartdata.logging.js';
import { IMongoDescriptor } from './interfaces';
/** /**
* interface - indicates the connection status of the db * interface - indicates the connection status of the db
@ -12,17 +12,24 @@ import { IMongoDescriptor } from './interfaces';
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed'; export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
export class SmartdataDb { export class SmartdataDb {
smartdataOptions: IMongoDescriptor; smartdataOptions: plugins.tsclass.database.IMongoDescriptor;
mongoDbClient: plugins.mongodb.MongoClient; mongoDbClient: plugins.mongodb.MongoClient;
mongoDb: plugins.mongodb.Db; mongoDb: plugins.mongodb.Db;
status: TConnectionStatus; status: TConnectionStatus;
statusConnectedDeferred = plugins.smartpromise.defer();
smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>(); smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>();
constructor(smartdataOptions: IMongoDescriptor) { constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
this.smartdataOptions = smartdataOptions; this.smartdataOptions = smartdataOptions;
this.status = 'initial'; this.status = 'initial';
} }
// easystore
public async createEasyStore(nameIdArg: string) {
const easyStore = new EasyStore(nameIdArg, this);
return easyStore;
}
// basic connection stuff ---------------------------------------------- // basic connection stuff ----------------------------------------------
/** /**
@ -40,13 +47,12 @@ export class SmartdataDb {
.replace('<dbname>', this.smartdataOptions.mongoDbName); .replace('<dbname>', this.smartdataOptions.mongoDbName);
this.mongoDbClient = await plugins.mongodb.MongoClient.connect(finalConnectionUrl, { this.mongoDbClient = await plugins.mongodb.MongoClient.connect(finalConnectionUrl, {
useNewUrlParser: true,
useUnifiedTopology: true,
maxPoolSize: 100, maxPoolSize: 100,
maxIdleTimeMS: 10, maxIdleTimeMS: 10,
}); });
this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName); this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName);
this.status = 'connected'; this.status = 'connected';
this.statusConnectedDeferred.resolve();
console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`); console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`);
} }
@ -71,7 +77,7 @@ export class SmartdataDb {
* @returns DbTable * @returns DbTable
*/ */
public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> { public async getSmartdataCollectionByName<T>(nameArg: string): Promise<SmartdataCollection<T>> {
const resultCollection = this.smartdataCollectionMap.find((dbTableArg) => { const resultCollection = await this.smartdataCollectionMap.find(async (dbTableArg) => {
return dbTableArg.collectionName === nameArg; return dbTableArg.collectionName === nameArg;
}); });
return resultCollection; return resultCollection;

View File

@ -0,0 +1,201 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { Manager, setDefaultManagerForDoc } from './smartdata.classes.collection.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
@Manager()
class DistributedClass extends SmartDataDbDoc<DistributedClass, DistributedClass> {
// INSTANCE
@unI()
public id: string;
@svDb()
public data: {
status: 'bidding' | 'settled' | 'initializing' | 'stopped';
biddingShortcode?: string;
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();
private ownInstance: DistributedClass;
constructor(dbArg?: SmartdataDb) {
super();
this.db = dbArg;
setDefaultManagerForDoc(this, DistributedClass);
this.readyPromise = this.db.statusConnectedDeferred.promise;
this.init();
}
// smartdata specific stuff
public async start() {
await this.init();
}
public async stop() {
if (this.ownInstance?.data.elected) {
this.ownInstance.data.elected = false;
} else {
console.log(`can't stop a distributed instance that has not been started yet.`);
}
}
public id = plugins.smartunique.uni('distributedInstance');
private startHeartbeat = async () => {
while (this.ownInstance.data.status !== 'stopped') {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.lastUpdated = Date.now();
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(10000);
}
};
public 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();
});
}
// lets enable the heartbeat
this.startHeartbeat();
// lets do a leader check
await this.checkAndMaybeLead();
return this.ownInstance;
}
// --> leader election
public async checkAndMaybeLead() {
const allInstances = await DistributedClass.getInstances({});
let leader = allInstances.find((instanceArg) => instanceArg.data.elected === true);
if (
leader &&
leader.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ minutes: 1 })
) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
});
return;
} else {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance.data.status = 'bidding';
this.ownInstance.data.biddingShortcode = plugins.smartunique.shortId();
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(plugins.smarttime.getMilliSecondsFromUnits({ minutes: 2 }));
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
let biddingInstances = await DistributedClass.getInstances({});
biddingInstances = biddingInstances.filter(
(instanceArg) =>
!instanceArg.data.elected &&
instanceArg.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ minutes: 1 })
);
this.ownInstance.data.elected = true;
for (const biddingInstance of biddingInstances) {
if (biddingInstance.data.biddingShortcode < this.ownInstance.data.biddingShortcode) {
this.ownInstance.data.elected = false;
}
}
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() {
const ownInstance = await this.init();
const watcher = await DistributedClass.watch({});
/**
* this function is started once per unique job request
*/
const startResultTimer = async () => {};
watcher.changeSubject.subscribe({
next: async (distributedDoc) => {
distributedDoc;
},
});
while (this.ownInstance.data.status !== 'stopped') {
await plugins.smartdelay.delayFor(1000);
}
}
// abstract implemented methods
public async fireDistributedTaskRequest(
taskRequestArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult> {
const ownInstance = await this.init();
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance.data.taskRequests.push(taskRequestArg);
await this.ownInstance.save();
});
return null;
}
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
);
});
Object.assign(existingInfoBasis, infoBasisArg);
await this.ownInstance.save();
plugins.smartdelay.delayFor(60000).then(() => {
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const indexToRemove = this.ownInstance.data.taskRequests.indexOf(existingInfoBasis);
this.ownInstance.data.taskRequests.splice(indexToRemove, indexToRemove);
await this.ownInstance.save();
});
});
});
}
}

View File

@ -1,9 +1,11 @@
import * as plugins from './smartdata.plugins'; import * as plugins from './smartdata.plugins.js';
import { ObjectMap } from '@pushrocks/lik'; import { ObjectMap } from '@pushrocks/lik';
import { SmartdataDb } from './smartdata.classes.db'; import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartdataCollection } from './smartdata.classes.collection'; import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { type IManager, SmartdataCollection } from './smartdata.classes.collection.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
export type TDocCreation = 'db' | 'new' | 'mixed'; export type TDocCreation = 'db' | 'new' | 'mixed';
@ -41,12 +43,140 @@ export function unI() {
}; };
} }
export class SmartDataDbDoc<T extends TImplements, TImplements> { export const convertFilterForMongoDb = (filterArg: { [key: string]: any }) => {
const convertedFilter: { [key: string]: any } = {};
const convertFilterArgument = (keyPathArg2: string, filterArg2: any) => {
if (typeof filterArg2 === 'object') {
for (const key of Object.keys(filterArg2)) {
if (key.startsWith('$')) {
convertedFilter[keyPathArg2] = filterArg2;
return;
} else if (key.includes('.')) {
throw new Error('keys cannot contain dots');
}
}
for (const key of Object.keys(filterArg2)) {
convertFilterArgument(`${keyPathArg2}.${key}`, filterArg2[key]);
}
} else {
convertedFilter[keyPathArg2] = filterArg2;
}
};
for (const key of Object.keys(filterArg)) {
convertFilterArgument(key, filterArg[key]);
}
return convertedFilter;
};
export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends IManager = any> {
/** /**
* the collection object an Doc belongs to * the collection object an Doc belongs to
*/ */
public static collection: SmartdataCollection<any>; public static collection: SmartdataCollection<any>;
public 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 cursor
* @returns
*/
public static async getCursor<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
) {
const collection: SmartdataCollection<T> = (this as any).collection;
const cursor: SmartdataDbCursor<T> = await collection.getCursor(
convertFilterForMongoDb(filterArg),
this as any as typeof SmartDataDbDoc
);
return cursor;
}
/**
* watch the collection
* @param this
* @param filterArg
* @param forEachFunction
*/
public static async watch<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
) {
const collection: SmartdataCollection<T> = (this as any).collection;
const watcher: SmartdataDbWatcher<T> = await collection.watch(
convertFilterForMongoDb(filterArg),
this as any
);
return watcher;
}
/**
* run a function for all instances
* @returns
*/
public static async forEach<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>,
forEachFunction: (itemArg: T) => Promise<any>
) {
const cursor: SmartdataDbCursor<T> = await (this as any).getCursor(filterArg);
await cursor.forEach(forEachFunction);
}
// INSTANCE
/** /**
* how the Doc in memory was created, may prove useful later. * how the Doc in memory was created, may prove useful later.
@ -78,54 +208,6 @@ export class SmartDataDbDoc<T extends TImplements, TImplements> {
*/ */
constructor() {} constructor() {}
public static async getInstances<T>(
this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T>
): Promise<T[]> {
const convertedFilter: any = {};
const convertFilterArgument = (keyPathArg: string, filterArg2: any) => {
if (typeof filterArg2 === 'object') {
for (const key of Object.keys(filterArg2)) {
if (key.startsWith('$')) {
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);
}
return returnArray;
}
public static async getInstance<T>(
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) {
return result[0];
}
}
/** /**
* 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
@ -173,6 +255,16 @@ export class SmartDataDbDoc<T extends TImplements, TImplements> {
} }
} }
/**
* updates an object from db
*/
public async updateFromDb() {
const mongoDbNativeDoc = await this.collection.findOne(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 * creates a saveable object so the instance can be persisted as json in the database
*/ */

View File

@ -0,0 +1,109 @@
import * as plugins from './smartdata.plugins.js';
import { Collection } from './smartdata.classes.collection.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
/**
* EasyStore allows the storage of easy objects. It also allows easy sharing of the object between different instances
*/
export class EasyStore<T> {
// instance
public smartdataDbRef: SmartdataDb;
public nameId: string;
private easyStoreClass = (() => {
@Collection(() => this.smartdataDbRef)
class SmartdataEasyStore extends SmartDataDbDoc<SmartdataEasyStore, SmartdataEasyStore> {
@unI()
public nameId: string;
@svDb()
public ephermal: {
activated: boolean;
timeout: number;
};
@svDb()
lastEdit: number;
@svDb()
public data: Partial<T>;
}
return SmartdataEasyStore;
})();
constructor(nameIdArg: string, smnartdataDbRefArg: SmartdataDb) {
this.smartdataDbRef = smnartdataDbRefArg;
this.nameId = nameIdArg;
}
private async getEasyStore() {
let easyStore = await this.easyStoreClass.getInstance({
nameId: this.nameId,
});
if (!easyStore) {
easyStore = new this.easyStoreClass();
easyStore.nameId = this.nameId;
easyStore.data = {};
await easyStore.save();
}
return easyStore;
}
/**
* 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(keyArg: keyof T, valueArg: any) {
const easyStore = await this.getEasyStore();
easyStore.data[keyArg] = valueArg;
await easyStore.save();
}
public async deleteKey(keyArg: keyof T) {
const easyStore = await this.getEasyStore();
delete easyStore.data[keyArg];
await easyStore.save();
}
/**
* writes all keyValue pairs in the object argument
*/
public async writeAll(keyValueObject: Partial<T>) {
const easyStore = await this.getEasyStore();
easyStore.data = { ...easyStore.data, ...keyValueObject };
await easyStore.save();
}
/**
* wipes a key value store from disk
*/
public async wipe() {
const easyStore = await this.getEasyStore();
easyStore.data = {};
await easyStore.save();
}
public async cleanUpEphermal() {
while (
(await this.smartdataDbRef.statusConnectedDeferred.promise) &&
this.smartdataDbRef.status === 'connected'
) {}
}
}

View File

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

View File

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

View File

@ -4,11 +4,28 @@ import * as tsclass from '@tsclass/tsclass';
export { tsclass }; export { tsclass };
// @pushrocks scope // @pushrocks scope
import * as lik from '@pushrocks/lik';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartlog from '@pushrocks/smartlog'; import * as smartlog from '@pushrocks/smartlog';
import * as lodash from 'lodash'; import * as smartpromise from '@pushrocks/smartpromise';
import * as mongodb from 'mongodb';
import * as smartq from '@pushrocks/smartpromise'; import * as smartq from '@pushrocks/smartpromise';
import * as smartrx from '@pushrocks/smartrx';
import * as smartstring from '@pushrocks/smartstring'; import * as smartstring from '@pushrocks/smartstring';
import * as smarttime from '@pushrocks/smarttime';
import * as smartunique from '@pushrocks/smartunique'; import * as smartunique from '@pushrocks/smartunique';
import * as taskbuffer from '@pushrocks/taskbuffer';
import * as mongodb from 'mongodb';
export { smartlog, lodash, smartq, mongodb, smartstring, smartunique }; export {
lik,
smartdelay,
smartpromise,
smartlog,
smartq,
smartrx,
mongodb,
smartstring,
smarttime,
smartunique,
taskbuffer,
};

View File

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

View File

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