Compare commits

..

57 Commits

Author SHA1 Message Date
30d81581cf 5.0.41 2024-03-27 17:32:02 +01:00
5e9db12955 fix(core): update 2024-03-27 17:32:01 +01:00
ad2f422c86 5.0.40 2024-03-27 17:30:51 +01:00
17ce14bcb9 fix(core): update 2024-03-27 17:30:50 +01:00
32319e6e77 5.0.39 2024-03-27 17:30:15 +01:00
4cd284eaa9 fix(core): update 2024-03-27 17:30:14 +01:00
00ec2e57c2 5.0.38 2024-03-27 16:24:58 +01:00
765356ce3d fix(core): update 2024-03-27 16:24:57 +01:00
56b8581d2b 5.0.37 2024-03-26 13:22:34 +01:00
37a9df9086 fix(core): update 2024-03-26 13:22:33 +01:00
090fb668cd 5.0.36 2024-03-26 13:21:37 +01:00
a1c807261c fix(core): update 2024-03-26 13:21:36 +01:00
a2ccf15f69 5.0.35 2024-03-26 00:25:06 +01:00
84d48f1914 fix(core): update 2024-03-26 00:25:06 +01:00
1e258e5ffb 5.0.34 2024-03-22 18:36:35 +01:00
19d5f553b9 fix(core): update 2024-03-22 18:36:34 +01:00
7a257ea925 5.0.33 2023-08-21 12:39:49 +02:00
2fa1e89f34 fix(core): update 2023-08-21 12:39:48 +02:00
d6b3896dd3 5.0.32 2023-08-16 13:16:40 +02:00
49b11b17ce fix(core): update 2023-08-16 13:16:39 +02:00
4ac8a4c0cd 5.0.31 2023-08-16 12:08:28 +02:00
7f9983382a fix(core): update 2023-08-16 12:08:27 +02:00
54f529b0a7 5.0.30 2023-08-15 19:55:53 +02:00
f542463bf6 fix(core): update 2023-08-15 19:55:52 +02:00
1235ae2eb3 5.0.29 2023-08-15 19:55:23 +02:00
8166d2f7c2 fix(core): update 2023-08-15 19:55:22 +02:00
7c9f27e02f 5.0.28 2023-08-15 01:24:30 +02:00
842e4b280b fix(core): update 2023-08-15 01:24:29 +02:00
009f3297b2 5.0.27 2023-08-15 01:01:16 +02:00
2ff3a4e0b7 fix(core): update 2023-08-15 01:01:16 +02:00
0e55cd8876 5.0.26 2023-08-12 23:32:40 +02:00
eccdf3f00a fix(core): update 2023-08-12 23:32:39 +02:00
c7544133d9 5.0.25 2023-08-12 23:32:02 +02:00
c7c9acf5bd fix(core): update 2023-08-12 23:32:02 +02:00
c99ec50853 5.0.24 2023-08-10 18:06:46 +02:00
4dd9557e1d fix(core): update 2023-08-10 18:06:45 +02:00
52b34a6da1 5.0.23 2023-07-21 20:09:51 +02:00
1bf74fe04d fix(core): update 2023-07-21 20:09:51 +02:00
fdd875ad31 5.0.22 2023-07-21 20:08:50 +02:00
a7bf0c0298 fix(core): update 2023-07-21 20:08:50 +02:00
59d6336e43 5.0.21 2023-07-21 20:08:18 +02:00
e0fc81179a fix(core): update 2023-07-21 20:08:18 +02:00
5aa81a56a2 switch to new org scheme 2023-07-10 02:43:00 +02:00
9ae26177b8 5.0.20 2023-06-25 02:01:20 +02:00
26ac52d6c5 fix(core): update 2023-06-25 02:01:19 +02:00
fb39463b7d 5.0.19 2023-06-25 01:53:17 +02:00
44acba80c1 fix(core): update 2023-06-25 01:53:17 +02:00
8cf8315577 5.0.18 2023-06-25 01:51:44 +02:00
9b44b64a50 fix(core): update 2023-06-25 01:51:43 +02:00
699e25201c 5.0.17 2023-06-25 01:30:14 +02:00
2ef9aace68 fix(core): update 2023-06-25 01:30:14 +02:00
cc55a57dfd 5.0.16 2023-06-25 01:29:40 +02:00
b2df512552 fix(core): update 2023-06-25 01:29:39 +02:00
23c62fbd69 5.0.15 2023-06-25 01:28:30 +02:00
5f70ea0b05 fix(core): update 2023-06-25 01:28:29 +02:00
49a595876a 5.0.14 2023-06-25 01:27:10 +02:00
db38a1ef85 fix(core): update 2023-06-25 01:27:09 +02:00
23 changed files with 3324 additions and 1997 deletions

