Compare commits
125 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20182a00f8 | |||
| ddf4e698c9 | |||
| 597e9e15c3 | |||
| 03a33195bc | |||
| 47d339bb2b | |||
| bf3b4e832a | |||
| 685f4ebb3b | |||
| 2ff7efe6d8 | |||
| 99072d5fdf | |||
| 4442ddffcd | |||
| 7c5b3825ac | |||
| 84babb3cd4 | |||
| 5d9624bd56 | |||
| 32397a97cd | |||
| 753b829d18 | |||
| 4be2784bf4 | |||
| cd08cf370c | |||
| b64fe567a8 | |||
| 2a68c9ad90 | |||
| aa978413d1 | |||
| f24636ba80 | |||
| 5e31dbb504 | |||
| b2ca6e13e7 | |||
| 052322fb98 | |||
| 9e1eb0b3a0 | |||
| af9b045d31 | |||
| d7718d4340 | |||
| 92592d9e9a | |||
| a786c43970 | |||
| 66658dc877 | |||
| be78d74124 | |||
| bde0404777 | |||
| dfe973f5d8 | |||
| 326030456f | |||
| 184dc98127 | |||
| 702ce00288 | |||
| ff0d745170 | |||
| 8fc0438c75 | |||
| 355c2d132e | |||
| c083a47f07 | |||
| 9eb9403bea | |||
| 029f2a6872 | |||
| 66676d89a5 | |||
| 775a307056 | |||
| 4aa7621401 | |||
| b7ac5cb864 | |||
| f580281ccd | |||
| cc66f81c7e | |||
| 6f1e32284d | |||
| 47b511f1f0 | |||
| 24f82fe570 | |||
| 60a0fab9db | |||
| 61e287fbe4 | |||
| 984297c9c3 | |||
| 55e4edc785 | |||
| 0fdc891326 | |||
| 69ab47ed41 | |||
| b1bd8132de | |||
| 4e672b13f4 | |||
| 75c2f23d02 | |||
| da0c459d74 | |||
| 079b093e2c | |||
| 55a3caec44 | |||
| 28b3666aad | |||
| b17dd2ed64 | |||
| e551a68237 | |||
| f402e55ff3 | |||
| 15b8fe406a | |||
| c7557163cd | |||
| f84822dd5d | |||
| 21d6e19a22 | |||
| 8947738dc1 | |||
| 14bc5dcd25 | |||
| c2f366de7f | |||
| ae47276569 | |||
| 42b59c109d | |||
| 8f18faaf1c | |||
| ccd5b80d67 | |||
| 298904e17e | |||
| fa91e67aef | |||
| 5e99066bee | |||
| a838f7eb80 | |||
| 979e93be27 | |||
| 60587f052c | |||
| da5bd43a42 | |||
| 24559d1582 | |||
| d92657b130 | |||
| f8f34bf8a3 | |||
| 861d2c04b3 | |||
| 5845d74160 | |||
| 9dd952e6b7 | |||
| 742a711359 | |||
| 8878a0ef8b | |||
| 6a52e81cdd | |||
| 757e709f3f | |||
| e0d9c3a3e0 | |||
| 150953adaf | |||
| e0eba5d206 | |||
| d9eb836f98 | |||
| 6d29798660 | |||
| f80a84086c | |||
| 929a6eff07 | |||
| 87243881bb | |||
| 5d51c14bd6 | |||
| 453c6d6ae8 | |||
| 2e9fe308df | |||
| 96a71638a0 | |||
| 1c7582d328 | |||
| ccff9b39be | |||
| 749be9ac60 | |||
| 7c9ad26519 | |||
| 6fdf08c8a9 | |||
| de04d75e18 | |||
| 5159d7e4bf | |||
| 7b78aaea72 | |||
| 54a63d1f41 | |||
| dd5a009d0f | |||
| e7933cd7f4 | |||
| 56a8d9182b | |||
| 3a971301a4 | |||
| b880036b64 | |||
| 9d756dbff7 | |||
| b8111c66ff | |||
| 8d660a8573 | |||
| 596a897afc |
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 @git.zone/tsdoc
|
||||||
|
npmci command tsdoc
|
||||||
|
continue-on-error: true
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -15,8 +15,6 @@ node_modules/
|
|||||||
|
|
||||||
# builds
|
# builds
|
||||||
dist/
|
dist/
|
||||||
dist_web/
|
dist_*/
|
||||||
dist_serve/
|
|
||||||
dist_ts_web/
|
|
||||||
|
|
||||||
# custom
|
# custom
|
||||||
121
.gitlab-ci.yml
121
.gitlab-ci.yml
@@ -1,121 +0,0 @@
|
|||||||
# gitzone ci_default
|
|
||||||
image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
|
|
||||||
variables:
|
|
||||||
GIT_STRATEGY: clone
|
|
||||||
|
|
||||||
cache:
|
|
||||||
paths:
|
|
||||||
- .npmci_cache/
|
|
||||||
key: "$CI_BUILD_STAGE"
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- security
|
|
||||||
- test
|
|
||||||
- release
|
|
||||||
- metadata
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# security stage
|
|
||||||
# ====================
|
|
||||||
mirror:
|
|
||||||
stage: security
|
|
||||||
script:
|
|
||||||
- npmci git mirror
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
snyk:
|
|
||||||
stage: security
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci command npm install -g snyk
|
|
||||||
- npmci command npm install --ignore-scripts
|
|
||||||
- npmci command snyk test
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# test stage
|
|
||||||
# ====================
|
|
||||||
|
|
||||||
testStable:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci node install stable
|
|
||||||
- npmci npm install
|
|
||||||
- npmci npm test
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- priv
|
|
||||||
|
|
||||||
testBuild:
|
|
||||||
stage: test
|
|
||||||
script:
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci node install lts
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command npm run build
|
|
||||||
coverage: /\d+.?\d+?\%\s*coverage/
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
release:
|
|
||||||
stage: release
|
|
||||||
script:
|
|
||||||
- npmci node install lts
|
|
||||||
- npmci npm publish
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
# ====================
|
|
||||||
# metadata stage
|
|
||||||
# ====================
|
|
||||||
codequality:
|
|
||||||
stage: metadata
|
|
||||||
allow_failure: true
|
|
||||||
script:
|
|
||||||
- npmci command npm install -g tslint typescript
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command "tslint -c tslint.json ./ts/**/*.ts"
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- priv
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci trigger
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
|
|
||||||
pages:
|
|
||||||
image: hosttoday/ht-docker-dbase:npmci
|
|
||||||
services:
|
|
||||||
- docker:18-dind
|
|
||||||
stage: metadata
|
|
||||||
script:
|
|
||||||
- npmci command npm install -g @gitzone/tsdoc
|
|
||||||
- npmci npm prepare
|
|
||||||
- npmci npm install
|
|
||||||
- npmci command tsdoc
|
|
||||||
tags:
|
|
||||||
- docker
|
|
||||||
- notpriv
|
|
||||||
only:
|
|
||||||
- tags
|
|
||||||
artifacts:
|
|
||||||
expire_in: 1 week
|
|
||||||
paths:
|
|
||||||
- public
|
|
||||||
allow_failure: true
|
|
||||||
11
.vscode/launch.json
vendored
Normal file
11
.vscode/launch.json
vendored
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
{
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"command": "npm test",
|
||||||
|
"name": "Run npm test",
|
||||||
|
"request": "launch",
|
||||||
|
"type": "node-terminal"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
.vscode/settings.json
vendored
25
.vscode/settings.json
vendored
@@ -1,3 +1,26 @@
|
|||||||
{
|
{
|
||||||
"workbench.colorCustomizations": {}
|
"json.schemas": [
|
||||||
|
{
|
||||||
|
"fileMatch": ["/npmextra.json"],
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"npmci": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for npmci"
|
||||||
|
},
|
||||||
|
"gitzone": {
|
||||||
|
"type": "object",
|
||||||
|
"description": "settings for gitzone",
|
||||||
|
"properties": {
|
||||||
|
"projectType": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["website", "element", "service", "npm", "wcc"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
43
README.md
43
README.md
@@ -1,43 +0,0 @@
|
|||||||
# @pushrocks/lik
|
|
||||||
light little helpers for node
|
|
||||||
|
|
||||||
## Availabililty and Links
|
|
||||||
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/lik)
|
|
||||||
* [gitlab.com (source)](https://gitlab.com/pushrocks/lik)
|
|
||||||
* [github.com (source mirror)](https://github.com/pushrocks/lik)
|
|
||||||
* [docs (typedoc)](https://pushrocks.gitlab.io/lik/)
|
|
||||||
|
|
||||||
## Status for master
|
|
||||||
[](https://gitlab.com/pushrocks/lik/commits/master)
|
|
||||||
[](https://gitlab.com/pushrocks/lik/commits/master)
|
|
||||||
[](https://www.npmjs.com/package/@pushrocks/lik)
|
|
||||||
[](https://snyk.io/test/npm/@pushrocks/lik)
|
|
||||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
|
||||||
[](https://nodejs.org/dist/latest-v10.x/docs/api/)
|
|
||||||
[](https://prettier.io/)
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
Use TypeScript for best in class instellisense.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// import any tool that you need from lik
|
|
||||||
import { Stringmap, Objectmap, Observablemap } from 'lik';
|
|
||||||
```
|
|
||||||
|
|
||||||
### class Stringmap
|
|
||||||
|
|
||||||
Stringmap allows you to keep track of strings. It allows you to register triggers for certain events
|
|
||||||
like when a certain string is removed or added to the map
|
|
||||||
|
|
||||||
### class Objectmap
|
|
||||||
|
|
||||||
Sometimes you need to keep track of objects, but implementing logic for removing, finding or updating is tedious.
|
|
||||||
Objectmap takes care of keeping track of objects for you.
|
|
||||||
|
|
||||||
For further information read the linked docs at the top of this readme.
|
|
||||||
|
|
||||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
|
||||||
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
|
|
||||||
|
|
||||||
[](https://maintainedby.lossless.com)
|
|
||||||
104
changelog.md
Normal file
104
changelog.md
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-03-01 - 6.3.1 - fix(classes)
|
||||||
|
cleanup resources, add cancellable timeouts, and fix bugs in several core utility classes
|
||||||
|
|
||||||
|
- Replace one-shot delayFor usage with plugins.smartdelay.Timeout in AsyncExecutionStack so timeouts are cancellable and properly cleaned up on success or error
|
||||||
|
- Add destroy() to BackpressuredArray to complete subjects and unblock waiters; waitForSpace/waitForItems now respect destruction to avoid hangs
|
||||||
|
- Make Interest instances cancel mark-lost timers and guard against double-destroy; destruction now clears fulfillment store and resolves default fulfillment without mutual recursion
|
||||||
|
- Add InterestMap.destroy() to clean up all interests and complete observable
|
||||||
|
- ObjectMap: removeMappedUnique now returns removed object and emits a remove event; wipe now emits remove events for cleared entries and destroy() completes eventSubject
|
||||||
|
- StringMap.destroy() clears stored strings and pending triggers
|
||||||
|
- TimedAggregtor: add stop(flushRemaining) and isStopped guards to stop timer chain and optionally flush remaining items
|
||||||
|
- LoopTracker: add reset() and destroy() helpers to clear and destroy internal maps
|
||||||
|
- Fix compareTreePosition to call symbolTree.compareTreePosition instead of recursively calling itself
|
||||||
|
|
||||||
|
## 2026-03-01 - 6.3.0 - feat(tooling)
|
||||||
|
update build tooling, developer dependencies, npmextra configuration, and expand README documentation
|
||||||
|
|
||||||
|
- Bump devDependencies for @git.zone toolchain and related packages (@git.zone/tsbuild, tsbundle, tsrun, tstest, @push.rocks/tapbundle, @types/node)
|
||||||
|
- Bump runtime deps: @push.rocks/smartrx and @push.rocks/smarttime
|
||||||
|
- Adjust npm build script: remove trailing 'npm' argument from tsbundle invocation
|
||||||
|
- Rework npmextra.json: rename/unify keys to @git.zone/* scoped entries, add release registries and accessLevel, add tsbundle bundle configuration, and reorganize CI/tool settings
|
||||||
|
- Significant README rewrite: expanded descriptions, clearer usage examples and API snippets, formatting and example updates
|
||||||
|
|
||||||
|
## 2026-03-01 - 6.2.3 - fix(interestmap)
|
||||||
|
remove interest from InterestMap immediately after fulfillment
|
||||||
|
|
||||||
|
- Call destroy() in fullfillInterest to remove the interest entry from the InterestMap right after resolving interestDeferred.
|
||||||
|
- Prevents stale entries and ensures immediate cleanup of fulfilled interests
|
||||||
|
|
||||||
|
## 2025-04-25 - 6.2.2 - fix(docs)
|
||||||
|
Update @push.rocks/tapbundle dependency and refine AsyncExecutionStack documentation examples
|
||||||
|
|
||||||
|
- Bump @push.rocks/tapbundle from ^5.0.8 to ^5.5.6 in package.json
|
||||||
|
- Improve README documentation for AsyncExecutionStack with clearer examples for exclusive and non-exclusive task execution
|
||||||
|
- Demonstrate usage of concurrency controls in AsyncExecutionStack
|
||||||
|
|
||||||
|
## 2025-04-25 - 6.2.1 - fix(AsyncExecutionStack tests)
|
||||||
|
Refactor AsyncExecutionStack tests: update non-exclusive concurrency assertions and clean up test logic
|
||||||
|
|
||||||
|
- Replace 'toBe' with 'toEqual' for active and pending counts to ensure consistency
|
||||||
|
- Simplify default non-exclusive concurrency test by asserting Infinity is non-finite using toBeFalse
|
||||||
|
- Adjust test comments and scheduling for clarity in concurrency behavior
|
||||||
|
|
||||||
|
## 2025-04-25 - 6.2.0 - feat(AsyncExecutionStack)
|
||||||
|
Improve non-exclusive task management with concurrency limit controls and enhanced monitoring in AsyncExecutionStack.
|
||||||
|
|
||||||
|
- Added methods to set and get non-exclusive concurrency limits and statistics (setNonExclusiveMaxConcurrency, getActiveNonExclusiveCount, getPendingNonExclusiveCount, and getNonExclusiveMaxConcurrency).
|
||||||
|
- Integrated proper waiting and release mechanisms for non-exclusive slots.
|
||||||
|
- Extended test coverage to validate concurrency limits and ensure correct behavior.
|
||||||
|
|
||||||
|
## 2024-10-13 - 6.1.0 - feat(BackpressuredArray)
|
||||||
|
Add method to check if items are present in BackpressuredArray
|
||||||
|
|
||||||
|
- Implemented a new method `checkHasItems` in the BackpressuredArray class to determine if the array contains any items.
|
||||||
|
|
||||||
|
## 2024-05-29 to 2024-04-18 - 6.0.15
|
||||||
|
Minor updates were made to documentation and descriptions.
|
||||||
|
|
||||||
|
- Update project description
|
||||||
|
|
||||||
|
## 2024-04-18 to 2024-02-25 - 6.0.14
|
||||||
|
Several updates were made to configurations and json files.
|
||||||
|
|
||||||
|
- Updated core components in the codebase
|
||||||
|
- Modified tsconfig settings
|
||||||
|
- Revised npmextra.json with githost configurations
|
||||||
|
|
||||||
|
## 2024-02-25 to 2024-02-23 - 6.0.13
|
||||||
|
No relevant changes.
|
||||||
|
|
||||||
|
## 2024-02-23 to 2023-11-13 - 6.0.12 to 6.0.8
|
||||||
|
Multiple core updates were performed to ensure stability and performance.
|
||||||
|
|
||||||
|
- Fixed various issues in core components
|
||||||
|
|
||||||
|
## 2023-11-13 to 2023-08-14 - 6.0.7 to 6.0.3
|
||||||
|
Minor internal core updates.
|
||||||
|
|
||||||
|
## 2023-08-14 to 2023-07-12 - 6.0.2
|
||||||
|
Implemented a switch to a new organizational scheme.
|
||||||
|
|
||||||
|
## 2023-01-18 to 2022-05-27 - 6.0.0
|
||||||
|
Updated core functionalities; introduced breaking changes for compatibility with ECMAScript modules.
|
||||||
|
|
||||||
|
- Core updates
|
||||||
|
- Switching from CommonJS to ECMAScript modules
|
||||||
|
|
||||||
|
## 2022-05-27 to 2022-05-27 - 5.0.6 to 5.0.0
|
||||||
|
Minor updates and a significant change in `objectmap` behavior to support async operations.
|
||||||
|
|
||||||
|
- Included async behaviors in objectmap as a breaking change
|
||||||
|
|
||||||
|
## 2020-05-04 to 2020-02-17 - 4.0.0
|
||||||
|
Refactored ObjectMap; introduced new features.
|
||||||
|
|
||||||
|
- Refactored ObjectMap with concat functionality as a breaking change
|
||||||
|
- Added .clean() to FastMap
|
||||||
|
|
||||||
|
## 2020-02-17 to 2020-02-06 - 3.0.19 to 3.0.15
|
||||||
|
Enhancements and new functionality in ObjectMap.
|
||||||
|
|
||||||
|
- Added object mapping enhancements
|
||||||
|
- Introduced object map with unique keys
|
||||||
@@ -1,16 +1,49 @@
|
|||||||
{
|
{
|
||||||
"npmci": {
|
"@git.zone/cli": {
|
||||||
"npmGlobalTools": [],
|
"projectType": "npm",
|
||||||
"npmAccessLevel": "public"
|
|
||||||
},
|
|
||||||
"gitzone": {
|
|
||||||
"module": {
|
"module": {
|
||||||
"githost": "gitlab.com",
|
"githost": "code.foss.global",
|
||||||
"gitscope": "pushrocks",
|
"gitscope": "push.rocks",
|
||||||
"gitrepo": "lik",
|
"gitrepo": "lik",
|
||||||
"shortDescription": "light little helpers for node",
|
"description": "Provides a collection of lightweight helpers and utilities for Node.js projects.",
|
||||||
"npmPackagename": "@pushrocks/lik",
|
"npmPackagename": "@push.rocks/lik",
|
||||||
"license": "MIT"
|
"license": "MIT",
|
||||||
}
|
"keywords": [
|
||||||
|
"Utilities",
|
||||||
|
"Helpers",
|
||||||
|
"Typescript",
|
||||||
|
"Node.js",
|
||||||
|
"String manipulation",
|
||||||
|
"Object management",
|
||||||
|
"Execution control",
|
||||||
|
"Data structures",
|
||||||
|
"Asynchronous programming",
|
||||||
|
"Event handling",
|
||||||
|
"Data aggregation"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"release": {
|
||||||
|
"registries": [
|
||||||
|
"https://verdaccio.lossless.digital",
|
||||||
|
"https://registry.npmjs.org"
|
||||||
|
],
|
||||||
|
"accessLevel": "public"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@git.zone/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"
|
||||||
|
},
|
||||||
|
"@git.zone/tsbundle": {
|
||||||
|
"bundles": [
|
||||||
|
{
|
||||||
|
"from": "./ts/index.ts",
|
||||||
|
"to": "./dist_bundle/bundle.js",
|
||||||
|
"outputMode": "bundle",
|
||||||
|
"bundler": "esbuild"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"@ship.zone/szci": {
|
||||||
|
"npmGlobalTools": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1897
package-lock.json
generated
1897
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
81
package.json
81
package.json
@@ -1,51 +1,78 @@
|
|||||||
{
|
{
|
||||||
"name": "@pushrocks/lik",
|
"name": "@push.rocks/lik",
|
||||||
"version": "3.0.13",
|
"version": "6.3.1",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "light little helpers for node",
|
"description": "Provides a collection of lightweight helpers and utilities for Node.js projects.",
|
||||||
"main": "dist/index.js",
|
"main": "dist_ts/index.js",
|
||||||
"typings": "dist/index.d.ts",
|
"typings": "dist_ts/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "(tstest test/)",
|
"test": "(tstest test/)",
|
||||||
"build": "(tsbuild)"
|
"build": "(tsbuild --web --allowimplicitany && tsbundle)",
|
||||||
|
"buildDocs": "tsdoc"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+ssh://git@gitlab.com/pushrocks/lik.git"
|
"url": "https://code.foss.global/push.rocks/lik.git"
|
||||||
},
|
},
|
||||||
"author": "Lossless GmbH",
|
"author": "Lossless GmbH",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://gitlab.com/pushrocks/lik/issues"
|
"url": "https://gitlab.com/pushrocks/lik/issues"
|
||||||
},
|
},
|
||||||
"homepage": "https://gitlab.com/pushrocks/lik#README",
|
"homepage": "https://code.foss.global/push.rocks/lik",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@gitzone/tsbuild": "^2.1.17",
|
"@git.zone/tsbuild": "^4.1.2",
|
||||||
"@gitzone/tsrun": "^1.2.8",
|
"@git.zone/tsbundle": "^2.9.0",
|
||||||
"@gitzone/tstest": "^1.0.28",
|
"@git.zone/tsrun": "^2.0.1",
|
||||||
"@pushrocks/tapbundle": "^3.2.0",
|
"@git.zone/tstest": "^3.1.8",
|
||||||
"@types/node": "^12.12.14",
|
"@push.rocks/tapbundle": "^6.0.3",
|
||||||
"tslint": "^5.20.1",
|
"@types/node": "^25.3.3"
|
||||||
"tslint-config-prettier": "^1.18.0"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@pushrocks/smartdelay": "^2.0.6",
|
"@push.rocks/smartdelay": "^3.0.5",
|
||||||
"@pushrocks/smartpromise": "^3.0.6",
|
"@push.rocks/smartmatch": "^2.0.0",
|
||||||
"@pushrocks/smartrx": "^2.0.5",
|
"@push.rocks/smartpromise": "^4.0.3",
|
||||||
"@pushrocks/smarttime": "^3.0.12",
|
"@push.rocks/smartrx": "^3.0.10",
|
||||||
"@types/minimatch": "^3.0.3",
|
"@push.rocks/smarttime": "^4.2.3",
|
||||||
"minimatch": "^3.0.4",
|
"@types/minimatch": "^5.1.2",
|
||||||
|
"@types/symbol-tree": "^3.2.5",
|
||||||
"symbol-tree": "^3.2.4"
|
"symbol-tree": "^3.2.4"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/*",
|
"ts/**/*",
|
||||||
"ts_web/*",
|
"ts_web/**/*",
|
||||||
"dist/*",
|
"dist/**/*",
|
||||||
"dist_web/*",
|
"dist_*/**/*",
|
||||||
"dist_ts_web/*",
|
"dist_ts/**/*",
|
||||||
"assets/*",
|
"dist_ts_web/**/*",
|
||||||
|
"assets/**/*",
|
||||||
"cli.js",
|
"cli.js",
|
||||||
"npmextra.json",
|
"npmextra.json",
|
||||||
"readme.md"
|
"readme.md"
|
||||||
|
],
|
||||||
|
"browserslist": [
|
||||||
|
"last 1 chrome versions"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"Utilities",
|
||||||
|
"Helpers",
|
||||||
|
"Typescript",
|
||||||
|
"Node.js",
|
||||||
|
"String manipulation",
|
||||||
|
"Object management",
|
||||||
|
"Execution control",
|
||||||
|
"Data structures",
|
||||||
|
"Asynchronous programming",
|
||||||
|
"Event handling",
|
||||||
|
"Data aggregation"
|
||||||
|
],
|
||||||
|
"pnpm": {
|
||||||
|
"onlyBuiltDependencies": [
|
||||||
|
"esbuild",
|
||||||
|
"mongodb-memory-server",
|
||||||
|
"puppeteer"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.7.0+sha512.6b865ad4b62a1d9842b61d674a393903b871d9244954f652b8842c2b553c72176b278f64c463e52d40fff8aba385c235c8c9ecf5cc7de4fd78b8bb6d49633ab6"
|
||||||
}
|
}
|
||||||
|
|||||||
12100
pnpm-lock.yaml
generated
Normal file
12100
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
1
readme.hints.md
Normal file
1
readme.hints.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
315
readme.md
Normal file
315
readme.md
Normal file
@@ -0,0 +1,315 @@
|
|||||||
|
# @push.rocks/lik
|
||||||
|
|
||||||
|
A collection of lightweight utility classes for TypeScript/Node.js projects, providing efficient data structures and async helpers.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @push.rocks/lik
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
`@push.rocks/lik` provides a set of focused helper classes for common programming tasks: managing collections, controlling async execution, tracking interests, and more. All classes are fully typed and work in both Node.js and browser environments.
|
||||||
|
|
||||||
|
### AsyncExecutionStack
|
||||||
|
|
||||||
|
Controls execution of asynchronous tasks in two modes:
|
||||||
|
|
||||||
|
- **Exclusive**: tasks run one at a time, blocking all others until complete.
|
||||||
|
- **Non-exclusive**: tasks run in parallel with optional concurrency limits.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { AsyncExecutionStack } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const stack = new AsyncExecutionStack();
|
||||||
|
|
||||||
|
// Exclusive execution (sequential, blocks other tasks)
|
||||||
|
await stack.getExclusiveExecutionSlot(async () => {
|
||||||
|
// critical section work
|
||||||
|
}, 5000); // optional timeout in ms
|
||||||
|
|
||||||
|
// Non-exclusive execution (parallel)
|
||||||
|
const p1 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
// concurrent work 1
|
||||||
|
});
|
||||||
|
const p2 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
// concurrent work 2
|
||||||
|
});
|
||||||
|
await Promise.all([p1, p2]);
|
||||||
|
|
||||||
|
// Control concurrency
|
||||||
|
stack.setNonExclusiveMaxConcurrency(3);
|
||||||
|
console.log(stack.getNonExclusiveMaxConcurrency()); // 3
|
||||||
|
console.log(stack.getActiveNonExclusiveCount()); // currently running
|
||||||
|
console.log(stack.getPendingNonExclusiveCount()); // waiting for slots
|
||||||
|
```
|
||||||
|
|
||||||
|
### BackpressuredArray
|
||||||
|
|
||||||
|
An array with backpressure support using RxJS subjects. Useful for producer/consumer patterns where you need to throttle the producer when the consumer can't keep up.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { BackpressuredArray } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const buffer = new BackpressuredArray<string>(16); // high water mark of 16
|
||||||
|
|
||||||
|
// Producer: push items, returns false when full
|
||||||
|
const hasSpace = buffer.push('item1');
|
||||||
|
if (!hasSpace) {
|
||||||
|
await buffer.waitForSpace(); // wait until consumer frees space
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumer: shift items out
|
||||||
|
await buffer.waitForItems(); // wait until items are available
|
||||||
|
const item = buffer.shift();
|
||||||
|
|
||||||
|
// Check state
|
||||||
|
buffer.checkSpaceAvailable(); // true if below high water mark
|
||||||
|
buffer.checkHasItems(); // true if items exist
|
||||||
|
```
|
||||||
|
|
||||||
|
### FastMap
|
||||||
|
|
||||||
|
A high-performance key-value map optimized for rapid lookups and modifications.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { FastMap } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const map = new FastMap<string>();
|
||||||
|
|
||||||
|
map.addToMap('key1', 'value1');
|
||||||
|
map.addToMap('key2', 'value2');
|
||||||
|
|
||||||
|
const value = map.getByKey('key1'); // 'value1'
|
||||||
|
map.isUniqueKey('key1'); // false (already exists)
|
||||||
|
map.removeFromMap('key1');
|
||||||
|
|
||||||
|
// Merge maps
|
||||||
|
const otherMap = new FastMap<string>();
|
||||||
|
otherMap.addToMap('key3', 'value3');
|
||||||
|
const merged = map.concat(otherMap);
|
||||||
|
|
||||||
|
// Or merge in place
|
||||||
|
map.addAllFromOther(otherMap);
|
||||||
|
|
||||||
|
// Async find
|
||||||
|
const found = await map.find(async (item) => item === 'value2');
|
||||||
|
```
|
||||||
|
|
||||||
|
### InterestMap
|
||||||
|
|
||||||
|
Manages subscriptions/interests in events or entities. Multiple parties can express interest in the same thing; the interest is deduplicated and fulfilled once.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { InterestMap } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const interestMap = new InterestMap<string, number>(
|
||||||
|
(str) => str, // comparison function to deduplicate interests
|
||||||
|
{ markLostAfterDefault: 30000 } // optional: auto-mark lost after 30s
|
||||||
|
);
|
||||||
|
|
||||||
|
// Express interest
|
||||||
|
const interest = await interestMap.addInterest('event1');
|
||||||
|
|
||||||
|
// Wait for fulfillment
|
||||||
|
interest.interestFullfilled.then((result) => {
|
||||||
|
console.log('Got result:', result);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Fulfill from elsewhere
|
||||||
|
const found = interestMap.findInterest('event1');
|
||||||
|
found.fullfillInterest(42);
|
||||||
|
|
||||||
|
// Check and manage interests
|
||||||
|
interestMap.checkInterest('event1'); // true/false
|
||||||
|
interestMap.informLostInterest('event1'); // starts destruction timer
|
||||||
|
|
||||||
|
// Observable stream of new interests
|
||||||
|
interestMap.interestObservable; // ObservableIntake<Interest>
|
||||||
|
```
|
||||||
|
|
||||||
|
### LimitedArray
|
||||||
|
|
||||||
|
An array that automatically enforces a maximum size, discarding oldest items when the limit is exceeded.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LimitedArray } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const arr = new LimitedArray<number>(5);
|
||||||
|
|
||||||
|
arr.addMany([1, 2, 3, 4, 5, 6]);
|
||||||
|
console.log(arr.array.length); // 5 (oldest items dropped)
|
||||||
|
|
||||||
|
arr.addOne(7);
|
||||||
|
arr.setLimit(3); // dynamically adjust limit
|
||||||
|
|
||||||
|
// Compute average (for numeric arrays)
|
||||||
|
const numArr = new LimitedArray<number>(10);
|
||||||
|
numArr.addMany([10, 20, 30]);
|
||||||
|
console.log(numArr.getAverage()); // 20
|
||||||
|
```
|
||||||
|
|
||||||
|
### LoopTracker
|
||||||
|
|
||||||
|
Detects and prevents infinite loops by tracking object references during iterations.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { LoopTracker } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const tracker = new LoopTracker<object>();
|
||||||
|
const obj1 = {};
|
||||||
|
|
||||||
|
tracker.checkAndTrack(obj1); // true (first time, tracked)
|
||||||
|
tracker.checkAndTrack(obj1); // false (already seen - loop detected!)
|
||||||
|
```
|
||||||
|
|
||||||
|
### ObjectMap
|
||||||
|
|
||||||
|
A managed collection of objects with add/remove/find operations and event notifications via RxJS.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { ObjectMap } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
interface IUser {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const users = new ObjectMap<IUser>();
|
||||||
|
|
||||||
|
// Add objects
|
||||||
|
const key = users.add({ id: 1, name: 'Alice' });
|
||||||
|
users.addArray([{ id: 2, name: 'Bob' }, { id: 3, name: 'Carol' }]);
|
||||||
|
|
||||||
|
// Find objects
|
||||||
|
const alice = users.findSync((u) => u.id === 1);
|
||||||
|
const bob = await users.find(async (u) => u.id === 2);
|
||||||
|
|
||||||
|
// Find and remove in one step
|
||||||
|
const removed = await users.findOneAndRemove(async (u) => u.id === 3);
|
||||||
|
const removedSync = users.findOneAndRemoveSync((u) => u.id === 2);
|
||||||
|
|
||||||
|
// Direct add/get by unique key
|
||||||
|
users.addMappedUnique('admin', { id: 99, name: 'Admin' });
|
||||||
|
const admin = users.getMappedUnique('admin');
|
||||||
|
|
||||||
|
// Get one and remove (FIFO-style)
|
||||||
|
const first = users.getOneAndRemove();
|
||||||
|
|
||||||
|
// Iterate, check, and manage
|
||||||
|
await users.forEach((u) => console.log(u.name));
|
||||||
|
users.checkForObject(alice); // true/false
|
||||||
|
users.isEmpty(); // true/false
|
||||||
|
users.getArray(); // cloned array of all objects
|
||||||
|
users.wipe(); // remove all
|
||||||
|
|
||||||
|
// Listen for changes
|
||||||
|
users.eventSubject.subscribe((event) => {
|
||||||
|
console.log(event.operation, event.payload); // 'add' | 'remove'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Merge object maps
|
||||||
|
const merged = users.concat(otherObjectMap);
|
||||||
|
users.addAllFromOther(otherObjectMap);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stringmap
|
||||||
|
|
||||||
|
Manages a collection of strings with add/remove/query operations and minimatch pattern matching.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Stringmap } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const strings = new Stringmap();
|
||||||
|
|
||||||
|
strings.addString('hello');
|
||||||
|
strings.addStringArray(['world', 'example']);
|
||||||
|
|
||||||
|
strings.checkString('hello'); // true
|
||||||
|
strings.checkMinimatch('hel*'); // true (glob matching)
|
||||||
|
strings.checkIsEmpty(); // false
|
||||||
|
|
||||||
|
strings.removeString('hello');
|
||||||
|
strings.getStringArray(); // ['world', 'example']
|
||||||
|
|
||||||
|
// Register trigger that fires when condition is met
|
||||||
|
await strings.registerUntilTrue((arr) => arr.length === 0);
|
||||||
|
strings.wipe(); // triggers the above
|
||||||
|
```
|
||||||
|
|
||||||
|
### TimedAggregator
|
||||||
|
|
||||||
|
Batches items over a time interval, then processes them in bulk. Useful for aggregating logs, metrics, or events.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { TimedAggregtor } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
const aggregator = new TimedAggregtor<string>({
|
||||||
|
aggregationIntervalInMillis: 5000,
|
||||||
|
functionForAggregation: (items) => {
|
||||||
|
console.log('Batch:', items);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
aggregator.add('event1');
|
||||||
|
aggregator.add('event2');
|
||||||
|
// After 5 seconds: Batch: ['event1', 'event2']
|
||||||
|
```
|
||||||
|
|
||||||
|
### Tree
|
||||||
|
|
||||||
|
A typed wrapper around `symbol-tree` for managing hierarchical data structures with parent/child/sibling relationships.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Tree } from '@push.rocks/lik';
|
||||||
|
|
||||||
|
class TreeNode {
|
||||||
|
constructor(public value: string) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tree = new Tree<TreeNode>();
|
||||||
|
const root = new TreeNode('root');
|
||||||
|
tree.initialize(root);
|
||||||
|
|
||||||
|
const child1 = new TreeNode('child1');
|
||||||
|
const child2 = new TreeNode('child2');
|
||||||
|
tree.appendChild(root, child1);
|
||||||
|
tree.appendChild(root, child2);
|
||||||
|
|
||||||
|
// Navigate
|
||||||
|
tree.hasChildren(root); // true
|
||||||
|
tree.firstChild(root); // child1
|
||||||
|
tree.lastChild(root); // child2
|
||||||
|
tree.nextSibling(child1); // child2
|
||||||
|
tree.parent(child1); // root
|
||||||
|
|
||||||
|
// Query
|
||||||
|
tree.childrenCount(root); // 2
|
||||||
|
tree.index(child2); // 1
|
||||||
|
tree.childrenToArray(root, {}); // [child1, child2]
|
||||||
|
tree.treeToArray(root, {}); // full tree as array
|
||||||
|
|
||||||
|
// Mutate
|
||||||
|
tree.insertBefore(child2, new TreeNode('between'));
|
||||||
|
tree.remove(child2);
|
||||||
|
```
|
||||||
|
|
||||||
|
## License and Legal Information
|
||||||
|
|
||||||
|
This 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.
|
||||||
|
|
||||||
|
**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.
|
||||||
|
|
||||||
|
### Trademarks
|
||||||
|
|
||||||
|
This 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.
|
||||||
|
|
||||||
|
### Company Information
|
||||||
|
|
||||||
|
Task Venture Capital GmbH
|
||||||
|
Registered at District court Bremen HRB 35230 HB, Germany
|
||||||
|
|
||||||
|
For any legal inquiries or if you require further information, please contact us via email at hello@task.vc.
|
||||||
|
|
||||||
|
By 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.
|
||||||
91
test/test.asyncexecutionstack.both.ts
Normal file
91
test/test.asyncexecutionstack.both.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
|
let testAsyncExecutionStack: lik.AsyncExecutionStack;
|
||||||
|
|
||||||
|
tap.test('should create a valid instance of AsyncExectionStack', async () => {
|
||||||
|
testAsyncExecutionStack = new lik.AsyncExecutionStack();
|
||||||
|
expect(testAsyncExecutionStack).toBeInstanceOf(lik.AsyncExecutionStack);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should run in parallel', async (toolsArg) => {
|
||||||
|
await testAsyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||||
|
await toolsArg.delayFor(2000);
|
||||||
|
console.log('should run first');
|
||||||
|
}, 2500);
|
||||||
|
testAsyncExecutionStack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await toolsArg.delayFor(2000);
|
||||||
|
console.log('should run third');
|
||||||
|
}, 2500);
|
||||||
|
testAsyncExecutionStack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await toolsArg.delayFor(1000);
|
||||||
|
console.log('should run second');
|
||||||
|
}, 2500);
|
||||||
|
await testAsyncExecutionStack.getExclusiveExecutionSlot(async () => {
|
||||||
|
console.log('should run fourth');
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test default non-exclusive has no concurrency limit property (Infinity)
|
||||||
|
tap.test('default non-exclusive has no concurrency limit', () => {
|
||||||
|
const stack = new lik.AsyncExecutionStack();
|
||||||
|
// default maxConcurrency is Infinity (not finite)
|
||||||
|
expect(Number.isFinite(stack.getNonExclusiveMaxConcurrency())).toBeFalse();
|
||||||
|
});
|
||||||
|
// Test respecting a non-exclusive concurrency limit
|
||||||
|
tap.test('non-exclusive respects maxConcurrency', async (tools) => {
|
||||||
|
const stack = new lik.AsyncExecutionStack();
|
||||||
|
stack.setNonExclusiveMaxConcurrency(2);
|
||||||
|
const activeCounts: number[] = [];
|
||||||
|
const tasks: Promise<void>[] = [];
|
||||||
|
for (let i = 0; i < 5; i++) {
|
||||||
|
tasks.push(
|
||||||
|
stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
activeCounts.push(stack.getActiveNonExclusiveCount());
|
||||||
|
await tools.delayFor(50);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await Promise.all(tasks);
|
||||||
|
// never more than 2 at once
|
||||||
|
const maxActive = Math.max(...activeCounts);
|
||||||
|
expect(maxActive).toBeLessThanOrEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Test concurrency stats (active vs pending) for non-exclusive tasks
|
||||||
|
tap.test('non-exclusive concurrency stats reflect active and pending', async (tools) => {
|
||||||
|
const stack = new lik.AsyncExecutionStack();
|
||||||
|
stack.setNonExclusiveMaxConcurrency(2);
|
||||||
|
// initially, no tasks
|
||||||
|
expect(stack.getActiveNonExclusiveCount()).toEqual(0);
|
||||||
|
expect(stack.getPendingNonExclusiveCount()).toEqual(0);
|
||||||
|
|
||||||
|
// enqueue four tasks
|
||||||
|
const p1 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await tools.delayFor(30);
|
||||||
|
});
|
||||||
|
const p2 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await tools.delayFor(30);
|
||||||
|
});
|
||||||
|
const p3 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await tools.delayFor(30);
|
||||||
|
});
|
||||||
|
const p4 = stack.getNonExclusiveExecutionSlot(async () => {
|
||||||
|
await tools.delayFor(30);
|
||||||
|
});
|
||||||
|
|
||||||
|
// wait for first task to finish and scheduling of next batch
|
||||||
|
await p1;
|
||||||
|
await tools.delayFor(0);
|
||||||
|
// second batch: two active, one pending (4 tasks, limit=2)
|
||||||
|
expect(stack.getActiveNonExclusiveCount()).toEqual(2);
|
||||||
|
expect(stack.getPendingNonExclusiveCount()).toEqual(1);
|
||||||
|
|
||||||
|
// wait for remaining tasks to complete
|
||||||
|
await Promise.all([p2, p3, p4]);
|
||||||
|
// after completion, counts reset
|
||||||
|
expect(stack.getActiveNonExclusiveCount()).toEqual(0);
|
||||||
|
expect(stack.getPendingNonExclusiveCount()).toEqual(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
30
test/test.fastmap.both.ts
Normal file
30
test/test.fastmap.both.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
|
tap.test('should create a valid fastmap', async () => {
|
||||||
|
const fastmap = new lik.FastMap();
|
||||||
|
expect(fastmap).toBeInstanceOf(lik.FastMap);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should find an entry', async () => {
|
||||||
|
const fastmap = new lik.FastMap<{
|
||||||
|
value1: string;
|
||||||
|
value2: string;
|
||||||
|
}>();
|
||||||
|
fastmap.addToMap('heythere', {
|
||||||
|
value1: 'heyho',
|
||||||
|
value2: 'heyho2',
|
||||||
|
});
|
||||||
|
fastmap.addToMap('heythere2', {
|
||||||
|
value1: 'heyho3',
|
||||||
|
value2: 'heyho4',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await fastmap.find(async (itemArg) => {
|
||||||
|
return itemArg.value2 === 'heyho4';
|
||||||
|
});
|
||||||
|
expect(result.value1).toEqual('heyho3');
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { tap, expect } from '@pushrocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import * as lik from '../ts/index';
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
let testInterestmap: lik.InterestMap<number, number>;
|
let testInterestmap: lik.InterestMap<number, number>;
|
||||||
|
|
||||||
tap.test('should create an interestmap', async () => {
|
tap.test('should create an interestmap', async () => {
|
||||||
testInterestmap = new lik.InterestMap(numberArg => {
|
testInterestmap = new lik.InterestMap((numberArg) => {
|
||||||
return numberArg.toString();
|
return numberArg.toString();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -22,4 +22,4 @@ tap.test('should be able to inform about a lost interest', async () => {
|
|||||||
testInterestmap.informLostInterest(3);
|
testInterestmap.informLostInterest(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
@@ -1,17 +1,17 @@
|
|||||||
import { tap, expect } from '@pushrocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
import { LimitedArray } from '../ts/index';
|
import { LimitedArray } from '../ts/index.js';
|
||||||
|
|
||||||
let testLimitedArray: LimitedArray<string>;
|
let testLimitedArray: LimitedArray<string>;
|
||||||
|
|
||||||
tap.test('should create a LimitedArray', async () => {
|
tap.test('should create a LimitedArray', async () => {
|
||||||
testLimitedArray = new LimitedArray(6);
|
testLimitedArray = new LimitedArray(6);
|
||||||
expect(testLimitedArray).to.be.instanceof(LimitedArray);
|
expect(testLimitedArray).toBeInstanceOf(LimitedArray);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should never be longer than the set length', async () => {
|
tap.test('should never be longer than the set length', async () => {
|
||||||
testLimitedArray.addMany(['hi', 'this', 'is', 'quite', 'a', 'long', 'string', ':)']);
|
testLimitedArray.addMany(['hi', 'this', 'is', 'quite', 'a', 'long', 'string', ':)']);
|
||||||
expect(testLimitedArray.array.length).to.be.lessThan(7);
|
expect(testLimitedArray.array.length < 7).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
24
test/test.looptracker.both.ts
Normal file
24
test/test.looptracker.both.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// import test framework
|
||||||
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
|
|
||||||
|
// import the module
|
||||||
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
|
let object1 = {};
|
||||||
|
let object2 = {};
|
||||||
|
let myLoopTracker: lik.LoopTracker<any>;
|
||||||
|
|
||||||
|
// tests
|
||||||
|
tap.test('should create a valid looptracker instance', async () => {
|
||||||
|
myLoopTracker = new lik.LoopTracker();
|
||||||
|
expect(myLoopTracker).toBeInstanceOf(lik.LoopTracker);
|
||||||
|
});
|
||||||
|
|
||||||
|
tap.test('should add objects once and return true', async () => {
|
||||||
|
expect(myLoopTracker.checkAndTrack(object1)).toBeTrue();
|
||||||
|
expect(myLoopTracker.checkAndTrack(object1)).toBeFalse();
|
||||||
|
expect(myLoopTracker.checkAndTrack(object2)).toBeTrue();
|
||||||
|
expect(myLoopTracker.checkAndTrack(object2)).toBeFalse();
|
||||||
|
});
|
||||||
|
|
||||||
|
export default tap.start();
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
// import test framework
|
|
||||||
import { expect, tap } from '@pushrocks/tapbundle';
|
|
||||||
import * as events from 'events';
|
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
|
||||||
|
|
||||||
// import the module
|
|
||||||
import * as lik from '../ts/index';
|
|
||||||
|
|
||||||
let object1 = {};
|
|
||||||
let object2 = {};
|
|
||||||
let myLoopTracker: lik.LoopTracker<any>;
|
|
||||||
|
|
||||||
// tests
|
|
||||||
tap.test('should create a valid looptracker instance', async () => {
|
|
||||||
myLoopTracker = new lik.LoopTracker();
|
|
||||||
expect(myLoopTracker).to.be.instanceof(lik.LoopTracker);
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.test('should add objects once and return true', async () => {
|
|
||||||
expect(myLoopTracker.checkAndTrack(object1)).to.be.true;
|
|
||||||
expect(myLoopTracker.checkAndTrack(object1)).to.be.false;
|
|
||||||
expect(myLoopTracker.checkAndTrack(object2)).to.be.true;
|
|
||||||
expect(myLoopTracker.checkAndTrack(object2)).to.be.false;
|
|
||||||
});
|
|
||||||
|
|
||||||
tap.start();
|
|
||||||
@@ -1,78 +1,76 @@
|
|||||||
// import test framework
|
// import test framework
|
||||||
import { expect, tap } from '@pushrocks/tapbundle';
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
import * as events from 'events';
|
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
|
||||||
|
|
||||||
// import the module
|
// import the module
|
||||||
import * as lik from '../ts/index';
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
// Objectmap
|
// Objectmap
|
||||||
interface ITestObject {
|
interface ITestObject {
|
||||||
propOne: string;
|
propOne: string;
|
||||||
propTwo: string;
|
propTwo: string;
|
||||||
}
|
}
|
||||||
let testObjectmap: lik.Objectmap<ITestObject>;
|
let testObjectmap: lik.ObjectMap<ITestObject>;
|
||||||
let testObject1: ITestObject = {
|
let testObject1: ITestObject = {
|
||||||
propOne: 'hello',
|
propOne: 'hello',
|
||||||
propTwo: 'hello2'
|
propTwo: 'hello2',
|
||||||
};
|
};
|
||||||
let testObject2: ITestObject = {
|
let testObject2: ITestObject = {
|
||||||
propOne: 'hello',
|
propOne: 'hello',
|
||||||
propTwo: 'hello2'
|
propTwo: 'hello2',
|
||||||
};
|
};
|
||||||
|
|
||||||
tap.test('new lik.Objectmap() -> should correctly instantiate an Objectmap', async () => {
|
tap.test('new lik.Objectmap() -> should correctly instantiate an Objectmap', async () => {
|
||||||
testObjectmap = new lik.Objectmap<ITestObject>();
|
testObjectmap = new lik.ObjectMap<ITestObject>();
|
||||||
expect(testObjectmap).be.instanceof(lik.Objectmap);
|
expect(testObjectmap).toBeInstanceOf(lik.ObjectMap);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Objectmap.add() -> should correctly add an object to Objectmap', async () => {
|
tap.test('lik.Objectmap.add() -> should correctly add an object to Objectmap', async () => {
|
||||||
testObjectmap.add(testObject1);
|
testObjectmap.add(testObject1);
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testObjectmap.checkForObject(testObject1)).be.true;
|
expect(testObjectmap.checkForObject(testObject1)).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testObjectmap.checkForObject(testObject2)).be.false;
|
expect(testObjectmap.checkForObject(testObject2)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Objectmap.remove() -> should correctly remove an object to Objectmap', async () => {
|
tap.test('lik.Objectmap.remove() -> should correctly remove an object to Objectmap', async () => {
|
||||||
testObjectmap.add(testObject2);
|
testObjectmap.add(testObject2);
|
||||||
testObjectmap.remove(testObject1);
|
testObjectmap.remove(testObject1);
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testObjectmap.checkForObject(testObject1)).be.false;
|
expect(testObjectmap.checkForObject(testObject1)).toBeFalse();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testObjectmap.checkForObject(testObject2)).be.true;
|
expect(testObjectmap.checkForObject(testObject2)).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('Objectmap.forEach -> should correctly run a function forEach map object', async () => {
|
tap.test('Objectmap.forEach -> should correctly run a function forEach map object', async () => {
|
||||||
testObjectmap.forEach(itemArg => {
|
testObjectmap.forEach((itemArg) => {
|
||||||
expect(itemArg).to.have.property('propOne');
|
expect(itemArg).toHaveProperty('propOne');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Objectmap.find() -> should correctly find an object', async () => {
|
tap.test('lik.Objectmap.find() -> should correctly find an object', async () => {
|
||||||
let myObject = { propOne: 'helloThere', propTwo: 'helloAnyway' };
|
let myObject = { propOne: 'helloThere', propTwo: 'helloAnyway' };
|
||||||
testObjectmap.add(myObject);
|
testObjectmap.add(myObject);
|
||||||
let referenceObject = testObjectmap.find(itemArg => {
|
let referenceObject = await testObjectmap.find(async (itemArg) => {
|
||||||
return itemArg.propOne === 'helloThere';
|
return itemArg.propOne === 'helloThere';
|
||||||
});
|
});
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(myObject === referenceObject).be.true;
|
expect(myObject === referenceObject).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Objectmap.getArray() -> should return a cloned array', async () => {
|
tap.test('lik.Objectmap.getArray() -> should return a cloned array', async () => {
|
||||||
let myObject = { propOne: 'test1', propTwo: 'wow, how awesome' };
|
let myObject = { propOne: 'test1', propTwo: 'wow, how awesome' };
|
||||||
testObjectmap.add(myObject);
|
testObjectmap.add(myObject);
|
||||||
let clonedArray = testObjectmap.getArray();
|
let clonedArray = testObjectmap.getArray();
|
||||||
expect(clonedArray[clonedArray.length - 1]).to.eql(myObject);
|
expect(clonedArray[clonedArray.length - 1]).toEqual(myObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should get one object and then remove it', async () => {
|
tap.test('should get one object and then remove it', async () => {
|
||||||
let originalLength = testObjectmap.getArray().length;
|
let originalLength = testObjectmap.getArray().length;
|
||||||
let oneObject = testObjectmap.getOneAndRemove();
|
let oneObject = testObjectmap.getOneAndRemove();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(oneObject).not.be.null;
|
expect(oneObject).not.toBeNull();
|
||||||
expect(testObjectmap.getArray().length).equal(originalLength - 1);
|
expect(testObjectmap.getArray().length).toEqual(originalLength - 1);
|
||||||
expect(testObjectmap.getArray()).to.not.contain(oneObject);
|
expect(testObjectmap.getArray()).not.toContain(oneObject);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
// import test framework
|
// import test framework
|
||||||
import { expect, tap } from '@pushrocks/tapbundle';
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
import * as events from 'events';
|
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
|
||||||
|
|
||||||
// import the module
|
// import the module
|
||||||
import * as lik from '../ts/index';
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
// testData
|
// testData
|
||||||
let testStringmap: lik.Stringmap;
|
let testStringmap: lik.Stringmap;
|
||||||
@@ -18,14 +16,14 @@ let testString6 = 'testString6';
|
|||||||
// tests
|
// tests
|
||||||
tap.test('new lik.Objectmap() -> should create an instance of Stringmap', async () => {
|
tap.test('new lik.Objectmap() -> should create an instance of Stringmap', async () => {
|
||||||
testStringmap = new lik.Stringmap();
|
testStringmap = new lik.Stringmap();
|
||||||
expect(testStringmap).be.instanceof(lik.Stringmap);
|
expect(testStringmap).toBeInstanceOf(lik.Stringmap);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test(
|
tap.test(
|
||||||
'lik.Stringmap.checkString -> should return false for an string not in Stringmap',
|
'lik.Stringmap.checkString -> should return false for an string not in Stringmap',
|
||||||
async () => {
|
async () => {
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkString(testString1)).be.false;
|
expect(testStringmap.checkString(testString1)).toBeFalse();
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -34,37 +32,37 @@ tap.test('lik.Stringmap.addString -> should add an string to Stringmap', async (
|
|||||||
testStringmap.addString(testString2);
|
testStringmap.addString(testString2);
|
||||||
testStringmap.addString(testString3);
|
testStringmap.addString(testString3);
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkString(testString1)).be.true;
|
expect(testStringmap.checkString(testString1)).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkString(testString2)).be.true;
|
expect(testStringmap.checkString(testString2)).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkString(testString3)).be.true;
|
expect(testStringmap.checkString(testString3)).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkMinimatch('*String1')).be.true;
|
expect(testStringmap.checkMinimatch('*String1')).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkMinimatch('*String2')).be.true;
|
expect(testStringmap.checkMinimatch('*String2')).toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkMinimatch('*String4')).be.false;
|
expect(testStringmap.checkMinimatch('*String4')).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Stringmap.addStringArray -> should add an array of strings', async () => {
|
tap.test('lik.Stringmap.addStringArray -> should add an array of strings', async () => {
|
||||||
testStringmap.addStringArray([testString4, testString5, testString6]);
|
testStringmap.addStringArray([testString4, testString5, testString6]);
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkMinimatch('*String4')).be.true;
|
expect(testStringmap.checkMinimatch('*String4')).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Stringmap.removeString -> should remove a string from Stringmap', async () => {
|
tap.test('lik.Stringmap.removeString -> should remove a string from Stringmap', async () => {
|
||||||
testStringmap.removeString(testString2);
|
testStringmap.removeString(testString2);
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(testStringmap.checkString(testString2)).be.false;
|
expect(testStringmap.checkString(testString2)).toBeFalse();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('lik.Stringmap.getStringArray() -> should return a copy of stringArray', async () => {
|
tap.test('lik.Stringmap.getStringArray() -> should return a copy of stringArray', async () => {
|
||||||
let clonedArray = testStringmap.getStringArray();
|
let clonedArray = testStringmap.getStringArray();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(clonedArray[0] === 'testString1').be.true;
|
expect(clonedArray[0] === 'testString1').toBeTrue();
|
||||||
// tslint:disable-next-line:no-unused-expression
|
// tslint:disable-next-line:no-unused-expression
|
||||||
expect(clonedArray[0] === testString1).be.true;
|
expect(clonedArray[0] === testString1).toBeTrue();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test(
|
tap.test(
|
||||||
@@ -85,4 +83,4 @@ tap.test('lik.Stringmap.empty() -> should remove wipe and then notify', async ()
|
|||||||
testStringmap.wipe();
|
testStringmap.wipe();
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
@@ -1,19 +1,17 @@
|
|||||||
// import test framework
|
// import test framework
|
||||||
import { expect, tap } from '@pushrocks/tapbundle';
|
import { expect, tap } from '@push.rocks/tapbundle';
|
||||||
import * as events from 'events';
|
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
|
||||||
|
|
||||||
// import the module
|
// import the module
|
||||||
import * as lik from '../ts/index';
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
let testTimedAggregator: lik.TimedAggregtor<string>;
|
let testTimedAggregator: lik.TimedAggregtor<string>;
|
||||||
|
|
||||||
tap.test('should create a timed aggregaotor', async tools => {
|
tap.test('should create a timed aggregaotor', async (tools) => {
|
||||||
testTimedAggregator = new lik.TimedAggregtor<string>({
|
testTimedAggregator = new lik.TimedAggregtor<string>({
|
||||||
aggregationIntervalInMillis: 1000,
|
aggregationIntervalInMillis: 1000,
|
||||||
functionForAggregation: aggregation => {
|
functionForAggregation: (aggregation) => {
|
||||||
console.log(aggregation);
|
console.log(aggregation);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
testTimedAggregator.add('This');
|
testTimedAggregator.add('This');
|
||||||
testTimedAggregator.add('is a whole sentence.');
|
testTimedAggregator.add('is a whole sentence.');
|
||||||
@@ -22,4 +20,4 @@ tap.test('should create a timed aggregaotor', async tools => {
|
|||||||
await tools.delayFor(2000);
|
await tools.delayFor(2000);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { tap, expect } from '@pushrocks/tapbundle';
|
import { tap, expect } from '@push.rocks/tapbundle';
|
||||||
import * as lik from '../ts/index';
|
import * as lik from '../ts/index.js';
|
||||||
|
|
||||||
class TestClass {
|
class TestClass {
|
||||||
constructor(public hey: string) {
|
constructor(public hey: string) {
|
||||||
@@ -18,13 +18,13 @@ let testInstance6 = new TestClass('sixth');
|
|||||||
|
|
||||||
tap.test('create a valid tree instance', async () => {
|
tap.test('create a valid tree instance', async () => {
|
||||||
testTree = new lik.Tree();
|
testTree = new lik.Tree();
|
||||||
expect(testTree).to.be.instanceOf(lik.Tree);
|
expect(testTree).toBeInstanceOf(lik.Tree);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should insert an object', async () => {
|
tap.test('should insert an object', async () => {
|
||||||
testTree.initialize(testInstance);
|
testTree.initialize(testInstance);
|
||||||
let resultArray = testTree.treeToArray(testInstance, {});
|
let resultArray = testTree.treeToArray(testInstance, {});
|
||||||
expect(resultArray).to.contain(testInstance);
|
expect(resultArray).toContain(testInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.test('should add other objects in a hierachy', async () => {
|
tap.test('should add other objects in a hierachy', async () => {
|
||||||
@@ -37,4 +37,4 @@ tap.test("should create a JSON object that reflects a tree's hierachy", async ()
|
|||||||
const jsonTreet = testTree.toJsonWithHierachy(testInstance);
|
const jsonTreet = testTree.toJsonWithHierachy(testInstance);
|
||||||
});
|
});
|
||||||
|
|
||||||
tap.start();
|
export default tap.start();
|
||||||
@@ -1 +0,0 @@
|
|||||||
import './test.objectmap';
|
|
||||||
8
ts/00_commitinfo_data.ts
Normal file
8
ts/00_commitinfo_data.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/**
|
||||||
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
|
*/
|
||||||
|
export const commitinfo = {
|
||||||
|
name: '@push.rocks/lik',
|
||||||
|
version: '6.3.1',
|
||||||
|
description: 'Provides a collection of lightweight helpers and utilities for Node.js projects.'
|
||||||
|
}
|
||||||
177
ts/classes.asyncexecutionstack.ts
Normal file
177
ts/classes.asyncexecutionstack.ts
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
|
interface IExecutionSlot<T> {
|
||||||
|
executionDeferred: plugins.smartpromise.Deferred<T>;
|
||||||
|
funcToExecute: () => Promise<T>;
|
||||||
|
timeout?: number;
|
||||||
|
mode: 'exclusive' | 'nonexclusive';
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AsyncExecutionStack {
|
||||||
|
private executionSlots: IExecutionSlot<any>[] = [];
|
||||||
|
private isProcessing = false;
|
||||||
|
/** Maximum concurrent non-exclusive tasks (Infinity = unlimited) */
|
||||||
|
private nonExclusiveMaxConcurrency: number = Infinity;
|
||||||
|
/** Currently running non-exclusive task count */
|
||||||
|
private nonExclusiveCurrentCount: number = 0;
|
||||||
|
/** Queue of resolvers waiting for a non-exclusive slot */
|
||||||
|
private nonExclusivePendingQueue: Array<() => void> = [];
|
||||||
|
|
||||||
|
public async getExclusiveExecutionSlot<T = any>(
|
||||||
|
funcArg: () => Promise<T>,
|
||||||
|
timeoutArg?: number
|
||||||
|
): Promise<T> {
|
||||||
|
const executionDeferred = plugins.smartpromise.defer<T>();
|
||||||
|
const executionSlot: IExecutionSlot<T> = {
|
||||||
|
funcToExecute: funcArg,
|
||||||
|
executionDeferred,
|
||||||
|
timeout: timeoutArg,
|
||||||
|
mode: 'exclusive',
|
||||||
|
};
|
||||||
|
this.executionSlots.push(executionSlot);
|
||||||
|
this.processExecutionSlots();
|
||||||
|
return executionDeferred.promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getNonExclusiveExecutionSlot<T = any>(
|
||||||
|
funcArg: () => Promise<T>,
|
||||||
|
timeoutArg?: number
|
||||||
|
): Promise<T> {
|
||||||
|
const executionDeferred = plugins.smartpromise.defer<T>();
|
||||||
|
const executionSlot: IExecutionSlot<T> = {
|
||||||
|
funcToExecute: funcArg,
|
||||||
|
executionDeferred,
|
||||||
|
timeout: timeoutArg,
|
||||||
|
mode: 'nonexclusive',
|
||||||
|
};
|
||||||
|
this.executionSlots.push(executionSlot);
|
||||||
|
this.processExecutionSlots();
|
||||||
|
return executionDeferred.promise;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Set the maximum number of concurrent non-exclusive tasks.
|
||||||
|
* @param concurrency minimum 1 (Infinity means unlimited)
|
||||||
|
*/
|
||||||
|
public setNonExclusiveMaxConcurrency(concurrency: number): void {
|
||||||
|
if (!Number.isFinite(concurrency) || concurrency < 1) {
|
||||||
|
throw new Error('nonExclusiveMaxConcurrency must be a finite number >= 1');
|
||||||
|
}
|
||||||
|
this.nonExclusiveMaxConcurrency = concurrency;
|
||||||
|
}
|
||||||
|
/** Get the configured max concurrency for non-exclusive tasks */
|
||||||
|
public getNonExclusiveMaxConcurrency(): number {
|
||||||
|
return this.nonExclusiveMaxConcurrency;
|
||||||
|
}
|
||||||
|
/** Number of non-exclusive tasks currently running */
|
||||||
|
public getActiveNonExclusiveCount(): number {
|
||||||
|
return this.nonExclusiveCurrentCount;
|
||||||
|
}
|
||||||
|
/** Number of non-exclusive tasks waiting for a free slot */
|
||||||
|
public getPendingNonExclusiveCount(): number {
|
||||||
|
return this.nonExclusivePendingQueue.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async processExecutionSlots() {
|
||||||
|
if (this.isProcessing) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isProcessing = true;
|
||||||
|
|
||||||
|
while (this.executionSlots.length > 0) {
|
||||||
|
const currentSlot = this.executionSlots[0];
|
||||||
|
if (currentSlot.mode === 'exclusive') {
|
||||||
|
await this.executeExclusiveSlot(currentSlot);
|
||||||
|
this.executionSlots.shift();
|
||||||
|
} else {
|
||||||
|
// Gather all non-exclusive slots at the front of the queue
|
||||||
|
const nonExclusiveSlots: IExecutionSlot<any>[] = [];
|
||||||
|
while (this.executionSlots.length > 0 && this.executionSlots[0].mode === 'nonexclusive') {
|
||||||
|
nonExclusiveSlots.push(this.executionSlots.shift()!);
|
||||||
|
}
|
||||||
|
await this.executeNonExclusiveSlots(nonExclusiveSlots);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isProcessing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeExclusiveSlot(slot: IExecutionSlot<any>) {
|
||||||
|
try {
|
||||||
|
if (slot.timeout) {
|
||||||
|
const timeoutInstance = new plugins.smartdelay.Timeout(slot.timeout);
|
||||||
|
try {
|
||||||
|
const result = await Promise.race([
|
||||||
|
slot.funcToExecute(),
|
||||||
|
timeoutInstance.promise.then(() => {
|
||||||
|
throw new Error('Timeout reached');
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
timeoutInstance.cancel();
|
||||||
|
slot.executionDeferred.resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
timeoutInstance.cancel();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result = await slot.funcToExecute();
|
||||||
|
slot.executionDeferred.resolve(result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
slot.executionDeferred.reject(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async executeNonExclusiveSlots(slots: IExecutionSlot<any>[]) {
|
||||||
|
const promises = slots.map(async (slot) => {
|
||||||
|
// wait for an available non-exclusive slot
|
||||||
|
await this.waitForNonExclusiveSlot();
|
||||||
|
try {
|
||||||
|
// execute with optional timeout
|
||||||
|
if (slot.timeout) {
|
||||||
|
const timeoutInstance = new plugins.smartdelay.Timeout(slot.timeout);
|
||||||
|
try {
|
||||||
|
const result = await Promise.race([
|
||||||
|
slot.funcToExecute(),
|
||||||
|
timeoutInstance.promise.then(() => { throw new Error('Timeout reached'); }),
|
||||||
|
]);
|
||||||
|
timeoutInstance.cancel();
|
||||||
|
slot.executionDeferred.resolve(result);
|
||||||
|
} catch (error) {
|
||||||
|
timeoutInstance.cancel();
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const result = await slot.funcToExecute();
|
||||||
|
slot.executionDeferred.resolve(result);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
slot.executionDeferred.reject(error);
|
||||||
|
} finally {
|
||||||
|
this.releaseNonExclusiveSlot();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Wait until a non-exclusive slot is available (respects max concurrency).
|
||||||
|
*/
|
||||||
|
private waitForNonExclusiveSlot(): Promise<void> {
|
||||||
|
if (this.nonExclusiveCurrentCount < this.nonExclusiveMaxConcurrency) {
|
||||||
|
this.nonExclusiveCurrentCount++;
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
this.nonExclusivePendingQueue.push(() => {
|
||||||
|
this.nonExclusiveCurrentCount++;
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
/** Release a non-exclusive slot and wake the next waiter, if any. */
|
||||||
|
private releaseNonExclusiveSlot(): void {
|
||||||
|
this.nonExclusiveCurrentCount--;
|
||||||
|
const next = this.nonExclusivePendingQueue.shift();
|
||||||
|
if (next) {
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
86
ts/classes.backpressuredarray.ts
Normal file
86
ts/classes.backpressuredarray.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
|
export class BackpressuredArray<T> {
|
||||||
|
public data: T[];
|
||||||
|
private highWaterMark: number;
|
||||||
|
public hasSpace = new plugins.smartrx.rxjs.Subject<'hasSpace'>();
|
||||||
|
private itemsAvailable = new plugins.smartrx.rxjs.Subject<'itemsAvailable'>();
|
||||||
|
private isDestroyed = false;
|
||||||
|
|
||||||
|
constructor(highWaterMark: number = 16) {
|
||||||
|
this.data = [];
|
||||||
|
this.highWaterMark = highWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
push(item: T): boolean {
|
||||||
|
this.data.push(item);
|
||||||
|
this.itemsAvailable.next('itemsAvailable');
|
||||||
|
|
||||||
|
const spaceAvailable = this.checkSpaceAvailable();
|
||||||
|
if (spaceAvailable) {
|
||||||
|
this.hasSpace.next('hasSpace');
|
||||||
|
}
|
||||||
|
return spaceAvailable;
|
||||||
|
}
|
||||||
|
|
||||||
|
shift(): T | undefined {
|
||||||
|
const item = this.data.shift();
|
||||||
|
if (this.checkSpaceAvailable()) {
|
||||||
|
this.hasSpace.next('hasSpace');
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkSpaceAvailable(): boolean {
|
||||||
|
return this.data.length < this.highWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkHasItems(): boolean {
|
||||||
|
return this.data.length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForSpace(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
if (this.checkSpaceAvailable() || this.isDestroyed) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const subscription = this.hasSpace.subscribe({
|
||||||
|
next: () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForItems(): Promise<void> {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
if (this.data.length > 0 || this.isDestroyed) {
|
||||||
|
resolve();
|
||||||
|
} else {
|
||||||
|
const subscription = this.itemsAvailable.subscribe({
|
||||||
|
next: () => {
|
||||||
|
subscription.unsubscribe();
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
complete: () => {
|
||||||
|
resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the BackpressuredArray, completing all subjects
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.isDestroyed = true;
|
||||||
|
this.hasSpace.complete();
|
||||||
|
this.itemsAvailable.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
97
ts/classes.fastmap.ts
Normal file
97
ts/classes.fastmap.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/* ============
|
||||||
|
The FastMap has the goal of creating the fastes to use map possible in JS
|
||||||
|
|
||||||
|
============ */
|
||||||
|
|
||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fast map allows for very quick lookups of objects with a unique key
|
||||||
|
*/
|
||||||
|
export class FastMap<T> {
|
||||||
|
private mapObject: { [key: string]: T } = {};
|
||||||
|
|
||||||
|
public isUniqueKey(keyArg: string): boolean {
|
||||||
|
return this.mapObject[keyArg] ? false : true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public addToMap(
|
||||||
|
keyArg: string,
|
||||||
|
objectArg: T,
|
||||||
|
optionsArg?: {
|
||||||
|
force: boolean;
|
||||||
|
}
|
||||||
|
): boolean {
|
||||||
|
if (this.isUniqueKey(keyArg) || (optionsArg && optionsArg.force)) {
|
||||||
|
this.mapObject[keyArg] = objectArg;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getByKey(keyArg: string) {
|
||||||
|
return this.mapObject[keyArg];
|
||||||
|
}
|
||||||
|
|
||||||
|
public removeFromMap(keyArg: string): T {
|
||||||
|
const removedItem = this.getByKey(keyArg);
|
||||||
|
delete this.mapObject[keyArg];
|
||||||
|
return removedItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKeys() {
|
||||||
|
const keys: string[] = [];
|
||||||
|
for (const keyArg in this.mapObject) {
|
||||||
|
if (this.mapObject[keyArg]) {
|
||||||
|
keys.push(keyArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clean() {
|
||||||
|
this.mapObject = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a new Fastmap that includes all values from this and all from the fastmap in the argument
|
||||||
|
*/
|
||||||
|
public concat(fastMapArg: FastMap<T>) {
|
||||||
|
const concatedFastmap = new FastMap<T>();
|
||||||
|
for (const key of this.getKeys()) {
|
||||||
|
concatedFastmap.addToMap(key, this.getByKey(key));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of fastMapArg.getKeys()) {
|
||||||
|
concatedFastmap.addToMap(key, fastMapArg.getByKey(key), {
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return concatedFastmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tries to merge another Fastmap
|
||||||
|
* Note: uniqueKeyCollisions will cause overwrite
|
||||||
|
* @param fastMapArg
|
||||||
|
*/
|
||||||
|
public addAllFromOther(fastMapArg: FastMap<T>) {
|
||||||
|
for (const key of fastMapArg.getKeys()) {
|
||||||
|
this.addToMap(key, fastMapArg.getByKey(key), {
|
||||||
|
force: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async find(findFunctionArg: (mapItemArg: T) => Promise<boolean>) {
|
||||||
|
for (const key of this.getKeys()) {
|
||||||
|
const item = this.getByKey(key);
|
||||||
|
const findFunctionResult = await findFunctionArg(item);
|
||||||
|
if (findFunctionResult) {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
ts/classes.interestmap.interest.ts
Normal file
123
ts/classes.interestmap.interest.ts
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
|
import { InterestMap, type IInterestComparisonFunc } from './classes.interestmap.js';
|
||||||
|
|
||||||
|
export interface IInterestOptions<DTInterestFullfillment> {
|
||||||
|
markLostAfterDefault: number;
|
||||||
|
defaultFullfillment?: DTInterestFullfillment;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Interest<DTInterestId, DTInterestFullfillment> {
|
||||||
|
public options: IInterestOptions<DTInterestFullfillment>;
|
||||||
|
|
||||||
|
private interestMapRef: InterestMap<DTInterestId, DTInterestFullfillment>;
|
||||||
|
public originalInterest: DTInterestId;
|
||||||
|
public comparisonFunc: IInterestComparisonFunc<DTInterestId>;
|
||||||
|
public destructionTimer = new plugins.smarttime.Timer(10000);
|
||||||
|
public isFullfilled = false;
|
||||||
|
private isDestroyed = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a generic store to store objects in that are needed for fullfillment;
|
||||||
|
*/
|
||||||
|
public fullfillmentStore: any[] = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* a cancellable timeout for the markLostAfterDefault feature
|
||||||
|
*/
|
||||||
|
private markLostTimeout: InstanceType<typeof plugins.smartdelay.Timeout> | null = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* quick access to a string that makes the interest comparable for checking for similar interests
|
||||||
|
*/
|
||||||
|
public get comparisonString() {
|
||||||
|
return this.comparisonFunc(this.originalInterest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private interestDeferred: plugins.smartpromise.Deferred<DTInterestFullfillment> =
|
||||||
|
new plugins.smartpromise.Deferred();
|
||||||
|
public interestFullfilled = this.interestDeferred.promise;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fullfill the interest
|
||||||
|
*/
|
||||||
|
public fullfillInterest(objectArg: DTInterestFullfillment) {
|
||||||
|
this.isFullfilled = true;
|
||||||
|
this.fullfillmentStore = [];
|
||||||
|
this.interestDeferred.resolve(objectArg);
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
interestMapArg: InterestMap<DTInterestId, DTInterestFullfillment>,
|
||||||
|
interestArg: DTInterestId,
|
||||||
|
comparisonFuncArg: IInterestComparisonFunc<DTInterestId>,
|
||||||
|
optionsArg?: IInterestOptions<DTInterestFullfillment>
|
||||||
|
) {
|
||||||
|
this.interestMapRef = interestMapArg;
|
||||||
|
this.originalInterest = interestArg;
|
||||||
|
this.comparisonFunc = comparisonFuncArg;
|
||||||
|
this.options = optionsArg;
|
||||||
|
|
||||||
|
this.destructionTimer.completed.then(() => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (this.options?.markLostAfterDefault) {
|
||||||
|
this.markLostTimeout = new plugins.smartdelay.Timeout(this.options.markLostAfterDefault);
|
||||||
|
this.markLostTimeout.promise.then(() => {
|
||||||
|
if (!this.isDestroyed) {
|
||||||
|
this.markLost();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===============================
|
||||||
|
// LIFECYCLE MANAGEMENT
|
||||||
|
// ===============================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* self destructs the interest
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
if (this.isDestroyed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isDestroyed = true;
|
||||||
|
|
||||||
|
// Cancel timers to release references
|
||||||
|
this.destructionTimer.reset();
|
||||||
|
if (this.markLostTimeout) {
|
||||||
|
this.markLostTimeout.cancel();
|
||||||
|
this.markLostTimeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the fulfillment store
|
||||||
|
this.fullfillmentStore = [];
|
||||||
|
|
||||||
|
// Remove from the InterestMap
|
||||||
|
this.interestMapRef.removeInterest(this);
|
||||||
|
|
||||||
|
// Fulfill with default if not yet fulfilled (inlined to avoid mutual recursion)
|
||||||
|
if (!this.isFullfilled && this.options?.defaultFullfillment) {
|
||||||
|
this.isFullfilled = true;
|
||||||
|
this.interestDeferred.resolve(this.options.defaultFullfillment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notifies the interest that the interest in it has been lost
|
||||||
|
*/
|
||||||
|
public markLost() {
|
||||||
|
this.destructionTimer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* notifies the interest that the interest in it has been restored
|
||||||
|
*/
|
||||||
|
public renew() {
|
||||||
|
this.destructionTimer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,43 +1,65 @@
|
|||||||
import * as plugins from './lik.plugins';
|
/* ===========
|
||||||
import { Objectmap } from './lik.objectmap';
|
The InterestMap is an mechanism that collects interests into something
|
||||||
|
An interest is expressed by an object, string or number.
|
||||||
|
A comparison func can be specified to make interests comparable
|
||||||
|
|
||||||
import { Observable } from 'rxjs';
|
For every unique interestId an interest is created.
|
||||||
|
Subssequent interests will be mapped to the same interest
|
||||||
|
which is then is only fullfilled once.
|
||||||
|
=========== */
|
||||||
|
|
||||||
import { Interest } from './lik.interestmap.interest';
|
import * as plugins from './classes.plugins.js';
|
||||||
|
import { ObjectMap } from './classes.objectmap.js';
|
||||||
|
import { Interest } from './classes.interestmap.interest.js';
|
||||||
|
|
||||||
export type IInterestComparisonFunc<T> = (objectArg: T) => string;
|
export type IInterestComparisonFunc<T> = (objectArg: T) => string;
|
||||||
|
|
||||||
|
export interface IInterestMapOptions {
|
||||||
|
markLostAfterDefault?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
||||||
|
public options: IInterestMapOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* stores interests that are currently fullfilled by the cache
|
* stores interests that are currently fullfilled by the cache
|
||||||
*/
|
*/
|
||||||
private interestObjectMap = new Objectmap<Interest<DTInterestId, DTInterestFullfillment>>();
|
private interestObjectMap = new ObjectMap<Interest<DTInterestId, DTInterestFullfillment>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* a function to compare interests
|
* a function to compare interests
|
||||||
*/
|
*/
|
||||||
private comparisonFunc: IInterestComparisonFunc<DTInterestId>;
|
private comparisonFunc: IInterestComparisonFunc<DTInterestId>;
|
||||||
|
|
||||||
constructor(comparisonFuncArg: IInterestComparisonFunc<DTInterestId>) {
|
constructor(
|
||||||
|
comparisonFuncArg: IInterestComparisonFunc<DTInterestId>,
|
||||||
|
optionsArg: IInterestMapOptions = {}
|
||||||
|
) {
|
||||||
this.comparisonFunc = comparisonFuncArg;
|
this.comparisonFunc = comparisonFuncArg;
|
||||||
|
this.options = optionsArg;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* adds an interest to the InterestMap
|
* adds an interest to the InterestMap
|
||||||
* @param objectArg
|
* @param interestId
|
||||||
*/
|
*/
|
||||||
public async addInterest(
|
public async addInterest(
|
||||||
objectArg: DTInterestId
|
interestId: DTInterestId,
|
||||||
|
defaultFullfillmentArg?: DTInterestFullfillment
|
||||||
): Promise<Interest<DTInterestId, DTInterestFullfillment>> {
|
): Promise<Interest<DTInterestId, DTInterestFullfillment>> {
|
||||||
const comparisonString = this.comparisonFunc(objectArg);
|
const comparisonString = this.comparisonFunc(interestId);
|
||||||
let returnInterest: Interest<DTInterestId, DTInterestFullfillment>;
|
let returnInterest: Interest<DTInterestId, DTInterestFullfillment>;
|
||||||
const newInterest = new Interest<DTInterestId, DTInterestFullfillment>(
|
const newInterest = new Interest<DTInterestId, DTInterestFullfillment>(
|
||||||
this,
|
this,
|
||||||
objectArg,
|
interestId,
|
||||||
this.comparisonFunc
|
this.comparisonFunc,
|
||||||
|
{
|
||||||
|
markLostAfterDefault: this.options.markLostAfterDefault,
|
||||||
|
defaultFullfillment: defaultFullfillmentArg,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
let interestExists = false;
|
let interestExists = false;
|
||||||
await this.interestObjectMap.forEach(interestArg => {
|
await this.interestObjectMap.forEach((interestArg) => {
|
||||||
if (!interestExists && interestArg.comparisonString === newInterest.comparisonString) {
|
if (!interestExists && interestArg.comparisonString === newInterest.comparisonString) {
|
||||||
console.log('info', `interest already exists for ${newInterest.comparisonString}`);
|
console.log('info', `interest already exists for ${newInterest.comparisonString}`);
|
||||||
interestExists = true;
|
interestExists = true;
|
||||||
@@ -48,20 +70,20 @@ export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
|||||||
if (!returnInterest) {
|
if (!returnInterest) {
|
||||||
returnInterest = newInterest;
|
returnInterest = newInterest;
|
||||||
this.interestObjectMap.add(returnInterest);
|
this.interestObjectMap.add(returnInterest);
|
||||||
|
} else {
|
||||||
|
newInterest.destroy(); // clean up abandoned Interest's timers
|
||||||
}
|
}
|
||||||
this.interestObservable.push(returnInterest);
|
this.interestObservable.push(returnInterest);
|
||||||
return returnInterest;
|
return returnInterest;
|
||||||
}
|
}
|
||||||
|
|
||||||
// tslint:disable-next-line:member-ordering
|
|
||||||
public interestObservable = new plugins.smartrx.ObservableIntake<Interest<DTInterestId, any>>();
|
public interestObservable = new plugins.smartrx.ObservableIntake<Interest<DTInterestId, any>>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* removes an interest from the interest map
|
* removes an interest from the interest map
|
||||||
* @param objectArg removes an interest from the InterestMap
|
|
||||||
*/
|
*/
|
||||||
public removeInterest(interestArg: Interest<DTInterestId, DTInterestFullfillment>) {
|
public removeInterest(interestArg: Interest<DTInterestId, DTInterestFullfillment>) {
|
||||||
const interestToRemove = this.interestObjectMap.findOneAndRemove(interestArg2 => {
|
const interestToRemove = this.interestObjectMap.findOneAndRemoveSync((interestArg2) => {
|
||||||
return interestArg.comparisonString === interestArg2.comparisonString;
|
return interestArg.comparisonString === interestArg2.comparisonString;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -79,7 +101,7 @@ export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
|||||||
* @param comparisonStringArg
|
* @param comparisonStringArg
|
||||||
*/
|
*/
|
||||||
public checkInterestByString(comparisonStringArg: string): boolean {
|
public checkInterestByString(comparisonStringArg: string): boolean {
|
||||||
const foundInterest = this.interestObjectMap.find(interest => {
|
const foundInterest = this.interestObjectMap.findSync((interest) => {
|
||||||
return interest.comparisonString === comparisonStringArg;
|
return interest.comparisonString === comparisonStringArg;
|
||||||
});
|
});
|
||||||
if (foundInterest) {
|
if (foundInterest) {
|
||||||
@@ -102,13 +124,25 @@ export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* finds an interest
|
* finds an interest
|
||||||
* @param objectArg
|
* @param interestId
|
||||||
*/
|
*/
|
||||||
public findInterest(objectArg: DTInterestId): Interest<DTInterestId, DTInterestFullfillment> {
|
public findInterest(interestId: DTInterestId): Interest<DTInterestId, DTInterestFullfillment> {
|
||||||
const comparableString = this.comparisonFunc(objectArg);
|
const comparableString = this.comparisonFunc(interestId);
|
||||||
const interest = this.interestObjectMap.find(interest => {
|
const interest = this.interestObjectMap.findSync((interestArg) => {
|
||||||
return interest.comparisonString === comparableString;
|
return interestArg.comparisonString === comparableString;
|
||||||
});
|
});
|
||||||
return interest; // if an interest is found, the interest is returned, otherwise interest is null
|
return interest; // if an interest is found, the interest is returned, otherwise interest is null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the InterestMap and cleans up all resources
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
const interests = this.interestObjectMap.getArray();
|
||||||
|
for (const interest of interests) {
|
||||||
|
interest.destroy();
|
||||||
|
}
|
||||||
|
this.interestObjectMap.wipe();
|
||||||
|
this.interestObservable.signalComplete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './lik.plugins';
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
export class LimitedArray<T> {
|
export class LimitedArray<T> {
|
||||||
array: T[] = [];
|
array: T[] = [];
|
||||||
37
ts/classes.looptracker.ts
Normal file
37
ts/classes.looptracker.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
|
import { ObjectMap } from './classes.objectmap.js';
|
||||||
|
|
||||||
|
export class LoopTracker<T> {
|
||||||
|
referenceObjectMap = new ObjectMap<any>();
|
||||||
|
constructor() {
|
||||||
|
// nothing here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* checks and tracks an object
|
||||||
|
* @param objectArg
|
||||||
|
*/
|
||||||
|
checkAndTrack(objectArg: T): boolean {
|
||||||
|
if (!this.referenceObjectMap.checkForObject(objectArg)) {
|
||||||
|
this.referenceObjectMap.add(objectArg);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* resets the loop tracker, clearing all tracked objects
|
||||||
|
*/
|
||||||
|
public reset() {
|
||||||
|
this.referenceObjectMap.wipe();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the loop tracker and its underlying ObjectMap
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.referenceObjectMap.destroy();
|
||||||
|
}
|
||||||
|
}
|
||||||
266
ts/classes.objectmap.ts
Normal file
266
ts/classes.objectmap.ts
Normal file
@@ -0,0 +1,266 @@
|
|||||||
|
import * as plugins from './classes.plugins.js';
|
||||||
|
import { FastMap } from './classes.fastmap.js';
|
||||||
|
|
||||||
|
export const uni = (prefix: string = 'uni') => {
|
||||||
|
return `${prefix}xxxxxxxxxxx`.replace(/[xy]/g, (c) => {
|
||||||
|
const r = (Math.random() * 16) | 0;
|
||||||
|
const v = c === 'x' ? r : (r & 0x3) | 0x8;
|
||||||
|
return v.toString(16);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IObjectmapForEachFunction<T> {
|
||||||
|
(itemArg: T): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IObjectmapFindFunctionSync<T> {
|
||||||
|
(itemArg: T): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IObjectmapFindFunction<T> {
|
||||||
|
(itemArg: T): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IObjectMapEventData<T> {
|
||||||
|
operation: 'add' | 'remove';
|
||||||
|
payload: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* allows keeping track of objects
|
||||||
|
*/
|
||||||
|
export class ObjectMap<T> {
|
||||||
|
private fastMap = new FastMap<T>();
|
||||||
|
|
||||||
|
// events
|
||||||
|
public eventSubject = new plugins.smartrx.rxjs.Subject<IObjectMapEventData<T>>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a new instance
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
// nothing here
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* adds an object mapped to a string
|
||||||
|
* the string must be unique
|
||||||
|
*/
|
||||||
|
addMappedUnique(uniqueKeyArg: string, objectArg: T) {
|
||||||
|
this.fastMap.addToMap(uniqueKeyArg, objectArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fastest way to get an object from the map
|
||||||
|
* @param uniqueKey
|
||||||
|
*/
|
||||||
|
public getMappedUnique(uniqueKeyArg: string) {
|
||||||
|
return this.fastMap.getByKey(uniqueKeyArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove key
|
||||||
|
* @param functionArg
|
||||||
|
*/
|
||||||
|
public removeMappedUnique(uniqueKey: string): T {
|
||||||
|
const object = this.fastMap.removeFromMap(uniqueKey);
|
||||||
|
if (object !== undefined) {
|
||||||
|
this.eventSubject.next({
|
||||||
|
operation: 'remove',
|
||||||
|
payload: object,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* add object to Objectmap
|
||||||
|
* returns false if the object is already in the map
|
||||||
|
* returns true if the object was added successfully
|
||||||
|
*/
|
||||||
|
public add(objectArg: T): string {
|
||||||
|
// lets search for an existing unique key
|
||||||
|
for (const keyArg of this.fastMap.getKeys()) {
|
||||||
|
const object = this.fastMap.getByKey(keyArg);
|
||||||
|
if (object === objectArg) {
|
||||||
|
return keyArg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise lets create it
|
||||||
|
const uniqueKey = uni('key');
|
||||||
|
this.addMappedUnique(uniqueKey, objectArg);
|
||||||
|
this.eventSubject.next({
|
||||||
|
operation: 'add',
|
||||||
|
payload: objectArg,
|
||||||
|
});
|
||||||
|
return uniqueKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* like .add but adds an whole array of objects
|
||||||
|
*/
|
||||||
|
public addArray(objectArrayArg: T[]) {
|
||||||
|
for (const item of objectArrayArg) {
|
||||||
|
this.add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if object is in Objectmap
|
||||||
|
*/
|
||||||
|
public checkForObject(objectArg: T): boolean {
|
||||||
|
return !!this.getKeyForObject(objectArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get key for object
|
||||||
|
* @param findFunction
|
||||||
|
*/
|
||||||
|
public getKeyForObject(objectArg: T) {
|
||||||
|
let foundKey: string = null;
|
||||||
|
for (const keyArg of this.fastMap.getKeys()) {
|
||||||
|
if (!foundKey && this.fastMap.getByKey(keyArg) === objectArg) {
|
||||||
|
foundKey = keyArg;
|
||||||
|
} else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return foundKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* find object
|
||||||
|
*/
|
||||||
|
public async find(findFunction: IObjectmapFindFunction<T>): Promise<T> {
|
||||||
|
return this.fastMap.find(findFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public findSync(findFunction: IObjectmapFindFunctionSync<T>): T {
|
||||||
|
for (const keyArg of this.fastMap.getKeys()) {
|
||||||
|
if (findFunction(this.fastMap.getByKey(keyArg))) {
|
||||||
|
return this.getMappedUnique(keyArg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* finds a specific element and then removes it
|
||||||
|
*/
|
||||||
|
public async findOneAndRemove(findFunction: IObjectmapFindFunction<T>): Promise<T> {
|
||||||
|
const foundElement = await this.find(findFunction);
|
||||||
|
if (foundElement) {
|
||||||
|
this.remove(foundElement);
|
||||||
|
}
|
||||||
|
return foundElement;
|
||||||
|
}
|
||||||
|
public findOneAndRemoveSync(findFunction: IObjectmapFindFunctionSync<T>): T {
|
||||||
|
const foundElement = this.findSync(findFunction);
|
||||||
|
if (foundElement) {
|
||||||
|
this.remove(foundElement);
|
||||||
|
}
|
||||||
|
return foundElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* run function for each item in Objectmap
|
||||||
|
*/
|
||||||
|
public async forEach(functionArg: IObjectmapForEachFunction<T>) {
|
||||||
|
for (const keyArg of this.fastMap.getKeys()) {
|
||||||
|
await functionArg(this.fastMap.getByKey(keyArg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* gets an object in the Observablemap and removes it, so it can't be retrieved again
|
||||||
|
*/
|
||||||
|
public getOneAndRemove(): T {
|
||||||
|
const keys = this.fastMap.getKeys();
|
||||||
|
if (keys.length === 0) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
const keyToUse = keys[0];
|
||||||
|
const removedItem = this.fastMap.removeFromMap(keyToUse);
|
||||||
|
this.eventSubject.next({
|
||||||
|
operation: 'remove',
|
||||||
|
payload: removedItem,
|
||||||
|
});
|
||||||
|
return removedItem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a cloned array of all the objects currently in the Objectmap
|
||||||
|
*/
|
||||||
|
public getArray(): T[] {
|
||||||
|
const returnArray: any[] = [];
|
||||||
|
for (const keyArg of this.fastMap.getKeys()) {
|
||||||
|
returnArray.push(this.fastMap.getByKey(keyArg));
|
||||||
|
}
|
||||||
|
return returnArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* check if Objectmap ist empty
|
||||||
|
*/
|
||||||
|
public isEmpty(): boolean {
|
||||||
|
return this.fastMap.getKeys().length === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* remove object from Objectmap
|
||||||
|
*/
|
||||||
|
public remove(objectArg: T): T {
|
||||||
|
if (this.checkForObject(objectArg)) {
|
||||||
|
const keyArg = this.getKeyForObject(objectArg);
|
||||||
|
const removedObject = this.fastMap.removeFromMap(keyArg);
|
||||||
|
this.eventSubject.next({
|
||||||
|
operation: 'remove',
|
||||||
|
payload: removedObject,
|
||||||
|
});
|
||||||
|
return removedObject;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* wipe Objectmap
|
||||||
|
*/
|
||||||
|
public wipe() {
|
||||||
|
const keys = this.fastMap.getKeys();
|
||||||
|
for (const keyArg of keys) {
|
||||||
|
const removedObject = this.fastMap.removeFromMap(keyArg);
|
||||||
|
this.eventSubject.next({
|
||||||
|
operation: 'remove',
|
||||||
|
payload: removedObject,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* returns a new Objectmap that includes
|
||||||
|
*/
|
||||||
|
public concat(objectMapArg: ObjectMap<T>) {
|
||||||
|
const concattedObjectMap = new ObjectMap<T>();
|
||||||
|
concattedObjectMap.fastMap.addAllFromOther(this.fastMap);
|
||||||
|
concattedObjectMap.fastMap.addAllFromOther(objectMapArg.fastMap);
|
||||||
|
return concattedObjectMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tries to merge another Objectmap
|
||||||
|
* Note: uniqueKeyCollisions will cause overwrite
|
||||||
|
* @param objectMapArg
|
||||||
|
*/
|
||||||
|
public addAllFromOther(objectMapArg: ObjectMap<T>) {
|
||||||
|
this.fastMap.addAllFromOther(objectMapArg.fastMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the ObjectMap, completing the eventSubject and clearing all entries
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this.wipe();
|
||||||
|
this.eventSubject.complete();
|
||||||
|
}
|
||||||
|
}
|
||||||
17
ts/classes.plugins.ts
Normal file
17
ts/classes.plugins.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
// ==============
|
||||||
|
// @pushrocks
|
||||||
|
// ==============
|
||||||
|
import * as smartdelay from '@push.rocks/smartdelay';
|
||||||
|
import * as smartmatch from '@push.rocks/smartmatch';
|
||||||
|
import * as smartpromise from '@push.rocks/smartpromise';
|
||||||
|
import * as smartrx from '@push.rocks/smartrx';
|
||||||
|
import * as smarttime from '@push.rocks/smarttime';
|
||||||
|
|
||||||
|
export { smartdelay, smartmatch, smartpromise, smartrx, smarttime };
|
||||||
|
|
||||||
|
// ==============
|
||||||
|
// third party
|
||||||
|
// ==============
|
||||||
|
import symbolTree from 'symbol-tree';
|
||||||
|
|
||||||
|
export { symbolTree };
|
||||||
@@ -1,16 +1,14 @@
|
|||||||
import * as plugins from './lik.plugins';
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* allows you to easily keep track of a bunch of strings
|
* allows you to easily keep track of a bunch of strings
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface ITriggerFunction {
|
export type TTriggerFunction = (stringArray?: string[]) => boolean;
|
||||||
(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class Stringmap {
|
export class Stringmap {
|
||||||
private _stringArray: string[] = [];
|
private _stringArray: string[] = [];
|
||||||
private _triggerUntilTrueFunctionArray: ITriggerFunction[] = [];
|
private _triggerUntilTrueFunctionArray: TTriggerFunction[] = [];
|
||||||
constructor() {}
|
constructor() {}
|
||||||
/**
|
/**
|
||||||
* add a string to the Stringmap
|
* add a string to the Stringmap
|
||||||
@@ -24,7 +22,7 @@ export class Stringmap {
|
|||||||
* like addString, but accepts an array of strings
|
* like addString, but accepts an array of strings
|
||||||
*/
|
*/
|
||||||
addStringArray(stringArrayArg: string[]) {
|
addStringArray(stringArrayArg: string[]) {
|
||||||
for (let stringItem of stringArrayArg) {
|
for (const stringItem of stringArrayArg) {
|
||||||
this.addString(stringItem);
|
this.addString(stringItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -33,7 +31,7 @@ export class Stringmap {
|
|||||||
* removes a string from Stringmap
|
* removes a string from Stringmap
|
||||||
*/
|
*/
|
||||||
removeString(stringArg: string) {
|
removeString(stringArg: string) {
|
||||||
for (let keyArg in this._stringArray) {
|
for (const keyArg in this._stringArray) {
|
||||||
if (this._stringArray[keyArg] === stringArg) {
|
if (this._stringArray[keyArg] === stringArg) {
|
||||||
this._stringArray.splice(parseInt(keyArg), 1);
|
this._stringArray.splice(parseInt(keyArg), 1);
|
||||||
}
|
}
|
||||||
@@ -60,9 +58,10 @@ export class Stringmap {
|
|||||||
* checks stringPresence with minimatch
|
* checks stringPresence with minimatch
|
||||||
*/
|
*/
|
||||||
public checkMinimatch(miniMatchStringArg: string): boolean {
|
public checkMinimatch(miniMatchStringArg: string): boolean {
|
||||||
|
const smartMatchInstance = new plugins.smartmatch.SmartMatch(miniMatchStringArg);
|
||||||
let foundMatch: boolean = false;
|
let foundMatch: boolean = false;
|
||||||
for (let stringItem of this._stringArray) {
|
for (const stringItem of this._stringArray) {
|
||||||
if (plugins.minimatch(stringItem, miniMatchStringArg)) {
|
if (smartMatchInstance.match(stringItem)) {
|
||||||
foundMatch = true;
|
foundMatch = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,24 +91,37 @@ export class Stringmap {
|
|||||||
/**
|
/**
|
||||||
* register a new trigger
|
* register a new trigger
|
||||||
*/
|
*/
|
||||||
public registerUntilTrue(functionArg: ITriggerFunction, doFunctionArg) {
|
public registerUntilTrue(functionArg: TTriggerFunction, callbackArg?: () => any) {
|
||||||
|
const trueDeferred = plugins.smartpromise.defer();
|
||||||
this._triggerUntilTrueFunctionArray.push(() => {
|
this._triggerUntilTrueFunctionArray.push(() => {
|
||||||
let result = functionArg();
|
const result = functionArg(this.getStringArray());
|
||||||
if (result === true) {
|
if (result === true) {
|
||||||
doFunctionArg();
|
if (callbackArg) {
|
||||||
|
callbackArg();
|
||||||
|
}
|
||||||
|
trueDeferred.resolve();
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
this.notifyTrigger();
|
this.notifyTrigger();
|
||||||
|
return trueDeferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* notifies triggers
|
* notifies triggers
|
||||||
*/
|
*/
|
||||||
private notifyTrigger() {
|
private notifyTrigger() {
|
||||||
let filteredArray = this._triggerUntilTrueFunctionArray.filter(functionArg => {
|
const filteredArray = this._triggerUntilTrueFunctionArray.filter((functionArg) => {
|
||||||
return !functionArg();
|
return !functionArg();
|
||||||
});
|
});
|
||||||
this._triggerUntilTrueFunctionArray = filteredArray;
|
this._triggerUntilTrueFunctionArray = filteredArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* destroys the Stringmap, clearing all strings and pending triggers
|
||||||
|
*/
|
||||||
|
public destroy() {
|
||||||
|
this._stringArray = [];
|
||||||
|
this._triggerUntilTrueFunctionArray = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './lik.plugins';
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
export interface ITimedAggregatorOptions<T> {
|
export interface ITimedAggregatorOptions<T> {
|
||||||
aggregationIntervalInMillis: number;
|
aggregationIntervalInMillis: number;
|
||||||
@@ -8,6 +8,7 @@ export interface ITimedAggregatorOptions<T> {
|
|||||||
export class TimedAggregtor<T> {
|
export class TimedAggregtor<T> {
|
||||||
public options: ITimedAggregatorOptions<T>;
|
public options: ITimedAggregatorOptions<T>;
|
||||||
private storageArray: T[] = [];
|
private storageArray: T[] = [];
|
||||||
|
private isStopped = false;
|
||||||
|
|
||||||
constructor(optionsArg: ITimedAggregatorOptions<T>) {
|
constructor(optionsArg: ITimedAggregatorOptions<T>) {
|
||||||
this.options = optionsArg;
|
this.options = optionsArg;
|
||||||
@@ -15,9 +16,16 @@ export class TimedAggregtor<T> {
|
|||||||
|
|
||||||
private aggregationTimer: plugins.smarttime.Timer;
|
private aggregationTimer: plugins.smarttime.Timer;
|
||||||
private checkAggregationStatus() {
|
private checkAggregationStatus() {
|
||||||
|
if (this.isStopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const addAggregationTimer = () => {
|
const addAggregationTimer = () => {
|
||||||
this.aggregationTimer = new plugins.smarttime.Timer(this.options.aggregationIntervalInMillis);
|
this.aggregationTimer = new plugins.smarttime.Timer(this.options.aggregationIntervalInMillis);
|
||||||
this.aggregationTimer.completed.then(() => {
|
this.aggregationTimer.completed.then(() => {
|
||||||
|
if (this.isStopped) {
|
||||||
|
this.aggregationTimer = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const aggregateForProcessing = this.storageArray;
|
const aggregateForProcessing = this.storageArray;
|
||||||
if (aggregateForProcessing.length === 0) {
|
if (aggregateForProcessing.length === 0) {
|
||||||
this.aggregationTimer = null;
|
this.aggregationTimer = null;
|
||||||
@@ -35,7 +43,29 @@ export class TimedAggregtor<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public add(aggregationArg: T) {
|
public add(aggregationArg: T) {
|
||||||
|
if (this.isStopped) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.storageArray.push(aggregationArg);
|
this.storageArray.push(aggregationArg);
|
||||||
this.checkAggregationStatus();
|
this.checkAggregationStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stops the aggregation timer chain
|
||||||
|
* @param flushRemaining if true, calls functionForAggregation with any remaining items
|
||||||
|
*/
|
||||||
|
public stop(flushRemaining: boolean = false) {
|
||||||
|
this.isStopped = true;
|
||||||
|
if (this.aggregationTimer) {
|
||||||
|
this.aggregationTimer.reset();
|
||||||
|
this.aggregationTimer = null;
|
||||||
|
}
|
||||||
|
if (flushRemaining && this.storageArray.length > 0) {
|
||||||
|
const remaining = this.storageArray;
|
||||||
|
this.storageArray = [];
|
||||||
|
this.options.functionForAggregation(remaining);
|
||||||
|
} else {
|
||||||
|
this.storageArray = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import * as plugins from './lik.plugins';
|
import * as plugins from './classes.plugins.js';
|
||||||
|
|
||||||
export class Tree<T> {
|
export class Tree<T> {
|
||||||
symbolTree: any;
|
symbolTree: any;
|
||||||
@@ -62,15 +62,15 @@ export class Tree<T> {
|
|||||||
return this.symbolTree.ancestorsToArray(objectArg, optionsArg);
|
return this.symbolTree.ancestorsToArray(objectArg, optionsArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
treeToArray(rootArg, optionsArg: any): T[] {
|
treeToArray(rootArg: T, optionsArg: any): T[] {
|
||||||
return this.symbolTree.treeToArray(rootArg, optionsArg);
|
return this.symbolTree.treeToArray(rootArg, optionsArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
childrenIterator(parentArg: T, optionsArg): T {
|
childrenIterator(parentArg: T, optionsArg: any): T {
|
||||||
return this.symbolTree.childrenIterator(parentArg, optionsArg);
|
return this.symbolTree.childrenIterator(parentArg, optionsArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
previousSiblingsIterator(objectArg): T {
|
previousSiblingsIterator(objectArg: T): T {
|
||||||
return this.symbolTree.previousSiblingsIterator(objectArg);
|
return this.symbolTree.previousSiblingsIterator(objectArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,11 +78,11 @@ export class Tree<T> {
|
|||||||
return this.symbolTree.nextSiblingsIterator();
|
return this.symbolTree.nextSiblingsIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
ancestorsIterator(objectArg) {
|
ancestorsIterator(objectArg: T) {
|
||||||
this.symbolTree.ancestorsIterator();
|
this.symbolTree.ancestorsIterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
treeIterator(rootArg: T, optionsArg): Iterable<T> {
|
treeIterator(rootArg: T, optionsArg: any): Iterable<T> {
|
||||||
return this.symbolTree.treeIterator(rootArg);
|
return this.symbolTree.treeIterator(rootArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +95,7 @@ export class Tree<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compareTreePosition(leftArg: T, rightArg: T): number {
|
compareTreePosition(leftArg: T, rightArg: T): number {
|
||||||
return this.compareTreePosition(leftArg, rightArg);
|
return this.symbolTree.compareTreePosition(leftArg, rightArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
remove(removeObjectArg: T): T {
|
remove(removeObjectArg: T): T {
|
||||||
@@ -114,7 +114,7 @@ export class Tree<T> {
|
|||||||
return this.symbolTree.prependChild(referenceObjectArg, newObjectArg);
|
return this.symbolTree.prependChild(referenceObjectArg, newObjectArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
appendChild(referenceObjectArg, newObjectArg) {
|
appendChild(referenceObjectArg: T, newObjectArg: T) {
|
||||||
return this.symbolTree.appendChild(referenceObjectArg, newObjectArg);
|
return this.symbolTree.appendChild(referenceObjectArg, newObjectArg);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +126,7 @@ export class Tree<T> {
|
|||||||
* returns a branch of the tree as JSON
|
* returns a branch of the tree as JSON
|
||||||
* can be user
|
* can be user
|
||||||
*/
|
*/
|
||||||
toJsonWithHierachy(rootElement) {
|
toJsonWithHierachy(rootElement: T) {
|
||||||
const treeIterable = this.treeIterator(rootElement, {});
|
const treeIterable = this.treeIterator(rootElement, {});
|
||||||
for (const treeItem of treeIterable) {
|
for (const treeItem of treeIterable) {
|
||||||
console.log(treeItem);
|
console.log(treeItem);
|
||||||
@@ -137,5 +137,5 @@ export class Tree<T> {
|
|||||||
* builds a tree from a JSON with hierachy
|
* builds a tree from a JSON with hierachy
|
||||||
* @param rootElement
|
* @param rootElement
|
||||||
*/
|
*/
|
||||||
fromJsonWithHierachy(rootElement) {}
|
fromJsonWithHierachy(rootElement: T) {}
|
||||||
}
|
}
|
||||||
22
ts/index.ts
22
ts/index.ts
@@ -1,11 +1,11 @@
|
|||||||
import * as plugins from './lik.plugins';
|
export * from './classes.asyncexecutionstack.js';
|
||||||
|
export * from './classes.backpressuredarray.js';
|
||||||
// import modules
|
export * from './classes.fastmap.js';
|
||||||
|
export * from './classes.interestmap.js';
|
||||||
export * from './lik.interestmap';
|
export * from './classes.interestmap.interest.js';
|
||||||
export * from './lik.limitedarray';
|
export * from './classes.limitedarray.js';
|
||||||
export * from './lik.looptracker';
|
export * from './classes.looptracker.js';
|
||||||
export * from './lik.objectmap';
|
export * from './classes.objectmap.js';
|
||||||
export * from './lik.stringmap';
|
export * from './classes.stringmap.js';
|
||||||
export * from './lik.timedaggregator';
|
export * from './classes.timedaggregator.js';
|
||||||
export * from './lik.tree';
|
export * from './classes.tree.js';
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
import * as plugins from './lik.plugins';
|
|
||||||
|
|
||||||
import { InterestMap, IInterestComparisonFunc } from './lik.interestmap';
|
|
||||||
|
|
||||||
export class Interest<DTInterestId, DTInterestFullfillment> {
|
|
||||||
private interestMapRef: InterestMap<DTInterestId, DTInterestFullfillment>;
|
|
||||||
public originalInterest: DTInterestId;
|
|
||||||
public comparisonFunc: IInterestComparisonFunc<DTInterestId>;
|
|
||||||
public destructionTimer = new plugins.smarttime.Timer(10000);
|
|
||||||
public isFullfilled = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* a generic store to store objects in that are needed for fullfillment;
|
|
||||||
*/
|
|
||||||
public fullfillmentStore: any[] = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* quick access to a string that makes the interest comparable for checking for similar interests
|
|
||||||
*/
|
|
||||||
public get comparisonString() {
|
|
||||||
return this.comparisonFunc(this.originalInterest);
|
|
||||||
}
|
|
||||||
|
|
||||||
private interestDeferred: plugins.smartpromise.Deferred<
|
|
||||||
DTInterestFullfillment
|
|
||||||
> = new plugins.smartpromise.Deferred();
|
|
||||||
public interestFullfilled = this.interestDeferred.promise;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* fullfill the interest
|
|
||||||
*/
|
|
||||||
public fullfillInterest(objectArg: DTInterestFullfillment) {
|
|
||||||
this.isFullfilled = true;
|
|
||||||
this.fullfillmentStore = [];
|
|
||||||
this.interestDeferred.resolve(objectArg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
interestMapArg: InterestMap<DTInterestId, DTInterestFullfillment>,
|
|
||||||
interestArg: DTInterestId,
|
|
||||||
comparisonFuncArg: IInterestComparisonFunc<DTInterestId>
|
|
||||||
) {
|
|
||||||
this.originalInterest = interestArg;
|
|
||||||
this.comparisonFunc = comparisonFuncArg;
|
|
||||||
this.interestMapRef = interestMapArg;
|
|
||||||
this.destructionTimer.completed.then(() => {
|
|
||||||
this.destroy();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===============================
|
|
||||||
// LIFECYCLE MANAGEMENT
|
|
||||||
// ===============================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* self destructs the interest
|
|
||||||
*/
|
|
||||||
public destroy() {
|
|
||||||
this.interestMapRef.removeInterest(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* notifies the interest that the interest in it has been lost
|
|
||||||
*/
|
|
||||||
public markLost() {
|
|
||||||
this.destructionTimer.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* notifies the interest that the interest in it has been restored
|
|
||||||
*/
|
|
||||||
public renew() {
|
|
||||||
this.destructionTimer.reset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import * as plugins from './lik.plugins';
|
|
||||||
|
|
||||||
import { Objectmap } from './lik.objectmap';
|
|
||||||
|
|
||||||
export class LoopTracker<T> {
|
|
||||||
referenceObjectMap = new Objectmap<any>();
|
|
||||||
constructor() {
|
|
||||||
// nothing here
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* checks and tracks an object
|
|
||||||
* @param objectArg
|
|
||||||
*/
|
|
||||||
checkAndTrack(objectArg: T) {
|
|
||||||
return this.referenceObjectMap.add(objectArg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
import * as plugins from './lik.plugins';
|
|
||||||
|
|
||||||
export interface IObjectmapForEachFunction<T> {
|
|
||||||
(itemArg: T): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IObjectmapFindFunction<T> {
|
|
||||||
(itemArg: T): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* allows keeping track of objects
|
|
||||||
*/
|
|
||||||
export class Objectmap<T> {
|
|
||||||
private objectArray: T[] = [];
|
|
||||||
|
|
||||||
// events
|
|
||||||
public eventSubject = new plugins.smartrx.rxjs.Subject<any>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a new instance
|
|
||||||
*/
|
|
||||||
constructor() {
|
|
||||||
// nothing here
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* add object to Objectmap
|
|
||||||
* returns false if the object is already in the map
|
|
||||||
* returns true if the object was added successfully
|
|
||||||
*/
|
|
||||||
public add(objectArg: T): boolean {
|
|
||||||
if (this.checkForObject(objectArg)) {
|
|
||||||
// the object is already in the objectmap
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// the object is not yet in the objectmap
|
|
||||||
this.objectArray.push(objectArg);
|
|
||||||
this.eventSubject.next('add');
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* like .add but adds an whole array of objects
|
|
||||||
*/
|
|
||||||
public addArray(objectArrayArg: T[]) {
|
|
||||||
for (let item of objectArrayArg) {
|
|
||||||
this.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check if object is in Objectmap
|
|
||||||
*/
|
|
||||||
public checkForObject(objectArg: T) {
|
|
||||||
return this.objectArray.indexOf(objectArg) !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* find object
|
|
||||||
*/
|
|
||||||
public find(findFunction: IObjectmapFindFunction<T>) {
|
|
||||||
const resultArray = this.objectArray.filter(findFunction);
|
|
||||||
if (resultArray.length > 0) {
|
|
||||||
return resultArray[0];
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* finds a specific element and then removes it
|
|
||||||
*/
|
|
||||||
public findOneAndRemove(findFunction: IObjectmapFindFunction<T>): T {
|
|
||||||
const foundElement = this.find(findFunction);
|
|
||||||
if (foundElement) {
|
|
||||||
this.remove(foundElement);
|
|
||||||
}
|
|
||||||
return foundElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* run function for each item in Objectmap
|
|
||||||
*/
|
|
||||||
public async forEach(functionArg: IObjectmapForEachFunction<T>) {
|
|
||||||
for (let object of this.objectArray) {
|
|
||||||
await functionArg(object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* gets an object in the Observablemap and removes it, so it can't be retrieved again
|
|
||||||
*/
|
|
||||||
public getOneAndRemove(): T {
|
|
||||||
const removedItem = this.objectArray.shift();
|
|
||||||
this.eventSubject.next('remove');
|
|
||||||
return removedItem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* returns a cloned array of all the objects currently in the Objectmap
|
|
||||||
*/
|
|
||||||
public getArray(): T[] {
|
|
||||||
const returnArray: any[] = [];
|
|
||||||
for (const objectItem of this.objectArray) {
|
|
||||||
returnArray.push(objectItem);
|
|
||||||
}
|
|
||||||
return returnArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* check if Objectmap ist empty
|
|
||||||
*/
|
|
||||||
public isEmpty(): boolean {
|
|
||||||
if (this.objectArray.length === 0) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* remove object from Objectmap
|
|
||||||
*/
|
|
||||||
public remove(objectArg: T) {
|
|
||||||
let replacementArray = [];
|
|
||||||
for (let item of this.objectArray) {
|
|
||||||
if (item !== objectArg) {
|
|
||||||
replacementArray.push(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.objectArray = replacementArray;
|
|
||||||
this.eventSubject.next('remove');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* wipe Objectmap
|
|
||||||
*/
|
|
||||||
public wipe() {
|
|
||||||
this.objectArray = [];
|
|
||||||
this.eventSubject.next('wiped');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
// ==============
|
|
||||||
// native
|
|
||||||
// ==============
|
|
||||||
|
|
||||||
import * as events from 'events';
|
|
||||||
|
|
||||||
export { events };
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// @pushrocks
|
|
||||||
// ==============
|
|
||||||
import * as smartdelay from '@pushrocks/smartdelay';
|
|
||||||
import * as smartpromise from '@pushrocks/smartpromise';
|
|
||||||
import * as smartrx from '@pushrocks/smartrx';
|
|
||||||
import * as smarttime from '@pushrocks/smarttime';
|
|
||||||
|
|
||||||
export { smartdelay, smartpromise, smartrx, smarttime };
|
|
||||||
|
|
||||||
// ==============
|
|
||||||
// third party
|
|
||||||
// ==============
|
|
||||||
import minimatch from 'minimatch';
|
|
||||||
const symbolTree = require('symbol-tree');
|
|
||||||
|
|
||||||
export { minimatch, symbolTree };
|
|
||||||
14
tsconfig.json
Normal file
14
tsconfig.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"verbatimModuleSyntax": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"dist_*/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
17
tslint.json
17
tslint.json
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": ["tslint:latest", "tslint-config-prettier"],
|
|
||||||
"rules": {
|
|
||||||
"semicolon": [true, "always"],
|
|
||||||
"no-console": false,
|
|
||||||
"ordered-imports": false,
|
|
||||||
"object-literal-sort-keys": false,
|
|
||||||
"member-ordering": {
|
|
||||||
"options":{
|
|
||||||
"order": [
|
|
||||||
"static-method"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"defaultSeverity": "warning"
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user