首次提交

This commit is contained in:
2026-07-13 12:06:16 +08:00
commit e3c810b1a6
1690 changed files with 228324 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Christoph Guttandin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+70
View File
@@ -0,0 +1,70 @@
# worker-timers-broker
**The broker which is used by the worker-timers package.**
[![version](https://img.shields.io/npm/v/worker-timers-broker.svg?style=flat-square)](https://www.npmjs.com/package/worker-timers-broker)
## Usage
In most cases using this package via [`worker-timers`](https://github.com/chrisguttandin/worker-timers) is probably the most convenient choice.
However `worker-timers-broker` is published as a separate package on npm. It can be installed using the following command:
```shell
npm install worker-timers-broker
```
The package exports two functions.
### load()
The `load()` function can be used to create a custom instance of `worker-timers` by explicitly specifying the URL which points to the code of the [`worker-timers-worker`](https://github.com/chrisguttandin/worker-timers-worker) package.
```js
import { load } from 'worker-timers-broker';
const { clearInterval, clearTimeout, setInterval, setTimeout } = load('./the/worker-timers-worker/file');
const intervalId = setInterval(() => {
// do something many times
}, 100);
clearInterval(intervalId);
const timeoutId = setTimeout(() => {
// do something once
}, 100);
clearTimeout(timeoutId);
```
### wrap()
The `wrap()` function can be used to wrap a Web Worker instance which already runs the code of the [`worker-timers-worker`](https://github.com/chrisguttandin/worker-timers-worker) package.
In a project using [Vite](https://vite.dev) it can for example be used by leveraging [query suffixes](https://vite.dev/guide/features.html#import-with-query-suffixes).
```js
import { wrap } from 'worker-timers-broker';
import createWorkerTimersWorker from 'worker-timers-worker?worker';
const { clearInterval, clearTimeout, setInterval, setTimeout } = wrap(createWorkerTimersWorker());
const intervalId = setInterval(() => {
// do something many times
}, 100);
clearInterval(intervalId);
const timeoutId = setTimeout(() => {
// do something once
}, 100);
clearTimeout(timeoutId);
```
Please note the syntax for other build tools may vary.
## Security contact information
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
+73
View File
@@ -0,0 +1,73 @@
{
"author": "Christoph Guttandin",
"bugs": {
"url": "https://github.com/chrisguttandin/worker-timers-broker/issues"
},
"config": {
"commitizen": {
"path": "cz-conventional-changelog"
}
},
"dependencies": {
"@babel/runtime": "^7.29.2",
"broker-factory": "^3.1.14",
"fast-unique-numbers": "^9.0.27",
"tslib": "^2.8.1",
"worker-timers-worker": "^9.0.14"
},
"description": "The broker which is used by the worker-timers package.",
"devDependencies": {
"@babel/core": "^7.29.0",
"@babel/plugin-external-helpers": "^7.27.1",
"@babel/plugin-transform-runtime": "^7.29.0",
"@babel/preset-env": "^7.29.2",
"@commitlint/cli": "^20.5.0",
"@commitlint/config-angular": "^20.5.0",
"@rollup/plugin-babel": "^7.0.0",
"@vitest/browser-webdriverio": "^4.0.18",
"chai": "^6.2.2",
"commitizen": "^4.3.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.57.0",
"eslint-config-holy-grail": "^61.0.11",
"husky": "^9.1.7",
"lint-staged": "^16.4.0",
"prettier": "^3.8.1",
"rimraf": "^6.1.3",
"rollup": "^4.59.0",
"sinon": "^21.0.3",
"sinon-chai": "^4.0.1",
"tsconfig-holy-grail": "^15.0.3",
"tslint": "^6.1.3",
"tslint-config-holy-grail": "^56.0.7",
"typescript": "^5.9.3",
"vitest": "^4.0.18"
},
"files": [
"build/es2019/",
"build/es5/",
"src/"
],
"homepage": "https://github.com/chrisguttandin/worker-timers-broker",
"license": "MIT",
"main": "build/es5/bundle.js",
"module": "build/es2019/module.js",
"name": "worker-timers-broker",
"repository": {
"type": "git",
"url": "https://github.com/chrisguttandin/worker-timers-broker.git"
},
"scripts": {
"build": "rimraf build/* && tsc --project src/tsconfig.json && rollup --config config/rollup/bundle.mjs",
"lint": "npm run lint:config && npm run lint:src && npm run lint:test",
"lint:config": "eslint --config config/eslint/config.json --ext .cjs --ext .js --ext .mjs --report-unused-disable-directives config/",
"lint:src": "tslint --config config/tslint/src.json --project src/tsconfig.json src/*.ts src/**/*.ts",
"lint:test": "eslint --config config/eslint/test.json --ext .js --report-unused-disable-directives test/",
"prepare": "husky",
"prepublishOnly": "npm run build",
"test": "npm run lint && npm run build && npm run test:unit",
"test:unit": "if [ \"$TYPE\" = \"\" -o \"$TYPE\" = \"unit\" ]; then npx vitest --config config/vitest/unit.ts; fi"
},
"types": "build/es2019/module.d.ts",
"version": "8.0.16"
}
@@ -0,0 +1,10 @@
export const createClearIntervalFactory =
(scheduledIntervalsState: Map<number, null | symbol>) => (clear: (timerId: number) => Promise<boolean>) => (timerId: number) => {
if (typeof scheduledIntervalsState.get(timerId) === 'symbol') {
scheduledIntervalsState.set(timerId, null);
clear(timerId).then(() => {
scheduledIntervalsState.delete(timerId);
});
}
};
@@ -0,0 +1,10 @@
export const createClearTimeoutFactory =
(scheduledTimeoutsState: Map<number, null | symbol>) => (clear: (timerId: number) => Promise<boolean>) => (timerId: number) => {
if (typeof scheduledTimeoutsState.get(timerId) === 'symbol') {
scheduledTimeoutsState.set(timerId, null);
clear(timerId).then(() => {
scheduledTimeoutsState.delete(timerId);
});
}
};
@@ -0,0 +1,33 @@
import type { generateUniqueNumber as generateUniqueNumberFunction } from 'fast-unique-numbers';
export const createSetIntervalFactory =
(generateUniqueNumber: typeof generateUniqueNumberFunction, scheduledIntervalsState: Map<number, null | symbol>) =>
(set: (delay: number, timerId: number) => Promise<boolean>) =>
(func: Function, delay = 0, ...args: any[]) => {
const symbol = Symbol();
const timerId = generateUniqueNumber(scheduledIntervalsState);
scheduledIntervalsState.set(timerId, symbol);
const schedule = () =>
set(delay, timerId).then(() => {
const state = scheduledIntervalsState.get(timerId);
if (state === undefined) {
throw new Error('The timer is in an undefined state.');
}
if (state === symbol) {
func(...args);
// Doublecheck if the interval should still be rescheduled because it could have been cleared inside of func().
if (scheduledIntervalsState.get(timerId) === symbol) {
schedule();
}
}
});
schedule();
return timerId;
};
@@ -0,0 +1,28 @@
import type { generateUniqueNumber as generateUniqueNumberFunction } from 'fast-unique-numbers';
export const createSetTimeoutFactory =
(generateUniqueNumber: typeof generateUniqueNumberFunction, scheduledTimeoutsState: Map<number, null | symbol>) =>
(set: (delay: number, timerId: number) => Promise<boolean>) =>
(func: Function, delay = 0, ...args: any[]) => {
const symbol = Symbol();
const timerId = generateUniqueNumber(scheduledTimeoutsState);
scheduledTimeoutsState.set(timerId, symbol);
set(delay, timerId).then(() => {
const state = scheduledTimeoutsState.get(timerId);
if (state === undefined) {
throw new Error('The timer is in an undefined state.');
}
if (state === symbol) {
// A timeout can be savely deleted because it is only called once.
scheduledTimeoutsState.delete(timerId);
func(...args);
}
});
return timerId;
};
@@ -0,0 +1 @@
export * from './worker-timers-broker-definition';
@@ -0,0 +1,11 @@
import { IBrokerDefinition } from 'broker-factory';
export interface IWorkerTimersBrokerDefinition extends IBrokerDefinition {
clearInterval(timerId: number): void;
clearTimeout(timerId: number): void;
setInterval(func: Function, delay?: number, ...args: any[]): number;
setTimeout(func: Function, delay?: number, ...args: any[]): number;
}
+44
View File
@@ -0,0 +1,44 @@
import { createBroker } from 'broker-factory';
import { generateUniqueNumber } from 'fast-unique-numbers';
import { TWorkerTimersWorkerDefinition } from 'worker-timers-worker';
import { createClearIntervalFactory } from './factories/clear-interval-factory';
import { createClearTimeoutFactory } from './factories/clear-timeout-factory';
import { createSetIntervalFactory } from './factories/set-interval-factory';
import { createSetTimeoutFactory } from './factories/set-timeout-factory';
import { IWorkerTimersBrokerDefinition } from './interfaces';
import { TWorkerTimersBrokerLoader, TWorkerTimersBrokerWrapper } from './types';
/*
* @todo Explicitly referencing the barrel file seems to be necessary when enabling the
* isolatedModules compiler option.
*/
export * from './interfaces/index';
export * from './types/index';
// Prefilling the Maps with a function indexed by zero is necessary to be compliant with the specification.
const scheduledIntervalsState: Map<number, null | symbol> = new Map([[0, null]]); // tslint:disable-line no-empty
const scheduledTimeoutsState: Map<number, null | symbol> = new Map([[0, null]]); // tslint:disable-line no-empty
const createClearInterval = createClearIntervalFactory(scheduledIntervalsState);
const createClearTimeout = createClearTimeoutFactory(scheduledTimeoutsState);
const createSetInterval = createSetIntervalFactory(generateUniqueNumber, scheduledIntervalsState);
const createSetTimeout = createSetTimeoutFactory(generateUniqueNumber, scheduledTimeoutsState);
export const wrap: TWorkerTimersBrokerWrapper = createBroker<IWorkerTimersBrokerDefinition, TWorkerTimersWorkerDefinition>({
clearInterval: ({ call }) => createClearInterval((timerId) => call('clear', { timerId, timerType: 'interval' })),
clearTimeout: ({ call }) => createClearTimeout((timerId) => call('clear', { timerId, timerType: 'timeout' })),
setInterval: ({ call }) =>
createSetInterval((delay, timerId) =>
call('set', { delay, now: performance.timeOrigin + performance.now(), timerId, timerType: 'interval' })
),
setTimeout: ({ call }) =>
createSetTimeout((delay, timerId) =>
call('set', { delay, now: performance.timeOrigin + performance.now(), timerId, timerType: 'timeout' })
)
});
export const load: TWorkerTimersBrokerLoader = (url: string) => {
const worker = new Worker(url);
return wrap(worker);
};
+6
View File
@@ -0,0 +1,6 @@
{
"compilerOptions": {
"isolatedModules": true
},
"extends": "tsconfig-holy-grail/src/tsconfig-browser"
}
+2
View File
@@ -0,0 +1,2 @@
export * from './worker-timers-broker-loader';
export * from './worker-timers-broker-wrapper';
@@ -0,0 +1,4 @@
import { IDefaultBrokerDefinition } from 'broker-factory';
import { IWorkerTimersBrokerDefinition } from '../interfaces';
export type TWorkerTimersBrokerLoader = (url: string) => IWorkerTimersBrokerDefinition & IDefaultBrokerDefinition;
@@ -0,0 +1,4 @@
import { IDefaultBrokerDefinition } from 'broker-factory';
import { IWorkerTimersBrokerDefinition } from '../interfaces';
export type TWorkerTimersBrokerWrapper = (sender: MessagePort | Worker) => IWorkerTimersBrokerDefinition & IDefaultBrokerDefinition;