View File

@ -0,0 +1,66 @@
name: Default (not tags)
on:
push:
tags-ignore:
- '**'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Install pnpm and npmci
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
- name: Run npm prepare
run: npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build

View File

@ -0,0 +1,124 @@
name: Default (tags)
on:
push:
tags:
- '*'
env:
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
jobs:
security:
runs-on: ubuntu-latest
continue-on-error: true
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Audit production dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --prod
continue-on-error: true
- name: Audit development dependencies
run: |
npmci command npm config set registry https://registry.npmjs.org
npmci command pnpm audit --audit-level=high --dev
continue-on-error: true
test:
if: ${{ always() }}
needs: security
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Test stable
run: |
npmci node install stable
npmci npm install
npmci npm test
- name: Test build
run: |
npmci node install stable
npmci npm install
npmci npm build
release:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Release
run: |
npmci node install stable
npmci npm publish
metadata:
needs: test
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
container:
image: ${{ env.IMAGE }}
continue-on-error: true
steps:
- uses: actions/checkout@v3
- name: Prepare
run: |
pnpm install -g pnpm
pnpm install -g @shipzone/npmci
npmci npm prepare
- name: Code quality
run: |
npmci command npm install -g typescript
npmci npm install
- name: Trigger
run: npmci trigger
- name: Build docs and upload artifacts
run: |
npmci node install stable
npmci npm install
pnpm install -g @gitzone/tsdoc
npmci command tsdoc
continue-on-error: true

View File

