version 1.0.0
This commit is contained in:
parent
a9a155be9f
commit
46f9f1400f
|
@ -0,0 +1,25 @@
|
|||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
.DS_Store
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# Build folder
|
||||
dist
|
||||
|
||||
# Lock files
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
|
||||
# Compressed files
|
||||
*.tgz
|
||||
*.tar.gz
|
||||
*.zip
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"arrowParens": "always",
|
||||
"endOfLine": "auto",
|
||||
"jsxSingleQuote": true,
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
# Scheduler
|
||||
|
||||
Scheduler is a TypeScript class that provides methods for managing time-based tasks, such as starting and stopping timeouts and intervals at specific times.
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### TimeOfDay
|
||||
|
||||
A `TimeOfDay` object is used to represent a specific time of day. It is an object containing the `hour`, `minute`, and `seconds`.
|
||||
|
||||
```typescript
|
||||
export interface TimeOfDay {
|
||||
hour: number
|
||||
minute?: number
|
||||
seconds?: number
|
||||
}
|
||||
```
|
||||
|
||||
### TimeUntil
|
||||
|
||||
A `TimeUntil` object is used to represent a specific time until a certain event. It can represent time until a certain date, milliseconds from now, or a specific time of day.
|
||||
|
||||
```typescript
|
||||
export type TimeUntil = {
|
||||
timeOfDay?: TimeOfDay
|
||||
date?: Date
|
||||
ms?: number
|
||||
}
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
The `Scheduler` class provides two main static methods: `startTimeout` and `startInterval`.
|
||||
|
||||
### startTimeout
|
||||
|
||||
The `startTimeout` method starts a timeout that calls a given function after a specific delay. The delay is calculated based on the `TimeUntil` object passed to it. The method returns a `StopFunction` (see below).
|
||||
|
||||
```typescript
|
||||
public static startTimeout(
|
||||
timerFunc: Function,
|
||||
start: TimeUntil
|
||||
): StopFunction;
|
||||
```
|
||||
|
||||
### startInterval
|
||||
|
||||
The `startInterval` method starts an interval that calls a given function repeatedly with a fixed time delay between each call. Like `startTimeout`, the initial delay is calculated based on a `TimeUntil` object. The method returns a `StopFunction` (see below).
|
||||
|
||||
```typescript
|
||||
public static startInterval(
|
||||
intervalFunc: Function,
|
||||
intervalMS: number,
|
||||
start?: TimeUntil
|
||||
): StopFunction;
|
||||
```
|
||||
|
||||
## Stop Functions
|
||||
|
||||
Both the `startTimeout` and `startInterval` methods return a `StopFunction`. This function can be called to cancel a timeout or interval.
|
||||
|
||||
When called with no arguments, the `StopFunction` stops the timeout or interval immediately. If called with a `TimeUntil` argument, it schedules a stop at the specified time.
|
||||
|
||||
Here is the type definition of a `StopFunction`:
|
||||
|
||||
```typescript
|
||||
type StopFunction = (stopTime?: TimeUntil) => StopCancelFunction | null
|
||||
```
|
||||
|
||||
## Stop Cancel Functions
|
||||
|
||||
The `StopFunction` returns a `StopCancelFunction` when called. This function can be called to cancel a scheduled stop.
|
||||
|
||||
```typescript
|
||||
type StopCancelFunction = (stopRunning: boolean = false) => void
|
||||
```
|
||||
|
||||
In the `StopCancelFunction`, if the `stopRunning` parameter is `true`, it stops the timeout or interval immediately. If `stopRunning` is `false`, it cancels the scheduled stop.
|
||||
|
||||
## Examples
|
||||
|
||||
1. Start a timeout that says "Hello, world!" after 10 seconds, then stop it after 5 seconds.
|
||||
|
||||
```typescript
|
||||
const sayHello = () => console.log('Hello, world!')
|
||||
|
||||
// Start a timeout that says "Hello, world!" after 10 seconds, and stop it after 5 seconds.
|
||||
let stopTimeout = Scheduler.startTimeout(sayHello, { ms: 10000 })
|
||||
stopTimeout({ ms: 5000 })
|
||||
```
|
||||
|
||||
2. Using startTimeout with a specific time of day
|
||||
|
||||
```typescript
|
||||
const goodMorning = () => console.log('Good morning!')
|
||||
let stopTimeout = Scheduler.startTimeout(goodMorning, {
|
||||
timeOfDay: { hour: 7, minute: 0 },
|
||||
})
|
||||
|
||||
// Later, if you want to cancel the morning greeting
|
||||
stopTimeout()
|
||||
```
|
||||
|
||||
In this example, the goodMorning function will be called at 7:00 AM. If you want to cancel the morning greeting (for example, the user chose to sleep in), you can call the stopTimeout function.
|
||||
|
||||
|
||||
3. Using startInterval with a specific interval, basically a regular setInterval
|
||||
|
||||
```typescript
|
||||
const sayHello = () => console.log('Hello, world!')
|
||||
let stopInterval = Scheduler.startInterval(sayHello, 1000)
|
||||
|
||||
// Later, if you want to stop the interval
|
||||
stopInterval()
|
||||
```
|
||||
|
||||
4. Using startInterval with a specific time of day
|
||||
|
||||
```typescript
|
||||
const sayHello = () => console.log('Hello, world!')
|
||||
let stopInterval = Scheduler.startInterval(sayHello, 1000, {
|
||||
timeOfDay: { hour: 7, minute: 0 },
|
||||
})
|
||||
|
||||
// Later, if you want to stop the interval
|
||||
stopInterval()
|
||||
```
|
||||
|
||||
In this example, the sayHello function will be called every 1000 milliseconds starting at 7:00 AM. If you want to cancel the morning greeting (for example, the user chose to sleep in), you can call the stopInterval function.
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
moduleFileExtensions: ['js', 'mjs'],
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "schedule-later",
|
||||
"version": "1.0.0",
|
||||
"main": "dist/Scheduler.js",
|
||||
"author": "Nightness",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"test": "./node_modules/.bin/jest --verbose",
|
||||
"watch": "./node_modules/.bin/jest --watch",
|
||||
"deploy": "npm run build && npm publish"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.3",
|
||||
"@types/node": "^18.11.18",
|
||||
"jest": "^29.6.1",
|
||||
"typescript": "^4.6.4"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
import Scheduler from './Scheduler'
|
||||
|
||||
describe('Scheduler', () => {
|
||||
beforeEach(() => {
|
||||
jest.useFakeTimers()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllTimers()
|
||||
})
|
||||
|
||||
test('startTimeout should correctly start and stop timeout', () => {
|
||||
const callback = jest.fn()
|
||||
|
||||
const stop = Scheduler.startTimeout(callback, { ms: 5000 })
|
||||
|
||||
// Advance timers by less than the delay and check if callback has not been called
|
||||
jest.advanceTimersByTime(2000)
|
||||
expect(callback).not.toBeCalled()
|
||||
|
||||
// Advance timers to exactly the delay and check if callback has been called
|
||||
jest.advanceTimersByTime(3000)
|
||||
expect(callback).toBeCalled()
|
||||
|
||||
// Cleanup
|
||||
stop()
|
||||
})
|
||||
|
||||
test('startInterval should correctly start and stop interval', () => {
|
||||
const callback = jest.fn()
|
||||
|
||||
const stop = Scheduler.startInterval(callback, 2000, { ms: 5000 })
|
||||
|
||||
// Advance timers by less than the initial delay and check if callback has not been called
|
||||
jest.advanceTimersByTime(2000)
|
||||
expect(callback).not.toBeCalled()
|
||||
|
||||
// Advance timers to exactly the delay and check if callback has been called
|
||||
jest.advanceTimersByTime(3000)
|
||||
expect(callback).toBeCalledTimes(1)
|
||||
|
||||
// Advance timers by the interval and check if callback has been called again
|
||||
jest.advanceTimersByTime(2000)
|
||||
expect(callback).toBeCalledTimes(2)
|
||||
|
||||
// Cleanup
|
||||
stop()
|
||||
})
|
||||
})
|
|
@ -0,0 +1,166 @@
|
|||
export interface TimeOfDay {
|
||||
hour: number
|
||||
minute?: number
|
||||
seconds?: number
|
||||
}
|
||||
export type TimeUntil = {
|
||||
timeOfDay?: TimeOfDay
|
||||
date?: Date
|
||||
ms?: number
|
||||
}
|
||||
type StopCancelFunction = (stopRunning: boolean) => void
|
||||
type StopFunction = (stopTime?: TimeUntil) => StopCancelFunction | null
|
||||
|
||||
export default class Scheduler {
|
||||
private static timeUntil(start: TimeUntil) {
|
||||
if (start.ms) {
|
||||
return start.ms
|
||||
}
|
||||
|
||||
// get the current date
|
||||
let now = new Date()
|
||||
|
||||
// set the target time
|
||||
let targetTime = !start.timeOfDay
|
||||
? start.date
|
||||
: new Date(
|
||||
now.getFullYear(),
|
||||
now.getMonth(),
|
||||
now.getDate(),
|
||||
start.timeOfDay.hour,
|
||||
start.timeOfDay.minute ?? 0,
|
||||
start.timeOfDay.seconds ?? 0,
|
||||
0
|
||||
)
|
||||
|
||||
// if the target time has already passed today, set it for tomorrow
|
||||
if (start.timeOfDay && now > targetTime) {
|
||||
targetTime.setDate(targetTime.getDate() + 1)
|
||||
}
|
||||
|
||||
// calculate the delay until the next target time
|
||||
// @ts-ignore - TS doesn't know this is allowed
|
||||
let delay = targetTime - now
|
||||
|
||||
return delay
|
||||
}
|
||||
|
||||
/**
|
||||
* Start a timeout
|
||||
* @param {Function} timerFunc
|
||||
* @param {TimeUntil} start
|
||||
* @return {StopFunction}
|
||||
*/
|
||||
public static startTimeout(
|
||||
timerFunc: Function,
|
||||
start: TimeUntil
|
||||
): StopFunction {
|
||||
let delay = this.timeUntil(start)
|
||||
|
||||
// set a timeout to start the interval at the target time
|
||||
let timeout: NodeJS.Timeout = null
|
||||
timeout = setTimeout(function () {
|
||||
// Clear the timeout variable
|
||||
timeout = null
|
||||
// call the function immediately
|
||||
timerFunc()
|
||||
}, delay)
|
||||
|
||||
const stopNow = () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
}
|
||||
|
||||
// stop() function to stop the interval and timeout
|
||||
// stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds
|
||||
// stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute
|
||||
const stop = (stopTime?: TimeUntil): StopCancelFunction => {
|
||||
if (stopTime === undefined) {
|
||||
stopNow()
|
||||
return null
|
||||
}
|
||||
|
||||
if ((stopTime as any) instanceof Date) {
|
||||
// @ts-ignore - TS doesn't know this is allowed
|
||||
const timeFromNow = stopTime - new Date()
|
||||
const stopTimeout = setTimeout(stopNow, timeFromNow)
|
||||
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
|
||||
return (stopRunning: boolean = false) => {
|
||||
clearTimeout(stopTimeout)
|
||||
if (stopRunning) stopNow()
|
||||
}
|
||||
}
|
||||
|
||||
const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime))
|
||||
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
|
||||
return (stopRunning: boolean = false) => {
|
||||
clearTimeout(stopTimeout)
|
||||
if (stopRunning) stopNow()
|
||||
}
|
||||
}
|
||||
|
||||
// return a cleanup function
|
||||
return stop
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an interval
|
||||
* @param {Function} intervalFunc
|
||||
* @param {number} intervalMS
|
||||
* @param {TimeUntil} start
|
||||
* @return {StopFunction}
|
||||
*/
|
||||
public static startInterval(
|
||||
intervalFunc: Function,
|
||||
intervalMS: number,
|
||||
start?: TimeUntil
|
||||
): StopFunction {
|
||||
let delay = this.timeUntil(start)
|
||||
|
||||
// set a timeout to start the interval at the target time
|
||||
let interval: number = null
|
||||
let timeout = setTimeout(function () {
|
||||
// Clear the timeout variable
|
||||
timeout = null
|
||||
// start the interval
|
||||
interval = setInterval(intervalFunc, intervalMS)
|
||||
// call the function immediately
|
||||
intervalFunc()
|
||||
}, delay)
|
||||
|
||||
const stopNow = () => {
|
||||
if (timeout) clearTimeout(timeout)
|
||||
if (interval) clearInterval(interval)
|
||||
}
|
||||
|
||||
// stop() function to stop the interval and timeout
|
||||
// stop(stopInMS) will stop the interval and timeout in stopInMS milliseconds
|
||||
// stop(stopHour, stopMinute) will stop the interval and timeout at the next stopHour:stopMinute
|
||||
const stop = (stopTime?: TimeUntil): StopCancelFunction => {
|
||||
if (stopTime === undefined) {
|
||||
stopNow()
|
||||
return null
|
||||
}
|
||||
|
||||
if ((stopTime as any) instanceof Date) {
|
||||
// @ts-ignore - TS doesn't know this is allowed
|
||||
const timeFromNow = stopTime - new Date()
|
||||
const stopTimeout = setTimeout(stopNow, timeFromNow)
|
||||
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
|
||||
return (stopRunning: boolean = false) => {
|
||||
clearTimeout(stopTimeout)
|
||||
if (stopRunning) stopNow()
|
||||
}
|
||||
}
|
||||
|
||||
const stopTimeout = setTimeout(stopNow, this.timeUntil(stopTime))
|
||||
// stopRunning is a boolean that will either cancel the stop (false), or stop the interval now (true)
|
||||
return (stopRunning: boolean = false) => {
|
||||
clearTimeout(stopTimeout)
|
||||
if (stopRunning) stopNow()
|
||||
}
|
||||
}
|
||||
|
||||
// return a cleanup function
|
||||
return stop
|
||||
}
|
||||
}
|
|
@ -0,0 +1,104 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||
|
||||
/* Projects */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||
// "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */
|
||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects */
|
||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||
|
||||
/* Language and Environment */
|
||||
"target": "es2022" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
|
||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||
"experimentalDecorators": true /* Enable experimental support for TC39 stage 2 draft decorators. */,
|
||||
"emitDecoratorMetadata": true /* Emit design-type metadata for decorated declarations in source files. */,
|
||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h' */
|
||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using `jsx: react-jsx*`.` */
|
||||
// "reactNamespace": "", /* Specify the object invoked for `createElement`. This only applies when targeting `react` JSX emit. */
|
||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||
|
||||
/* Modules */
|
||||
"module": "nodenext" /* Specify what module code is generated. */,
|
||||
// "rootDir": "./src" /* Specify the root folder within your source files. */,
|
||||
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||
"rootDirs": [
|
||||
"./prisma",
|
||||
"./src"
|
||||
] /* Allow multiple folders to be treated as one when resolving modules. */,
|
||||
// "typeRoots": [], /* Specify multiple folders that act like `./node_modules/@types`. */
|
||||
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||
// "resolveJsonModule": true, /* Enable importing .json files */
|
||||
// "noResolve": true, /* Disallow `import`s, `require`s or `<reference>`s from expanding the number of files TypeScript should add to a project. */
|
||||
|
||||
/* JavaScript Support */
|
||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
|
||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from `node_modules`. Only applicable with `allowJs`. */
|
||||
|
||||
/* Emit */
|
||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If `declaration` is true, also designates a file that bundles all .d.ts output. */
|
||||
"outDir": "./dist" /* Specify an output folder for all emitted files. */,
|
||||
// "removeComments": true, /* Disable emitting comments. */
|
||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types */
|
||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||
// "stripInternal": true, /* Disable emitting declarations that have `@internal` in their JSDoc comments. */
|
||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like `__extends` in compiled output. */
|
||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||
// "preserveConstEnums": true, /* Disable erasing `const enum` declarations in generated code. */
|
||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||
|
||||
/* Interop Constraints */
|
||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||
"esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */,
|
||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
|
||||
|
||||
/* Type Checking */
|
||||
"strict": false /* Enable all strict type-checking options. */,
|
||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied `any` type.. */
|
||||
// "strictNullChecks": true, /* When type checking, take into account `null` and `undefined`. */
|
||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||
// "strictBindCallApply": true, /* Check that the arguments for `bind`, `call`, and `apply` methods match the original function. */
|
||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||
// "noImplicitThis": true, /* Enable error reporting when `this` is given the type `any`. */
|
||||
// "useUnknownInCatchVariables": true, /* Type catch clause variables as 'unknown' instead of 'any'. */
|
||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||
// "noUnusedLocals": true, /* Enable error reporting when a local variables aren't read. */
|
||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read */
|
||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type */
|
||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||
|
||||
/* Completeness */
|
||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue