Initial Commit
This commit is contained in:
175
.gitignore
vendored
Normal file
175
.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
2
.npmrc
Normal file
2
.npmrc
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
@techniker-me:registry=https://registry-node.techniker.me
|
||||||
|
//registry-node.techniker.me/:_authToken="${NODE_REGISTRY_AUTH_TOKEN}"
|
||||||
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"arrowParens": "avoid",
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"bracketSpacing": false,
|
||||||
|
"printWidth": 160,
|
||||||
|
"semi": true,
|
||||||
|
"singleAttributePerLine": false,
|
||||||
|
"singleQuote": true,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"useTabs": false
|
||||||
|
}
|
||||||
27
README
Normal file
27
README
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# @zinntechniker/tools
|
||||||
|
|
||||||
|
A library of useful tools
|
||||||
|
|
||||||
|
### Assertions
|
||||||
|
|
||||||
|
- Assert unreachable
|
||||||
|
|
||||||
|
### Disposables
|
||||||
|
|
||||||
|
- Disposable
|
||||||
|
- DisposabeList
|
||||||
|
|
||||||
|
### Events
|
||||||
|
|
||||||
|
- EventEmitter
|
||||||
|
- EventPublisher
|
||||||
|
|
||||||
|
### Maths
|
||||||
|
|
||||||
|
- Averager
|
||||||
|
- Random
|
||||||
|
|
||||||
|
### Observables
|
||||||
|
|
||||||
|
- Subject
|
||||||
|
- ReadOnlySubject
|
||||||
11
bunfig.toml
Normal file
11
bunfig.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
telemetry = false
|
||||||
|
|
||||||
|
[install]
|
||||||
|
exact = true
|
||||||
|
|
||||||
|
[install.lockfile]
|
||||||
|
save = false
|
||||||
|
|
||||||
|
[test]
|
||||||
|
coverage = true
|
||||||
|
coverageSkipTestFiles = true
|
||||||
41
eslint.config.js
Normal file
41
eslint.config.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import globals from 'globals';
|
||||||
|
import pluginJs from '@eslint/js';
|
||||||
|
import tseslint from 'typescript-eslint';
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
...tseslint.configs.recommended,
|
||||||
|
...pluginJs.configs.recommended,
|
||||||
|
ignores: [
|
||||||
|
// Existing ignores
|
||||||
|
'./test/',
|
||||||
|
'node_modules/**', // Explicitly ignore node_modules and subdirs
|
||||||
|
// Add Bun-specific cache ignores (adjust paths if needed)
|
||||||
|
'**/.bun/**', // Covers Bun's cache dirs
|
||||||
|
'/home/teamcity-agent/.bun/**', // Absolute path for CI (if known; customize for your agent)
|
||||||
|
// Other common ignores
|
||||||
|
'**/dist/**', // Build outputs
|
||||||
|
'**/build/**',
|
||||||
|
'**/temp/**',
|
||||||
|
'**/cache/**',
|
||||||
|
'**/*.min.js', // Minified files
|
||||||
|
],
|
||||||
|
files: ['./src/**/*.{ts,js,tsx,jsx}'], // Expanded to include JSX if needed; keeps it scoped to src
|
||||||
|
languageOptions: {
|
||||||
|
globals: { ...globals.browser, ...globals.node },
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 'latest', // Ensure modern JS support
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rules: {
|
||||||
|
// Optional: Suppress specific warnings seen in log (e.g., unused disables)
|
||||||
|
'no-unused-vars': 'warn', // Downgrade if needed
|
||||||
|
// Add rules to handle common log issues (customize as needed)
|
||||||
|
'@typescript-eslint/no-non-null-assertion': 'off', // Temporarily disable if causing many errors
|
||||||
|
'@typescript-eslint/no-unsafe-enum-comparison': 'off',
|
||||||
|
// Ignore undefined rules in third-party code
|
||||||
|
'eslint-plugin/no-property-in-node': 'off',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
175
examples/.gitignore
vendored
Normal file
175
examples/.gitignore
vendored
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore
|
||||||
|
|
||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Caches
|
||||||
|
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.*
|
||||||
|
|
||||||
|
# IntelliJ based IDEs
|
||||||
|
.idea
|
||||||
|
|
||||||
|
# Finder (MacOS) folder config
|
||||||
|
.DS_Store
|
||||||
15
examples/README.md
Normal file
15
examples/README.md
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# examples
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run index.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
This project was created using `bun init` in bun v1.1.20. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime.
|
||||||
BIN
examples/bun.lockb
Executable file
BIN
examples/bun.lockb
Executable file
Binary file not shown.
11
examples/bunfig.toml
Normal file
11
examples/bunfig.toml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
telemetry = false
|
||||||
|
|
||||||
|
[install]
|
||||||
|
exact = true
|
||||||
|
|
||||||
|
[install.lockfile]
|
||||||
|
save = false
|
||||||
|
|
||||||
|
[test]
|
||||||
|
coverage = true
|
||||||
|
coverageSkipTestFiles = true
|
||||||
17
examples/package.json
Normal file
17
examples/package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "@zinntechniker/tools-examples",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"type": "module",
|
||||||
|
"author": {
|
||||||
|
"name": "Alexander Zinn",
|
||||||
|
"git+url": "https://github.com/zinntechniker/tools"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@zinntechniker/tools": "latest",
|
||||||
|
"typescript": "5.5.2"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "https://registry-node.techniker.me"
|
||||||
|
}
|
||||||
|
}
|
||||||
13
examples/src/example-observables.ts
Normal file
13
examples/src/example-observables.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import Tools from '@zinntechniker/tools';
|
||||||
|
|
||||||
|
const {
|
||||||
|
observables: {Subject, ReadOnlySubject}
|
||||||
|
} = Tools;
|
||||||
|
|
||||||
|
const subject = new Subject<number>(0);
|
||||||
|
const subjectSubscriber = (value: number) => {
|
||||||
|
console.log("Subject's value changed to [%o]", value);
|
||||||
|
};
|
||||||
|
subject.subscribe(subjectSubscriber);
|
||||||
|
|
||||||
|
subject.value = 12;
|
||||||
28
examples/tsconfig.json
Normal file
28
examples/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["./dist", "./test"]
|
||||||
|
}
|
||||||
56
package.json
Normal file
56
package.json
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
{
|
||||||
|
"name": "@techniker-me/tools",
|
||||||
|
"version": "2025.0.16",
|
||||||
|
"type": "module",
|
||||||
|
"private": false,
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
"types": "./dist/types/index.d.ts",
|
||||||
|
"import": "./dist/node/index.js",
|
||||||
|
"browser": "./dist/browser/index.js",
|
||||||
|
"default": "./dist/node/index.js"
|
||||||
|
},
|
||||||
|
"author": {
|
||||||
|
"name": "Alexander Zinn",
|
||||||
|
"git+url": "https://git.techniker.me/techniker-me/tools.git"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"registry": "http://localhost:7873"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"ci-install": "bun install",
|
||||||
|
"ci-test": "bun test",
|
||||||
|
"ci-build": "bash scripts/ci-build.sh",
|
||||||
|
"ci-deploy:ga": "bash scripts/ci-deploy.sh --beta false",
|
||||||
|
"ci-deploy:beta": "bash scripts/ci-deploy.sh --beta true",
|
||||||
|
"lint": "eslint",
|
||||||
|
"lint:fix": "eslint --fix",
|
||||||
|
"test": "bun test",
|
||||||
|
"build:node:debug": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/node",
|
||||||
|
"build:browser:debug": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --sourcemap=inline --outdir=dist/browser",
|
||||||
|
"build:node": "bun build ./src/index.ts --target=node --sourcemap=none --format=esm --splitting --minify --outdir=dist/node",
|
||||||
|
"build:browser": "bun build ./src/index.ts --target=browser --sourcemap=none --format=esm --splitting --minify --outdir=dist/browser",
|
||||||
|
"build:types": "bunx tsc -p tsconfig.d.json",
|
||||||
|
"build:prepare-package-json": "bash scripts/prepare-package-json.sh"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "8.57.1",
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@types/node": "22.5.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "8.37.0",
|
||||||
|
"@typescript-eslint/parser": "8.37.0",
|
||||||
|
"eslint": "8.57.1",
|
||||||
|
"globals": "15.9.0",
|
||||||
|
"prettier": "3.3.3",
|
||||||
|
"typescript": "5.5.4",
|
||||||
|
"typescript-eslint": "8.4.0"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"browser",
|
||||||
|
"node",
|
||||||
|
"types",
|
||||||
|
"package.json",
|
||||||
|
"README.md"
|
||||||
|
]
|
||||||
|
}
|
||||||
29
scripts/ci-build.sh
Executable file
29
scripts/ci-build.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||||
|
|
||||||
|
rm -rf ${distDirectory}
|
||||||
|
|
||||||
|
bun run lint
|
||||||
|
bun run build:node
|
||||||
|
bun run build:browser
|
||||||
|
bun run build:types
|
||||||
|
bun run build:prepare-package-json
|
||||||
|
|
||||||
|
echo "Copying [.npmrc] to [${distDirectory}]"
|
||||||
|
cp .npmrc ./${distDirectory}
|
||||||
|
|
||||||
|
echo "Copying [.nvmrc] to [${distDirectory}]"
|
||||||
|
cp .nvmrc ./${distDirectory}
|
||||||
|
|
||||||
|
echo "Copying [README.md] to [${distDirectory}]"
|
||||||
|
cp README ./${distDirectory}
|
||||||
|
|
||||||
|
ls ${distDirectory}
|
||||||
|
|
||||||
|
echo -e "\nci-build complete!"
|
||||||
|
exit 0
|
||||||
83
scripts/ci-deploy.sh
Executable file
83
scripts/ci-deploy.sh
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
|
||||||
|
registryUrl="http://localhost:4873"
|
||||||
|
# registryUrl="https://registry-node.techniker.me"
|
||||||
|
packageVersionToDeploy=""
|
||||||
|
isBeta="false"
|
||||||
|
|
||||||
|
while [[ "${#}" -gt 0 ]]; do
|
||||||
|
case "${1}" in
|
||||||
|
--beta)
|
||||||
|
isBeta="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
--version)
|
||||||
|
packageVersionToDeploy="${2}"
|
||||||
|
shift 2
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Unknown option [${1}]"
|
||||||
|
exit "${LINENO}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
function cleanDirectory {
|
||||||
|
local directory="${1}"
|
||||||
|
|
||||||
|
if [ -d "${directory}" ]; then
|
||||||
|
echo "Deleting [${directory}]..."
|
||||||
|
|
||||||
|
rm -rf "${directory}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePackageJsonMember {
|
||||||
|
local packageJsonPath="dist/package.json"
|
||||||
|
local memberToRemove="${1}"
|
||||||
|
|
||||||
|
if [ -f "${packageJsonPath}" ]; then
|
||||||
|
echo "Removing [${memberToRemove}] from the dist/package.json"
|
||||||
|
|
||||||
|
jq "del(.${memberToRemove})" "${packageJsonPath}" >tmp.$$.json && mv tmp.$$.json "$packageJsonPath"
|
||||||
|
else
|
||||||
|
echo "Error: [${packageJsonPath}] not found."
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePackageJsonVersion {
|
||||||
|
local versionToUpdate="${1}"
|
||||||
|
|
||||||
|
if [ isBeta == "true" ]; then
|
||||||
|
echo "Version to update [${versionToUpdate}] Contains beta"
|
||||||
|
echo "Updating package.json version to [${versionToUpdate}]"
|
||||||
|
|
||||||
|
local packageJsonVersion=$(jq -r '.version' package.json)
|
||||||
|
|
||||||
|
sed -i "s/\"version\": \"${packageJsonVersion}\"/\"version\": \"${versionToUpdate}\"/" dist/package.json
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "Deploying [${packageVersionToDeploy}]"
|
||||||
|
echo "isBeta [${isBeta}]"
|
||||||
|
|
||||||
|
cleanDirectory "dist"
|
||||||
|
|
||||||
|
bun run ci-build
|
||||||
|
|
||||||
|
removePackageJsonMember "devDependencies"
|
||||||
|
removePackageJsonMember "scripts"
|
||||||
|
|
||||||
|
echo "publishing to ${registryUrl}"
|
||||||
|
if [ "${isBeta}" == "true" ]; then
|
||||||
|
updatePackageJsonVersion "${packageVersionToDeploy}"
|
||||||
|
npm publish --registry "${registryUrl}" --tag beta
|
||||||
|
else
|
||||||
|
npm publish --registry "${registryUrl}"
|
||||||
|
fi
|
||||||
|
|
||||||
16
scripts/prepare-package-json.sh
Executable file
16
scripts/prepare-package-json.sh
Executable file
@@ -0,0 +1,16 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -o errexit
|
||||||
|
set -o nounset
|
||||||
|
set -o pipefail
|
||||||
|
|
||||||
|
distDirectory=${DIST_DIRECTORY:-"dist"}
|
||||||
|
packageJsonPath=${PACKAGE_JSON_PATH:-"package.json"}
|
||||||
|
|
||||||
|
if [ ! -d "${distDirectory}" ]; then
|
||||||
|
echo "Unable to prepare package.json, [${distDirectory}] not found"
|
||||||
|
exit $LINENO
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Preparing [package.json] to [${distDirectory}]"
|
||||||
|
jq '{name, version, author, type, types, exports, files, publishConfig}' "${packageJsonPath}" > "${distDirectory}/package.json"
|
||||||
3
src/assertions/assertUnreachable.ts
Normal file
3
src/assertions/assertUnreachable.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default function assertUnreachable(never: never): never {
|
||||||
|
throw new Error(`Should not have reached [${never}]`);
|
||||||
|
}
|
||||||
3
src/assertions/index.ts
Normal file
3
src/assertions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import assertUnreachable from './assertUnreachable';
|
||||||
|
|
||||||
|
export {assertUnreachable};
|
||||||
23
src/disposables/Disposable.ts
Normal file
23
src/disposables/Disposable.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type IDisposable from './IDisposable';
|
||||||
|
|
||||||
|
export default class Disposable implements IDisposable {
|
||||||
|
private _isDisposed = false;
|
||||||
|
private _disposable: () => void;
|
||||||
|
|
||||||
|
constructor(disposable: () => void) {
|
||||||
|
this._disposable = disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
get isDisposed(): boolean {
|
||||||
|
return this._isDisposed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
if (this._isDisposed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._disposable();
|
||||||
|
this._isDisposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/disposables/DisposableList.ts
Normal file
17
src/disposables/DisposableList.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type IDisposable from './IDisposable';
|
||||||
|
|
||||||
|
export default class DisposableList implements IDisposable {
|
||||||
|
private readonly _disposables: IDisposable[] = [];
|
||||||
|
|
||||||
|
public add(disposable: IDisposable): void {
|
||||||
|
this._disposables.push(disposable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
while (this._disposables.length) {
|
||||||
|
const disposable = this._disposables.shift() as IDisposable;
|
||||||
|
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
src/disposables/IDisposable.ts
Normal file
3
src/disposables/IDisposable.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export default interface IDisposable {
|
||||||
|
dispose(): void;
|
||||||
|
}
|
||||||
6
src/disposables/index.ts
Normal file
6
src/disposables/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type IDisposable from './IDisposable';
|
||||||
|
import Disposable from './Disposable';
|
||||||
|
import DisposableList from './DisposableList';
|
||||||
|
|
||||||
|
export type {IDisposable};
|
||||||
|
export {Disposable, DisposableList};
|
||||||
23
src/events/EventEmitter.ts
Normal file
23
src/events/EventEmitter.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import type IDisposable from '../disposables/IDisposable';
|
||||||
|
import type IEvent from './IEvent';
|
||||||
|
import {Disposable} from '../disposables';
|
||||||
|
|
||||||
|
export default class EventEmitter<T> implements IDisposable {
|
||||||
|
private readonly _listeners: Array<(event: IEvent<T>) => void> = [];
|
||||||
|
|
||||||
|
public subscribe(listener: (event: IEvent<T>) => void): Disposable {
|
||||||
|
const listenerIndex = this._listeners.push(listener);
|
||||||
|
|
||||||
|
return new Disposable(() => this._listeners.splice(listenerIndex - 1, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
public emit<Event extends IEvent<T>>(event: Event): void {
|
||||||
|
this._listeners.forEach(listener => listener(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
while (this._listeners.length) {
|
||||||
|
this._listeners.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/events/EventPublisher.ts
Normal file
39
src/events/EventPublisher.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import type IEvent from './IEvent';
|
||||||
|
import {Disposable, DisposableList} from '../disposables';
|
||||||
|
import EventEmitter from './EventEmitter';
|
||||||
|
|
||||||
|
export default class EventPublisher {
|
||||||
|
private readonly _eventEmitters: Record<string, EventEmitter<any>> = {};
|
||||||
|
private readonly _disposables = new DisposableList();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this._eventEmitters['no-subscribers'] = new EventEmitter();
|
||||||
|
}
|
||||||
|
|
||||||
|
public addNoSubscriberHandler(handler: (event: IEvent<any>) => Promise<void> | void) {
|
||||||
|
return this._eventEmitters['no-subscribers'].subscribe(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe<T>(event: string | number, handler: (event: IEvent<T>) => Promise<void> | void): Disposable {
|
||||||
|
const emitter = this._eventEmitters[event] ?? this.createEmitter<T>(event);
|
||||||
|
const subscription = emitter.subscribe(handler);
|
||||||
|
|
||||||
|
this._disposables.add(subscription);
|
||||||
|
|
||||||
|
return subscription;
|
||||||
|
}
|
||||||
|
|
||||||
|
public publish<T>(eventName: string | number, event: IEvent<T>): void {
|
||||||
|
(this._eventEmitters[eventName] || this._eventEmitters['no-subscribers']).emit<IEvent<T>>(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
public dispose(): void {
|
||||||
|
Object.values(this._eventEmitters).forEach(emitter => emitter.dispose());
|
||||||
|
}
|
||||||
|
|
||||||
|
private createEmitter<T>(event: string | number): EventEmitter<IEvent<T>> {
|
||||||
|
const eventEmitter = new EventEmitter<IEvent<T>>();
|
||||||
|
|
||||||
|
return (this._eventEmitters[event] = eventEmitter);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/events/IEvent.ts
Normal file
4
src/events/IEvent.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default interface IEvent<Payload> {
|
||||||
|
type: string;
|
||||||
|
payload?: Payload;
|
||||||
|
}
|
||||||
6
src/events/index.ts
Normal file
6
src/events/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import type IEvent from './IEvent';
|
||||||
|
import EventEmitter from './EventEmitter';
|
||||||
|
import EventPublisher from './EventPublisher';
|
||||||
|
|
||||||
|
export type {IEvent};
|
||||||
|
export {EventEmitter, EventPublisher};
|
||||||
3
src/functions/index.ts
Normal file
3
src/functions/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import {createRangeIterator} from './rangeIterator';
|
||||||
|
|
||||||
|
export {createRangeIterator};
|
||||||
16
src/functions/rangeIterator.ts
Normal file
16
src/functions/rangeIterator.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
export function createRangeIterator(start: number, end: number, step = 1) {
|
||||||
|
return {
|
||||||
|
[Symbol.iterator]() {
|
||||||
|
return this;
|
||||||
|
},
|
||||||
|
next() {
|
||||||
|
if (start < end) {
|
||||||
|
start = start + step;
|
||||||
|
|
||||||
|
return {value: start, done: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {value: end, done: true};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
12
src/index.ts
Normal file
12
src/index.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type IDisposable from './disposables/IDisposable';
|
||||||
|
import type IEvent from './events/IEvent';
|
||||||
|
|
||||||
|
export type {IDisposable, IEvent};
|
||||||
|
|
||||||
|
export {assertUnreachable} from './assertions';
|
||||||
|
export {Disposable, DisposableList} from './disposables';
|
||||||
|
export {EventEmitter, EventPublisher} from './events';
|
||||||
|
export {Averager, Random} from './maths';
|
||||||
|
export {Subject, ReadOnlySubject} from './observables';
|
||||||
|
export {Strings} from './strings';
|
||||||
|
export {createRangeIterator} from './functions';
|
||||||
33
src/maths/Averager.ts
Normal file
33
src/maths/Averager.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import {Subject, ReadOnlySubject} from '../observables';
|
||||||
|
|
||||||
|
export default class Averager {
|
||||||
|
private readonly _sampleSize: number;
|
||||||
|
private readonly _samples: number[] = [];
|
||||||
|
private readonly _average = new Subject<number>(0);
|
||||||
|
private readonly _readOnlyAverage = new ReadOnlySubject<number>(this._average);
|
||||||
|
private _onPush: (sample: number) => void = this.onPushOnly.bind(this);
|
||||||
|
|
||||||
|
constructor(sampleSize: number) {
|
||||||
|
this._sampleSize = sampleSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
get average(): ReadOnlySubject<number> {
|
||||||
|
return this._readOnlyAverage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public push(value: number): void {
|
||||||
|
this._onPush(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private onPushOnly(sample: number) {
|
||||||
|
if (this._samples.push(sample) === this._sampleSize) {
|
||||||
|
this._onPush = this.pushAndCalculateAverage.bind(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private pushAndCalculateAverage(sample: number) {
|
||||||
|
this._samples.push(sample);
|
||||||
|
this._samples.shift();
|
||||||
|
this._average.value = this._samples.reduce((sum, sampleValue) => sum + sampleValue, 0) / this._sampleSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
23
src/maths/Random.ts
Normal file
23
src/maths/Random.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
export default class Random {
|
||||||
|
private static readonly lowercaseLetters = 'abcdefghijklmnopqrstuvwxyz';
|
||||||
|
private static readonly uppercaseLatters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||||
|
private static readonly letters = this.lowercaseLetters + this.uppercaseLatters;
|
||||||
|
|
||||||
|
public static number(): number {
|
||||||
|
return Math.random();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static numberInRange(min: number, max: number): number {
|
||||||
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string(length: number): string {
|
||||||
|
let result = '';
|
||||||
|
|
||||||
|
for (let idx = 0; idx < length; idx++) {
|
||||||
|
result += this.letters[this.numberInRange(0, this.letters.length - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/maths/index.ts
Normal file
4
src/maths/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import Averager from './Averager';
|
||||||
|
import Random from './Random';
|
||||||
|
|
||||||
|
export {Averager, Random};
|
||||||
18
src/observables/ReadOnlySubject.ts
Normal file
18
src/observables/ReadOnlySubject.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import Disposable from '../disposables/Disposable';
|
||||||
|
import Subject from './Subject';
|
||||||
|
|
||||||
|
export default class ReadOnlySubject<T> {
|
||||||
|
private readonly _subject: Subject<T>;
|
||||||
|
|
||||||
|
constructor(subject: Subject<T>) {
|
||||||
|
this._subject = subject;
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): T {
|
||||||
|
return this._subject.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(listener: (value: T) => void): Disposable {
|
||||||
|
return this._subject.subscribe(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/observables/Subject.ts
Normal file
39
src/observables/Subject.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import Disposable from '../disposables/Disposable';
|
||||||
|
|
||||||
|
export default class Subject<T> {
|
||||||
|
private readonly _listeners: Array<(value: T) => void> = [];
|
||||||
|
private _value: T;
|
||||||
|
|
||||||
|
constructor(value: T) {
|
||||||
|
this._value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
set value(value: T) {
|
||||||
|
if (this._value === value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._value = value;
|
||||||
|
this.notifyListeners(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
get value(): T {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public subscribe(listener: (value: T) => void): Disposable {
|
||||||
|
const listenerIndex = this._listeners.push(listener);
|
||||||
|
|
||||||
|
listener(this.value);
|
||||||
|
|
||||||
|
return new Disposable(() => {
|
||||||
|
this._listeners.splice(listenerIndex, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private notifyListeners(value: T): void {
|
||||||
|
for (const listener of this._listeners) {
|
||||||
|
listener(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/observables/index.ts
Normal file
4
src/observables/index.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import Subject from './Subject';
|
||||||
|
import ReadOnlySubject from './ReadOnlySubject';
|
||||||
|
|
||||||
|
export {Subject, ReadOnlySubject};
|
||||||
15
src/strings/index.ts
Normal file
15
src/strings/index.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
export class Strings {
|
||||||
|
static random(length = 8) {
|
||||||
|
if (length > 11) {
|
||||||
|
throw new Error(`[Strings] max length [11]`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substring(2, length + 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
throw new Error('Strings is a static class that may not be instantiated');
|
||||||
|
}
|
||||||
|
}
|
||||||
10
test/assertions/assertUnreachable.test.ts
Normal file
10
test/assertions/assertUnreachable.test.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {describe, it, expect} from 'bun:test';
|
||||||
|
import {assertUnreachable} from '../../src/assertions';
|
||||||
|
|
||||||
|
describe(`When asserting unreachable code`, () => {
|
||||||
|
it(`throws an error`, () => {
|
||||||
|
const value = 'NEVER_VALUE';
|
||||||
|
|
||||||
|
expect(() => assertUnreachable(value)).toThrowError();
|
||||||
|
});
|
||||||
|
});
|
||||||
41
test/disposables/Disposable.test.ts
Normal file
41
test/disposables/Disposable.test.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import type {Mock} from 'bun:test';
|
||||||
|
import {mock, describe, it, beforeEach, expect} from 'bun:test';
|
||||||
|
import {Disposable} from '../../src/disposables';
|
||||||
|
|
||||||
|
describe(`When using a Disposable`, () => {
|
||||||
|
describe(`Given a Disposable`, () => {
|
||||||
|
let mockFunction: Mock<any>;
|
||||||
|
let disposable: Disposable;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockFunction = mock(() => {});
|
||||||
|
disposable = new Disposable(mockFunction);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`Given the disposable is disposed`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
disposable.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`executes the disposble`, () => {
|
||||||
|
expect(mockFunction).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`marks the disposbale disposed`, () => {
|
||||||
|
expect(disposable.isDisposed).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`Given the disposable is repeatedly disposed`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
for (let count = 0; count < 10; count++) {
|
||||||
|
disposable.dispose();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`disposes the disposable only once`, () => {
|
||||||
|
expect(mockFunction).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
36
test/disposables/DisposableList.test.ts
Normal file
36
test/disposables/DisposableList.test.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import type {Mock} from 'bun:test';
|
||||||
|
import {mock, describe, it, beforeAll, beforeEach, expect} from 'bun:test';
|
||||||
|
import {Disposable, DisposableList} from '../../src/disposables';
|
||||||
|
|
||||||
|
describe(`When using a DisposableList`, () => {
|
||||||
|
let disposableList: DisposableList;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
disposableList = new DisposableList();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`Given a disposbale list with multiple disposables`, () => {
|
||||||
|
let mocks: Array<Mock<any>>;
|
||||||
|
let mockDisposables: Disposable[];
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mocks = [mock(() => {}), mock(() => {}), mock(() => {}), mock(() => {})];
|
||||||
|
mockDisposables = mocks.map(mock => new Disposable(mock));
|
||||||
|
mockDisposables.forEach(mockDisposable => disposableList.add(mockDisposable));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`Given the disposable list is disposed`, () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
disposableList.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`disposes all of the disposables in the list`, () => {
|
||||||
|
mocks.forEach(mock => expect(mock).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`marks the disposable as disposed`, () => {
|
||||||
|
mockDisposables.forEach(disposable => expect(disposable.isDisposed).toBeTrue());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
19
test/events/EventEmitter.test.ts
Normal file
19
test/events/EventEmitter.test.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import {afterAll, beforeEach, describe, expect, it, mock} from 'bun:test';
|
||||||
|
import {EventEmitter} from '../../src/events';
|
||||||
|
|
||||||
|
describe(`When emitting an event`, () => {
|
||||||
|
const eventEmitter: EventEmitter<string> = new EventEmitter<string>();
|
||||||
|
|
||||||
|
describe(`Given a callback is subscribed`, () => {
|
||||||
|
const mockListener = mock(() => undefined);
|
||||||
|
const subscription = eventEmitter.subscribe(mockListener);
|
||||||
|
|
||||||
|
describe(`Given an event is emitted`, () => {
|
||||||
|
beforeEach(() => eventEmitter.emit('mock event'));
|
||||||
|
|
||||||
|
it(`notifies the callback with the event`, () => expect(mockListener).toHaveBeenCalled());
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => subscription.dispose());
|
||||||
|
});
|
||||||
|
});
|
||||||
3
test/events/EventPublisher.test.ts
Normal file
3
test/events/EventPublisher.test.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
import {describe} from 'bun:test';
|
||||||
|
|
||||||
|
describe.todo(`When publishing events`);
|
||||||
20
test/functions/range.test.ts
Normal file
20
test/functions/range.test.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import {describe, it, expect, mock} from 'bun:test';
|
||||||
|
import {createRangeIterator} from '../../src/functions';
|
||||||
|
|
||||||
|
describe('When generating a range iterator', () => {
|
||||||
|
describe('Given start and stop values', () => {
|
||||||
|
const start = 1;
|
||||||
|
const stop = 10;
|
||||||
|
const mockFn = mock(() => undefined);
|
||||||
|
|
||||||
|
it('generates an iterator of the correct length', () => {
|
||||||
|
const rangeIterator = createRangeIterator(1, 10);
|
||||||
|
|
||||||
|
for (const idx of rangeIterator) {
|
||||||
|
mockFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(mockFn).toHaveBeenCalledTimes(9);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
60
test/observables/ReadOnlySubject.test.ts
Normal file
60
test/observables/ReadOnlySubject.test.ts
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
import {jest, describe, it, expect} from 'bun:test';
|
||||||
|
import {Subject, ReadOnlySubject} from '../../src/observables';
|
||||||
|
|
||||||
|
describe(`A ReadOnlySubject`, () => {
|
||||||
|
it(`exposes the Subject value`, () => {
|
||||||
|
const subject = new Subject<string>(`init-value`);
|
||||||
|
const readOnlySubject = new ReadOnlySubject(subject);
|
||||||
|
|
||||||
|
expect(readOnlySubject.value).toBe(`init-value`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`updates the ReadOnlySubject value when set`, () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const readOnlySubject = new ReadOnlySubject(subject);
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(readOnlySubject.value).toBe('some thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`notifies subcribers when subscribing`, () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const readOnlySubject = new ReadOnlySubject(subject);
|
||||||
|
const listener = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
readOnlySubject.subscribe(listener);
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith('init-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`notifies subcribers when value changes`, () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const readOnlySubject = new ReadOnlySubject(subject);
|
||||||
|
const listener = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
readOnlySubject.subscribe(listener);
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith('some thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`notifies all subcribers when value changes`, () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const readOnlySubject = new ReadOnlySubject(subject);
|
||||||
|
const listener1 = jest.fn() as (value: string) => void;
|
||||||
|
const listener2 = jest.fn() as (value: string) => void;
|
||||||
|
const listener3 = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
readOnlySubject.subscribe(listener1);
|
||||||
|
readOnlySubject.subscribe(listener2);
|
||||||
|
readOnlySubject.subscribe(listener3);
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(listener1).toHaveBeenCalledWith('some thing');
|
||||||
|
expect(listener2).toHaveBeenCalledWith('some thing');
|
||||||
|
expect(listener3).toHaveBeenCalledWith('some thing');
|
||||||
|
});
|
||||||
|
});
|
||||||
55
test/observables/Subject.test.ts
Normal file
55
test/observables/Subject.test.ts
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
import {jest, describe, it, expect} from 'bun:test';
|
||||||
|
import {Subject} from '../../src/observables';
|
||||||
|
|
||||||
|
describe('A Subject', () => {
|
||||||
|
it('exposes the Subject value', () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
|
||||||
|
expect(subject.value).toBe('init-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates the Subject value when set', () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(subject.value).toBe('some thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('notifies subcribers when subscribing', () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const listener = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
subject.subscribe(listener);
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith('init-value');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('notifies subcribers when value changes', () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const listener = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
subject.subscribe(listener);
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(listener).toHaveBeenCalledWith('some thing');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('notifies all subcribers when value changes', () => {
|
||||||
|
const subject = new Subject<string>('init-value');
|
||||||
|
const listener1 = jest.fn() as (value: string) => void;
|
||||||
|
const listener2 = jest.fn() as (value: string) => void;
|
||||||
|
const listener3 = jest.fn() as (value: string) => void;
|
||||||
|
|
||||||
|
subject.subscribe(listener1);
|
||||||
|
subject.subscribe(listener2);
|
||||||
|
subject.subscribe(listener3);
|
||||||
|
|
||||||
|
subject.value = 'some thing';
|
||||||
|
|
||||||
|
expect(listener1).toHaveBeenCalledWith('some thing');
|
||||||
|
expect(listener2).toHaveBeenCalledWith('some thing');
|
||||||
|
expect(listener3).toHaveBeenCalledWith('some thing');
|
||||||
|
});
|
||||||
|
});
|
||||||
39
test/strings/WhenGeneratingRandomStrings.test.ts
Normal file
39
test/strings/WhenGeneratingRandomStrings.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import {describe, beforeAll, it, expect} from 'bun:test';
|
||||||
|
import {Strings} from '../../src/strings';
|
||||||
|
|
||||||
|
const randomStringCount = 11;
|
||||||
|
|
||||||
|
describe('When generating random strings', () => {
|
||||||
|
it('generates a random string', () => {
|
||||||
|
const randomString = Strings.random();
|
||||||
|
|
||||||
|
expect(typeof randomString).toBe('string');
|
||||||
|
expect(randomString.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Given a length argument', () => {
|
||||||
|
const length = 4;
|
||||||
|
|
||||||
|
it('generates a random string of the given length', () => {
|
||||||
|
const randomString = Strings.random(length);
|
||||||
|
|
||||||
|
expect(typeof randomString).toBe('string');
|
||||||
|
expect(randomString.length).toBeGreaterThan(0);
|
||||||
|
expect(randomString.length).toBe(length);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe(`Given [${randomStringCount}] are generated`, () => {
|
||||||
|
let randomStrings;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
randomStrings = Array.from({length: randomStringCount}, () => Strings.random());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('generates random strings such that no strings are identical', () => {
|
||||||
|
const uniqueRandomStrings = new Set(randomStrings);
|
||||||
|
|
||||||
|
expect(uniqueRandomStrings.size).toBe(randomStringCount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
30
tsconfig.d.json
Normal file
30
tsconfig.d.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["esnext", "dom"],
|
||||||
|
"target": "es6",
|
||||||
|
"module": "esnext",
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"declaration": true,
|
||||||
|
"emitDeclarationOnly": true,
|
||||||
|
"declarationDir": "dist/types",
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["./dist", "./test"]
|
||||||
|
}
|
||||||
28
tsconfig.json
Normal file
28
tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
// Enable latest features
|
||||||
|
"lib": ["ESNext", "DOM"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"allowJs": true,
|
||||||
|
|
||||||
|
// Bundler mode
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
|
||||||
|
// Best practices
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
|
||||||
|
// Some stricter flags (disabled by default)
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": false
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["./dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user