@ -1,128 +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
before_script:
- pnpm install -g pnpm
- pnpm install -g @shipzone/npmci
- npmci npm prepare
# ====================
# security stage
# ====================
# ====================
# security stage
# ====================
auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --prod
tags:
- lossless
- docker
allow_failure: true
auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security
script:
- npmci command npm config set registry https://registry.npmjs.org
- npmci command pnpm audit --audit-level=high --dev
tags:
- lossless
- docker
allow_failure: true
# ====================
# test stage
# ====================
testStable:
stage: test
script:
- npmci node install stable
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
testBuild:
stage: test
script:
- 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 typescript
- npmci npm prepare
- npmci npm install
tags:
- lossless
- docker
- priv
trigger:
stage: metadata
script:
- npmci trigger
only:
- tags
tags:
- lossless
- docker
- notpriv
pages:
stage: metadata
script:
- npmci node install stable
- npmci npm install
- npmci command npm run buildDocs
tags:
- lossless
- docker
- notpriv
only:
- tags
artifacts:
expire_in: 1 week
paths:
- public
allow_failure: true

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "@pushrocks/smartdata",
"version": "5.0.13",
"name": "@push.rocks/smartdata",
"version": "5.0.41",
"private": false,
"description": "do more with data",
"main": "dist_ts/index.js",
@ -22,27 +22,26 @@
},
"homepage": "https://gitlab.com/pushrocks/smartdata#README",
"dependencies": {
"@pushrocks/lik": "^6.0.2",
"@pushrocks/smartdelay": "^3.0.1",
"@pushrocks/smartlog": "^3.0.1",
"@pushrocks/smartmongo": "^2.0.7",
"@pushrocks/smartpromise": "^4.0.2",
"@pushrocks/smartrx": "^3.0.2",
"@pushrocks/smartstring": "^4.0.2",
"@pushrocks/smarttime": "^4.0.1",
"@pushrocks/smartunique": "^3.0.3",
"@pushrocks/taskbuffer": "^3.0.10",
"@tsclass/tsclass": "^4.0.42",
"mongodb": "^5.6.0"
"@push.rocks/lik": "^6.0.14",
"@push.rocks/smartdelay": "^3.0.1",
"@push.rocks/smartlog": "^3.0.2",
"@push.rocks/smartmongo": "^2.0.10",
"@push.rocks/smartpromise": "^4.0.2",
"@push.rocks/smartrx": "^3.0.7",
"@push.rocks/smartstring": "^4.0.15",
"@push.rocks/smarttime": "^4.0.6",
"@push.rocks/smartunique": "^3.0.8",
"@push.rocks/taskbuffer": "^3.1.7",
"@tsclass/tsclass": "^4.0.52",
"mongodb": "^6.5.0"
},
"devDependencies": {
"@gitzone/tsbuild": "^2.1.66",
"@gitzone/tsrun": "^1.2.42",
"@gitzone/tstest": "^1.0.74",
"@pushrocks/qenv": "^5.0.2",
"@pushrocks/tapbundle": "^5.0.8",
"@types/node": "^20.3.1",
"@types/shortid": "0.0.29"
"@gitzone/tsrun": "^1.2.44",
"@gitzone/tstest": "^1.0.77",
"@push.rocks/qenv": "^6.0.5",
"@push.rocks/tapbundle": "^5.0.22",
"@types/node": "^20.11.30"
},
"files": [
"ts/**/*",

4471
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -1,27 +1,26 @@
# @pushrocks/smartdata
# @push.rocks/smartdata
do more with data
## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartdata)
* [gitlab.com (source)](https://gitlab.com/pushrocks/smartdata)
* [github.com (source mirror)](https://github.com/pushrocks/smartdata)
* [docs (typedoc)](https://pushrocks.gitlab.io/smartdata/)
* [npmjs.org (npm package)](https://www.npmjs.com/package/@push.rocks/smartdata)
* [gitlab.com (source)](https://gitlab.com/push.rocks/smartdata)
* [github.com (source mirror)](https://github.com/push.rocks/smartdata)
* [docs (typedoc)](https://push.rocks.gitlab.io/smartdata/)
## Status for master
Status Category | Status Badge
-- | --
GitLab Pipelines | [![pipeline status](https://gitlab.com/pushrocks/smartdata/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/pushrocks/smartdata/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@pushrocks/smartdata)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/pushrocks/smartdata)](https://lossless.cloud)
GitLab Pipelines | [![pipeline status](https://gitlab.com/push.rocks/smartdata/badges/master/pipeline.svg)](https://lossless.cloud)
GitLab Pipline Test Coverage | [![coverage report](https://gitlab.com/push.rocks/smartdata/badges/master/coverage.svg)](https://lossless.cloud)
npm | [![npm downloads per month](https://badgen.net/npm/dy/@push.rocks/smartdata)](https://lossless.cloud)
Snyk | [![Known Vulnerabilities](https://badgen.net/snyk/push.rocks/smartdata)](https://lossless.cloud)
TypeScript Support | [![TypeScript](https://badgen.net/badge/TypeScript/>=%203.x/blue?icon=typescript)](https://lossless.cloud)
node Support | [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@push.rocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@push.rocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@push.rocks/smartdata)](https://lossless.cloud)
## Usage
@ -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.
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
## Legal
> MIT licensed | **©** [Task Venture Capital GmbH](https://task.vc)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

View File

@ -0,0 +1,112 @@
import { tap, expect } from '@push.rocks/tapbundle';
import * as smartmongo from '@push.rocks/smartmongo';
import type * as taskbuffer from '@push.rocks/taskbuffer';
import * as smartdata from '../ts/index.js';
import { SmartdataDistributedCoordinator, DistributedClass } from '../ts/smartdata.classes.distributedcoordinator.js'; // path might need adjusting
const totalInstances = 10;
// =======================================
// Connecting to the database server
// =======================================
let smartmongoInstance: smartmongo.SmartMongo;
let testDb: smartdata.SmartdataDb;
tap.test('should create a testinstance as database', async () => {
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
await testDb.init();
});
tap.test('should instantiate DistributedClass', async (tools) => {
const instance = new DistributedClass();
expect(instance).toBeInstanceOf(DistributedClass);
});
tap.test('DistributedClass should update the time', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const initialTime = distributedCoordinator.ownInstance.data.lastUpdated;
await distributedCoordinator.sendHeartbeat();
const updatedTime = distributedCoordinator.ownInstance.data.lastUpdated;
expect(updatedTime).toBeGreaterThan(initialTime);
await distributedCoordinator.stop();
});
tap.test('should instantiate SmartdataDistributedCoordinator', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
expect(distributedCoordinator).toBeInstanceOf(SmartdataDistributedCoordinator);
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should update leader status', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
await distributedCoordinator.checkAndMaybeLead();
expect(distributedCoordinator.ownInstance.data.elected).toBeOneOf([true, false]);
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should handle distributed task requests', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
taskName: "SampleTask",
taskVersion: "1.0.0", // Assuming it's a version string
taskExecutionTime: Date.now(),
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
status: 'requesting'
};
const response = await distributedCoordinator.fireDistributedTaskRequest(mockTaskRequest);
console.log(response) // based on your expected structure for the response
await distributedCoordinator.stop();
});
tap.test('SmartdataDistributedCoordinator should update distributed task requests', async (tools) => {
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
await distributedCoordinator.start();
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
taskName: "SampleTask",
taskVersion: "1.0.0", // Assuming it's a version string
taskExecutionTime: Date.now(),
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
status: 'requesting'
};
await distributedCoordinator.updateDistributedTaskRequest(mockTaskRequest);
// Here, we can potentially check if a DB entry got updated or some other side-effect of the update method.
await distributedCoordinator.stop();
});
tap.test('should elect only one leader amongst multiple instances', async (tools) => {
const coordinators = Array.from({ length: totalInstances }).map(() => new SmartdataDistributedCoordinator(testDb));
await Promise.all(coordinators.map(coordinator => coordinator.start()));
const leaders = coordinators.filter(coordinator => coordinator.ownInstance.data.elected);
for (const leader of leaders) {
console.log(leader.ownInstance);
}
expect(leaders.length).toEqual(1);
// stopping clears a coordinator from being elected.
await Promise.all(coordinators.map(coordinator => coordinator.stop()));
});
tap.test('should clean up', async () => {
await smartmongoInstance.stopAndDumpToDir(`.nogit/testdata/`);
setTimeout(() => process.exit(), 2000);
})
tap.start({ throwOnError: true });

View File

@ -1,6 +1,6 @@
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo';
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
@ -26,7 +26,7 @@ tap.test('should create a testinstance as database', async () => {
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();

View File

@ -1,6 +1,6 @@
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo';
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
import * as mongodb from 'mongodb';
@ -30,7 +30,7 @@ tap.test('should create a testinstance as database', async () => {
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();
@ -72,6 +72,11 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
}
}
tap.test('should create a new id', async () => {
const newid = await Car.getNewId();
console.log(newid);
})
tap.test('should save the car to the db', async (toolsArg) => {
const myCar = new Car('red', 'Volvo');
await myCar.save();

View File

@ -1,6 +1,6 @@
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo';
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
@ -28,7 +28,7 @@ tap.test('should create a testinstance as database', async () => {
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();

View File

@ -1,6 +1,6 @@
import { tap, expect } from '@pushrocks/tapbundle';
import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo';
import { tap, expect } from '@push.rocks/tapbundle';
import { Qenv } from '@push.rocks/qenv';
import * as smartmongo from '@push.rocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
@ -28,7 +28,7 @@ tap.test('should create a testinstance as database', async () => {
tap.skip.test('should connect to atlas', async (tools) => {
const databaseName = `test-smartdata-${smartunique.shortId()}`;
testDb = new smartdata.SmartdataDb({
mongoDbUrl: testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbUrl: await testQenv.getEnvVarOnDemand('MONGO_URL'),
mongoDbName: databaseName,
});
await testDb.init();

View File

@ -2,7 +2,7 @@
* autocreated commitinfo by @pushrocks/commitinfo
*/
export const commitinfo = {
name: '@pushrocks/smartdata',
version: '5.0.13',
name: '@push.rocks/smartdata',
version: '5.0.41',
description: 'do more with data'
}

View File

@ -6,9 +6,7 @@ export * from './smartdata.classes.cursor.js';
import * as convenience from './smartadata.convenience.js';
export {
convenience
}
export { convenience };
// to be removed with the next breaking update
import type * as plugins from './smartdata.plugins.js';

View File

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

View File

@ -26,7 +26,8 @@ const collectionFactory = new CollectionFactory();
*/
export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
return function classDecorator<T extends { new (...args: any[]): {} }>(constructor: T) {
return class extends constructor {
const decoratedClass = class extends constructor {
public static className = constructor.name;
public static get collection() {
if (!(dbArg instanceof SmartdataDb)) {
dbArg = dbArg();
@ -40,6 +41,7 @@ export function Collection(dbArg: SmartdataDb | TDelayed<SmartdataDb>) {
return collectionFactory.getCollection(constructor.name, dbArg);
}
};
return decoratedClass;
};
}
@ -56,9 +58,10 @@ export const setDefaultManagerForDoc = <T>(managerArg: IManager, dbDocArg: T): T
* 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>) {
export function managed<TManager extends IManager>(managerArg?: TManager | TDelayed<TManager>) {
return function classDecorator<T extends { new (...args: any[]): any }>(constructor: T) {
return class extends constructor {
const decoratedClass = class extends constructor {
public static className = constructor.name;
public static get collection() {
let dbArg: SmartdataDb;
if (!managerArg) {
@ -106,9 +109,15 @@ export function Manager<TManager extends IManager>(managerArg?: TManager | TDela
return manager;
}
};
return decoratedClass;
};
}
/**
* @dpecrecated use @managed instead
*/
export const Manager = managed;
export class SmartdataCollection<T> {
/**
* the collection that is used
@ -269,7 +278,7 @@ export class SmartdataCollection<T> {
* if this.objectValidation is not set it passes.
*/
private checkDoc(docArg: T): Promise<void> {
const done = plugins.smartq.defer<void>();
const done = plugins.smartpromise.defer<void>();
let validationResult = true;
if (this.objectValidation) {
validationResult = this.objectValidation(docArg);

View File

@ -1,5 +1,4 @@
import * as plugins from './smartdata.plugins.js';
import { ObjectMap } from '@pushrocks/lik';
import { SmartdataCollection } from './smartdata.classes.collection.js';
import { EasyStore } from './smartdata.classes.easystore.js';
@ -17,7 +16,7 @@ export class SmartdataDb {
mongoDb: plugins.mongodb.Db;
status: TConnectionStatus;
statusConnectedDeferred = plugins.smartpromise.defer();
smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>();
smartdataCollectionMap = new plugins.lik.ObjectMap<SmartdataCollection<any>>();
constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
this.smartdataOptions = smartdataOptions;

View File

@ -1,30 +1,32 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { Manager, setDefaultManagerForDoc } from './smartdata.classes.collection.js';
import { managed, setDefaultManagerForDoc } from './smartdata.classes.collection.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
@Manager()
class DistributedClass extends SmartDataDbDoc<DistributedClass, DistributedClass> {
@managed()
export class DistributedClass extends SmartDataDbDoc<DistributedClass, DistributedClass> {
// INSTANCE
@unI()
public id: string;
@svDb()
public data: {
status: 'bidding' | 'settled' | 'initializing' | 'stopped';
status: 'initializing' | 'bidding' | 'settled' | 'stopped';
biddingShortcode?: string;
biddingStartTime?: number;
lastUpdated: number;
elected: boolean;
/**
* used to store request
*/
taskRequests: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest[],
taskRequests: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest[];
/**
* only used by the leader to convey consultation results
*/
taskRequestResults: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult[];
}
};
}
/**
@ -37,14 +39,14 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
public readyPromise: Promise<any>;
public db: SmartdataDb;
private asyncExecutionStack = new plugins.lik.AsyncExecutionStack();
private ownInstance: DistributedClass;
public ownInstance: DistributedClass;
public distributedWatcher: SmartdataDbWatcher<DistributedClass>;
constructor(dbArg?: SmartdataDb) {
constructor(dbArg: SmartdataDb) {
super();
this.db = dbArg;
setDefaultManagerForDoc(this, DistributedClass);
this.readyPromise = this.db.statusConnectedDeferred.promise;
this.init();
}
// smartdata specific stuff
@ -53,26 +55,56 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
}
public async stop() {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (this.distributedWatcher) {
await this.distributedWatcher.close();
}
if (this.ownInstance?.data.elected) {
this.ownInstance.data.elected = false;
} else {
console.log(`can't stop a distributed instance that has not been started yet.`);
}
if (this.ownInstance?.data.status === 'stopped') {
console.log(`stopping a distributed instance that has not been started yet.`);
}
this.ownInstance.data.status = 'stopped';
await this.ownInstance.save();
console.log(`stopped ${this.ownInstance.id}`);
});
}
public id = plugins.smartunique.uni('distributedInstance');
private startHeartbeat = async () => {
while (this.ownInstance.data.status !== 'stopped') {
await this.sendHeartbeat();
await plugins.smartdelay.delayForRandom(5000, 10000);
}
};
public async sendHeartbeat() {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (this.ownInstance.data.status === 'stopped') {
console.log(`aborted sending heartbeat because status is stopped`);
return;
}
await this.ownInstance.updateFromDb();
this.ownInstance.data.lastUpdated = Date.now();
await this.ownInstance.save();
console.log(`sent heartbeat for ${this.ownInstance.id}`);
const allInstances = DistributedClass.getInstances({});
});
await plugins.smartdelay.delayFor(10000);
if (this.ownInstance.data.status === 'stopped') {
console.log(`aborted sending heartbeat because status is stopped`);
return;
}
};
public async init() {
const eligibleLeader = await this.getEligibleLeader();
// not awaiting here because we don't want to block the heartbeat
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (!eligibleLeader && this.ownInstance.data.status === 'settled') {
this.checkAndMaybeLead();
}
});
}
private async init() {
await this.readyPromise;
if (!this.ownInstance) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
@ -83,10 +115,12 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
lastUpdated: Date.now(),
status: 'initializing',
taskRequests: [],
taskRequestResults: []
taskRequestResults: [],
};
await this.ownInstance.save();
});
} else {
console.warn(`distributed instance already initialized`);
}
// lets enable the heartbeat
@ -98,42 +132,73 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
return this.ownInstance;
}
public async getEligibleLeader() {
return this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const allInstances = await DistributedClass.getInstances({});
let leaders = allInstances.filter((instanceArg) => instanceArg.data.elected === true);
const eligibleLeader = leaders.find(
(leader) =>
leader.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ seconds: 20 })
);
return eligibleLeader;
});
}
// --> leader election
public async checkAndMaybeLead() {
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 () => {
this.ownInstance.data.status = 'initializing';
this.ownInstance.save();
});
if (await this.getEligibleLeader()) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
console.log(`${this.ownInstance.id} settled as follower`);
});
return;
} else if (
(await DistributedClass.getInstances({})).find((instanceArg) => {
instanceArg.data.status === 'bidding' &&
instanceArg.data.biddingStartTime <= Date.now() - 4000 &&
instanceArg.data.biddingStartTime >= Date.now() - 30000;
})
) {
console.log('too late to the bidding party... waiting for next round.');
return;
} else {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'bidding';
this.ownInstance.data.biddingStartTime = Date.now();
this.ownInstance.data.biddingShortcode = plugins.smartunique.shortId();
await this.ownInstance.save();
console.log('bidding code stored.');
});
await plugins.smartdelay.delayFor(plugins.smarttime.getMilliSecondsFromUnits({ minutes: 2 }));
console.log(`bidding for leadership...`);
await plugins.smartdelay.delayFor(
plugins.smarttime.getMilliSecondsFromUnits({ seconds: 5 })
);
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
let biddingInstances = await DistributedClass.getInstances({});
biddingInstances = biddingInstances.filter(
(instanceArg) =>
!instanceArg.data.elected &&
instanceArg.data.status === 'bidding' &&
instanceArg.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ minutes: 1 })
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ seconds: 10 })
);
console.log(`found ${biddingInstances.length} bidding instances...`);
this.ownInstance.data.elected = true;
for (const biddingInstance of biddingInstances) {
if (biddingInstance.data.biddingShortcode < this.ownInstance.data.biddingShortcode) {
this.ownInstance.data.elected = false;
}
}
await plugins.smartdelay.delayFor(5000);
console.log(`settling with status elected = ${this.ownInstance.data.elected}`);
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
});
if (this.ownInstance.data.elected) {
@ -148,45 +213,83 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
* the leading is implemented here
*/
public async leadFunction() {
const ownInstance = await this.init();
const watcher = await DistributedClass.watch({});
this.distributedWatcher = await DistributedClass.watch({});
const currentTaskRequests: Array<{
taskName: string;
taskExecutionTime: number;
/**
* this function is started once per unique job request
* all instances that requested this task
*/
const startResultTimer = async () => {
requestingDistibutedInstanceIds: string[];
responseTimeout: plugins.smartdelay.Timeout<any>;
}> = [];
}
watcher.changeSubject.subscribe({
this.distributedWatcher.changeSubject.subscribe({
next: async (distributedDoc) => {
distributedDoc
}
})
while (this.ownInstance.data.status !== 'stopped') {
await plugins.smartdelay.delayFor(1000);
if (!distributedDoc) {
console.log(`registered deletion of instance...`);
return;
}
console.log(distributedDoc);
console.log(`registered change for ${distributedDoc.id}`);
distributedDoc;
},
});
while (this.ownInstance.data.status !== 'stopped' && this.ownInstance.data.elected) {
const allInstances = await DistributedClass.getInstances({});
for (const instance of allInstances) {
if (instance.data.status === 'stopped') {
await instance.delete();
};
}
await plugins.smartdelay.delayFor(10000);
}
}
// abstract implemented methods
public async fireDistributedTaskRequest(
taskRequestArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult> {
const ownInstance = await this.init();
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
if (!this.ownInstance) {
console.error('instance need to be started first...');
return;
}
await this.ownInstance.updateFromDb();
this.ownInstance.data.taskRequests.push(taskRequestArg);
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(10000);
const result = await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
const taskRequestResult = this.ownInstance.data.taskRequestResults.find((resultItem) => {
return resultItem.requestResponseId === taskRequestArg.requestResponseId;
});
return taskRequestResult;
});
if (!result) {
console.warn('no result found for task request...');
return null;
}
return result;
}
public async updateDistributedTaskRequest(
infoBasisArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<void> {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const existingInfoBasis = this.ownInstance.data.taskRequests.find(infoBasisItem => {
return infoBasisItem.taskName === infoBasisArg.taskName
&& infoBasisItem.taskExecutionTime === infoBasisArg.taskExecutionTime;
const existingInfoBasis = this.ownInstance.data.taskRequests.find((infoBasisItem) => {
return (
infoBasisItem.taskName === infoBasisArg.taskName &&
infoBasisItem.taskExecutionTime === infoBasisArg.taskExecutionTime
);
});
if (!existingInfoBasis) {
console.warn('trying to update a non existing task request... aborting!');
return;
}
Object.assign(existingInfoBasis, infoBasisArg);
await this.ownInstance.save();
plugins.smartdelay.delayFor(60000).then(() => {
@ -194,8 +297,8 @@ export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distribu
const indexToRemove = this.ownInstance.data.taskRequests.indexOf(existingInfoBasis);
this.ownInstance.data.taskRequests.splice(indexToRemove, indexToRemove);
await this.ownInstance.save();
})
})
});
});
});
}
}

View File

@ -1,7 +1,5 @@
import * as plugins from './smartdata.plugins.js';
import { ObjectMap } from '@pushrocks/lik';
import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { type IManager, SmartdataCollection } from './smartdata.classes.collection.js';
@ -129,6 +127,13 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
}
}
/**
* get a unique id prefixed with the class name
*/
public static async getNewId<T = any>(this: plugins.tsclass.typeFest.Class<T>, lengthArg: number = 20) {
return `${(this as any).className}:${plugins.smartunique.shortId(lengthArg)}`;
}
/**
* get cursor
* @returns
@ -241,9 +246,9 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
* also store any referenced objects to DB
* better for data consistency
*/
public saveDeep(savedMapArg: ObjectMap<SmartDataDbDoc<any, any>> = null) {
public saveDeep(savedMapArg: plugins.lik.ObjectMap<SmartDataDbDoc<any, any>> = null) {
if (!savedMapArg) {
savedMapArg = new ObjectMap<SmartDataDbDoc<any, any>>();
savedMapArg = new plugins.lik.ObjectMap<SmartDataDbDoc<any, any>>();
}
savedMapArg.add(this);
this.save();
@ -259,7 +264,7 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
* updates an object from db
*/
public async updateFromDb() {
const mongoDbNativeDoc = await this.collection.findOne(this.createIdentifiableObject());
const mongoDbNativeDoc = await this.collection.findOne(await this.createIdentifiableObject());
for (const key of Object.keys(mongoDbNativeDoc)) {
this[key] = mongoDbNativeDoc[key];
}

View File

@ -37,7 +37,16 @@ export class EasyStore<T> {
this.nameId = nameIdArg;
}
private async getEasyStore() {
private easyStorePromise: Promise<InstanceType<typeof this.easyStoreClass>>;
private async getEasyStore(): Promise<InstanceType<typeof this.easyStoreClass>> {
if (this.easyStorePromise) {
return this.easyStorePromise;
};
// first run from here
const deferred = plugins.smartpromise.defer<InstanceType<typeof this.easyStoreClass>>();
this.easyStorePromise = deferred.promise;
let easyStore = await this.easyStoreClass.getInstance({
nameId: this.nameId,
});
@ -48,7 +57,8 @@ export class EasyStore<T> {
easyStore.data = {};
await easyStore.save();
}
return easyStore;
deferred.resolve(easyStore);
return this.easyStorePromise;
}
/**
@ -70,7 +80,7 @@ export class EasyStore<T> {
/**
* writes a specific key to the keyValueStore
*/
public async writeKey(keyArg: keyof T, valueArg: any) {
public async writeKey<TKey extends keyof T>(keyArg: TKey, valueArg: T[TKey]) {
const easyStore = await this.getEasyStore();
easyStore.data[keyArg] = valueArg;
await easyStore.save();
@ -101,8 +111,9 @@ export class EasyStore<T> {
}
public async cleanUpEphermal() {
while(await this.smartdataDbRef.statusConnectedDeferred.promise && this.smartdataDbRef.status === 'connected') {
}
while (
(await this.smartdataDbRef.statusConnectedDeferred.promise) &&
this.smartdataDbRef.status === 'connected'
) {}
}
}

View File

@ -17,9 +17,13 @@ export class SmartdataDbWatcher<T = any> {
smartdataDbDocArg: typeof SmartDataDbDoc
) {
this.changeStream = changeStreamArg;
this.changeStream.on('change', async (item: T) => {
this.changeStream.on('change', async (item: any) => {
if (!item.fullDocument) {
this.changeSubject.next(null);
return;
}
this.changeSubject.next(
smartdataDbDocArg.createInstanceFromMongoDbNativeDoc(item) as any as T
smartdataDbDocArg.createInstanceFromMongoDbNativeDoc(item.fullDocument) as any as T
);
});
plugins.smartdelay.delayFor(0).then(() => {

View File

@ -4,16 +4,15 @@ import * as tsclass from '@tsclass/tsclass';
export { tsclass };
// @pushrocks scope
import * as lik from '@pushrocks/lik';
import * as smartdelay from '@pushrocks/smartdelay';
import * as smartlog from '@pushrocks/smartlog';
import * as smartpromise from '@pushrocks/smartpromise';
import * as smartq from '@pushrocks/smartpromise';
import * as smartrx from '@pushrocks/smartrx';
import * as smartstring from '@pushrocks/smartstring';
import * as smarttime from '@pushrocks/smarttime';
import * as smartunique from '@pushrocks/smartunique';
import * as taskbuffer from '@pushrocks/taskbuffer';
import * as lik from '@push.rocks/lik';
import * as smartdelay from '@push.rocks/smartdelay';
import * as smartlog from '@push.rocks/smartlog';
import * as smartpromise from '@push.rocks/smartpromise';
import * as smartrx from '@push.rocks/smartrx';
import * as smartstring from '@push.rocks/smartstring';
import * as smarttime from '@push.rocks/smarttime';
import * as smartunique from '@push.rocks/smartunique';
import * as taskbuffer from '@push.rocks/taskbuffer';
import * as mongodb from 'mongodb';
export {
@ -21,7 +20,6 @@ export {
smartdelay,
smartpromise,
smartlog,
smartq,
smartrx,
mongodb,
smartstring,