Compare commits

..

24 Commits

Author SHA1 Message Date
49a595876a 5.0.14 2023-06-25 01:27:10 +02:00
db38a1ef85 fix(core): update 2023-06-25 01:27:09 +02:00
94854638dd 5.0.13 2023-06-24 23:57:35 +02:00
902fab4cc0 fix(core): update 2023-06-24 23:57:34 +02:00
ed3b19abc5 5.0.12 2023-03-21 20:16:08 +01:00
5b88da7dce fix(core): update 2023-03-21 20:16:07 +01:00
df273e9efa 5.0.11 2023-02-06 11:43:11 +01:00
fd590e0be3 fix(core): update 2023-02-06 11:43:11 +01:00
ef97b390d4 5.0.10 2022-11-08 10:26:21 +01:00
cd14eb8bf3 fix(core): update 2022-11-08 10:26:20 +01:00
f48443dcd3 5.0.9 2022-11-01 18:23:58 +01:00
3f28ff80cb fix(core): update 2022-11-01 18:23:57 +01:00
64005a0b32 5.0.8 2022-09-12 11:37:54 +02:00
8a77bb3281 fix(core): update 2022-09-12 11:37:53 +02:00
25f50ecf51 5.0.7 2022-06-14 22:04:35 +02:00
ad87f8147b fix(core): update 2022-06-14 22:04:34 +02:00
da0c9873eb 5.0.6 2022-06-14 22:02:57 +02:00
2fcd3f1550 fix(core): update 2022-06-14 22:02:57 +02:00
f726cf4c5b 5.0.5 2022-06-05 17:19:12 +02:00
c198969fae fix(core): update 2022-06-05 17:19:12 +02:00
be1badeb23 5.0.4 2022-06-05 17:18:13 +02:00
fe065b966f fix(core): update 2022-06-05 17:18:13 +02:00
811e2490b8 5.0.3 2022-05-19 16:15:28 +02:00
206ccd40e9 fix(watcher.changeSubject): now emits correct type into observer functions 2022-05-19 16:15:28 +02:00
22 changed files with 6312 additions and 10878 deletions

View File

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

View File

@ -13,31 +13,24 @@ stages:
- metadata - metadata
before_script: before_script:
- npm install -g @shipzone/npmci - pnpm install -g pnpm
- pnpm install -g @shipzone/npmci
- npmci npm prepare
# ==================== # ====================
# security stage # security stage
# ==================== # ====================
mirror: # ====================
stage: security # security stage
script: # ====================
- npmci git mirror
only:
- tags
tags:
- lossless
- docker
- notpriv
auditProductionDependencies: auditProductionDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security stage: security
script: script:
- npmci npm prepare - npmci command npm config set registry https://registry.npmjs.org
- npmci command npm install --production --ignore-scripts - npmci command pnpm audit --audit-level=high --prod
- npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=prod --production
tags: tags:
- lossless
- docker - docker
allow_failure: true allow_failure: true
@ -45,11 +38,10 @@ auditDevDependencies:
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
stage: security stage: security
script: script:
- npmci npm prepare
- npmci command npm install --ignore-scripts
- npmci command npm config set registry https://registry.npmjs.org - npmci command npm config set registry https://registry.npmjs.org
- npmci command npm audit --audit-level=high --only=dev - npmci command pnpm audit --audit-level=high --dev
tags: tags:
- lossless
- docker - docker
allow_failure: true allow_failure: true
@ -60,7 +52,6 @@ auditDevDependencies:
testStable: testStable:
stage: test stage: test
script: script:
- npmci npm prepare
- npmci node install stable - npmci node install stable
- npmci npm install - npmci npm install
- npmci npm test - npmci npm test
@ -71,10 +62,9 @@ testStable:
testBuild: testBuild:
stage: test stage: test
script: script:
- npmci npm prepare
- npmci node install stable - npmci node install stable
- npmci npm install - npmci npm install
- npmci command npm run build - npmci npm build
coverage: /\d+.?\d+?\%\s*coverage/ coverage: /\d+.?\d+?\%\s*coverage/
tags: tags:
- docker - docker
@ -100,10 +90,9 @@ codequality:
only: only:
- tags - tags
script: script:
- npmci command npm install -g tslint typescript - npmci command npm install -g typescript
- npmci npm prepare - npmci npm prepare
- npmci npm install - npmci npm install
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
tags: tags:
- lossless - lossless
- docker - docker
@ -123,11 +112,9 @@ trigger:
pages: pages:
stage: metadata stage: metadata
script: script:
- npmci node install lts - npmci node install stable
- npmci command npm install -g @gitzone/tsdoc
- npmci npm prepare
- npmci npm install - npmci npm install
- npmci command tsdoc - npmci command npm run buildDocs
tags: tags:
- lossless - lossless
- docker - docker

