Compare commits
219 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fc00655cef | |||
| f4db131ede | |||
| 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 | |||
| e1442b1bc8 | |||
| 2ca9e14f76 | |||
| e65f36dfa2 | |||
| adb95cd683 | |||
| d2106690b4 | |||
| 2ea5111eb8 | |||
| aae10344fe | |||
| 52b65d7dc3 | |||
| 1135b418cb | |||
| 2eb1dbd0b3 | |||
| 8ec54c17a1 | |||
| 500a291b2a | |||
| 256a14fcb0 | |||
| 7aa0f05bae | |||
| 6208cab36a | |||
| 50a9697baf | |||
| 7956cf6ace | |||
| d8956d4e32 | |||
| c93a67c351 | |||
| fafd9867ad | |||
| 2dfa96bbc5 | |||
| a7484e791a | |||
|
|
2dba68df8c | ||
| 6aff4c9031 | |||
| e81e0ebd41 | |||
| 18866b9a05 | |||
| 71b12b2d9c | |||
| 0669fc3779 | |||
| 7021f8f1ae | |||
| 2392915959 | |||
| b8100f8dfc | |||
| 04e9d8be77 | |||
| a9715381c1 | |||
| c2f081d69a | |||
| b98f075cbd | |||
| 5ff74aea88 | |||
| a6eaf78c80 | |||
| adda1c932a | |||
| 7c8643b090 | |||
| 9e81960095 | |||
| a76832bedb | |||
| 3c229e8e26 | |||
| 29d6f2d7e0 | |||
| 2b5ff6a4ab | |||
| c75437e73f | |||
| 61e4a62689 | |||
| d53f5410cd | |||
| 53516b1c8f | |||
| e59205490b | |||
| 38884ff59c | |||
| 861833e930 | |||
| 1274840d17 | |||
| d2ce1094de | |||
| 6b3fcff971 | |||
| 011a865271 | |||
| 78914e54b3 | |||
| 281cfdc38a | |||
| 217252de1e | |||
| ab7f7230ef | |||
| 7f03ae5248 | |||
| 0060ec2971 | |||
| eec89a596a | |||
| d34a91d283 | |||
| db14306bcf | |||
| cf446f90ee | |||
| 9c2f3cf678 | |||
| 18df32ae5a | |||
| 66b6e78bb1 | |||
| 408faa97a4 | |||
| 4a7749be47 | |||
| 570283551f | |||
| c6c5c5ea40 | |||
| ddd071ccb6 | |||
| 8b9c87d381 | |||
| 89ea1fdb18 | |||
| 5b2aea7626 | |||
| cfbc47e365 | |||
| bb3b02ed74 | |||
| bb4820e980 | |||
| fa17a998f1 | |||
| afad357ed0 | |||
| 59173eebc4 | |||
| 515571dcb3 | |||
| 177ed2d03d | |||
| 23ecaddd5a | |||
| 924058c90a | |||
| 30e23a77b7 | |||
| b228c3b2f0 | |||
| 610a037879 | |||
| 2f681911ae | |||
| 3b04f81b68 | |||
| 0c05cf4f7b |
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
|
||||
23
.gitignore
vendored
23
.gitignore
vendored
@@ -1,5 +1,20 @@
|
||||
node_modules/
|
||||
pages/
|
||||
public/
|
||||
.nogit/
|
||||
|
||||
# artifacts
|
||||
coverage/
|
||||
.nogit/
|
||||
public/
|
||||
pages/
|
||||
|
||||
# installs
|
||||
node_modules/
|
||||
|
||||
# caches
|
||||
.yarn/
|
||||
.cache/
|
||||
.rpt2_cache
|
||||
|
||||
# builds
|
||||
dist/
|
||||
dist_*/
|
||||
|
||||
# custom
|
||||
@@ -1,49 +0,0 @@
|
||||
image: hosttoday/ht-docker-node:npmts
|
||||
|
||||
stages:
|
||||
- test
|
||||
- release
|
||||
- page
|
||||
|
||||
testLEGACY:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test legacy
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testLTS:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test lts
|
||||
tags:
|
||||
- docker
|
||||
|
||||
testSTABLE:
|
||||
stage: test
|
||||
script:
|
||||
- npmci test stable
|
||||
tags:
|
||||
- docker
|
||||
|
||||
release:
|
||||
stage: release
|
||||
environment: npmjs-com_registry
|
||||
script:
|
||||
- npmci publish
|
||||
only:
|
||||
- tags
|
||||
tags:
|
||||
- docker
|
||||
|
||||
pages:
|
||||
image: hosttoday/ht-docker-node:npmpage
|
||||
stage: page
|
||||
script:
|
||||
- npmci command npmpage --host gitlab
|
||||
only:
|
||||
- tags
|
||||
artifacts:
|
||||
expire_in: 1 week
|
||||
paths:
|
||||
- public
|
||||
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"
|
||||
}
|
||||
]
|
||||
}
|
||||
26
.vscode/settings.json
vendored
Normal file
26
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"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"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
28
README.md
28
README.md
@@ -1,28 +0,0 @@
|
||||
# lik
|
||||
light little helpers for node
|
||||
|
||||
## Availabililty
|
||||
[](https://www.npmjs.com/package/lik)
|
||||
[](https://gitlab.com/pushrocks/lik)
|
||||
[](https://github.com/pushrocks/lik)
|
||||
[](https://pushrocks.gitlab.io/lik/gitbook)
|
||||
|
||||
## Status for master
|
||||
[](https://gitlab.com/pushrocks/lik/commits/master)
|
||||
[](https://gitlab.com/pushrocks/lik/commits/master)
|
||||
[](https://david-dm.org/pushrocks/lik)
|
||||
[](https://www.bithound.io/github/pushrocks/lik/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/lik)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](http://standardjs.com/)
|
||||
|
||||
## Usage
|
||||
We recommend the use of TypeScript for good intellisense.
|
||||
|
||||
```typescript
|
||||
// import any tool that you need from lik
|
||||
import { Stringmap, Objectmap } from "lik";
|
||||
```
|
||||
|
||||
For a class overview, please read the docs
|
||||
113
changelog.md
Normal file
113
changelog.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# Changelog
|
||||
|
||||
## 2026-03-22 - 6.4.0 - feat(collections)
|
||||
add new collection APIs, iterator support, and tree serialization utilities
|
||||
|
||||
- adds new convenience methods and properties across BackpressuredArray, FastMap, LimitedArray, ObjectMap, and Tree, including length/size accessors, iterators, batch operations, and utility helpers
|
||||
- improves lookup performance by replacing object scans with Map-backed indexing in FastMap, ObjectMap, and InterestMap
|
||||
- adds TimedAggregator restart support and exports a correctly spelled TimedAggregator alias
|
||||
- fixes Tree iterator methods and implements JSON hierarchy serialization/deserialization
|
||||
- expands test coverage substantially for collection classes and related utilities
|
||||
|
||||
## 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
|
||||
2
dist/index.d.ts
vendored
2
dist/index.d.ts
vendored
@@ -1,2 +0,0 @@
|
||||
export * from './lik.stringmap';
|
||||
export * from './lik.objectmap';
|
||||
8
dist/index.js
vendored
8
dist/index.js
vendored
@@ -1,8 +0,0 @@
|
||||
"use strict";
|
||||
function __export(m) {
|
||||
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
|
||||
}
|
||||
// import modules
|
||||
__export(require("./lik.stringmap"));
|
||||
__export(require("./lik.objectmap"));
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7O0FBRUEsaUJBQWlCO0FBRWpCLHFDQUErQjtBQUMvQixxQ0FBK0IifQ==
|
||||
40
dist/lik.objectmap.d.ts
vendored
40
dist/lik.objectmap.d.ts
vendored
@@ -1,40 +0,0 @@
|
||||
export interface IObjectmapForEachFunction<T> {
|
||||
(itemArg: T): void;
|
||||
}
|
||||
export interface IObjectmapFindFunction<T> {
|
||||
(itemArg: T): boolean;
|
||||
}
|
||||
/**
|
||||
* allows keeping track of objects
|
||||
*/
|
||||
export declare class Objectmap<T> {
|
||||
private objectArray;
|
||||
/**
|
||||
* returns a new instance
|
||||
*/
|
||||
constructor();
|
||||
/**
|
||||
* add object to Objectmap
|
||||
*/
|
||||
add(objectArg: T): void;
|
||||
/**
|
||||
* remove object from Objectmap
|
||||
*/
|
||||
remove(objectArg: T): void;
|
||||
/**
|
||||
* check if object is in Objectmap
|
||||
*/
|
||||
checkForObject(objectArg: T): boolean;
|
||||
/**
|
||||
* find object
|
||||
*/
|
||||
find(findFunction: IObjectmapFindFunction<T>): T;
|
||||
/**
|
||||
* run function for each item in Objectmap
|
||||
*/
|
||||
forEach(functionArg: IObjectmapForEachFunction<T>): void;
|
||||
/**
|
||||
* wipe Objectmap
|
||||
*/
|
||||
wipe(): void;
|
||||
}
|
||||
62
dist/lik.objectmap.js
vendored
62
dist/lik.objectmap.js
vendored
@@ -1,62 +0,0 @@
|
||||
"use strict";
|
||||
/**
|
||||
* allows keeping track of objects
|
||||
*/
|
||||
class Objectmap {
|
||||
/**
|
||||
* returns a new instance
|
||||
*/
|
||||
constructor() {
|
||||
this.objectArray = [];
|
||||
}
|
||||
/**
|
||||
* add object to Objectmap
|
||||
*/
|
||||
add(objectArg) {
|
||||
this.objectArray.push(objectArg);
|
||||
}
|
||||
/**
|
||||
* remove object from Objectmap
|
||||
*/
|
||||
remove(objectArg) {
|
||||
let replacmentArray = [];
|
||||
for (let item of this.objectArray) {
|
||||
if (item !== objectArg) {
|
||||
replacmentArray.push(item);
|
||||
}
|
||||
}
|
||||
this.objectArray = replacmentArray;
|
||||
}
|
||||
/**
|
||||
* check if object is in Objectmap
|
||||
*/
|
||||
checkForObject(objectArg) {
|
||||
return this.objectArray.indexOf(objectArg) !== -1;
|
||||
}
|
||||
/**
|
||||
* find object
|
||||
*/
|
||||
find(findFunction) {
|
||||
let resultArray = this.objectArray.filter(findFunction);
|
||||
if (resultArray.length > 0) {
|
||||
return resultArray[0];
|
||||
}
|
||||
else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* run function for each item in Objectmap
|
||||
*/
|
||||
forEach(functionArg) {
|
||||
return this.objectArray.forEach(functionArg);
|
||||
}
|
||||
/**
|
||||
* wipe Objectmap
|
||||
*/
|
||||
wipe() {
|
||||
this.objectArray = [];
|
||||
}
|
||||
}
|
||||
exports.Objectmap = Objectmap;
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlrLm9iamVjdG1hcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2xpay5vYmplY3RtYXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQVVBOztHQUVHO0FBQ0g7SUFHSTs7T0FFRztJQUNIO1FBTFEsZ0JBQVcsR0FBUSxFQUFFLENBQUE7SUFPN0IsQ0FBQztJQUVEOztPQUVHO0lBQ0gsR0FBRyxDQUFDLFNBQVk7UUFDWixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQTtJQUNwQyxDQUFDO0lBRUQ7O09BRUc7SUFDSCxNQUFNLENBQUMsU0FBWTtRQUNmLElBQUksZUFBZSxHQUFHLEVBQUUsQ0FBQTtRQUN4QixHQUFHLENBQUMsQ0FBQyxJQUFJLElBQUksSUFBSSxJQUFJLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQztZQUNoQyxFQUFFLENBQUMsQ0FBQyxJQUFJLEtBQUssU0FBUyxDQUFDLENBQUMsQ0FBQztnQkFDckIsZUFBZSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQTtZQUM5QixDQUFDO1FBQ0wsQ0FBQztRQUNELElBQUksQ0FBQyxXQUFXLEdBQUcsZUFBZSxDQUFBO0lBQ3RDLENBQUM7SUFFRDs7T0FFRztJQUNILGNBQWMsQ0FBQyxTQUFZO1FBQ3ZCLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQTtJQUNyRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJLENBQUMsWUFBdUM7UUFDeEMsSUFBSSxXQUFXLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxNQUFNLENBQUMsWUFBWSxDQUFDLENBQUE7UUFDdkQsRUFBRSxDQUFDLENBQUMsV0FBVyxDQUFDLE1BQU0sR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ3pCLE1BQU0sQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLENBQUE7UUFDekIsQ0FBQztRQUFDLElBQUksQ0FBQyxDQUFDO1lBQ0osTUFBTSxDQUFDLFNBQVMsQ0FBQTtRQUNwQixDQUFDO0lBQ0wsQ0FBQztJQUVEOztPQUVHO0lBQ0gsT0FBTyxDQUFDLFdBQXlDO1FBQzdDLE1BQU0sQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLE9BQU8sQ0FBQyxXQUFXLENBQUMsQ0FBQTtJQUNoRCxDQUFDO0lBRUQ7O09BRUc7SUFDSCxJQUFJO1FBQ0EsSUFBSSxDQUFDLFdBQVcsR0FBRyxFQUFFLENBQUE7SUFDekIsQ0FBQztDQUNKO0FBOURELDhCQThEQyJ9
|
||||
4
dist/lik.plugins.d.ts
vendored
4
dist/lik.plugins.d.ts
vendored
@@ -1,4 +0,0 @@
|
||||
import 'typings-global';
|
||||
export import q = require('q');
|
||||
export import lodash = require('lodash');
|
||||
export import minimatch = require('minimatch');
|
||||
6
dist/lik.plugins.js
vendored
6
dist/lik.plugins.js
vendored
@@ -1,6 +0,0 @@
|
||||
"use strict";
|
||||
require("typings-global");
|
||||
exports.q = require("q");
|
||||
exports.lodash = require("lodash");
|
||||
exports.minimatch = require("minimatch");
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlrLnBsdWdpbnMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9saWsucGx1Z2lucy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUEsMEJBQXVCO0FBQ3ZCLHlCQUE4QjtBQUM5QixtQ0FBd0M7QUFDeEMseUNBQThDIn0=
|
||||
47
dist/lik.stringmap.d.ts
vendored
47
dist/lik.stringmap.d.ts
vendored
@@ -1,47 +0,0 @@
|
||||
/**
|
||||
* allows you to easily keep track of a bunch of strings
|
||||
*/
|
||||
export interface ITriggerFunction {
|
||||
(): boolean;
|
||||
}
|
||||
export declare class Stringmap {
|
||||
private _stringArray;
|
||||
private _triggerUntilTrueFunctionArray;
|
||||
constructor();
|
||||
/**
|
||||
* add a string to the Stringmap
|
||||
*/
|
||||
addString(stringArg: string): void;
|
||||
/**
|
||||
* removes a string from Stringmap
|
||||
*/
|
||||
removeString(stringArg: string): void;
|
||||
/**
|
||||
* wipes the Stringmap
|
||||
*/
|
||||
wipe(): void;
|
||||
/**
|
||||
* check if string is in Stringmap
|
||||
*/
|
||||
checkString(stringArg: string): boolean;
|
||||
/**
|
||||
* checks stringPresence with minimatch
|
||||
*/
|
||||
checkMinimatch(miniMatchStringArg: string): boolean;
|
||||
/**
|
||||
* checks if the Stringmap is empty
|
||||
*/
|
||||
checkIsEmpty(): boolean;
|
||||
/**
|
||||
* gets a cloned copy of the current string Array
|
||||
*/
|
||||
getStringArray(): string[];
|
||||
/**
|
||||
* register a new trigger
|
||||
*/
|
||||
registerUntilTrue(functionArg: ITriggerFunction, doFunctionArg: any): void;
|
||||
/**
|
||||
* notifies triggers
|
||||
*/
|
||||
private notifyTrigger();
|
||||
}
|
||||
88
dist/lik.stringmap.js
vendored
88
dist/lik.stringmap.js
vendored
@@ -1,88 +0,0 @@
|
||||
"use strict";
|
||||
const plugins = require("./lik.plugins");
|
||||
class Stringmap {
|
||||
constructor() {
|
||||
this._stringArray = [];
|
||||
this._triggerUntilTrueFunctionArray = [];
|
||||
}
|
||||
/**
|
||||
* add a string to the Stringmap
|
||||
*/
|
||||
addString(stringArg) {
|
||||
this._stringArray.push(stringArg);
|
||||
this.notifyTrigger();
|
||||
}
|
||||
/**
|
||||
* removes a string from Stringmap
|
||||
*/
|
||||
removeString(stringArg) {
|
||||
for (let keyArg in this._stringArray) {
|
||||
if (this._stringArray[keyArg] === stringArg) {
|
||||
this._stringArray.splice(parseInt(keyArg), 1);
|
||||
}
|
||||
}
|
||||
this.notifyTrigger();
|
||||
}
|
||||
/**
|
||||
* wipes the Stringmap
|
||||
*/
|
||||
wipe() {
|
||||
this._stringArray = [];
|
||||
this.notifyTrigger();
|
||||
}
|
||||
/**
|
||||
* check if string is in Stringmap
|
||||
*/
|
||||
checkString(stringArg) {
|
||||
return this._stringArray.indexOf(stringArg) !== -1;
|
||||
}
|
||||
/**
|
||||
* checks stringPresence with minimatch
|
||||
*/
|
||||
checkMinimatch(miniMatchStringArg) {
|
||||
let foundMatch = false;
|
||||
for (let stringItem of this._stringArray) {
|
||||
if (plugins.minimatch(stringItem, miniMatchStringArg)) {
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
return foundMatch;
|
||||
}
|
||||
/**
|
||||
* checks if the Stringmap is empty
|
||||
*/
|
||||
checkIsEmpty() {
|
||||
return (this._stringArray.length === 0);
|
||||
}
|
||||
/**
|
||||
* gets a cloned copy of the current string Array
|
||||
*/
|
||||
getStringArray() {
|
||||
return plugins.lodash.cloneDeep(this._stringArray);
|
||||
}
|
||||
// trigger registering
|
||||
/**
|
||||
* register a new trigger
|
||||
*/
|
||||
registerUntilTrue(functionArg, doFunctionArg) {
|
||||
this._triggerUntilTrueFunctionArray.push(() => {
|
||||
let result = functionArg();
|
||||
if (result === true) {
|
||||
doFunctionArg();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
this.notifyTrigger();
|
||||
}
|
||||
/**
|
||||
* notifies triggers
|
||||
*/
|
||||
notifyTrigger() {
|
||||
let filteredArray = this._triggerUntilTrueFunctionArray.filter((functionArg) => {
|
||||
return !functionArg();
|
||||
});
|
||||
this._triggerUntilTrueFunctionArray = filteredArray;
|
||||
}
|
||||
}
|
||||
exports.Stringmap = Stringmap;
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGlrLnN0cmluZ21hcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL2xpay5zdHJpbmdtYXAudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHlDQUF3QztBQVV4QztJQUdJO1FBRlEsaUJBQVksR0FBYSxFQUFFLENBQUE7UUFDM0IsbUNBQThCLEdBQXVCLEVBQUUsQ0FBQTtJQUNoRCxDQUFDO0lBQ2hCOztPQUVHO0lBQ0gsU0FBUyxDQUFDLFNBQWlCO1FBQ3ZCLElBQUksQ0FBQyxZQUFZLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxDQUFBO1FBQ2pDLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQTtJQUN4QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxZQUFZLENBQUMsU0FBaUI7UUFDMUIsR0FBRyxDQUFDLENBQUMsSUFBSSxNQUFNLElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxDQUFDLENBQUM7WUFDbkMsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsS0FBSyxTQUFTLENBQUMsQ0FBQyxDQUFDO2dCQUMxQyxJQUFJLENBQUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxRQUFRLENBQUMsTUFBTSxDQUFDLEVBQUMsQ0FBQyxDQUFDLENBQUE7WUFDaEQsQ0FBQztRQUNMLENBQUM7UUFDRCxJQUFJLENBQUMsYUFBYSxFQUFFLENBQUE7SUFDeEIsQ0FBQztJQUVEOztPQUVHO0lBQ0gsSUFBSTtRQUNBLElBQUksQ0FBQyxZQUFZLEdBQUcsRUFBRSxDQUFBO1FBQ3RCLElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQTtJQUN4QixDQUFDO0lBRUQ7O09BRUc7SUFDSCxXQUFXLENBQUMsU0FBaUI7UUFDekIsTUFBTSxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLFNBQVMsQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFBO0lBQ3RELENBQUM7SUFFRDs7T0FFRztJQUNILGNBQWMsQ0FBQyxrQkFBMEI7UUFDckMsSUFBSSxVQUFVLEdBQVksS0FBSyxDQUFBO1FBQy9CLEdBQUcsQ0FBQyxDQUFDLElBQUksVUFBVSxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQSxDQUFDO1lBQ3RDLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsVUFBVSxFQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNuRCxVQUFVLEdBQUcsSUFBSSxDQUFBO1lBQ3JCLENBQUM7UUFDTCxDQUFDO1FBQ0QsTUFBTSxDQUFDLFVBQVUsQ0FBQTtJQUNyQixDQUFDO0lBRUQ7O09BRUc7SUFDSCxZQUFZO1FBQ1IsTUFBTSxDQUFDLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLEtBQUssQ0FBQyxDQUFDLENBQUE7SUFDM0MsQ0FBQztJQUVEOztPQUVHO0lBQ0gsY0FBYztRQUNWLE1BQU0sQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUE7SUFDdEQsQ0FBQztJQUVELHNCQUFzQjtJQUV0Qjs7T0FFRztJQUNILGlCQUFpQixDQUFDLFdBQTZCLEVBQUMsYUFBYTtRQUN6RCxJQUFJLENBQUMsOEJBQThCLENBQUMsSUFBSSxDQUNwQztZQUNJLElBQUksTUFBTSxHQUFHLFdBQVcsRUFBRSxDQUFBO1lBQzFCLEVBQUUsQ0FBQyxDQUFDLE1BQU0sS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDO2dCQUNsQixhQUFhLEVBQUUsQ0FBQTtZQUNuQixDQUFDO1lBQ0QsTUFBTSxDQUFDLE1BQU0sQ0FBQTtRQUNqQixDQUFDLENBQ0osQ0FBQTtRQUNELElBQUksQ0FBQyxhQUFhLEVBQUUsQ0FBQTtJQUN4QixDQUFDO0lBRUQ7O09BRUc7SUFDSyxhQUFhO1FBQ2pCLElBQUksYUFBYSxHQUFHLElBQUksQ0FBQyw4QkFBOEIsQ0FBQyxNQUFNLENBQUMsQ0FBQyxXQUFXO1lBQ3ZFLE1BQU0sQ0FBQyxDQUFDLFdBQVcsRUFBRSxDQUFBO1FBQ3pCLENBQUMsQ0FBQyxDQUFBO1FBQ0YsSUFBSSxDQUFDLDhCQUE4QixHQUFHLGFBQWEsQ0FBQTtJQUN2RCxDQUFDO0NBRUo7QUE5RkQsOEJBOEZDIn0=
|
||||
46
docs/index.md
Normal file
46
docs/index.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# lik
|
||||
|
||||
light little helpers for node
|
||||
|
||||
## Availabililty
|
||||
|
||||
[](https://www.npmjs.com/package/lik)
|
||||
[](https://GitLab.com/pushrocks/lik)
|
||||
[](https://github.com/pushrocks/lik)
|
||||
[](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/lik)
|
||||
[](https://david-dm.org/pushrocks/lik)
|
||||
[](https://www.bithound.io/github/pushrocks/lik/master/dependencies/npm)
|
||||
[](https://www.bithound.io/github/pushrocks/lik)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](https://nodejs.org/dist/latest-v6.x/docs/api/)
|
||||
[](http://standardjs.com/)
|
||||
|
||||
## 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.
|
||||
|
||||
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
|
||||
> | By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy.html)
|
||||
|
||||
[](https://push.rocks)
|
||||
49
npmextra.json
Normal file
49
npmextra.json
Normal file
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"@git.zone/cli": {
|
||||
"projectType": "npm",
|
||||
"module": {
|
||||
"githost": "code.foss.global",
|
||||
"gitscope": "push.rocks",
|
||||
"gitrepo": "lik",
|
||||
"description": "Provides a collection of lightweight helpers and utilities for Node.js projects.",
|
||||
"npmPackagename": "@push.rocks/lik",
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
81
package.json
81
package.json
@@ -1,34 +1,77 @@
|
||||
{
|
||||
"name": "lik",
|
||||
"version": "1.0.18",
|
||||
"description": "light little helpers for node",
|
||||
"main": "dist/index.js",
|
||||
"typings": "dist/index.d.ts",
|
||||
"name": "@push.rocks/lik",
|
||||
"version": "6.4.0",
|
||||
"private": false,
|
||||
"description": "Provides a collection of lightweight helpers and utilities for Node.js projects.",
|
||||
"main": "dist_ts/index.js",
|
||||
"typings": "dist_ts/index.d.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "(npmts)"
|
||||
"test": "(tstest test/)",
|
||||
"build": "(tsbuild --web --allowimplicitany && tsbundle)",
|
||||
"buildDocs": "tsdoc"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+ssh://git@gitlab.com/pushrocks/lik.git"
|
||||
"url": "https://code.foss.global/push.rocks/lik.git"
|
||||
},
|
||||
"author": "Lossless GmbH",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://gitlab.com/pushrocks/lik/issues"
|
||||
},
|
||||
"homepage": "https://gitlab.com/pushrocks/lik#README",
|
||||
"homepage": "https://code.foss.global/push.rocks/lik",
|
||||
"devDependencies": {
|
||||
"@types/should": "^8.1.30",
|
||||
"npmts-g": "^5.2.8",
|
||||
"should": "^11.1.0",
|
||||
"typings-test": "^1.0.3"
|
||||
"@git.zone/tsbuild": "^4.3.0",
|
||||
"@git.zone/tsbundle": "^2.9.1",
|
||||
"@git.zone/tsrun": "^2.0.1",
|
||||
"@git.zone/tstest": "^3.5.0",
|
||||
"@push.rocks/tapbundle": "^6.0.3",
|
||||
"@types/node": "^25.5.0",
|
||||
"@types/symbol-tree": "^3.2.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/lodash": "^4.14.35",
|
||||
"@types/minimatch": "2.x.x",
|
||||
"@types/q": "0.x.x",
|
||||
"lodash": "^4.16.1",
|
||||
"minimatch": "^3.0.3",
|
||||
"q": "^1.4.1"
|
||||
}
|
||||
"@push.rocks/smartdelay": "^3.0.5",
|
||||
"@push.rocks/smartmatch": "^2.0.0",
|
||||
"@push.rocks/smartpromise": "^4.2.3",
|
||||
"@push.rocks/smartrx": "^3.0.10",
|
||||
"@push.rocks/smarttime": "^4.2.3",
|
||||
"symbol-tree": "^3.2.4"
|
||||
},
|
||||
"files": [
|
||||
"ts/**/*",
|
||||
"ts_web/**/*",
|
||||
"dist/**/*",
|
||||
"dist_*/**/*",
|
||||
"dist_ts/**/*",
|
||||
"dist_ts_web/**/*",
|
||||
"assets/**/*",
|
||||
"cli.js",
|
||||
"npmextra.json",
|
||||
"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"
|
||||
}
|
||||
|
||||
10821
pnpm-lock.yaml
generated
Normal file
10821
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 @@
|
||||
|
||||
418
readme.md
Normal file
418
readme.md
Normal file
@@ -0,0 +1,418 @@
|
||||
# @push.rocks/lik
|
||||
|
||||
⚡ A lean, fully-typed collection of utility classes for TypeScript — efficient data structures, async execution control, and reactive helpers that work seamlessly in both Node.js and the browser.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
# pnpm (recommended)
|
||||
pnpm install @push.rocks/lik
|
||||
|
||||
# npm
|
||||
npm install @push.rocks/lik
|
||||
```
|
||||
|
||||
This is a pure ESM package. Use `import` syntax in your TypeScript/JavaScript projects.
|
||||
|
||||
## Issue Reporting and Security
|
||||
|
||||
For reporting bugs, issues, or security vulnerabilities, please visit [community.foss.global/](https://community.foss.global/). This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a [code.foss.global/](https://code.foss.global/) account to submit Pull Requests directly.
|
||||
|
||||
## Usage
|
||||
|
||||
`@push.rocks/lik` gives you **10 focused utility classes** — each solving one problem well. No bloat, no deep dependency trees, just clean tools for everyday TypeScript.
|
||||
|
||||
---
|
||||
|
||||
### 🔒 AsyncExecutionStack
|
||||
|
||||
Control async task execution with **exclusive** (sequential, mutex-like) and **non-exclusive** (parallel with concurrency limits) modes. Think of it as a lightweight task scheduler.
|
||||
|
||||
```typescript
|
||||
import { AsyncExecutionStack } from '@push.rocks/lik';
|
||||
|
||||
const stack = new AsyncExecutionStack();
|
||||
|
||||
// 🔐 Exclusive: only one task runs at a time (mutex-style)
|
||||
const result = await stack.getExclusiveExecutionSlot(async () => {
|
||||
// critical section — no other task runs until this resolves
|
||||
return await doSomethingImportant();
|
||||
}, 5000); // optional timeout in ms
|
||||
|
||||
// 🚀 Non-exclusive: tasks run in parallel
|
||||
const p1 = stack.getNonExclusiveExecutionSlot(async () => fetchUser(1));
|
||||
const p2 = stack.getNonExclusiveExecutionSlot(async () => fetchUser(2));
|
||||
const p3 = stack.getNonExclusiveExecutionSlot(async () => fetchUser(3));
|
||||
await Promise.all([p1, p2, p3]);
|
||||
|
||||
// 🎚️ Concurrency control for non-exclusive tasks
|
||||
stack.setNonExclusiveMaxConcurrency(3); // max 3 in parallel
|
||||
stack.getNonExclusiveMaxConcurrency(); // 3
|
||||
stack.getActiveNonExclusiveCount(); // how many are running right now
|
||||
stack.getPendingNonExclusiveCount(); // how many are waiting for a slot
|
||||
```
|
||||
|
||||
**Key behavior:** Exclusive and non-exclusive slots are processed in order. When an exclusive slot comes up, it waits for all running non-exclusive tasks to finish, then runs alone. Non-exclusive tasks arriving together are batched and run in parallel (respecting the concurrency limit).
|
||||
|
||||
---
|
||||
|
||||
### 🌊 BackpressuredArray
|
||||
|
||||
A bounded buffer with **backpressure** — perfect for producer/consumer patterns where you need to throttle the producer when the consumer falls behind. Uses RxJS subjects under the hood.
|
||||
|
||||
```typescript
|
||||
import { BackpressuredArray } from '@push.rocks/lik';
|
||||
|
||||
const buffer = new BackpressuredArray<string>(16); // high water mark
|
||||
|
||||
// Producer side
|
||||
const hasSpace = buffer.push('item1');
|
||||
if (!hasSpace) {
|
||||
await buffer.waitForSpace(); // blocks until consumer drains enough
|
||||
}
|
||||
|
||||
// Consumer side
|
||||
await buffer.waitForItems(); // blocks until something is available
|
||||
const item = buffer.shift(); // grab the oldest item
|
||||
|
||||
// Introspect
|
||||
buffer.checkSpaceAvailable(); // true if below high water mark
|
||||
buffer.checkHasItems(); // true if items exist
|
||||
|
||||
// Teardown
|
||||
buffer.destroy(); // completes all internal subjects
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⚡ FastMap
|
||||
|
||||
A high-performance string-keyed map. Faster than native `Map` for simple key-value lookups thanks to plain-object backing. Supports merging, concatenation, and async search.
|
||||
|
||||
```typescript
|
||||
import { FastMap } from '@push.rocks/lik';
|
||||
|
||||
const map = new FastMap<{ name: string; score: number }>();
|
||||
|
||||
// CRUD
|
||||
map.addToMap('player1', { name: 'Alice', score: 100 });
|
||||
map.addToMap('player2', { name: 'Bob', score: 85 });
|
||||
map.getByKey('player1'); // { name: 'Alice', score: 100 }
|
||||
map.isUniqueKey('player1'); // false (already exists)
|
||||
map.removeFromMap('player2'); // returns the removed object
|
||||
map.getKeys(); // ['player1']
|
||||
|
||||
// Force overwrite existing key
|
||||
map.addToMap('player1', { name: 'Alice', score: 200 }, { force: true });
|
||||
|
||||
// Merge two maps
|
||||
const otherMap = new FastMap<{ name: string; score: number }>();
|
||||
otherMap.addToMap('player3', { name: 'Carol', score: 90 });
|
||||
const merged = map.concat(otherMap); // new FastMap with all entries
|
||||
map.addAllFromOther(otherMap); // merge in-place
|
||||
|
||||
// Async find
|
||||
const found = await map.find(async (item) => item.score > 95);
|
||||
|
||||
// Reset
|
||||
map.clean();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🎯 InterestMap & Interest
|
||||
|
||||
A deduplicating interest/subscription tracker. Multiple callers can express interest in the same thing — the `InterestMap` deduplicates them and fulfills all waiters at once. Great for caching layers, resource pooling, or subscription fan-out.
|
||||
|
||||
```typescript
|
||||
import { InterestMap } from '@push.rocks/lik';
|
||||
|
||||
const interestMap = new InterestMap<string, Response>(
|
||||
(id) => id, // comparison function for deduplication
|
||||
{ markLostAfterDefault: 30000 } // auto-destroy unfulfilled interests after 30s
|
||||
);
|
||||
|
||||
// Express interest (returns existing Interest if one matches)
|
||||
const interest = await interestMap.addInterest('user:42');
|
||||
|
||||
// The interest is a promise-like object
|
||||
interest.interestFullfilled.then((response) => {
|
||||
console.log('Got it!', response);
|
||||
});
|
||||
|
||||
// Somewhere else — fulfill the interest for everyone waiting
|
||||
const found = interestMap.findInterest('user:42');
|
||||
found.fullfillInterest(apiResponse);
|
||||
|
||||
// Provide a default fulfillment value (returned if interest is destroyed unfulfilled)
|
||||
const interest2 = await interestMap.addInterest('user:99', fallbackResponse);
|
||||
|
||||
// Check / manage
|
||||
interestMap.checkInterest('user:42'); // true if active
|
||||
interestMap.informLostInterest('user:42'); // starts the destruction timer
|
||||
|
||||
// Observable stream of all new interests as they arrive
|
||||
interestMap.interestObservable.subscribe((interest) => {
|
||||
console.log('New interest:', interest.comparisonString);
|
||||
});
|
||||
|
||||
// Clean up everything
|
||||
interestMap.destroy();
|
||||
```
|
||||
|
||||
**Interest lifecycle:** Created → (optionally) `markLost()` starts a 10s destruction timer → `renew()` resets the timer → `fullfillInterest(value)` resolves all waiters → `destroy()` cleans up.
|
||||
|
||||
---
|
||||
|
||||
### 📏 LimitedArray
|
||||
|
||||
A fixed-capacity array that automatically discards the oldest items when the limit is exceeded. Ideal for rolling logs, recent-history buffers, or sliding-window metrics.
|
||||
|
||||
```typescript
|
||||
import { LimitedArray } from '@push.rocks/lik';
|
||||
|
||||
const recentLogs = new LimitedArray<string>(100);
|
||||
|
||||
recentLogs.addOne('request received');
|
||||
recentLogs.addMany(['processed', 'response sent']);
|
||||
|
||||
console.log(recentLogs.array.length); // never exceeds 100
|
||||
|
||||
// Dynamically adjust the limit
|
||||
recentLogs.setLimit(50); // trims immediately if over
|
||||
|
||||
// Built-in average for numeric arrays
|
||||
const latencies = new LimitedArray<number>(1000);
|
||||
latencies.addMany([12, 15, 9, 22, 18]);
|
||||
console.log(latencies.getAverage()); // 15.2
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔄 LoopTracker
|
||||
|
||||
Detects infinite loops by tracking which objects have already been visited during a traversal. Lightweight guard for recursive algorithms.
|
||||
|
||||
```typescript
|
||||
import { LoopTracker } from '@push.rocks/lik';
|
||||
|
||||
const tracker = new LoopTracker<object>();
|
||||
|
||||
function traverse(node: any) {
|
||||
if (!tracker.checkAndTrack(node)) {
|
||||
console.warn('Cycle detected — skipping');
|
||||
return;
|
||||
}
|
||||
// safe to process this node
|
||||
for (const child of node.children) {
|
||||
traverse(child);
|
||||
}
|
||||
}
|
||||
|
||||
tracker.reset(); // clear for reuse
|
||||
tracker.destroy(); // free resources
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 📦 ObjectMap
|
||||
|
||||
A managed, observable object collection. Add, remove, find, and iterate objects — with **RxJS event notifications** for every mutation. Supports both auto-keyed and manually-keyed entries.
|
||||
|
||||
```typescript
|
||||
import { ObjectMap } from '@push.rocks/lik';
|
||||
|
||||
interface IUser { id: number; name: string; }
|
||||
|
||||
const users = new ObjectMap<IUser>();
|
||||
|
||||
// Auto-keyed add (returns generated key)
|
||||
const key = users.add({ id: 1, name: 'Alice' });
|
||||
users.addArray([{ id: 2, name: 'Bob' }, { id: 3, name: 'Carol' }]);
|
||||
|
||||
// Manually-keyed add/get/remove
|
||||
users.addMappedUnique('admin', { id: 99, name: 'Admin' });
|
||||
users.getMappedUnique('admin');
|
||||
users.removeMappedUnique('admin');
|
||||
|
||||
// Find (sync and async)
|
||||
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);
|
||||
|
||||
// FIFO-style pop
|
||||
const first = users.getOneAndRemove();
|
||||
|
||||
// Iterate and inspect
|
||||
await users.forEach((u) => console.log(u.name));
|
||||
users.checkForObject(alice); // true/false
|
||||
users.getKeyForObject(alice); // the internal key string
|
||||
users.isEmpty(); // true/false
|
||||
users.getArray(); // cloned array of all objects
|
||||
|
||||
// 🔔 Observe mutations
|
||||
users.eventSubject.subscribe((event) => {
|
||||
// event.operation: 'add' | 'remove'
|
||||
// event.payload: the object
|
||||
});
|
||||
|
||||
// Merge
|
||||
const merged = users.concat(otherObjectMap); // new ObjectMap
|
||||
users.addAllFromOther(otherObjectMap); // merge in-place
|
||||
|
||||
// Teardown
|
||||
users.wipe(); // remove all entries (fires 'remove' events)
|
||||
users.destroy(); // wipe + complete eventSubject
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🔤 Stringmap
|
||||
|
||||
A string collection with add/remove/query operations and **glob pattern matching** (via minimatch). Supports reactive triggers that fire when a condition becomes true.
|
||||
|
||||
```typescript
|
||||
import { Stringmap } from '@push.rocks/lik';
|
||||
|
||||
const tags = new Stringmap();
|
||||
|
||||
tags.addString('feature:dark-mode');
|
||||
tags.addStringArray(['feature:i18n', 'bug:login']);
|
||||
|
||||
tags.checkString('feature:dark-mode'); // true
|
||||
tags.checkMinimatch('feature:*'); // true (glob matching!)
|
||||
tags.checkIsEmpty(); // false
|
||||
|
||||
tags.removeString('bug:login');
|
||||
tags.getStringArray(); // cloned array of current strings
|
||||
|
||||
// 🔔 Trigger: resolves when condition is met
|
||||
const waitForEmpty = tags.registerUntilTrue((arr) => arr.length === 0);
|
||||
tags.wipe(); // triggers the above → waitForEmpty resolves
|
||||
|
||||
// Clean up
|
||||
tags.destroy();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⏱️ TimedAggregator
|
||||
|
||||
Batches incoming items over a configurable time window, then processes the entire batch at once. Perfect for log aggregation, metric flushing, or debounced event processing.
|
||||
|
||||
```typescript
|
||||
import { TimedAggregator } from '@push.rocks/lik';
|
||||
// Also available as: import { TimedAggregtor } from '@push.rocks/lik'; (legacy spelling)
|
||||
|
||||
const batcher = new TimedAggregator<string>({
|
||||
aggregationIntervalInMillis: 5000,
|
||||
functionForAggregation: (batch) => {
|
||||
console.log(`Processing ${batch.length} items:`, batch);
|
||||
},
|
||||
});
|
||||
|
||||
batcher.add('event-a');
|
||||
batcher.add('event-b');
|
||||
batcher.add('event-c');
|
||||
// After 5 seconds → "Processing 3 items: ['event-a', 'event-b', 'event-c']"
|
||||
|
||||
// Stop and flush any remaining items
|
||||
batcher.stop(true); // true = flush remaining immediately
|
||||
|
||||
// Restart after stopping
|
||||
batcher.restart();
|
||||
batcher.add('event-d'); // timer starts again
|
||||
```
|
||||
|
||||
**How it works:** The timer starts when the first item arrives. When it fires, all accumulated items are passed to `functionForAggregation` and a new timer starts. If no items arrive, the timer doesn't restart — it's lazy.
|
||||
|
||||
---
|
||||
|
||||
### 🌳 Tree
|
||||
|
||||
A typed wrapper around [`symbol-tree`](https://www.npmjs.com/package/symbol-tree) for managing hierarchical data. Full parent/child/sibling navigation, ordered iteration, and structural mutation.
|
||||
|
||||
```typescript
|
||||
import { Tree } from '@push.rocks/lik';
|
||||
|
||||
interface INode { name: string; }
|
||||
|
||||
const tree = new Tree<INode>();
|
||||
const root: INode = { name: 'root' };
|
||||
tree.initialize(root);
|
||||
|
||||
const child1: INode = { name: 'child1' };
|
||||
const child2: INode = { name: 'child2' };
|
||||
const grandchild: INode = { name: 'grandchild' };
|
||||
|
||||
tree.appendChild(root, child1);
|
||||
tree.appendChild(root, child2);
|
||||
tree.appendChild(child1, grandchild);
|
||||
|
||||
// 🧭 Navigate
|
||||
tree.parent(child1); // root
|
||||
tree.firstChild(root); // child1
|
||||
tree.lastChild(root); // child2
|
||||
tree.nextSibling(child1); // child2
|
||||
tree.previousSibling(child2); // child1
|
||||
tree.hasChildren(child1); // true
|
||||
|
||||
// 📊 Query
|
||||
tree.childrenCount(root); // 2
|
||||
tree.index(child2); // 1
|
||||
tree.childrenToArray(root, {}); // [child1, child2]
|
||||
tree.ancestorsToArray(grandchild, {}); // [child1, root]
|
||||
tree.treeToArray(root, {}); // entire tree as flat array
|
||||
|
||||
// 🔁 Iterate
|
||||
for (const node of tree.treeIterator(root, {})) {
|
||||
console.log(node.name);
|
||||
}
|
||||
|
||||
// ✂️ Mutate
|
||||
tree.insertBefore(child2, { name: 'between' });
|
||||
tree.insertAfter(child1, { name: 'after1' });
|
||||
tree.prependChild(root, { name: 'new-first' });
|
||||
tree.remove(child2);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API at a Glance
|
||||
|
||||
| Class | Purpose | Key Feature |
|
||||
|-------|---------|-------------|
|
||||
| `AsyncExecutionStack` | Async task scheduling | Exclusive/non-exclusive modes with concurrency limits |
|
||||
| `BackpressuredArray` | Bounded buffer | Producer/consumer backpressure via RxJS |
|
||||
| `FastMap` | Key-value store | O(1) lookups, merge/concat support |
|
||||
| `InterestMap` | Deduplicated subscriptions | Multiple waiters, single fulfillment |
|
||||
| `LimitedArray` | Rolling buffer | Auto-trim + built-in average |
|
||||
| `LoopTracker` | Cycle detection | Track-and-check in one call |
|
||||
| `ObjectMap` | Observable collection | RxJS event stream on every mutation |
|
||||
| `Stringmap` | String set | Glob matching + reactive triggers |
|
||||
| `TimedAggregator` | Batch processor | Time-windowed aggregation with flush |
|
||||
| `Tree` | Hierarchical data | Full navigation, iteration, mutation |
|
||||
|
||||
## License and Legal Information
|
||||
|
||||
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [LICENSE](./LICENSE) file.
|
||||
|
||||
**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 or third parties, 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 or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
|
||||
|
||||
### Company Information
|
||||
|
||||
Task Venture Capital GmbH
|
||||
Registered at District Court Bremen HRB 35230 HB, Germany
|
||||
|
||||
For any legal inquiries or 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();
|
||||
118
test/test.backpressuredarray.both.ts
Normal file
118
test/test.backpressuredarray.both.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
let testArray: lik.BackpressuredArray<string>;
|
||||
|
||||
tap.test('should create a BackpressuredArray with default high water mark', async () => {
|
||||
testArray = new lik.BackpressuredArray<string>();
|
||||
expect(testArray).toBeInstanceOf(lik.BackpressuredArray);
|
||||
expect(testArray.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('should push items and return true while under high water mark', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(4);
|
||||
expect(arr.push(1)).toBeTrue();
|
||||
expect(arr.push(2)).toBeTrue();
|
||||
expect(arr.push(3)).toBeTrue();
|
||||
expect(arr.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('should return false when at high water mark', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(2);
|
||||
arr.push(1);
|
||||
const result = arr.push(2);
|
||||
expect(result).toBeFalse();
|
||||
expect(arr.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('should shift items correctly', async () => {
|
||||
const arr = new lik.BackpressuredArray<string>(4);
|
||||
arr.push('a');
|
||||
arr.push('b');
|
||||
expect(arr.shift()).toEqual('a');
|
||||
expect(arr.shift()).toEqual('b');
|
||||
expect(arr.shift()).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('should peek without removing', async () => {
|
||||
const arr = new lik.BackpressuredArray<string>(4);
|
||||
arr.push('first');
|
||||
arr.push('second');
|
||||
expect(arr.peek()).toEqual('first');
|
||||
expect(arr.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('should peek return undefined on empty', async () => {
|
||||
const arr = new lik.BackpressuredArray<string>(4);
|
||||
expect(arr.peek()).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('should pushMany items', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(10);
|
||||
const result = arr.pushMany([1, 2, 3]);
|
||||
expect(arr.length).toEqual(3);
|
||||
expect(result).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('checkHasItems returns correct boolean', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(4);
|
||||
expect(arr.checkHasItems()).toBeFalse();
|
||||
arr.push(1);
|
||||
expect(arr.checkHasItems()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('checkSpaceAvailable works correctly', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(2);
|
||||
expect(arr.checkSpaceAvailable()).toBeTrue();
|
||||
arr.push(1);
|
||||
expect(arr.checkSpaceAvailable()).toBeTrue();
|
||||
arr.push(2);
|
||||
expect(arr.checkSpaceAvailable()).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('waitForItems resolves when items are pushed', async () => {
|
||||
const arr = new lik.BackpressuredArray<string>(4);
|
||||
let resolved = false;
|
||||
const waitPromise = arr.waitForItems().then(() => {
|
||||
resolved = true;
|
||||
});
|
||||
arr.push('hello');
|
||||
await waitPromise;
|
||||
expect(resolved).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('waitForSpace resolves when items are shifted', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(1);
|
||||
arr.push(1);
|
||||
let resolved = false;
|
||||
const waitPromise = arr.waitForSpace().then(() => {
|
||||
resolved = true;
|
||||
});
|
||||
arr.shift();
|
||||
await waitPromise;
|
||||
expect(resolved).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Symbol.iterator works', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(10);
|
||||
arr.pushMany([10, 20, 30]);
|
||||
const collected: number[] = [];
|
||||
for (const item of arr) {
|
||||
collected.push(item);
|
||||
}
|
||||
expect(collected).toEqual([10, 20, 30]);
|
||||
});
|
||||
|
||||
tap.test('destroy completes subjects and unblocks waiters', async () => {
|
||||
const arr = new lik.BackpressuredArray<number>(1);
|
||||
arr.push(1);
|
||||
let spaceResolved = false;
|
||||
const waitPromise = arr.waitForSpace().then(() => {
|
||||
spaceResolved = true;
|
||||
});
|
||||
arr.destroy();
|
||||
await waitPromise;
|
||||
expect(spaceResolved).toBeTrue();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
1
test/test.d.ts
vendored
1
test/test.d.ts
vendored
@@ -1 +0,0 @@
|
||||
import 'typings-test';
|
||||
151
test/test.fastmap.both.ts
Normal file
151
test/test.fastmap.both.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
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);
|
||||
expect(fastmap.size).toEqual(0);
|
||||
});
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
tap.test('isUniqueKey returns true for new key, false for existing', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
expect(map.isUniqueKey('foo')).toBeTrue();
|
||||
map.addToMap('foo', 'bar');
|
||||
expect(map.isUniqueKey('foo')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('has() works correctly', async () => {
|
||||
const map = new lik.FastMap<number>();
|
||||
expect(map.has('x')).toBeFalse();
|
||||
map.addToMap('x', 42);
|
||||
expect(map.has('x')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('addToMap with force overwrites existing key', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
map.addToMap('key1', 'original');
|
||||
const withoutForce = map.addToMap('key1', 'new');
|
||||
expect(withoutForce).toBeFalse();
|
||||
expect(map.getByKey('key1')).toEqual('original');
|
||||
|
||||
const withForce = map.addToMap('key1', 'new', { force: true });
|
||||
expect(withForce).toBeTrue();
|
||||
expect(map.getByKey('key1')).toEqual('new');
|
||||
});
|
||||
|
||||
tap.test('getByKey returns undefined for missing key', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
expect(map.getByKey('nonexistent')).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('removeFromMap removes and returns item', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
map.addToMap('a', 'hello');
|
||||
const removed = map.removeFromMap('a');
|
||||
expect(removed).toEqual('hello');
|
||||
expect(map.has('a')).toBeFalse();
|
||||
expect(map.size).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('getKeys returns all keys', async () => {
|
||||
const map = new lik.FastMap<number>();
|
||||
map.addToMap('a', 1);
|
||||
map.addToMap('b', 2);
|
||||
map.addToMap('c', 3);
|
||||
const keys = map.getKeys();
|
||||
expect(keys.length).toEqual(3);
|
||||
expect(keys).toContain('a');
|
||||
expect(keys).toContain('b');
|
||||
expect(keys).toContain('c');
|
||||
});
|
||||
|
||||
tap.test('values returns all values', async () => {
|
||||
const map = new lik.FastMap<number>();
|
||||
map.addToMap('x', 10);
|
||||
map.addToMap('y', 20);
|
||||
const vals = map.values();
|
||||
expect(vals.length).toEqual(2);
|
||||
expect(vals).toContain(10);
|
||||
expect(vals).toContain(20);
|
||||
});
|
||||
|
||||
tap.test('entries returns key-value pairs', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
map.addToMap('k1', 'v1');
|
||||
const entries = map.entries();
|
||||
expect(entries.length).toEqual(1);
|
||||
expect(entries[0][0]).toEqual('k1');
|
||||
expect(entries[0][1]).toEqual('v1');
|
||||
});
|
||||
|
||||
tap.test('clean empties the map', async () => {
|
||||
const map = new lik.FastMap<string>();
|
||||
map.addToMap('a', 'b');
|
||||
map.addToMap('c', 'd');
|
||||
map.clean();
|
||||
expect(map.size).toEqual(0);
|
||||
expect(map.getKeys().length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('concat merges two maps', async () => {
|
||||
const map1 = new lik.FastMap<number>();
|
||||
map1.addToMap('a', 1);
|
||||
const map2 = new lik.FastMap<number>();
|
||||
map2.addToMap('b', 2);
|
||||
const merged = map1.concat(map2);
|
||||
expect(merged.size).toEqual(2);
|
||||
expect(merged.getByKey('a')).toEqual(1);
|
||||
expect(merged.getByKey('b')).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('addAllFromOther merges in place', async () => {
|
||||
const map1 = new lik.FastMap<number>();
|
||||
map1.addToMap('a', 1);
|
||||
const map2 = new lik.FastMap<number>();
|
||||
map2.addToMap('b', 2);
|
||||
map2.addToMap('a', 99);
|
||||
map1.addAllFromOther(map2);
|
||||
expect(map1.size).toEqual(2);
|
||||
expect(map1.getByKey('a')).toEqual(99);
|
||||
expect(map1.getByKey('b')).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('Symbol.iterator works with for...of', async () => {
|
||||
const map = new lik.FastMap<number>();
|
||||
map.addToMap('x', 1);
|
||||
map.addToMap('y', 2);
|
||||
const collected: [string, number][] = [];
|
||||
for (const entry of map) {
|
||||
collected.push(entry);
|
||||
}
|
||||
expect(collected.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('find returns undefined when no match', async () => {
|
||||
const map = new lik.FastMap<number>();
|
||||
map.addToMap('a', 1);
|
||||
const result = await map.find(async (item) => item === 999);
|
||||
expect(result).toBeUndefined();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
65
test/test.interestmap.both.ts
Normal file
65
test/test.interestmap.both.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
let testInterestmap: lik.InterestMap<number, number>;
|
||||
|
||||
tap.test('should create an interestmap', async () => {
|
||||
testInterestmap = new lik.InterestMap((numberArg) => {
|
||||
return numberArg.toString();
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('should create an interest', async () => {
|
||||
testInterestmap.addInterest(3);
|
||||
testInterestmap.addInterest(4);
|
||||
});
|
||||
|
||||
tap.test('should return an already existing interest', async () => {
|
||||
const interest3a = await testInterestmap.addInterest(3);
|
||||
const interest3b = await testInterestmap.addInterest(3);
|
||||
expect(interest3a === interest3b).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('should be able to inform about a lost interest', async () => {
|
||||
testInterestmap.informLostInterest(3);
|
||||
});
|
||||
|
||||
tap.test('checkInterest returns true for existing', async () => {
|
||||
expect(testInterestmap.checkInterest(4)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('checkInterest returns false for non-existing', async () => {
|
||||
expect(testInterestmap.checkInterest(999)).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('findInterest returns the interest', async () => {
|
||||
const interest = testInterestmap.findInterest(4);
|
||||
expect(interest).not.toBeNull();
|
||||
expect(interest.originalInterest).toEqual(4);
|
||||
});
|
||||
|
||||
tap.test('findInterest returns null for non-existing', async () => {
|
||||
const interest = testInterestmap.findInterest(888);
|
||||
expect(interest).toBeNull();
|
||||
});
|
||||
|
||||
tap.test('fullfillInterest resolves the promise', async () => {
|
||||
const im = new lik.InterestMap<string, string>((s) => s);
|
||||
const interest = await im.addInterest('hello');
|
||||
interest.fullfillInterest('world');
|
||||
const result = await interest.interestFullfilled;
|
||||
expect(result).toEqual('world');
|
||||
expect(interest.isFullfilled).toBeTrue();
|
||||
im.destroy();
|
||||
});
|
||||
|
||||
tap.test('destroy cleans up interestmap', async () => {
|
||||
const im = new lik.InterestMap<string, string>((s) => s);
|
||||
await im.addInterest('a');
|
||||
await im.addInterest('b');
|
||||
im.destroy();
|
||||
expect(im.checkInterest('a')).toBeFalse();
|
||||
expect(im.checkInterest('b')).toBeFalse();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
83
test/test.js
83
test/test.js
@@ -1,83 +0,0 @@
|
||||
"use strict";
|
||||
require("typings-test");
|
||||
const lik = require("../dist/index");
|
||||
const should = require("should");
|
||||
describe('lik', function () {
|
||||
describe('Stringmap', function () {
|
||||
let testStringmap;
|
||||
let testString1 = 'testString1';
|
||||
let testString2 = 'testString2';
|
||||
let testString3 = 'testString3';
|
||||
it('should create an instance of Stringmap', function () {
|
||||
testStringmap = new lik.Stringmap();
|
||||
should(testStringmap).be.instanceof(lik.Stringmap);
|
||||
});
|
||||
it('should return false for an string not in Stringmap', function () {
|
||||
should(testStringmap.checkString(testString1)).be.false();
|
||||
});
|
||||
it('should add an string to Stringmap', function () {
|
||||
testStringmap.addString(testString1);
|
||||
testStringmap.addString(testString2);
|
||||
testStringmap.addString(testString3);
|
||||
should(testStringmap.checkString(testString1)).be.true();
|
||||
should(testStringmap.checkString(testString2)).be.true();
|
||||
should(testStringmap.checkString(testString3)).be.true();
|
||||
should(testStringmap.checkMinimatch('*String1')).be.true();
|
||||
should(testStringmap.checkMinimatch('*String2')).be.true();
|
||||
should(testStringmap.checkMinimatch('*String4')).be.false();
|
||||
});
|
||||
it('should remove a string from Stringmap', function () {
|
||||
testStringmap.removeString(testString2);
|
||||
should(testStringmap.checkString(testString2)).be.false();
|
||||
});
|
||||
it('should return a copy of stringArray', function () {
|
||||
let clonedArray = testStringmap.getStringArray();
|
||||
should(clonedArray[0] === 'testString1').be.true();
|
||||
should(clonedArray[0] === testString1).be.true();
|
||||
});
|
||||
it('should register a function to trigger when empty', function () {
|
||||
testStringmap.registerUntilTrue(() => { return testStringmap.checkIsEmpty(); }, () => { console.log('Stringmap now is empty'); });
|
||||
});
|
||||
it('should remove wipe and then notify', function () {
|
||||
testStringmap.wipe();
|
||||
});
|
||||
});
|
||||
describe('Objectmap', function () {
|
||||
let testObjectmap;
|
||||
let testObject1 = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2'
|
||||
};
|
||||
let testObject2 = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2'
|
||||
};
|
||||
it('should correctly instantiate an Objectmap', function () {
|
||||
testObjectmap = new lik.Objectmap();
|
||||
should(testObjectmap).be.instanceof(lik.Objectmap);
|
||||
});
|
||||
it('should correctly add an object to Objectmap', function () {
|
||||
testObjectmap.add(testObject1);
|
||||
should(testObjectmap.checkForObject(testObject1)).be.true;
|
||||
should(testObjectmap.checkForObject(testObject2)).be.false;
|
||||
});
|
||||
it('should correctly remove an object to Objectmap', function () {
|
||||
testObjectmap.add(testObject2);
|
||||
testObjectmap.remove(testObject1);
|
||||
should(testObjectmap.checkForObject(testObject1)).be.false;
|
||||
should(testObjectmap.checkForObject(testObject2)).be.true;
|
||||
});
|
||||
it('should correctly run a function forEach map object', function () {
|
||||
testObjectmap.forEach(itemArg => {
|
||||
should(itemArg).have.ownProperty('propOne');
|
||||
});
|
||||
});
|
||||
it('should correctly find an object', function () {
|
||||
let myObject = { propOne: 'helloThere', propTwo: 'helloAnyway' };
|
||||
testObjectmap.add(myObject);
|
||||
let referenceObject = testObjectmap.find((itemArg) => { return (itemArg.propOne === 'helloThere'); });
|
||||
should(myObject === referenceObject).be.true();
|
||||
});
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVzdC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbInRlc3QudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBLHdCQUFxQjtBQUNyQixxQ0FBcUM7QUFDckMsaUNBQWdDO0FBRWhDLFFBQVEsQ0FBQyxLQUFLLEVBQUM7SUFDWCxRQUFRLENBQUMsV0FBVyxFQUFDO1FBQ2pCLElBQUksYUFBNEIsQ0FBQTtRQUNoQyxJQUFJLFdBQVcsR0FBRyxhQUFhLENBQUE7UUFDL0IsSUFBSSxXQUFXLEdBQUcsYUFBYSxDQUFBO1FBQy9CLElBQUksV0FBVyxHQUFHLGFBQWEsQ0FBQTtRQUMvQixFQUFFLENBQUMsd0NBQXdDLEVBQUM7WUFDeEMsYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLFNBQVMsRUFBRSxDQUFBO1lBQ25DLE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUN0RCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxvREFBb0QsRUFBQztZQUNwRCxNQUFNLENBQUMsYUFBYSxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUM3RCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxtQ0FBbUMsRUFBQztZQUNuQyxhQUFhLENBQUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ3BDLGFBQWEsQ0FBQyxTQUFTLENBQUMsV0FBVyxDQUFDLENBQUE7WUFDcEMsYUFBYSxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsQ0FBQTtZQUNwQyxNQUFNLENBQUMsYUFBYSxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUN4RCxNQUFNLENBQUMsYUFBYSxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUN4RCxNQUFNLENBQUMsYUFBYSxDQUFDLFdBQVcsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUN4RCxNQUFNLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUMxRCxNQUFNLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUMxRCxNQUFNLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLEVBQUUsQ0FBQTtRQUMvRCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyx1Q0FBdUMsRUFBQztZQUN2QyxhQUFhLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ3ZDLE1BQU0sQ0FBQyxhQUFhLENBQUMsV0FBVyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssRUFBRSxDQUFBO1FBQzdELENBQUMsQ0FBQyxDQUFBO1FBQ0YsRUFBRSxDQUFDLHFDQUFxQyxFQUFDO1lBQ3JDLElBQUksV0FBVyxHQUFHLGFBQWEsQ0FBQyxjQUFjLEVBQUUsQ0FBQTtZQUNoRCxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLGFBQWEsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtZQUNsRCxNQUFNLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxLQUFLLFdBQVcsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUNwRCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxrREFBa0QsRUFBQztZQUNsRCxhQUFhLENBQUMsaUJBQWlCLENBQzNCLFFBQVEsTUFBTSxDQUFDLGFBQWEsQ0FBQyxZQUFZLEVBQUUsQ0FBQSxDQUFDLENBQUMsRUFDN0MsUUFBUSxPQUFPLENBQUMsR0FBRyxDQUFDLHdCQUF3QixDQUFDLENBQUEsQ0FBQyxDQUFDLENBQ2xELENBQUE7UUFDTCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyxvQ0FBb0MsRUFBQztZQUNwQyxhQUFhLENBQUMsSUFBSSxFQUFFLENBQUE7UUFDeEIsQ0FBQyxDQUFDLENBQUE7SUFDTixDQUFDLENBQUMsQ0FBQTtJQUNGLFFBQVEsQ0FBQyxXQUFXLEVBQUM7UUFLakIsSUFBSSxhQUF5QyxDQUFBO1FBQzdDLElBQUksV0FBVyxHQUFnQjtZQUMzQixPQUFPLEVBQUUsT0FBTztZQUNoQixPQUFPLEVBQUUsUUFBUTtTQUNwQixDQUFBO1FBQ0QsSUFBSSxXQUFXLEdBQWdCO1lBQzNCLE9BQU8sRUFBRSxPQUFPO1lBQ2hCLE9BQU8sRUFBRSxRQUFRO1NBQ3BCLENBQUE7UUFDRCxFQUFFLENBQUMsMkNBQTJDLEVBQUM7WUFDM0MsYUFBYSxHQUFHLElBQUksR0FBRyxDQUFDLFNBQVMsRUFBZSxDQUFBO1lBQ2hELE1BQU0sQ0FBQyxhQUFhLENBQUMsQ0FBQyxFQUFFLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUN0RCxDQUFDLENBQUMsQ0FBQTtRQUNGLEVBQUUsQ0FBQyw2Q0FBNkMsRUFBQztZQUM3QyxhQUFhLENBQUMsR0FBRyxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQzlCLE1BQU0sQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQTtZQUN6RCxNQUFNLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUE7UUFDOUQsQ0FBQyxDQUFDLENBQUE7UUFDRixFQUFFLENBQUMsZ0RBQWdELEVBQUM7WUFDaEQsYUFBYSxDQUFDLEdBQUcsQ0FBQyxXQUFXLENBQUMsQ0FBQTtZQUM5QixhQUFhLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxDQUFBO1lBQ2pDLE1BQU0sQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUFDLFdBQVcsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEtBQUssQ0FBQTtZQUMxRCxNQUFNLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUE7UUFDN0QsQ0FBQyxDQUFDLENBQUE7UUFDRixFQUFFLENBQUMsb0RBQW9ELEVBQUM7WUFDcEQsYUFBYSxDQUFDLE9BQU8sQ0FBQyxPQUFPO2dCQUN6QixNQUFNLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLFdBQVcsQ0FBQyxTQUFTLENBQUMsQ0FBQTtZQUMvQyxDQUFDLENBQUMsQ0FBQTtRQUNOLENBQUMsQ0FBQyxDQUFBO1FBQ0YsRUFBRSxDQUFDLGlDQUFpQyxFQUFDO1lBQ2pDLElBQUksUUFBUSxHQUFHLEVBQUMsT0FBTyxFQUFFLFlBQVksRUFBRSxPQUFPLEVBQUUsYUFBYSxFQUFDLENBQUE7WUFDOUQsYUFBYSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQTtZQUMzQixJQUFJLGVBQWUsR0FBRyxhQUFhLENBQUMsSUFBSSxDQUFDLENBQUMsT0FBTyxPQUFPLE1BQU0sQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLEtBQUssWUFBWSxDQUFDLENBQUEsQ0FBQyxDQUFDLENBQUMsQ0FBQTtZQUNwRyxNQUFNLENBQUMsUUFBUSxLQUFLLGVBQWUsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLEVBQUUsQ0FBQTtRQUNsRCxDQUFDLENBQUMsQ0FBQTtJQUNOLENBQUMsQ0FBQyxDQUFBO0FBQ04sQ0FBQyxDQUFDLENBQUEifQ==
|
||||
89
test/test.limitedarray.both.ts
Normal file
89
test/test.limitedarray.both.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
|
||||
import { LimitedArray } from '../ts/index.js';
|
||||
|
||||
let testLimitedArray: LimitedArray<string>;
|
||||
|
||||
tap.test('should create a LimitedArray', async () => {
|
||||
testLimitedArray = new LimitedArray(6);
|
||||
expect(testLimitedArray).toBeInstanceOf(LimitedArray);
|
||||
expect(testLimitedArray.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('should never be longer than the set length', async () => {
|
||||
testLimitedArray.addMany(['hi', 'this', 'is', 'quite', 'a', 'long', 'string', ':)']);
|
||||
expect(testLimitedArray.array.length < 7).toBeTrue();
|
||||
expect(testLimitedArray.length).toEqual(6);
|
||||
});
|
||||
|
||||
tap.test('addOne respects limit', async () => {
|
||||
const arr = new LimitedArray<number>(3);
|
||||
arr.addOne(1);
|
||||
arr.addOne(2);
|
||||
arr.addOne(3);
|
||||
arr.addOne(4);
|
||||
expect(arr.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('setLimit truncates when lowered', async () => {
|
||||
const arr = new LimitedArray<number>(5);
|
||||
arr.addMany([1, 2, 3, 4, 5]);
|
||||
expect(arr.length).toEqual(5);
|
||||
arr.setLimit(2);
|
||||
expect(arr.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('getAverage returns correct average for numbers', async () => {
|
||||
const arr = new LimitedArray<number>(10);
|
||||
arr.addMany([10, 20, 30]);
|
||||
expect(arr.getAverage()).toEqual(20);
|
||||
});
|
||||
|
||||
tap.test('getAverage returns 0 for empty array', async () => {
|
||||
const arr = new LimitedArray<number>(10);
|
||||
expect(arr.getAverage()).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('getAverage returns null for non-number array', async () => {
|
||||
const arr = new LimitedArray<string>(10);
|
||||
arr.addOne('hello');
|
||||
expect(arr.getAverage()).toBeNull();
|
||||
});
|
||||
|
||||
tap.test('remove removes an item', async () => {
|
||||
const arr = new LimitedArray<string>(10);
|
||||
arr.addMany(['a', 'b', 'c']);
|
||||
const removed = arr.remove('b');
|
||||
expect(removed).toBeTrue();
|
||||
expect(arr.length).toEqual(2);
|
||||
const notFound = arr.remove('zzz');
|
||||
expect(notFound).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('clear empties the array', async () => {
|
||||
const arr = new LimitedArray<number>(10);
|
||||
arr.addMany([1, 2, 3]);
|
||||
arr.clear();
|
||||
expect(arr.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('getArray returns a copy', async () => {
|
||||
const arr = new LimitedArray<number>(10);
|
||||
arr.addMany([1, 2, 3]);
|
||||
const copy = arr.getArray();
|
||||
expect(copy.length).toEqual(3);
|
||||
copy.push(99);
|
||||
expect(arr.length).toEqual(3);
|
||||
});
|
||||
|
||||
tap.test('Symbol.iterator enables for...of', async () => {
|
||||
const arr = new LimitedArray<number>(10);
|
||||
arr.addMany([5, 10, 15]);
|
||||
const collected: number[] = [];
|
||||
for (const item of arr) {
|
||||
collected.push(item);
|
||||
}
|
||||
expect(collected.length).toEqual(3);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
34
test/test.looptracker.both.ts
Normal file
34
test/test.looptracker.both.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
// 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();
|
||||
});
|
||||
|
||||
tap.test('reset allows re-tracking', async () => {
|
||||
myLoopTracker.reset();
|
||||
expect(myLoopTracker.checkAndTrack(object1)).toBeTrue();
|
||||
expect(myLoopTracker.checkAndTrack(object1)).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('destroy cleans up', async () => {
|
||||
myLoopTracker.destroy();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
209
test/test.objectmap.both.ts
Normal file
209
test/test.objectmap.both.ts
Normal file
@@ -0,0 +1,209 @@
|
||||
// import test framework
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
|
||||
// import the module
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
// Objectmap
|
||||
interface ITestObject {
|
||||
propOne: string;
|
||||
propTwo: string;
|
||||
}
|
||||
let testObjectmap: lik.ObjectMap<ITestObject>;
|
||||
let testObject1: ITestObject = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2',
|
||||
};
|
||||
let testObject2: ITestObject = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2',
|
||||
};
|
||||
|
||||
tap.test('new lik.Objectmap() -> should correctly instantiate an Objectmap', async () => {
|
||||
testObjectmap = new lik.ObjectMap<ITestObject>();
|
||||
expect(testObjectmap).toBeInstanceOf(lik.ObjectMap);
|
||||
expect(testObjectmap.length).toEqual(0);
|
||||
expect(testObjectmap.isEmpty()).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('lik.Objectmap.add() -> should correctly add an object to Objectmap', async () => {
|
||||
testObjectmap.add(testObject1);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testObjectmap.checkForObject(testObject1)).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testObjectmap.checkForObject(testObject2)).toBeFalse();
|
||||
expect(testObjectmap.length).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('add same object twice returns same key', async () => {
|
||||
const key1 = testObjectmap.add(testObject1);
|
||||
const key2 = testObjectmap.add(testObject1);
|
||||
expect(key1).toEqual(key2);
|
||||
expect(testObjectmap.length).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('lik.Objectmap.remove() -> should correctly remove an object to Objectmap', async () => {
|
||||
testObjectmap.add(testObject2);
|
||||
testObjectmap.remove(testObject1);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testObjectmap.checkForObject(testObject1)).toBeFalse();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testObjectmap.checkForObject(testObject2)).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('Objectmap.forEach -> should correctly run a function forEach map object', async () => {
|
||||
testObjectmap.forEach((itemArg) => {
|
||||
expect(itemArg).toHaveProperty('propOne');
|
||||
});
|
||||
});
|
||||
|
||||
tap.test('lik.Objectmap.find() -> should correctly find an object', async () => {
|
||||
let myObject = { propOne: 'helloThere', propTwo: 'helloAnyway' };
|
||||
testObjectmap.add(myObject);
|
||||
let referenceObject = await testObjectmap.find(async (itemArg) => {
|
||||
return itemArg.propOne === 'helloThere';
|
||||
});
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(myObject === referenceObject).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('lik.Objectmap.getArray() -> should return a cloned array', async () => {
|
||||
let myObject = { propOne: 'test1', propTwo: 'wow, how awesome' };
|
||||
testObjectmap.add(myObject);
|
||||
let clonedArray = testObjectmap.getArray();
|
||||
expect(clonedArray[clonedArray.length - 1]).toEqual(myObject);
|
||||
});
|
||||
|
||||
tap.test('should get one object and then remove it', async () => {
|
||||
let originalLength = testObjectmap.getArray().length;
|
||||
let oneObject = testObjectmap.getOneAndRemove();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(oneObject).not.toBeNull();
|
||||
expect(testObjectmap.getArray().length).toEqual(originalLength - 1);
|
||||
expect(testObjectmap.getArray()).not.toContain(oneObject);
|
||||
});
|
||||
|
||||
tap.test('addMappedUnique and getMappedUnique work', async () => {
|
||||
const map = new lik.ObjectMap<string>();
|
||||
map.addMappedUnique('myKey', 'myValue');
|
||||
expect(map.getMappedUnique('myKey')).toEqual('myValue');
|
||||
});
|
||||
|
||||
tap.test('removeMappedUnique works', async () => {
|
||||
const map = new lik.ObjectMap<string>();
|
||||
map.addMappedUnique('k1', 'v1');
|
||||
const removed = map.removeMappedUnique('k1');
|
||||
expect(removed).toEqual('v1');
|
||||
expect(map.getMappedUnique('k1')).toBeUndefined();
|
||||
});
|
||||
|
||||
tap.test('addArray adds multiple objects', async () => {
|
||||
const map = new lik.ObjectMap<{ id: number }>();
|
||||
const items = [{ id: 1 }, { id: 2 }, { id: 3 }];
|
||||
map.addArray(items);
|
||||
expect(map.length).toEqual(3);
|
||||
for (const item of items) {
|
||||
expect(map.checkForObject(item)).toBeTrue();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('findSync works', async () => {
|
||||
const map = new lik.ObjectMap<{ name: string }>();
|
||||
const obj = { name: 'target' };
|
||||
map.add({ name: 'other' });
|
||||
map.add(obj);
|
||||
const found = map.findSync((item) => item.name === 'target');
|
||||
expect(found).toEqual(obj);
|
||||
});
|
||||
|
||||
tap.test('findOneAndRemove works', async () => {
|
||||
const map = new lik.ObjectMap<{ val: number }>();
|
||||
map.add({ val: 1 });
|
||||
map.add({ val: 2 });
|
||||
const removed = await map.findOneAndRemove(async (item) => item.val === 1);
|
||||
expect(removed.val).toEqual(1);
|
||||
expect(map.length).toEqual(1);
|
||||
});
|
||||
|
||||
tap.test('isEmpty returns correct state', async () => {
|
||||
const map = new lik.ObjectMap<string>();
|
||||
expect(map.isEmpty()).toBeTrue();
|
||||
map.add('hello');
|
||||
expect(map.isEmpty()).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('wipe clears all entries', async () => {
|
||||
const map = new lik.ObjectMap<number>();
|
||||
map.add(1);
|
||||
map.add(2);
|
||||
map.add(3);
|
||||
map.wipe();
|
||||
expect(map.isEmpty()).toBeTrue();
|
||||
expect(map.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('concat creates merged map', async () => {
|
||||
const map1 = new lik.ObjectMap<string>();
|
||||
map1.add('a');
|
||||
const map2 = new lik.ObjectMap<string>();
|
||||
map2.add('b');
|
||||
const merged = map1.concat(map2);
|
||||
expect(merged.length).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('map/filter/reduce work', async () => {
|
||||
const map = new lik.ObjectMap<{ val: number }>();
|
||||
map.add({ val: 1 });
|
||||
map.add({ val: 2 });
|
||||
map.add({ val: 3 });
|
||||
|
||||
const mapped = map.map((item) => item.val * 2);
|
||||
expect(mapped.length).toEqual(3);
|
||||
expect(mapped).toContain(2);
|
||||
expect(mapped).toContain(4);
|
||||
expect(mapped).toContain(6);
|
||||
|
||||
const filtered = map.filter((item) => item.val > 1);
|
||||
expect(filtered.length).toEqual(2);
|
||||
|
||||
const sum = map.reduce((acc, item) => acc + item.val, 0);
|
||||
expect(sum).toEqual(6);
|
||||
});
|
||||
|
||||
tap.test('Symbol.iterator enables for...of', async () => {
|
||||
const map = new lik.ObjectMap<number>();
|
||||
map.add(10);
|
||||
map.add(20);
|
||||
const collected: number[] = [];
|
||||
for (const item of map) {
|
||||
collected.push(item);
|
||||
}
|
||||
expect(collected.length).toEqual(2);
|
||||
expect(collected).toContain(10);
|
||||
expect(collected).toContain(20);
|
||||
});
|
||||
|
||||
tap.test('eventSubject emits add and remove events', async () => {
|
||||
const map = new lik.ObjectMap<string>();
|
||||
const events: Array<{ operation: string; payload: string }> = [];
|
||||
map.eventSubject.subscribe((event) => {
|
||||
events.push(event);
|
||||
});
|
||||
map.add('x');
|
||||
map.remove('x');
|
||||
expect(events.length).toEqual(2);
|
||||
expect(events[0].operation).toEqual('add');
|
||||
expect(events[1].operation).toEqual('remove');
|
||||
});
|
||||
|
||||
tap.test('destroy wipes and completes eventSubject', async () => {
|
||||
const map = new lik.ObjectMap<number>();
|
||||
map.add(1);
|
||||
let completed = false;
|
||||
map.eventSubject.subscribe({ complete: () => { completed = true; } });
|
||||
map.destroy();
|
||||
expect(map.isEmpty()).toBeTrue();
|
||||
expect(completed).toBeTrue();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
86
test/test.stringmap.both.ts
Normal file
86
test/test.stringmap.both.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
// import test framework
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
|
||||
// import the module
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
// testData
|
||||
let testStringmap: lik.Stringmap;
|
||||
let testString1 = 'testString1';
|
||||
let testString2 = 'testString2';
|
||||
let testString3 = 'testString3';
|
||||
let testString4 = 'testString4';
|
||||
let testString5 = 'testString5';
|
||||
let testString6 = 'testString6';
|
||||
|
||||
// tests
|
||||
tap.test('new lik.Objectmap() -> should create an instance of Stringmap', async () => {
|
||||
testStringmap = new lik.Stringmap();
|
||||
expect(testStringmap).toBeInstanceOf(lik.Stringmap);
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'lik.Stringmap.checkString -> should return false for an string not in Stringmap',
|
||||
async () => {
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkString(testString1)).toBeFalse();
|
||||
}
|
||||
);
|
||||
|
||||
tap.test('lik.Stringmap.addString -> should add an string to Stringmap', async () => {
|
||||
testStringmap.addString(testString1);
|
||||
testStringmap.addString(testString2);
|
||||
testStringmap.addString(testString3);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkString(testString1)).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkString(testString2)).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkString(testString3)).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkMinimatch('*String1')).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkMinimatch('*String2')).toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkMinimatch('*String4')).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('lik.Stringmap.addStringArray -> should add an array of strings', async () => {
|
||||
testStringmap.addStringArray([testString4, testString5, testString6]);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkMinimatch('*String4')).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test('lik.Stringmap.removeString -> should remove a string from Stringmap', async () => {
|
||||
testStringmap.removeString(testString2);
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(testStringmap.checkString(testString2)).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('lik.Stringmap.getStringArray() -> should return a copy of stringArray', async () => {
|
||||
let clonedArray = testStringmap.getStringArray();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(clonedArray[0] === 'testString1').toBeTrue();
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
expect(clonedArray[0] === testString1).toBeTrue();
|
||||
});
|
||||
|
||||
tap.test(
|
||||
'lik.Stringmap.checkIsEmpty() -> should register a function to trigger when empty',
|
||||
async () => {
|
||||
testStringmap.registerUntilTrue(
|
||||
() => {
|
||||
return testStringmap.checkIsEmpty();
|
||||
},
|
||||
() => {
|
||||
console.log('Stringmap now is empty');
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
tap.test('lik.Stringmap.empty() -> should remove wipe and then notify', async () => {
|
||||
testStringmap.wipe();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
81
test/test.timedaggregator.both.ts
Normal file
81
test/test.timedaggregator.both.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
// import test framework
|
||||
import { expect, tap } from '@push.rocks/tapbundle';
|
||||
|
||||
// import the module
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
tap.test('should create a timed aggregator and aggregate items', async (tools) => {
|
||||
const batches: string[][] = [];
|
||||
const aggregator = new lik.TimedAggregtor<string>({
|
||||
aggregationIntervalInMillis: 200,
|
||||
functionForAggregation: (aggregation) => {
|
||||
batches.push(aggregation);
|
||||
},
|
||||
});
|
||||
aggregator.add('first');
|
||||
aggregator.add('second');
|
||||
await tools.delayFor(300);
|
||||
expect(batches.length).toEqual(1);
|
||||
expect(batches[0]).toContain('first');
|
||||
expect(batches[0]).toContain('second');
|
||||
|
||||
aggregator.add('third');
|
||||
await tools.delayFor(300);
|
||||
expect(batches.length).toEqual(2);
|
||||
expect(batches[1]).toContain('third');
|
||||
aggregator.stop();
|
||||
});
|
||||
|
||||
tap.test('stop() prevents further aggregation', async (tools) => {
|
||||
const batches: number[][] = [];
|
||||
const aggregator = new lik.TimedAggregtor<number>({
|
||||
aggregationIntervalInMillis: 100,
|
||||
functionForAggregation: (items) => {
|
||||
batches.push(items);
|
||||
},
|
||||
});
|
||||
aggregator.add(1);
|
||||
aggregator.stop();
|
||||
aggregator.add(2);
|
||||
await tools.delayFor(200);
|
||||
expect(batches.length).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('stop(true) flushes remaining items', async () => {
|
||||
const batches: number[][] = [];
|
||||
const aggregator = new lik.TimedAggregtor<number>({
|
||||
aggregationIntervalInMillis: 5000,
|
||||
functionForAggregation: (items) => {
|
||||
batches.push(items);
|
||||
},
|
||||
});
|
||||
aggregator.add(10);
|
||||
aggregator.add(20);
|
||||
aggregator.stop(true);
|
||||
expect(batches.length).toEqual(1);
|
||||
expect(batches[0]).toEqual([10, 20]);
|
||||
});
|
||||
|
||||
tap.test('restart allows adding again after stop', async (tools) => {
|
||||
const batches: string[][] = [];
|
||||
const aggregator = new lik.TimedAggregtor<string>({
|
||||
aggregationIntervalInMillis: 100,
|
||||
functionForAggregation: (items) => {
|
||||
batches.push(items);
|
||||
},
|
||||
});
|
||||
aggregator.add('a');
|
||||
aggregator.stop();
|
||||
aggregator.restart();
|
||||
aggregator.add('b');
|
||||
await tools.delayFor(200);
|
||||
expect(batches.length).toEqual(1);
|
||||
expect(batches[0]).toContain('b');
|
||||
aggregator.stop();
|
||||
});
|
||||
|
||||
tap.test('TimedAggregator alias exists', async () => {
|
||||
expect(lik.TimedAggregator).toEqual(lik.TimedAggregtor);
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
151
test/test.tree.both.ts
Normal file
151
test/test.tree.both.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
import { tap, expect } from '@push.rocks/tapbundle';
|
||||
import * as lik from '../ts/index.js';
|
||||
|
||||
class TestClass {
|
||||
constructor(public hey: string) {
|
||||
// nothing here
|
||||
}
|
||||
}
|
||||
|
||||
let testTree = new lik.Tree<TestClass>();
|
||||
|
||||
let testInstance = new TestClass('first');
|
||||
let testInstance2 = new TestClass('second');
|
||||
let testInstance3 = new TestClass('third');
|
||||
let testInstance4 = new TestClass('fourth');
|
||||
let testInstance5 = new TestClass('fifth');
|
||||
let testInstance6 = new TestClass('sixth');
|
||||
|
||||
tap.test('create a valid tree instance', async () => {
|
||||
testTree = new lik.Tree();
|
||||
expect(testTree).toBeInstanceOf(lik.Tree);
|
||||
});
|
||||
|
||||
tap.test('should insert an object', async () => {
|
||||
testTree.initialize(testInstance);
|
||||
let resultArray = testTree.treeToArray(testInstance, {});
|
||||
expect(resultArray).toContain(testInstance);
|
||||
});
|
||||
|
||||
tap.test('should add other objects in a hierachy', async () => {
|
||||
testTree.appendChild(testInstance, testInstance2);
|
||||
testTree.appendChild(testInstance, testInstance3);
|
||||
testTree.appendChild(testInstance, testInstance4);
|
||||
});
|
||||
|
||||
tap.test('hasChildren returns correct value', async () => {
|
||||
expect(testTree.hasChildren(testInstance)).toBeTrue();
|
||||
expect(testTree.hasChildren(testInstance2)).toBeFalse();
|
||||
});
|
||||
|
||||
tap.test('firstChild and lastChild work', async () => {
|
||||
const first = testTree.firstChild(testInstance);
|
||||
expect(first.hey).toEqual('second');
|
||||
const last = testTree.lastChild(testInstance);
|
||||
expect(last.hey).toEqual('fourth');
|
||||
});
|
||||
|
||||
tap.test('parent returns correct parent', async () => {
|
||||
const parent = testTree.parent(testInstance2);
|
||||
expect(parent.hey).toEqual('first');
|
||||
});
|
||||
|
||||
tap.test('nextSibling and previousSibling work', async () => {
|
||||
const next = testTree.nextSibling(testInstance2);
|
||||
expect(next.hey).toEqual('third');
|
||||
const prev = testTree.previousSibling(testInstance3);
|
||||
expect(prev.hey).toEqual('second');
|
||||
});
|
||||
|
||||
tap.test('childrenCount returns correct count', async () => {
|
||||
expect(testTree.childrenCount(testInstance)).toEqual(3);
|
||||
expect(testTree.childrenCount(testInstance2)).toEqual(0);
|
||||
});
|
||||
|
||||
tap.test('index returns sibling index', async () => {
|
||||
expect(testTree.index(testInstance2)).toEqual(0);
|
||||
expect(testTree.index(testInstance3)).toEqual(1);
|
||||
expect(testTree.index(testInstance4)).toEqual(2);
|
||||
});
|
||||
|
||||
tap.test('childrenToArray returns children', async () => {
|
||||
const children = testTree.childrenToArray(testInstance, {});
|
||||
expect(children.length).toEqual(3);
|
||||
expect(children[0].hey).toEqual('second');
|
||||
});
|
||||
|
||||
tap.test('insertBefore works', async () => {
|
||||
testTree.initialize(testInstance5);
|
||||
testTree.insertBefore(testInstance3, testInstance5);
|
||||
const idx = testTree.index(testInstance5);
|
||||
expect(idx).toEqual(1);
|
||||
expect(testTree.nextSibling(testInstance5).hey).toEqual('third');
|
||||
});
|
||||
|
||||
tap.test('insertAfter works', async () => {
|
||||
testTree.initialize(testInstance6);
|
||||
testTree.insertAfter(testInstance3, testInstance6);
|
||||
expect(testTree.previousSibling(testInstance6).hey).toEqual('third');
|
||||
});
|
||||
|
||||
tap.test('remove detaches node', async () => {
|
||||
const countBefore = testTree.childrenCount(testInstance);
|
||||
testTree.remove(testInstance6);
|
||||
expect(testTree.childrenCount(testInstance)).toEqual(countBefore - 1);
|
||||
});
|
||||
|
||||
tap.test('treeIterator with options works', async () => {
|
||||
const items: TestClass[] = [];
|
||||
for (const item of testTree.treeIterator(testInstance, {})) {
|
||||
items.push(item);
|
||||
}
|
||||
expect(items.length).toBeGreaterThan(1);
|
||||
expect(items[0].hey).toEqual('first');
|
||||
});
|
||||
|
||||
tap.test('nextSiblingsIterator works (bug was fixed)', async () => {
|
||||
const siblings: TestClass[] = [];
|
||||
for (const item of testTree.nextSiblingsIterator(testInstance2)) {
|
||||
siblings.push(item);
|
||||
}
|
||||
expect(siblings.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('ancestorsIterator works (bug was fixed)', async () => {
|
||||
const ancestors: TestClass[] = [];
|
||||
for (const item of testTree.ancestorsIterator(testInstance2)) {
|
||||
ancestors.push(item);
|
||||
}
|
||||
expect(ancestors.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test("should create a JSON object that reflects a tree's hierachy", async () => {
|
||||
const jsonTree = testTree.toJsonWithHierachy(testInstance);
|
||||
expect(jsonTree).toHaveProperty('data');
|
||||
expect(jsonTree).toHaveProperty('children');
|
||||
expect(jsonTree.data.hey).toEqual('first');
|
||||
expect(jsonTree.children.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
tap.test('fromJsonWithHierachy rebuilds a tree', async () => {
|
||||
const newTree = new lik.Tree<{ name: string }>();
|
||||
const jsonRoot = {
|
||||
data: { name: 'root' },
|
||||
children: [
|
||||
{ data: { name: 'child1' }, children: [] },
|
||||
{
|
||||
data: { name: 'child2' },
|
||||
children: [{ data: { name: 'grandchild' }, children: [] }],
|
||||
},
|
||||
],
|
||||
};
|
||||
const root = newTree.fromJsonWithHierachy(jsonRoot);
|
||||
expect(root.name).toEqual('root');
|
||||
expect(newTree.hasChildren(root)).toBeTrue();
|
||||
expect(newTree.childrenCount(root)).toEqual(2);
|
||||
const children = newTree.childrenToArray(root, {});
|
||||
expect(children[1].name).toEqual('child2');
|
||||
expect(newTree.hasChildren(children[1])).toBeTrue();
|
||||
});
|
||||
|
||||
export default tap.start();
|
||||
89
test/test.ts
89
test/test.ts
@@ -1,89 +0,0 @@
|
||||
import 'typings-test'
|
||||
import lik = require('../dist/index')
|
||||
import * as should from 'should'
|
||||
|
||||
describe('lik',function(){
|
||||
describe('Stringmap',function(){
|
||||
let testStringmap: lik.Stringmap
|
||||
let testString1 = 'testString1'
|
||||
let testString2 = 'testString2'
|
||||
let testString3 = 'testString3'
|
||||
it('should create an instance of Stringmap',function(){
|
||||
testStringmap = new lik.Stringmap()
|
||||
should(testStringmap).be.instanceof(lik.Stringmap)
|
||||
})
|
||||
it('should return false for an string not in Stringmap',function(){
|
||||
should(testStringmap.checkString(testString1)).be.false()
|
||||
})
|
||||
it('should add an string to Stringmap',function(){
|
||||
testStringmap.addString(testString1)
|
||||
testStringmap.addString(testString2)
|
||||
testStringmap.addString(testString3)
|
||||
should(testStringmap.checkString(testString1)).be.true()
|
||||
should(testStringmap.checkString(testString2)).be.true()
|
||||
should(testStringmap.checkString(testString3)).be.true()
|
||||
should(testStringmap.checkMinimatch('*String1')).be.true()
|
||||
should(testStringmap.checkMinimatch('*String2')).be.true()
|
||||
should(testStringmap.checkMinimatch('*String4')).be.false()
|
||||
})
|
||||
it('should remove a string from Stringmap',function(){
|
||||
testStringmap.removeString(testString2)
|
||||
should(testStringmap.checkString(testString2)).be.false()
|
||||
})
|
||||
it('should return a copy of stringArray',function(){
|
||||
let clonedArray = testStringmap.getStringArray()
|
||||
should(clonedArray[0] === 'testString1').be.true()
|
||||
should(clonedArray[0] === testString1).be.true()
|
||||
})
|
||||
it('should register a function to trigger when empty',function(){
|
||||
testStringmap.registerUntilTrue(
|
||||
() => { return testStringmap.checkIsEmpty() },
|
||||
() => { console.log('Stringmap now is empty') }
|
||||
)
|
||||
})
|
||||
it('should remove wipe and then notify',function(){
|
||||
testStringmap.wipe()
|
||||
})
|
||||
})
|
||||
describe('Objectmap',function(){
|
||||
interface ITestObject {
|
||||
propOne: string
|
||||
propTwo: string
|
||||
}
|
||||
let testObjectmap: lik.Objectmap<ITestObject>
|
||||
let testObject1: ITestObject = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2'
|
||||
}
|
||||
let testObject2: ITestObject = {
|
||||
propOne: 'hello',
|
||||
propTwo: 'hello2'
|
||||
}
|
||||
it('should correctly instantiate an Objectmap',function(){
|
||||
testObjectmap = new lik.Objectmap<ITestObject>()
|
||||
should(testObjectmap).be.instanceof(lik.Objectmap)
|
||||
})
|
||||
it('should correctly add an object to Objectmap',function(){
|
||||
testObjectmap.add(testObject1)
|
||||
should(testObjectmap.checkForObject(testObject1)).be.true
|
||||
should(testObjectmap.checkForObject(testObject2)).be.false
|
||||
})
|
||||
it('should correctly remove an object to Objectmap',function(){
|
||||
testObjectmap.add(testObject2)
|
||||
testObjectmap.remove(testObject1)
|
||||
should(testObjectmap.checkForObject(testObject1)).be.false
|
||||
should(testObjectmap.checkForObject(testObject2)).be.true
|
||||
})
|
||||
it('should correctly run a function forEach map object',function(){
|
||||
testObjectmap.forEach(itemArg => {
|
||||
should(itemArg).have.ownProperty('propOne')
|
||||
})
|
||||
})
|
||||
it('should correctly find an object',function(){
|
||||
let myObject = {propOne: 'helloThere', propTwo: 'helloAnyway'}
|
||||
testObjectmap.add(myObject)
|
||||
let referenceObject = testObjectmap.find((itemArg) => { return (itemArg.propOne === 'helloThere') })
|
||||
should(myObject === referenceObject).be.true()
|
||||
})
|
||||
})
|
||||
})
|
||||
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.4.0',
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
105
ts/classes.backpressuredarray.ts
Normal file
105
ts/classes.backpressuredarray.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
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;
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.data.length;
|
||||
}
|
||||
|
||||
push(item: T): boolean {
|
||||
this.data.push(item);
|
||||
this.itemsAvailable.next('itemsAvailable');
|
||||
|
||||
const spaceAvailable = this.checkSpaceAvailable();
|
||||
if (spaceAvailable) {
|
||||
this.hasSpace.next('hasSpace');
|
||||
}
|
||||
return spaceAvailable;
|
||||
}
|
||||
|
||||
pushMany(items: T[]): boolean {
|
||||
for (const item of items) {
|
||||
this.push(item);
|
||||
}
|
||||
return this.checkSpaceAvailable();
|
||||
}
|
||||
|
||||
shift(): T | undefined {
|
||||
const item = this.data.shift();
|
||||
if (this.checkSpaceAvailable()) {
|
||||
this.hasSpace.next('hasSpace');
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
peek(): T | undefined {
|
||||
return this.data[0];
|
||||
}
|
||||
|
||||
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();
|
||||
},
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<T> {
|
||||
return this.data[Symbol.iterator]();
|
||||
}
|
||||
|
||||
/**
|
||||
* destroys the BackpressuredArray, completing all subjects
|
||||
*/
|
||||
public destroy() {
|
||||
this.isDestroyed = true;
|
||||
this.hasSpace.complete();
|
||||
this.itemsAvailable.complete();
|
||||
}
|
||||
}
|
||||
111
ts/classes.fastmap.ts
Normal file
111
ts/classes.fastmap.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
/* ============
|
||||
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 = new Map<string, T>();
|
||||
|
||||
public isUniqueKey(keyArg: string): boolean {
|
||||
return !this.mapObject.has(keyArg);
|
||||
}
|
||||
|
||||
public has(keyArg: string): boolean {
|
||||
return this.mapObject.has(keyArg);
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.mapObject.size;
|
||||
}
|
||||
|
||||
public addToMap(
|
||||
keyArg: string,
|
||||
objectArg: T,
|
||||
optionsArg?: {
|
||||
force: boolean;
|
||||
}
|
||||
): boolean {
|
||||
if (this.isUniqueKey(keyArg) || (optionsArg && optionsArg.force)) {
|
||||
this.mapObject.set(keyArg, objectArg);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public getByKey(keyArg: string): T | undefined {
|
||||
return this.mapObject.get(keyArg);
|
||||
}
|
||||
|
||||
public removeFromMap(keyArg: string): T {
|
||||
const removedItem = this.mapObject.get(keyArg);
|
||||
this.mapObject.delete(keyArg);
|
||||
return removedItem;
|
||||
}
|
||||
|
||||
public getKeys(): string[] {
|
||||
return Array.from(this.mapObject.keys());
|
||||
}
|
||||
|
||||
public values(): T[] {
|
||||
return Array.from(this.mapObject.values());
|
||||
}
|
||||
|
||||
public entries(): [string, T][] {
|
||||
return Array.from(this.mapObject.entries());
|
||||
}
|
||||
|
||||
public clean() {
|
||||
this.mapObject.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<[string, T]> {
|
||||
return this.mapObject.entries();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
139
ts/classes.interestmap.ts
Normal file
139
ts/classes.interestmap.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/* ===========
|
||||
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
|
||||
|
||||
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 * 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 interface IInterestMapOptions {
|
||||
markLostAfterDefault?: number;
|
||||
}
|
||||
|
||||
export class InterestMap<DTInterestId, DTInterestFullfillment> {
|
||||
public options: IInterestMapOptions;
|
||||
|
||||
/**
|
||||
* stores interests that are currently fullfilled by the cache
|
||||
*/
|
||||
private interestObjectMap = new ObjectMap<Interest<DTInterestId, DTInterestFullfillment>>();
|
||||
|
||||
/**
|
||||
* O(1) lookup of interests by their comparison string
|
||||
*/
|
||||
private interestsByComparisonString = new Map<string, Interest<DTInterestId, DTInterestFullfillment>>();
|
||||
|
||||
/**
|
||||
* a function to compare interests
|
||||
*/
|
||||
private comparisonFunc: IInterestComparisonFunc<DTInterestId>;
|
||||
|
||||
constructor(
|
||||
comparisonFuncArg: IInterestComparisonFunc<DTInterestId>,
|
||||
optionsArg: IInterestMapOptions = {}
|
||||
) {
|
||||
this.comparisonFunc = comparisonFuncArg;
|
||||
this.options = optionsArg;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds an interest to the InterestMap
|
||||
* @param interestId
|
||||
*/
|
||||
public async addInterest(
|
||||
interestId: DTInterestId,
|
||||
defaultFullfillmentArg?: DTInterestFullfillment
|
||||
): Promise<Interest<DTInterestId, DTInterestFullfillment>> {
|
||||
const comparisonString = this.comparisonFunc(interestId);
|
||||
let returnInterest: Interest<DTInterestId, DTInterestFullfillment>;
|
||||
|
||||
const existingInterest = this.interestsByComparisonString.get(comparisonString);
|
||||
if (existingInterest) {
|
||||
returnInterest = existingInterest;
|
||||
returnInterest.renew();
|
||||
} else {
|
||||
returnInterest = new Interest<DTInterestId, DTInterestFullfillment>(
|
||||
this,
|
||||
interestId,
|
||||
this.comparisonFunc,
|
||||
{
|
||||
markLostAfterDefault: this.options.markLostAfterDefault,
|
||||
defaultFullfillment: defaultFullfillmentArg,
|
||||
}
|
||||
);
|
||||
this.interestObjectMap.add(returnInterest);
|
||||
this.interestsByComparisonString.set(comparisonString, returnInterest);
|
||||
}
|
||||
this.interestObservable.push(returnInterest);
|
||||
return returnInterest;
|
||||
}
|
||||
|
||||
public interestObservable = new plugins.smartrx.ObservableIntake<Interest<DTInterestId, any>>();
|
||||
|
||||
/**
|
||||
* removes an interest from the interest map
|
||||
*/
|
||||
public removeInterest(interestArg: Interest<DTInterestId, DTInterestFullfillment>) {
|
||||
this.interestObjectMap.findOneAndRemoveSync((interestArg2) => {
|
||||
return interestArg.comparisonString === interestArg2.comparisonString;
|
||||
});
|
||||
this.interestsByComparisonString.delete(interestArg.comparisonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* check interest
|
||||
*/
|
||||
public checkInterest(objectArg: DTInterestId): boolean {
|
||||
const comparisonString = this.comparisonFunc(objectArg);
|
||||
return this.checkInterestByString(comparisonString);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks an interest
|
||||
* @param comparisonStringArg
|
||||
*/
|
||||
public checkInterestByString(comparisonStringArg: string): boolean {
|
||||
return this.interestsByComparisonString.has(comparisonStringArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* inform lost interest
|
||||
* @param interestId
|
||||
*/
|
||||
public informLostInterest(interestId: DTInterestId) {
|
||||
const wantedInterest = this.findInterest(interestId);
|
||||
if (wantedInterest) {
|
||||
wantedInterest.markLost();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* finds an interest
|
||||
* @param interestId
|
||||
*/
|
||||
public findInterest(interestId: DTInterestId): Interest<DTInterestId, DTInterestFullfillment> {
|
||||
const comparableString = this.comparisonFunc(interestId);
|
||||
return this.interestsByComparisonString.get(comparableString) ?? 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.interestsByComparisonString.clear();
|
||||
this.interestObservable.signalComplete();
|
||||
}
|
||||
}
|
||||
70
ts/classes.limitedarray.ts
Normal file
70
ts/classes.limitedarray.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import * as plugins from './classes.plugins.js';
|
||||
|
||||
export class LimitedArray<T> {
|
||||
array: T[] = [];
|
||||
arrayLimit: number;
|
||||
constructor(limitArg: number) {
|
||||
this.arrayLimit = limitArg;
|
||||
}
|
||||
|
||||
public get length(): number {
|
||||
return this.array.length;
|
||||
}
|
||||
|
||||
addOne(objectArg: T) {
|
||||
this.array.unshift(objectArg);
|
||||
if (this.array.length > this.arrayLimit) {
|
||||
this.array.length = this.arrayLimit;
|
||||
}
|
||||
}
|
||||
|
||||
addMany(objectArrayArg: T[]) {
|
||||
for (let objectArg of objectArrayArg) {
|
||||
this.addOne(objectArg);
|
||||
}
|
||||
}
|
||||
|
||||
setLimit(limitArg: number) {
|
||||
this.arrayLimit = limitArg;
|
||||
if (this.array.length > this.arrayLimit) {
|
||||
this.array.length = this.arrayLimit;
|
||||
}
|
||||
}
|
||||
|
||||
getAverage(): number {
|
||||
if (this.array.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
if (typeof this.array[0] === 'number') {
|
||||
let sum = 0;
|
||||
for (let localNumber of this.array) {
|
||||
let localNumberAny: any = localNumber;
|
||||
sum = sum + localNumberAny;
|
||||
}
|
||||
return sum / this.array.length;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
remove(item: T): boolean {
|
||||
const idx = this.array.indexOf(item);
|
||||
if (idx !== -1) {
|
||||
this.array.splice(idx, 1);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
this.array.length = 0;
|
||||
}
|
||||
|
||||
getArray(): T[] {
|
||||
return [...this.array];
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<T> {
|
||||
return this.array[Symbol.iterator]();
|
||||
}
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
286
ts/classes.objectmap.ts
Normal file
286
ts/classes.objectmap.ts
Normal file
@@ -0,0 +1,286 @@
|
||||
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>();
|
||||
private reverseMap = new Map<T, string>();
|
||||
|
||||
// events
|
||||
public eventSubject = new plugins.smartrx.rxjs.Subject<IObjectMapEventData<T>>();
|
||||
|
||||
/**
|
||||
* returns a new instance
|
||||
*/
|
||||
constructor() {
|
||||
// nothing here
|
||||
}
|
||||
|
||||
/**
|
||||
* the number of objects in the map
|
||||
*/
|
||||
public get length(): number {
|
||||
return this.fastMap.size;
|
||||
}
|
||||
|
||||
/**
|
||||
* adds an object mapped to a string
|
||||
* the string must be unique
|
||||
*/
|
||||
addMappedUnique(uniqueKeyArg: string, objectArg: T) {
|
||||
this.fastMap.addToMap(uniqueKeyArg, objectArg);
|
||||
this.reverseMap.set(objectArg, uniqueKeyArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.reverseMap.delete(object);
|
||||
this.eventSubject.next({
|
||||
operation: 'remove',
|
||||
payload: object,
|
||||
});
|
||||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* add object to Objectmap
|
||||
* returns the key for the object (existing or new)
|
||||
*/
|
||||
public add(objectArg: T): string {
|
||||
const existingKey = this.reverseMap.get(objectArg);
|
||||
if (existingKey !== undefined) {
|
||||
return existingKey;
|
||||
}
|
||||
|
||||
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.reverseMap.has(objectArg);
|
||||
}
|
||||
|
||||
/**
|
||||
* get key for object
|
||||
*/
|
||||
public getKeyForObject(objectArg: T): string | null {
|
||||
return this.reverseMap.get(objectArg) ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.reverseMap.delete(removedItem);
|
||||
this.eventSubject.next({
|
||||
operation: 'remove',
|
||||
payload: removedItem,
|
||||
});
|
||||
return removedItem;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a cloned array of all the objects currently in the Objectmap
|
||||
*/
|
||||
public getArray(): T[] {
|
||||
return this.fastMap.values();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if Objectmap ist empty
|
||||
*/
|
||||
public isEmpty(): boolean {
|
||||
return this.fastMap.size === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* remove object from Objectmap
|
||||
*/
|
||||
public remove(objectArg: T): T {
|
||||
const keyArg = this.reverseMap.get(objectArg);
|
||||
if (keyArg !== undefined) {
|
||||
const removedObject = this.fastMap.removeFromMap(keyArg);
|
||||
this.reverseMap.delete(removedObject);
|
||||
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.reverseMap.delete(removedObject);
|
||||
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);
|
||||
// rebuild reverse map for the concatenated map
|
||||
for (const key of concattedObjectMap.fastMap.getKeys()) {
|
||||
concattedObjectMap.reverseMap.set(concattedObjectMap.fastMap.getByKey(key), key);
|
||||
}
|
||||
return concattedObjectMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* tries to merge another Objectmap
|
||||
* Note: uniqueKeyCollisions will cause overwrite
|
||||
* @param objectMapArg
|
||||
*/
|
||||
public addAllFromOther(objectMapArg: ObjectMap<T>) {
|
||||
this.fastMap.addAllFromOther(objectMapArg.fastMap);
|
||||
// rebuild reverse map
|
||||
for (const key of objectMapArg.fastMap.getKeys()) {
|
||||
this.reverseMap.set(objectMapArg.fastMap.getByKey(key), key);
|
||||
}
|
||||
}
|
||||
|
||||
public map<U>(fn: (item: T) => U): U[] {
|
||||
return this.getArray().map(fn);
|
||||
}
|
||||
|
||||
public filter(fn: (item: T) => boolean): T[] {
|
||||
return this.getArray().filter(fn);
|
||||
}
|
||||
|
||||
public reduce<U>(fn: (acc: U, item: T) => U, initial: U): U {
|
||||
return this.getArray().reduce(fn, initial);
|
||||
}
|
||||
|
||||
public [Symbol.iterator](): Iterator<T> {
|
||||
return this.getArray()[Symbol.iterator]();
|
||||
}
|
||||
|
||||
/**
|
||||
* destroys the ObjectMap, completing the eventSubject and clearing all entries
|
||||
*/
|
||||
public destroy() {
|
||||
this.wipe();
|
||||
this.reverseMap.clear();
|
||||
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 };
|
||||
123
ts/classes.stringmap.ts
Normal file
123
ts/classes.stringmap.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import * as plugins from './classes.plugins.js';
|
||||
|
||||
/**
|
||||
* allows you to easily keep track of a bunch of strings
|
||||
*/
|
||||
|
||||
export type TTriggerFunction = (stringArray?: string[]) => boolean;
|
||||
|
||||
export class Stringmap {
|
||||
private _stringArray: string[] = [];
|
||||
private _triggerUntilTrueFunctionArray: TTriggerFunction[] = [];
|
||||
constructor() {}
|
||||
/**
|
||||
* add a string to the Stringmap
|
||||
*/
|
||||
addString(stringArg: string) {
|
||||
this._stringArray.push(stringArg);
|
||||
this.notifyTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* like addString, but accepts an array of strings
|
||||
*/
|
||||
addStringArray(stringArrayArg: string[]) {
|
||||
for (const stringItem of stringArrayArg) {
|
||||
this.addString(stringItem);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* removes a string from Stringmap
|
||||
*/
|
||||
removeString(stringArg: string) {
|
||||
this._stringArray = this._stringArray.filter(s => s !== stringArg);
|
||||
this.notifyTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* wipes the Stringmap
|
||||
*/
|
||||
wipe() {
|
||||
this._stringArray = [];
|
||||
this.notifyTrigger();
|
||||
}
|
||||
|
||||
/**
|
||||
* check if string is in Stringmap
|
||||
*/
|
||||
public checkString(stringArg: string): boolean {
|
||||
return this._stringArray.indexOf(stringArg) !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks stringPresence with minimatch
|
||||
*/
|
||||
public checkMinimatch(miniMatchStringArg: string): boolean {
|
||||
const smartMatchInstance = new plugins.smartmatch.SmartMatch(miniMatchStringArg);
|
||||
let foundMatch: boolean = false;
|
||||
for (const stringItem of this._stringArray) {
|
||||
if (smartMatchInstance.match(stringItem)) {
|
||||
foundMatch = true;
|
||||
}
|
||||
}
|
||||
return foundMatch;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the Stringmap is empty
|
||||
*/
|
||||
public checkIsEmpty() {
|
||||
return this._stringArray.length === 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a cloned copy of the current string Array
|
||||
*/
|
||||
public getStringArray() {
|
||||
const returnArray: string[] = [];
|
||||
for (const stringItem of this._stringArray) {
|
||||
returnArray.push(stringItem);
|
||||
}
|
||||
return returnArray;
|
||||
}
|
||||
|
||||
// trigger registering
|
||||
|
||||
/**
|
||||
* register a new trigger
|
||||
*/
|
||||
public registerUntilTrue(functionArg: TTriggerFunction, callbackArg?: () => any) {
|
||||
const trueDeferred = plugins.smartpromise.defer();
|
||||
this._triggerUntilTrueFunctionArray.push(() => {
|
||||
const result = functionArg(this.getStringArray());
|
||||
if (result === true) {
|
||||
if (callbackArg) {
|
||||
callbackArg();
|
||||
}
|
||||
trueDeferred.resolve();
|
||||
}
|
||||
return result;
|
||||
});
|
||||
this.notifyTrigger();
|
||||
return trueDeferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
* notifies triggers
|
||||
*/
|
||||
private notifyTrigger() {
|
||||
const filteredArray = this._triggerUntilTrueFunctionArray.filter((functionArg) => {
|
||||
return !functionArg();
|
||||
});
|
||||
this._triggerUntilTrueFunctionArray = filteredArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* destroys the Stringmap, clearing all strings and pending triggers
|
||||
*/
|
||||
public destroy() {
|
||||
this._stringArray = [];
|
||||
this._triggerUntilTrueFunctionArray = [];
|
||||
}
|
||||
}
|
||||
78
ts/classes.timedaggregator.ts
Normal file
78
ts/classes.timedaggregator.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import * as plugins from './classes.plugins.js';
|
||||
|
||||
export interface ITimedAggregatorOptions<T> {
|
||||
aggregationIntervalInMillis: number;
|
||||
functionForAggregation: (input: T[]) => void;
|
||||
}
|
||||
|
||||
export class TimedAggregtor<T> {
|
||||
public options: ITimedAggregatorOptions<T>;
|
||||
private storageArray: T[] = [];
|
||||
private isStopped = false;
|
||||
|
||||
constructor(optionsArg: ITimedAggregatorOptions<T>) {
|
||||
this.options = optionsArg;
|
||||
}
|
||||
|
||||
private aggregationTimer: plugins.smarttime.Timer;
|
||||
private checkAggregationStatus() {
|
||||
if (this.isStopped) {
|
||||
return;
|
||||
}
|
||||
const addAggregationTimer = () => {
|
||||
this.aggregationTimer = new plugins.smarttime.Timer(this.options.aggregationIntervalInMillis);
|
||||
this.aggregationTimer.completed.then(() => {
|
||||
if (this.isStopped) {
|
||||
this.aggregationTimer = null;
|
||||
return;
|
||||
}
|
||||
const aggregateForProcessing = this.storageArray;
|
||||
if (aggregateForProcessing.length === 0) {
|
||||
this.aggregationTimer = null;
|
||||
return;
|
||||
}
|
||||
this.storageArray = [];
|
||||
addAggregationTimer();
|
||||
this.options.functionForAggregation(aggregateForProcessing);
|
||||
});
|
||||
this.aggregationTimer.start();
|
||||
};
|
||||
if (!this.aggregationTimer) {
|
||||
addAggregationTimer();
|
||||
}
|
||||
}
|
||||
|
||||
public add(aggregationArg: T) {
|
||||
if (this.isStopped) {
|
||||
return;
|
||||
}
|
||||
this.storageArray.push(aggregationArg);
|
||||
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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
public restart(): void {
|
||||
this.isStopped = false;
|
||||
}
|
||||
}
|
||||
|
||||
// correctly-spelled alias
|
||||
export { TimedAggregtor as TimedAggregator };
|
||||
166
ts/classes.tree.ts
Normal file
166
ts/classes.tree.ts
Normal file
@@ -0,0 +1,166 @@
|
||||
import * as plugins from './classes.plugins.js';
|
||||
|
||||
export class Tree<T> {
|
||||
symbolTree: any;
|
||||
constructor() {
|
||||
this.symbolTree = new plugins.symbolTree();
|
||||
}
|
||||
|
||||
// =======================================
|
||||
// Functions that map to the functionality of symbol-tree
|
||||
// =======================================
|
||||
|
||||
/**
|
||||
*
|
||||
* @param objectArg
|
||||
*/
|
||||
initialize(objectArg: T): T {
|
||||
return this.symbolTree.initialize(objectArg);
|
||||
}
|
||||
|
||||
hasChildren(objectArg: T): boolean {
|
||||
return this.symbolTree.hasChildren(objectArg);
|
||||
}
|
||||
|
||||
firstChild(objectArg: T): T {
|
||||
return this.symbolTree.firstChild(objectArg);
|
||||
}
|
||||
|
||||
lastChild(objectArg: T): T {
|
||||
return this.symbolTree.lastChild(objectArg);
|
||||
}
|
||||
|
||||
previousSibling(objectArg: T): T {
|
||||
return this.symbolTree.previousSibling(objectArg);
|
||||
}
|
||||
|
||||
nextSibling(objectArg: T): T {
|
||||
return this.symbolTree.nextSibling(objectArg);
|
||||
}
|
||||
|
||||
parent(objectArg: T): T {
|
||||
return this.symbolTree.parent(objectArg);
|
||||
}
|
||||
|
||||
lastInclusiveDescendant(objectArg: T): T {
|
||||
return this.symbolTree.lastInclusiveDescendant(objectArg);
|
||||
}
|
||||
|
||||
preceding(objectArg: T, optionsArg?: any): T {
|
||||
return this.symbolTree.preceding(objectArg, optionsArg);
|
||||
}
|
||||
|
||||
following(object: T, optionsArg: any) {
|
||||
return this.symbolTree.following(object, optionsArg);
|
||||
}
|
||||
|
||||
childrenToArray(parentArg: T, optionsArg: any): T[] {
|
||||
return this.symbolTree.childrenToArray(parentArg, optionsArg);
|
||||
}
|
||||
|
||||
ancestorsToArray(objectArg: T, optionsArg: any): T[] {
|
||||
return this.symbolTree.ancestorsToArray(objectArg, optionsArg);
|
||||
}
|
||||
|
||||
treeToArray(rootArg: T, optionsArg: any): T[] {
|
||||
return this.symbolTree.treeToArray(rootArg, optionsArg);
|
||||
}
|
||||
|
||||
childrenIterator(parentArg: T, optionsArg: any): T {
|
||||
return this.symbolTree.childrenIterator(parentArg, optionsArg);
|
||||
}
|
||||
|
||||
previousSiblingsIterator(objectArg: T): T {
|
||||
return this.symbolTree.previousSiblingsIterator(objectArg);
|
||||
}
|
||||
|
||||
nextSiblingsIterator(objectArg: T) {
|
||||
return this.symbolTree.nextSiblingsIterator(objectArg);
|
||||
}
|
||||
|
||||
ancestorsIterator(objectArg: T): Iterable<T> {
|
||||
return this.symbolTree.ancestorsIterator(objectArg);
|
||||
}
|
||||
|
||||
treeIterator(rootArg: T, optionsArg?: any): Iterable<T> {
|
||||
return this.symbolTree.treeIterator(rootArg, optionsArg);
|
||||
}
|
||||
|
||||
index(childArg: T): number {
|
||||
return this.symbolTree.index(childArg);
|
||||
}
|
||||
|
||||
childrenCount(parentArg: T): number {
|
||||
return this.symbolTree.childrenCount(parentArg);
|
||||
}
|
||||
|
||||
compareTreePosition(leftArg: T, rightArg: T): number {
|
||||
return this.symbolTree.compareTreePosition(leftArg, rightArg);
|
||||
}
|
||||
|
||||
remove(removeObjectArg: T): T {
|
||||
return this.symbolTree.remove(removeObjectArg);
|
||||
}
|
||||
|
||||
insertBefore(referenceObjectArg: T, newObjectArg: T): T {
|
||||
return this.symbolTree.insertBefore(referenceObjectArg, newObjectArg);
|
||||
}
|
||||
|
||||
insertAfter(referenceObject: T, newObjectArg: T) {
|
||||
return this.symbolTree.insertAfter(referenceObject, newObjectArg);
|
||||
}
|
||||
|
||||
prependChild(referenceObjectArg: T, newObjectArg: T): T {
|
||||
return this.symbolTree.prependChild(referenceObjectArg, newObjectArg);
|
||||
}
|
||||
|
||||
appendChild(referenceObjectArg: T, newObjectArg: T) {
|
||||
return this.symbolTree.appendChild(referenceObjectArg, newObjectArg);
|
||||
}
|
||||
|
||||
// ===========================================
|
||||
// Functionality that extends symbol-tree
|
||||
// ===========================================
|
||||
|
||||
/**
|
||||
* returns a branch of the tree as a recursive JSON structure
|
||||
*/
|
||||
toJsonWithHierachy(rootElement: T): ITreeNode<T> {
|
||||
const buildNode = (element: T): ITreeNode<T> => {
|
||||
const children: ITreeNode<T>[] = [];
|
||||
if (this.hasChildren(element)) {
|
||||
const childrenArray = this.childrenToArray(element, {});
|
||||
for (const child of childrenArray) {
|
||||
children.push(buildNode(child));
|
||||
}
|
||||
}
|
||||
return { data: element, children };
|
||||
};
|
||||
return buildNode(rootElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* builds a tree from a recursive JSON structure
|
||||
* @param jsonRoot the root node in ITreeNode format
|
||||
* @param reviver optional function to reconstruct T from serialized data
|
||||
*/
|
||||
fromJsonWithHierachy(jsonRoot: ITreeNode<T>, reviver?: (data: any) => T): T {
|
||||
const buildTree = (node: ITreeNode<T>, parentElement?: T): T => {
|
||||
const element = reviver ? reviver(node.data) : node.data;
|
||||
this.initialize(element);
|
||||
if (parentElement) {
|
||||
this.appendChild(parentElement, element);
|
||||
}
|
||||
for (const childNode of node.children) {
|
||||
buildTree(childNode, element);
|
||||
}
|
||||
return element;
|
||||
};
|
||||
return buildTree(jsonRoot);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ITreeNode<T> {
|
||||
data: T;
|
||||
children: ITreeNode<T>[];
|
||||
}
|
||||
18
ts/index.ts
18
ts/index.ts
@@ -1,6 +1,12 @@
|
||||
import * as plugins from './lik.plugins'
|
||||
|
||||
// import modules
|
||||
|
||||
export * from './lik.stringmap'
|
||||
export * from './lik.objectmap'
|
||||
export * from './classes.asyncexecutionstack.js';
|
||||
export * from './classes.backpressuredarray.js';
|
||||
export * from './classes.fastmap.js';
|
||||
export * from './classes.interestmap.js';
|
||||
export * from './classes.interestmap.interest.js';
|
||||
export * from './classes.limitedarray.js';
|
||||
export * from './classes.looptracker.js';
|
||||
export * from './classes.objectmap.js';
|
||||
export * from './classes.stringmap.js';
|
||||
export * from './classes.timedaggregator.js';
|
||||
export { TimedAggregator } from './classes.timedaggregator.js';
|
||||
export * from './classes.tree.js';
|
||||
|
||||
@@ -1,76 +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[] = []
|
||||
|
||||
/**
|
||||
* returns a new instance
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* add object to Objectmap
|
||||
*/
|
||||
add(objectArg: T) {
|
||||
this.objectArray.push(objectArg)
|
||||
}
|
||||
|
||||
/**
|
||||
* remove object from Objectmap
|
||||
*/
|
||||
remove(objectArg: T) {
|
||||
let replacmentArray = []
|
||||
for (let item of this.objectArray) {
|
||||
if (item !== objectArg) {
|
||||
replacmentArray.push(item)
|
||||
}
|
||||
}
|
||||
this.objectArray = replacmentArray
|
||||
}
|
||||
|
||||
/**
|
||||
* check if object is in Objectmap
|
||||
*/
|
||||
checkForObject(objectArg: T) {
|
||||
return this.objectArray.indexOf(objectArg) !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* find object
|
||||
*/
|
||||
find(findFunction: IObjectmapFindFunction<T>) {
|
||||
let resultArray = this.objectArray.filter(findFunction)
|
||||
if (resultArray.length > 0) {
|
||||
return resultArray[0]
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* run function for each item in Objectmap
|
||||
*/
|
||||
forEach(functionArg: IObjectmapForEachFunction<T>) {
|
||||
return this.objectArray.forEach(functionArg)
|
||||
}
|
||||
|
||||
/**
|
||||
* wipe Objectmap
|
||||
*/
|
||||
wipe() {
|
||||
this.objectArray = []
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
import 'typings-global'
|
||||
export import q = require('q')
|
||||
export import lodash = require('lodash')
|
||||
export import minimatch = require('minimatch')
|
||||
@@ -1,114 +0,0 @@
|
||||
import * as plugins from './lik.plugins'
|
||||
|
||||
/**
|
||||
* allows you to easily keep track of a bunch of strings
|
||||
*/
|
||||
|
||||
export interface ITriggerFunction {
|
||||
(): boolean
|
||||
}
|
||||
|
||||
export class Stringmap {
|
||||
private _stringArray: string[] = []
|
||||
private _triggerUntilTrueFunctionArray: ITriggerFunction[] = []
|
||||
constructor() {}
|
||||
/**
|
||||
* add a string to the Stringmap
|
||||
*/
|
||||
addString(stringArg: string) {
|
||||
this._stringArray.push(stringArg)
|
||||
this.notifyTrigger()
|
||||
}
|
||||
|
||||
/**
|
||||
* like addString, but accepts an array of strings
|
||||
*/
|
||||
addStringArray(stringArrayArg: string[]) {
|
||||
for (let stringItem of stringArrayArg){
|
||||
this.addString(stringItem)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* removes a string from Stringmap
|
||||
*/
|
||||
removeString(stringArg: string) {
|
||||
for (let keyArg in this._stringArray) {
|
||||
if (this._stringArray[keyArg] === stringArg) {
|
||||
this._stringArray.splice(parseInt(keyArg),1)
|
||||
}
|
||||
}
|
||||
this.notifyTrigger()
|
||||
}
|
||||
|
||||
/**
|
||||
* wipes the Stringmap
|
||||
*/
|
||||
wipe() {
|
||||
this._stringArray = []
|
||||
this.notifyTrigger()
|
||||
}
|
||||
|
||||
/**
|
||||
* check if string is in Stringmap
|
||||
*/
|
||||
checkString(stringArg: string): boolean {
|
||||
return this._stringArray.indexOf(stringArg) !== -1
|
||||
}
|
||||
|
||||
/**
|
||||
* checks stringPresence with minimatch
|
||||
*/
|
||||
checkMinimatch(miniMatchStringArg: string): boolean {
|
||||
let foundMatch: boolean = false
|
||||
for (let stringItem of this._stringArray){
|
||||
if (plugins.minimatch(stringItem,miniMatchStringArg)) {
|
||||
foundMatch = true
|
||||
}
|
||||
}
|
||||
return foundMatch
|
||||
}
|
||||
|
||||
/**
|
||||
* checks if the Stringmap is empty
|
||||
*/
|
||||
checkIsEmpty() {
|
||||
return (this._stringArray.length === 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* gets a cloned copy of the current string Array
|
||||
*/
|
||||
getStringArray() {
|
||||
return plugins.lodash.cloneDeep(this._stringArray)
|
||||
}
|
||||
|
||||
// trigger registering
|
||||
|
||||
/**
|
||||
* register a new trigger
|
||||
*/
|
||||
registerUntilTrue(functionArg: ITriggerFunction,doFunctionArg) {
|
||||
this._triggerUntilTrueFunctionArray.push(
|
||||
() => {
|
||||
let result = functionArg()
|
||||
if (result === true) {
|
||||
doFunctionArg()
|
||||
}
|
||||
return result
|
||||
}
|
||||
)
|
||||
this.notifyTrigger()
|
||||
}
|
||||
|
||||
/**
|
||||
* notifies triggers
|
||||
*/
|
||||
private notifyTrigger() {
|
||||
let filteredArray = this._triggerUntilTrueFunctionArray.filter((functionArg) => {
|
||||
return !functionArg()
|
||||
})
|
||||
this._triggerUntilTrueFunctionArray = filteredArray
|
||||
}
|
||||
|
||||
}
|
||||
12
tsconfig.json
Normal file
12
tsconfig.json
Normal file
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"esModuleInterop": true,
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": [
|
||||
"dist_*/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "tslint-config-standard"
|
||||
}
|
||||
Reference in New Issue
Block a user