Compare commits
66 Commits
Author | SHA1 | Date | |
---|---|---|---|
b4cd6b0fe1 | |||
b282f69b35 | |||
203a284c88 | |||
30ae641a9c | |||
cfe733621f | |||
1f76e2478e | |||
7d668bee05 | |||
bef7f68360 | |||
56e9754725 | |||
30d81581cf | |||
5e9db12955 | |||
ad2f422c86 | |||
17ce14bcb9 | |||
32319e6e77 | |||
4cd284eaa9 | |||
00ec2e57c2 | |||
765356ce3d | |||
56b8581d2b | |||
37a9df9086 | |||
090fb668cd | |||
a1c807261c | |||
a2ccf15f69 | |||
84d48f1914 | |||
1e258e5ffb | |||
19d5f553b9 | |||
7a257ea925 | |||
2fa1e89f34 | |||
d6b3896dd3 | |||
49b11b17ce | |||
4ac8a4c0cd | |||
7f9983382a | |||
54f529b0a7 | |||
f542463bf6 | |||
1235ae2eb3 | |||
8166d2f7c2 | |||
7c9f27e02f | |||
842e4b280b | |||
009f3297b2 | |||
2ff3a4e0b7 | |||
0e55cd8876 | |||
eccdf3f00a | |||
c7544133d9 | |||
c7c9acf5bd | |||
c99ec50853 | |||
4dd9557e1d | |||
52b34a6da1 | |||
1bf74fe04d | |||
fdd875ad31 | |||
a7bf0c0298 | |||
59d6336e43 | |||
e0fc81179a | |||
5aa81a56a2 | |||
9ae26177b8 | |||
26ac52d6c5 | |||
fb39463b7d | |||
44acba80c1 | |||
8cf8315577 | |||
9b44b64a50 | |||
699e25201c | |||
2ef9aace68 | |||
cc55a57dfd | |||
b2df512552 | |||
23c62fbd69 | |||
5f70ea0b05 | |||
49a595876a | |||
db38a1ef85 |
66
.gitea/workflows/default_nottags.yaml
Normal file
66
.gitea/workflows/default_nottags.yaml
Normal file
@ -0,0 +1,66 @@
|
||||
name: Default (not tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags-ignore:
|
||||
- '**'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm and npmci
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
|
||||
- name: Run npm prepare
|
||||
run: npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
124
.gitea/workflows/default_tags.yaml
Normal file
124
.gitea/workflows/default_tags.yaml
Normal file
@ -0,0 +1,124 @@
|
||||
name: Default (tags)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
env:
|
||||
IMAGE: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
||||
NPMCI_COMPUTED_REPOURL: https://${{gitea.repository_owner}}:${{secrets.GITEA_TOKEN}}@gitea.lossless.digital/${{gitea.repository}}.git
|
||||
NPMCI_TOKEN_NPM: ${{secrets.NPMCI_TOKEN_NPM}}
|
||||
NPMCI_TOKEN_NPM2: ${{secrets.NPMCI_TOKEN_NPM2}}
|
||||
NPMCI_GIT_GITHUBTOKEN: ${{secrets.NPMCI_GIT_GITHUBTOKEN}}
|
||||
NPMCI_URL_CLOUDLY: ${{secrets.NPMCI_URL_CLOUDLY}}
|
||||
|
||||
jobs:
|
||||
security:
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Audit production dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --prod
|
||||
continue-on-error: true
|
||||
|
||||
- name: Audit development dependencies
|
||||
run: |
|
||||
npmci command npm config set registry https://registry.npmjs.org
|
||||
npmci command pnpm audit --audit-level=high --dev
|
||||
continue-on-error: true
|
||||
|
||||
test:
|
||||
if: ${{ always() }}
|
||||
needs: security
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Test stable
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm test
|
||||
|
||||
- name: Test build
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
npmci npm build
|
||||
|
||||
release:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Release
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm publish
|
||||
|
||||
metadata:
|
||||
needs: test
|
||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ${{ env.IMAGE }}
|
||||
continue-on-error: true
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
pnpm install -g pnpm
|
||||
pnpm install -g @shipzone/npmci
|
||||
npmci npm prepare
|
||||
|
||||
- name: Code quality
|
||||
run: |
|
||||
npmci command npm install -g typescript
|
||||
npmci npm install
|
||||
|
||||
- name: Trigger
|
||||
run: npmci trigger
|
||||
|
||||
- name: Build docs and upload artifacts
|
||||
run: |
|
||||
npmci node install stable
|
||||
npmci npm install
|
||||
pnpm install -g @gitzone/tsdoc
|
||||
npmci command tsdoc
|
||||
continue-on-error: true
|
128
.gitlab-ci.yml
128
.gitlab-ci.yml
@ -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
|
@ -12,12 +12,15 @@
|
||||
"gitzone": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "gitlab.com",
|
||||
"gitscope": "pushrocks",
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "smartdata",
|
||||
"description": "do more with data",
|
||||
"npmPackagename": "@pushrocks/smartdata",
|
||||
"npmPackagename": "@push.rocks/smartdata",
|
||||
"license": "MIT"
|
||||
}
|
||||
},
|
||||
"tsdoc": {
|
||||
"legal": "\n## License and Legal Information\n\nThis repository contains open-source code that is licensed under the MIT License. A copy of the MIT License can be found in the [license](license) file within this repository. \n\n**Please note:** The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.\n\n### Trademarks\n\nThis project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH and are not included within the scope of the MIT license granted herein. Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines, and any usage must be approved in writing by Task Venture Capital GmbH.\n\n### Company Information\n\nTask Venture Capital GmbH \nRegistered at District court Bremen HRB 35230 HB, Germany\n\nFor any legal inquiries or if you require further information, please contact us via email at hello@task.vc.\n\nBy using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.\n"
|
||||
}
|
||||
}
|
39
package.json
39
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@pushrocks/smartdata",
|
||||
"version": "5.0.13",
|
||||
"name": "@push.rocks/smartdata",
|
||||
"version": "5.1.0",
|
||||
"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
4471
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
30
readme.md
30
readme.md
@ -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 | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
GitLab Pipelines | [](https://lossless.cloud)
|
||||
GitLab Pipline Test Coverage | [](https://lossless.cloud)
|
||||
npm | [](https://lossless.cloud)
|
||||
Snyk | [](https://lossless.cloud)
|
||||
TypeScript Support | [](https://lossless.cloud)
|
||||
node Support | [](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
||||
Code Style | [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](https://lossless.cloud)
|
||||
Platform support | [](https://lossless.cloud) [](https://lossless.cloud)
|
||||
PackagePhobia (total standalone install weight) | [](https://lossless.cloud)
|
||||
PackagePhobia (package size on registry) | [](https://lossless.cloud)
|
||||
BundlePhobia (total size when bundled) | [](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)
|
||||
|
||||
[](https://maintainedby.lossless.com)
|
||||
|
112
test/test.distributedcoordinator.ts
Normal file
112
test/test.distributedcoordinator.ts
Normal file
@ -0,0 +1,112 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as smartmongo from '@push.rocks/smartmongo';
|
||||
import type * as taskbuffer from '@push.rocks/taskbuffer';
|
||||
|
||||
import * as smartdata from '../ts/index.js';
|
||||
import { SmartdataDistributedCoordinator, DistributedClass } from '../ts/smartdata.classes.distributedcoordinator.js'; // path might need adjusting
|
||||
const totalInstances = 10;
|
||||
|
||||
// =======================================
|
||||
// Connecting to the database server
|
||||
// =======================================
|
||||
|
||||
let smartmongoInstance: smartmongo.SmartMongo;
|
||||
let testDb: smartdata.SmartdataDb;
|
||||
|
||||
tap.test('should create a testinstance as database', async () => {
|
||||
smartmongoInstance = await smartmongo.SmartMongo.createAndStart();
|
||||
testDb = new smartdata.SmartdataDb(await smartmongoInstance.getMongoDescriptor());
|
||||
await testDb.init();
|
||||
});
|
||||
|
||||
tap.test('should instantiate DistributedClass', async (tools) => {
|
||||
const instance = new DistributedClass();
|
||||
expect(instance).toBeInstanceOf(DistributedClass);
|
||||
});
|
||||
|
||||
tap.test('DistributedClass should update the time', async (tools) => {
|
||||
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
|
||||
await distributedCoordinator.start();
|
||||
const initialTime = distributedCoordinator.ownInstance.data.lastUpdated;
|
||||
await distributedCoordinator.sendHeartbeat();
|
||||
const updatedTime = distributedCoordinator.ownInstance.data.lastUpdated;
|
||||
expect(updatedTime).toBeGreaterThan(initialTime);
|
||||
await distributedCoordinator.stop();
|
||||
});
|
||||
|
||||
tap.test('should instantiate SmartdataDistributedCoordinator', async (tools) => {
|
||||
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
|
||||
await distributedCoordinator.start();
|
||||
expect(distributedCoordinator).toBeInstanceOf(SmartdataDistributedCoordinator);
|
||||
await distributedCoordinator.stop();
|
||||
});
|
||||
|
||||
tap.test('SmartdataDistributedCoordinator should update leader status', async (tools) => {
|
||||
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
|
||||
await distributedCoordinator.start();
|
||||
await distributedCoordinator.checkAndMaybeLead();
|
||||
expect(distributedCoordinator.ownInstance.data.elected).toBeOneOf([true, false]);
|
||||
await distributedCoordinator.stop();
|
||||
});
|
||||
|
||||
tap.test('SmartdataDistributedCoordinator should handle distributed task requests', async (tools) => {
|
||||
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
|
||||
await distributedCoordinator.start();
|
||||
|
||||
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
|
||||
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
|
||||
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
|
||||
taskName: "SampleTask",
|
||||
taskVersion: "1.0.0", // Assuming it's a version string
|
||||
taskExecutionTime: Date.now(),
|
||||
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
|
||||
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
|
||||
status: 'requesting'
|
||||
};
|
||||
|
||||
const response = await distributedCoordinator.fireDistributedTaskRequest(mockTaskRequest);
|
||||
console.log(response) // based on your expected structure for the response
|
||||
await distributedCoordinator.stop();
|
||||
});
|
||||
|
||||
tap.test('SmartdataDistributedCoordinator should update distributed task requests', async (tools) => {
|
||||
const distributedCoordinator = new SmartdataDistributedCoordinator(testDb);
|
||||
|
||||
await distributedCoordinator.start();
|
||||
|
||||
const mockTaskRequest: taskbuffer.distributedCoordination.IDistributedTaskRequest = {
|
||||
submitterId: "mockSubmitter12345", // Some unique mock submitter ID
|
||||
requestResponseId: 'uni879873462hjhfkjhsdf', // Some unique ID for the request-response
|
||||
taskName: "SampleTask",
|
||||
taskVersion: "1.0.0", // Assuming it's a version string
|
||||
taskExecutionTime: Date.now(),
|
||||
taskExecutionTimeout: 60000, // Let's say the timeout is 1 minute (60000 ms)
|
||||
taskExecutionParallel: 5, // Let's assume max 5 parallel executions
|
||||
status: 'requesting'
|
||||
};
|
||||
|
||||
|
||||
await distributedCoordinator.updateDistributedTaskRequest(mockTaskRequest);
|
||||
// Here, we can potentially check if a DB entry got updated or some other side-effect of the update method.
|
||||
await distributedCoordinator.stop();
|
||||
});
|
||||
|
||||
tap.test('should elect only one leader amongst multiple instances', async (tools) => {
|
||||
const coordinators = Array.from({ length: totalInstances }).map(() => new SmartdataDistributedCoordinator(testDb));
|
||||
await Promise.all(coordinators.map(coordinator => coordinator.start()));
|
||||
const leaders = coordinators.filter(coordinator => coordinator.ownInstance.data.elected);
|
||||
for (const leader of leaders) {
|
||||
console.log(leader.ownInstance);
|
||||
}
|
||||
expect(leaders.length).toEqual(1);
|
||||
|
||||
// stopping clears a coordinator from being elected.
|
||||
await Promise.all(coordinators.map(coordinator => coordinator.stop()));
|
||||
});
|
||||
|
||||
tap.test('should clean up', async () => {
|
||||
await smartmongoInstance.stopAndDumpToDir(`.nogit/dbdump/test.distributedcoordinator.ts`);
|
||||
setTimeout(() => process.exit(), 2000);
|
||||
})
|
||||
|
||||
tap.start({ throwOnError: true });
|
@ -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();
|
||||
|
21
test/test.ts
21
test/test.ts
@ -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();
|
||||
@ -213,11 +218,13 @@ tap.test('should use a cursor', async () => {
|
||||
// close the database connection
|
||||
// =======================================
|
||||
tap.test('close', async () => {
|
||||
await testDb.mongoDb.dropDatabase();
|
||||
await testDb.close();
|
||||
if (smartmongoInstance) {
|
||||
await smartmongoInstance.stop();
|
||||
await smartmongoInstance.stopAndDumpToDir('./.nogit/dbdump/test.ts');
|
||||
} else {
|
||||
await testDb.mongoDb.dropDatabase();
|
||||
await testDb.close();
|
||||
}
|
||||
setTimeout(() => process.exit(), 2000);
|
||||
});
|
||||
|
||||
tap.start({ throwOnError: true });
|
||||
|
@ -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();
|
||||
|
@ -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();
|
||||
|
@ -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.1.0',
|
||||
description: 'do more with data'
|
||||
}
|
||||
|
@ -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';
|
||||
|
@ -2,4 +2,4 @@ import * as plugins from './smartdata.plugins.js';
|
||||
|
||||
export const getNewUniqueId = async (prefixArg?: string) => {
|
||||
return plugins.smartunique.uni(prefixArg);
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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() {
|
||||
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.`);
|
||||
}
|
||||
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||
if (this.distributedWatcher) {
|
||||
await this.distributedWatcher.close();
|
||||
}
|
||||
if (this.ownInstance?.data.elected) {
|
||||
this.ownInstance.data.elected = false;
|
||||
}
|
||||
if (this.ownInstance?.data.status === 'stopped') {
|
||||
console.log(`stopping a distributed instance that has not been started yet.`);
|
||||
}
|
||||
this.ownInstance.data.status = 'stopped';
|
||||
await this.ownInstance.save();
|
||||
console.log(`stopped ${this.ownInstance.id}`);
|
||||
});
|
||||
}
|
||||
|
||||
public id = plugins.smartunique.uni('distributedInstance');
|
||||
|
||||
private startHeartbeat = async () => {
|
||||
while (this.ownInstance.data.status !== 'stopped') {
|
||||
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||
await this.ownInstance.updateFromDb();
|
||||
this.ownInstance.data.lastUpdated = Date.now();
|
||||
await this.ownInstance.save();
|
||||
});
|
||||
await plugins.smartdelay.delayFor(10000);
|
||||
await this.sendHeartbeat();
|
||||
await plugins.smartdelay.delayForRandom(5000, 10000);
|
||||
}
|
||||
};
|
||||
public async init() {
|
||||
|
||||
public async sendHeartbeat() {
|
||||
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||
if (this.ownInstance.data.status === 'stopped') {
|
||||
console.log(`aborted sending heartbeat because status is stopped`);
|
||||
return;
|
||||
}
|
||||
await this.ownInstance.updateFromDb();
|
||||
this.ownInstance.data.lastUpdated = Date.now();
|
||||
await this.ownInstance.save();
|
||||
console.log(`sent heartbeat for ${this.ownInstance.id}`);
|
||||
const allInstances = DistributedClass.getInstances({});
|
||||
});
|
||||
if (this.ownInstance.data.status === 'stopped') {
|
||||
console.log(`aborted sending heartbeat because status is stopped`);
|
||||
return;
|
||||
}
|
||||
const eligibleLeader = await this.getEligibleLeader();
|
||||
// not awaiting here because we don't want to block the heartbeat
|
||||
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||
if (!eligibleLeader && this.ownInstance.data.status === 'settled') {
|
||||
this.checkAndMaybeLead();
|
||||
}
|
||||
});
|
||||
}
|
||||
private async init() {
|
||||
await this.readyPromise;
|
||||
if (!this.ownInstance) {
|
||||
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||
@ -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 function is started once per unique job request
|
||||
*/
|
||||
const startResultTimer = async () => {
|
||||
this.distributedWatcher = await DistributedClass.watch({});
|
||||
|
||||
}
|
||||
|
||||
watcher.changeSubject.subscribe({
|
||||
const currentTaskRequests: Array<{
|
||||
taskName: string;
|
||||
taskExecutionTime: number;
|
||||
/**
|
||||
* all instances that requested this task
|
||||
*/
|
||||
requestingDistibutedInstanceIds: string[];
|
||||
responseTimeout: plugins.smartdelay.Timeout<any>;
|
||||
}> = [];
|
||||
|
||||
this.distributedWatcher.changeSubject.subscribe({
|
||||
next: async (distributedDoc) => {
|
||||
distributedDoc
|
||||
if (!distributedDoc) {
|
||||
console.log(`registered deletion of instance...`);
|
||||
return;
|
||||
}
|
||||
console.log(distributedDoc);
|
||||
console.log(`registered change for ${distributedDoc.id}`);
|
||||
distributedDoc;
|
||||
},
|
||||
});
|
||||
|
||||
while (this.ownInstance.data.status !== 'stopped' && this.ownInstance.data.elected) {
|
||||
const allInstances = await DistributedClass.getInstances({});
|
||||
for (const instance of allInstances) {
|
||||
if (instance.data.status === 'stopped') {
|
||||
await instance.delete();
|
||||
};
|
||||
}
|
||||
})
|
||||
while (this.ownInstance.data.status !== 'stopped') {
|
||||
await plugins.smartdelay.delayFor(1000);
|
||||
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();
|
||||
});
|
||||
return null;
|
||||
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();
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
@ -183,6 +188,18 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
||||
*/
|
||||
public creationStatus: TDocCreation = 'new';
|
||||
|
||||
/**
|
||||
* updated from db in any case where doc comes from db
|
||||
*/
|
||||
@svDb()
|
||||
_createdAt: number = Date.now();
|
||||
|
||||
/**
|
||||
* will be updated everytime the doc is saved
|
||||
*/
|
||||
@svDb()
|
||||
_updatedAt: number = Date.now();
|
||||
|
||||
/**
|
||||
* unique indexes
|
||||
*/
|
||||
@ -216,6 +233,9 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
|
||||
// tslint:disable-next-line: no-this-assignment
|
||||
const self: any = this;
|
||||
let dbResult: any;
|
||||
|
||||
this._updatedAt = Date.now();
|
||||
|
||||
switch (this.creationStatus) {
|
||||
case 'db':
|
||||
dbResult = await this.collection.update(self);
|
||||
@ -241,9 +261,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 +279,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];
|
||||
}
|
||||
|
@ -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'
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
@ -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(() => {
|
||||
|
@ -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,
|
||||
|
@ -3,7 +3,12 @@
|
||||
"experimentalDecorators": true,
|
||||
"useDefineForClassFields": false,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
Reference in New Issue
Block a user