View File

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

10765
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@pushrocks/smartdata", "name": "@pushrocks/smartdata",
"version": "5.0.2", "version": "5.0.14",
"private": false, "private": false,
"description": "do more with data", "description": "do more with data",
"main": "dist_ts/index.js", "main": "dist_ts/index.js",
@ -8,7 +8,8 @@
"type": "module", "type": "module",
"scripts": { "scripts": {
"test": "tstest test/", "test": "tstest test/",
"build": "tsbuild --web --allowimplicitany" "build": "tsbuild --web --allowimplicitany",
"buildDocs": "tsdoc"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
@ -21,26 +22,26 @@
}, },
"homepage": "https://gitlab.com/pushrocks/smartdata#README", "homepage": "https://gitlab.com/pushrocks/smartdata#README",
"dependencies": { "dependencies": {
"@pushrocks/lik": "^5.0.4", "@pushrocks/lik": "^6.0.2",
"@pushrocks/smartdelay": "^2.0.13", "@pushrocks/smartdelay": "^3.0.1",
"@pushrocks/smartlog": "^2.0.44", "@pushrocks/smartlog": "^3.0.1",
"@pushrocks/smartmongo": "^2.0.1", "@pushrocks/smartmongo": "^2.0.7",
"@pushrocks/smartpromise": "^3.1.7", "@pushrocks/smartpromise": "^4.0.2",
"@pushrocks/smartrx": "^2.0.25", "@pushrocks/smartrx": "^3.0.2",
"@pushrocks/smartstring": "^4.0.2", "@pushrocks/smartstring": "^4.0.2",
"@pushrocks/smarttime": "^4.0.1",
"@pushrocks/smartunique": "^3.0.3", "@pushrocks/smartunique": "^3.0.3",
"@tsclass/tsclass": "^4.0.2", "@pushrocks/taskbuffer": "^3.0.10",
"@types/lodash": "^4.14.182", "@tsclass/tsclass": "^4.0.42",
"@types/mongodb": "^4.0.7", "mongodb": "^5.6.0"
"lodash": "^4.17.21",
"mongodb": "^4.6.0"
}, },
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.1.61", "@gitzone/tsbuild": "^2.1.66",
"@gitzone/tstest": "^1.0.71", "@gitzone/tsrun": "^1.2.42",
"@pushrocks/qenv": "^4.0.10", "@gitzone/tstest": "^1.0.74",
"@pushrocks/tapbundle": "^5.0.3", "@pushrocks/qenv": "^5.0.2",
"@types/node": "^17.0.34", "@pushrocks/tapbundle": "^5.0.8",
"@types/node": "^20.3.1",
"@types/shortid": "0.0.29" "@types/shortid": "0.0.29"
}, },
"files": [ "files": [

5854
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -21,7 +21,6 @@ Code Style | [![Code Style](https://badgen.net/badge/style/prettier/purple)](htt
PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud) PackagePhobia (total standalone install weight) | [![PackagePhobia](https://badgen.net/packagephobia/install/@pushrocks/smartdata)](https://lossless.cloud)
PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud) PackagePhobia (package size on registry) | [![PackagePhobia](https://badgen.net/packagephobia/publish/@pushrocks/smartdata)](https://lossless.cloud)
BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud) BundlePhobia (total size when bundled) | [![BundlePhobia](https://badgen.net/bundlephobia/minzip/@pushrocks/smartdata)](https://lossless.cloud)
Platform support | [![Supports Windows 10](https://badgen.net/badge/supports%20Windows%2010/yes/green?icon=windows)](https://lossless.cloud) [![Supports Mac OS X](https://badgen.net/badge/supports%20Mac%20OS%20X/yes/green?icon=apple)](https://lossless.cloud)
## Usage ## Usage
@ -150,7 +149,6 @@ We are always happy for code contributions. If you are not the code contributing
For further information read the linked docs at the top of this readme. For further information read the linked docs at the top of this readme.
> MIT licensed | **©** [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) | 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

@ -3,6 +3,8 @@ import { Qenv } from '@pushrocks/qenv';
import * as smartmongo from '@pushrocks/smartmongo'; import * as smartmongo from '@pushrocks/smartmongo';
import { smartunique } from '../ts/smartdata.plugins.js'; import { smartunique } from '../ts/smartdata.plugins.js';
import * as mongodb from 'mongodb';
const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/'); const testQenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
console.log(process.memoryUsage()); console.log(process.memoryUsage());
@ -55,6 +57,9 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
@smartdata.svDb() @smartdata.svDb()
public brand: string; public brand: string;
@smartdata.svDb()
public testBuffer = Buffer.from('hello');
@smartdata.svDb() @smartdata.svDb()
deepData = { deepData = {
sodeep: 'yes', sodeep: 'yes',
@ -67,7 +72,7 @@ class Car extends smartdata.SmartDataDbDoc<Car, Car> {
} }
} }
tap.test('should save the car to the db', async () => { tap.test('should save the car to the db', async (toolsArg) => {
const myCar = new Car('red', 'Volvo'); const myCar = new Car('red', 'Volvo');
await myCar.save(); await myCar.save();
@ -75,6 +80,9 @@ tap.test('should save the car to the db', async () => {
await myCar2.save(); await myCar2.save();
let counter = 0; let counter = 0;
const gottenCarInstance = await Car.getInstance({});
console.log(gottenCarInstance.testBuffer instanceof mongodb.Binary);
process.memoryUsage(); process.memoryUsage();
do { do {
const myCar3 = new Car('red', 'Renault'); const myCar3 = new Car('red', 'Renault');
@ -92,7 +100,7 @@ tap.test('should save the car to the db', async () => {
}); });
tap.test('expect to get instance of Car with shallow match', async () => { tap.test('expect to get instance of Car with shallow match', async () => {
const totalQueryCycles = totalCars / 6; const totalQueryCycles = totalCars / 2;
let counter = 0; let counter = 0;
do { do {
const timeStart = Date.now(); const timeStart = Date.now();

View File

@ -42,14 +42,14 @@ class House extends smartdata.SmartDataDbDoc<House, House> {
@smartdata.svDb() @smartdata.svDb()
public data = { public data = {
id: smartunique.shortId(), id: smartunique.shortId(),
hello: 'hello' hello: 'hello',
} };
} }
tap.test('should watch a collection', async (toolsArg) => { tap.test('should watch a collection', async (toolsArg) => {
const done = toolsArg.defer(); const done = toolsArg.defer();
const watcher = await House.watch({}); const watcher = await House.watch({});
watcher.changeSubject.subscribe(async houseArg => { watcher.changeSubject.subscribe(async (houseArg) => {
console.log('hey there, we observed a house'); console.log('hey there, we observed a house');
await watcher.close(); await watcher.close();
done.resolve(); done.resolve();
@ -58,7 +58,7 @@ tap.test('should watch a collection', async (toolsArg) => {
await newHouse.save(); await newHouse.save();
console.log('saved a house'); console.log('saved a house');
await done.promise; await done.promise;
}) });
// ======================================= // =======================================
// close the database connection // close the database connection
@ -71,4 +71,4 @@ tap.test('close', async () => {
} }
}); });
tap.start({ throwOnError: true }); tap.start({ throwOnError: true });

View File

@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@pushrocks/smartdata', name: '@pushrocks/smartdata',
version: '5.0.2', version: '5.0.14',
description: 'do more with data' description: 'do more with data'
} }

View File

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

View File

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

View File

@ -1,22 +0,0 @@
export interface IMongoDescriptor {
/**
* the URL to connect to
*/
mongoDbUrl: string;
/**
* the db to use for the project
*/
mongoDbName?: string;
/**
* a username to use to connect to the database
*/
mongoDbUser?: string;
/**
* an optional password that will be replace <PASSWORD> in the connection string
*/
mongoDbPass?: string;
}

View File

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

View File

@ -109,7 +109,6 @@ export function Manager<TManager extends IManager>(managerArg?: TManager | TDela
}; };
} }
// tslint:disable-next-line: max-classes-per-file
export class SmartdataCollection<T> { export class SmartdataCollection<T> {
/** /**
* the collection that is used * the collection that is used
@ -180,7 +179,10 @@ export class SmartdataCollection<T> {
return result; return result;
} }
public async getCursor(filterObjectArg: any, dbDocArg: typeof SmartDataDbDoc): Promise<SmartdataDbCursor<any>> { public async getCursor(
filterObjectArg: any,
dbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbCursor<any>> {
await this.init(); await this.init();
const cursor = this.mongoDbCollection.find(filterObjectArg); const cursor = this.mongoDbCollection.find(filterObjectArg);
return new SmartdataDbCursor(cursor, dbDocArg); return new SmartdataDbCursor(cursor, dbDocArg);
@ -200,16 +202,22 @@ export class SmartdataCollection<T> {
/** /**
* watches the collection while applying a filter * watches the collection while applying a filter
*/ */
public async watch(filterObject: any, smartdataDbDocArg: typeof SmartDataDbDoc): Promise<SmartdataDbWatcher> { public async watch(
filterObject: any,
smartdataDbDocArg: typeof SmartDataDbDoc
): Promise<SmartdataDbWatcher> {
await this.init(); await this.init();
const changeStream = this.mongoDbCollection.watch([ const changeStream = this.mongoDbCollection.watch(
[
{
$match: filterObject,
},
],
{ {
$match: filterObject fullDocument: 'updateLookup',
} }
], { );
fullDocument: 'updateLookup' const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
});
const smartdataWatcher = new SmartdataDbWatcher(changeStream, smartdataDbDocArg);
await smartdataWatcher.readyDeferred.promise; await smartdataWatcher.readyDeferred.promise;
return smartdataWatcher; return smartdataWatcher;
} }

View File

@ -16,20 +16,23 @@ export class SmartdataDbCursor<T = any> {
} }
public async next(closeAtEnd = true) { public async next(closeAtEnd = true) {
const result = this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(await this.mongodbCursor.next()); const result = this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(
await this.mongodbCursor.next()
);
if (!result && closeAtEnd) { if (!result && closeAtEnd) {
await this.close(); await this.close();
} }
return result; return result;
} }
public async forEach(forEachFuncArg: (itemArg: any) => Promise<any>, closeCursorAtEnd = true) { public async forEach(forEachFuncArg: (itemArg: T) => Promise<any>, closeCursorAtEnd = true) {
let nextDocument: any; let nextDocument: any;
do { do {
nextDocument = await this.mongodbCursor.next(); nextDocument = await this.mongodbCursor.next();
if (nextDocument) { if (nextDocument) {
const nextClassInstance = this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(nextDocument); const nextClassInstance =
await forEachFuncArg(nextClassInstance); this.smartdataDbDoc.createInstanceFromMongoDbNativeDoc(nextDocument);
await forEachFuncArg(nextClassInstance as any);
} }
} while (nextDocument); } while (nextDocument);
if (closeCursorAtEnd) { if (closeCursorAtEnd) {

View File

@ -5,7 +5,6 @@ import { SmartdataCollection } from './smartdata.classes.collection.js';
import { EasyStore } from './smartdata.classes.easystore.js'; import { EasyStore } from './smartdata.classes.easystore.js';
import { logger } from './smartdata.logging.js'; import { logger } from './smartdata.logging.js';
import { IMongoDescriptor } from './interfaces/index.js';
/** /**
* interface - indicates the connection status of the db * interface - indicates the connection status of the db
@ -13,13 +12,14 @@ import { IMongoDescriptor } from './interfaces/index.js';
export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed'; export type TConnectionStatus = 'initial' | 'disconnected' | 'connected' | 'failed';
export class SmartdataDb { export class SmartdataDb {
smartdataOptions: IMongoDescriptor; smartdataOptions: plugins.tsclass.database.IMongoDescriptor;
mongoDbClient: plugins.mongodb.MongoClient; mongoDbClient: plugins.mongodb.MongoClient;
mongoDb: plugins.mongodb.Db; mongoDb: plugins.mongodb.Db;
status: TConnectionStatus; status: TConnectionStatus;
statusConnectedDeferred = plugins.smartpromise.defer();
smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>(); smartdataCollectionMap = new ObjectMap<SmartdataCollection<any>>();
constructor(smartdataOptions: IMongoDescriptor) { constructor(smartdataOptions: plugins.tsclass.database.IMongoDescriptor) {
this.smartdataOptions = smartdataOptions; this.smartdataOptions = smartdataOptions;
this.status = 'initial'; this.status = 'initial';
} }
@ -52,6 +52,7 @@ export class SmartdataDb {
}); });
this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName); this.mongoDb = this.mongoDbClient.db(this.smartdataOptions.mongoDbName);
this.status = 'connected'; this.status = 'connected';
this.statusConnectedDeferred.resolve();
console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`); console.log(`Connected to database ${this.smartdataOptions.mongoDbName}`);
} }

View File

@ -0,0 +1,201 @@
import * as plugins from './smartdata.plugins.js';
import { SmartdataDb } from './smartdata.classes.db.js';
import { Manager, setDefaultManagerForDoc } from './smartdata.classes.collection.js';
import { SmartDataDbDoc, svDb, unI } from './smartdata.classes.doc.js';
@Manager()
class DistributedClass extends SmartDataDbDoc<DistributedClass, DistributedClass> {
// INSTANCE
@unI()
public id: string;
@svDb()
public data: {
status: 'bidding' | 'settled' | 'initializing' | 'stopped';
biddingShortcode?: string;
lastUpdated: number;
elected: boolean;
/**
* used to store request
*/
taskRequests: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest[];
/**
* only used by the leader to convey consultation results
*/
taskRequestResults: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult[];
};
}
/**
* This file implements a distributed coordinator according to the @pushrocks/taskbuffer standard.
* you should not set up this yourself. Instead, there is a factory on the SmartdataDb class
* that will take care of setting this up.
*/
export class SmartdataDistributedCoordinator extends plugins.taskbuffer.distributedCoordination
.AbstractDistributedCoordinator {
public readyPromise: Promise<any>;
public db: SmartdataDb;
private asyncExecutionStack = new plugins.lik.AsyncExecutionStack();
private ownInstance: DistributedClass;
constructor(dbArg?: SmartdataDb) {
super();
this.db = dbArg;
setDefaultManagerForDoc(this, DistributedClass);
this.readyPromise = this.db.statusConnectedDeferred.promise;
this.init();
}
// smartdata specific stuff
public async start() {
await this.init();
}
public async stop() {
if (this.ownInstance?.data.elected) {
this.ownInstance.data.elected = false;
} else {
console.log(`can't stop a distributed instance that has not been started yet.`);
}
}
public id = plugins.smartunique.uni('distributedInstance');
private startHeartbeat = async () => {
while (this.ownInstance.data.status !== 'stopped') {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.lastUpdated = Date.now();
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(10000);
}
};
public async init() {
await this.readyPromise;
if (!this.ownInstance) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance = new DistributedClass();
this.ownInstance.id = this.id;
this.ownInstance.data = {
elected: false,
lastUpdated: Date.now(),
status: 'initializing',
taskRequests: [],
taskRequestResults: [],
};
await this.ownInstance.save();
});
}
// lets enable the heartbeat
this.startHeartbeat();
// lets do a leader check
await this.checkAndMaybeLead();
return this.ownInstance;
}
// --> leader election
public async checkAndMaybeLead() {
const allInstances = await DistributedClass.getInstances({});
let leader = allInstances.find((instanceArg) => instanceArg.data.elected === true);
if (
leader &&
leader.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ minutes: 1 })
) {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
await this.ownInstance.updateFromDb();
this.ownInstance.data.status = 'settled';
await this.ownInstance.save();
});
return;
} else {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance.data.status = 'bidding';
this.ownInstance.data.biddingShortcode = plugins.smartunique.shortId();
await this.ownInstance.save();
});
await plugins.smartdelay.delayFor(plugins.smarttime.getMilliSecondsFromUnits({ minutes: 2 }));
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
let biddingInstances = await DistributedClass.getInstances({});
biddingInstances = biddingInstances.filter(
(instanceArg) =>
!instanceArg.data.elected &&
instanceArg.data.lastUpdated >=
Date.now() - plugins.smarttime.getMilliSecondsFromUnits({ minutes: 1 })
);
this.ownInstance.data.elected = true;
for (const biddingInstance of biddingInstances) {
if (biddingInstance.data.biddingShortcode < this.ownInstance.data.biddingShortcode) {
this.ownInstance.data.elected = false;
}
}
await this.ownInstance.save();
});
if (this.ownInstance.data.elected) {
this.leadFunction();
}
}
}
/**
* when it has been determined
* that this instance is leading
* the leading is implemented here
*/
public async leadFunction() {
const ownInstance = await this.init();
const watcher = await DistributedClass.watch({});
/**
* this function is started once per unique job request
*/
const startResultTimer = async () => {};
watcher.changeSubject.subscribe({
next: async (distributedDoc) => {
distributedDoc;
},
});
while (this.ownInstance.data.status !== 'stopped') {
await plugins.smartdelay.delayFor(1000);
}
}
// abstract implemented methods
public async fireDistributedTaskRequest(
taskRequestArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<plugins.taskbuffer.distributedCoordination.IDistributedTaskRequestResult> {
const ownInstance = await this.init();
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
this.ownInstance.data.taskRequests.push(taskRequestArg);
await this.ownInstance.save();
});
return null;
}
public async updateDistributedTaskRequest(
infoBasisArg: plugins.taskbuffer.distributedCoordination.IDistributedTaskRequest
): Promise<void> {
await this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const existingInfoBasis = this.ownInstance.data.taskRequests.find((infoBasisItem) => {
return (
infoBasisItem.taskName === infoBasisArg.taskName &&
infoBasisItem.taskExecutionTime === infoBasisArg.taskExecutionTime
);
});
Object.assign(existingInfoBasis, infoBasisArg);
await this.ownInstance.save();
plugins.smartdelay.delayFor(60000).then(() => {
this.asyncExecutionStack.getExclusiveExecutionSlot(async () => {
const indexToRemove = this.ownInstance.data.taskRequests.indexOf(existingInfoBasis);
this.ownInstance.data.taskRequests.splice(indexToRemove, indexToRemove);
await this.ownInstance.save();
});
});
});
}
}

View File

@ -4,7 +4,7 @@ import { ObjectMap } from '@pushrocks/lik';
import { SmartdataDb } from './smartdata.classes.db.js'; import { SmartdataDb } from './smartdata.classes.db.js';
import { SmartdataDbCursor } from './smartdata.classes.cursor.js'; import { SmartdataDbCursor } from './smartdata.classes.cursor.js';
import { IManager, SmartdataCollection } from './smartdata.classes.collection.js'; import { type IManager, SmartdataCollection } from './smartdata.classes.collection.js';
import { SmartdataDbWatcher } from './smartdata.classes.watcher.js'; import { SmartdataDbWatcher } from './smartdata.classes.watcher.js';
export type TDocCreation = 'db' | 'new' | 'mixed'; export type TDocCreation = 'db' | 'new' | 'mixed';
@ -147,18 +147,18 @@ export class SmartDataDbDoc<T extends TImplements, TImplements, TManager extends
/** /**
* watch the collection * watch the collection
* @param this * @param this
* @param filterArg * @param filterArg
* @param forEachFunction * @param forEachFunction
*/ */
public static async watch<T>( public static async watch<T>(
this: plugins.tsclass.typeFest.Class<T>, this: plugins.tsclass.typeFest.Class<T>,
filterArg: plugins.tsclass.typeFest.PartialDeep<T> filterArg: plugins.tsclass.typeFest.PartialDeep<T>
) { ) {
const collection: SmartdataCollection<T> = (this as any).collection; const collection: SmartdataCollection<T> = (this as any).collection;
const watcher: SmartdataDbWatcher<T> = await collection.watch( const watcher: SmartdataDbWatcher<T> = await collection.watch(
convertFilterForMongoDb(filterArg), convertFilterForMongoDb(filterArg),
(this as any) this as any
); );
return watcher; return watcher;
} }
@ -255,6 +255,16 @@ 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());
for (const key of Object.keys(mongoDbNativeDoc)) {
this[key] = mongoDbNativeDoc[key];
}
}
/** /**
* creates a saveable object so the instance can be persisted as json in the database * creates a saveable object so the instance can be persisted as json in the database
*/ */

View File

@ -17,6 +17,15 @@ export class EasyStore<T> {
@unI() @unI()
public nameId: string; public nameId: string;
@svDb()
public ephermal: {
activated: boolean;
timeout: number;
};
@svDb()
lastEdit: number;
@svDb() @svDb()
public data: Partial<T>; public data: Partial<T>;
} }
@ -90,4 +99,11 @@ export class EasyStore<T> {
easyStore.data = {}; easyStore.data = {};
await easyStore.save(); await easyStore.save();
} }
public async cleanUpEphermal() {
while (
(await this.smartdataDbRef.statusConnectedDeferred.promise) &&
this.smartdataDbRef.status === 'connected'
) {}
}
} }

View File

@ -11,12 +11,17 @@ export class SmartdataDbWatcher<T = any> {
// INSTANCE // INSTANCE
private changeStream: plugins.mongodb.ChangeStream<T>; private changeStream: plugins.mongodb.ChangeStream<T>;
public changeSubject = new plugins.smartrx.rxjs.Subject<SmartDataDbDoc<T, T>>(); public changeSubject = new plugins.smartrx.rxjs.Subject<T>();
constructor(changeStreamArg: plugins.mongodb.ChangeStream<T>, smartdataDbDocArg: typeof SmartDataDbDoc) { constructor(
changeStreamArg: plugins.mongodb.ChangeStream<T>,
smartdataDbDocArg: typeof SmartDataDbDoc
) {
this.changeStream = changeStreamArg; this.changeStream = changeStreamArg;
this.changeStream.on('change', async (item: T) => { this.changeStream.on('change', async (item: T) => {
this.changeSubject.next(smartdataDbDocArg.createInstanceFromMongoDbNativeDoc(item)); this.changeSubject.next(
}) smartdataDbDocArg.createInstanceFromMongoDbNativeDoc(item) as any as T
);
});
plugins.smartdelay.delayFor(0).then(() => { plugins.smartdelay.delayFor(0).then(() => {
this.readyDeferred.resolve(); this.readyDeferred.resolve();
}); });

View File

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