Full Code of husa/timer.js for AI

master c358c184a9db cached
12 files
27.9 KB
7.6k tokens
13 symbols
1 requests
Download .txt
Repository: husa/timer.js
Branch: master
Commit: c358c184a9db
Files: 12
Total size: 27.9 KB

Directory structure:
gitextract_qg53h0nv/

├── .editorconfig
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── LICENCE
├── README.md
├── eslint.config.mjs
├── package.json
└── src/
    ├── timer.d.ts
    ├── timer.js
    └── timer.test.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
# http://editorconfig.org

root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true


================================================
FILE: .github/workflows/ci.yml
================================================
name: CI

on:
  push:
  pull_request:

jobs:
  test:
    name: Lint & Test (Node ${{ matrix.node-version }})
    runs-on: ubuntu-latest

    strategy:
      fail-fast: false
      matrix:
        node-version: [20, 22, 24]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test

      - name: Build
        run: npm run build

  release:
    name: Build & Release
    runs-on: ubuntu-latest
    needs: test

    # Only run build on push to master
    if: github.event_name == 'push' && github.ref == 'refs/heads/master'

    permissions:
      contents: write # to be able to publish a GitHub release
      issues: write # to be able to comment on released issues
      pull-requests: write # to be able to comment on released pull requests
      id-token: write # to enable use of OIDC for trusted publishing and npm provenance

    steps:
      - name: Checkout repository
        uses: actions/checkout@v6

      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 24

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

      - name: Release
        run: npx semantic-release
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================


# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# 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/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# 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
.cache

# 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.*


# MacOS
.DS_Store


================================================
FILE: .husky/commit-msg
================================================
npx --no -- commitlint --edit "${1}"



================================================
FILE: .husky/pre-commit
================================================
npx lint-staged


================================================
FILE: LICENCE
================================================
The MIT License (MIT)

Copyright (c) 2013-2015 Yuriy Husnay

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.


================================================
FILE: README.md
================================================
# Timer.js

Simple and lightweight, zero-dependency library to create and manage, well, _timers_.

- Chainable API
- No runtime dependencies
- UMD build for browsers and CommonJS/ESM interop for Node.js
- TypeScript definitions included

## Install

```sh
npm install timer.js
```

## Quick start

```js
const Timer = require("timer.js");

const pizzaTimer = new Timer({
  onend: () => console.log("Pizza is ready"),
});

pizzaTimer.start(15 * 60);
```

## Usage

Timer.js is published as a UMD bundle and works in browsers and Node.js.

### Node.js (CommonJS)

```js
const Timer = require("timer.js");
const timer = new Timer();
```

### Node.js (ESM)

```js
import Timer from "timer.js";
const timer = new Timer();
```

### Browser (UMD)

```html
<script src="./node_modules/timer.js/dist/timer.js"></script>
<script>
  const timer = new Timer();
</script>
```

## API

All methods return `this` for chaining.

```js
myTimer.start(10).on("pause", doSmth).pause().on("end", doSmthElse).start(); // and so on
```

All callbacks and event handlers receive the timer instance as `this`.

### Constructor

```js
const timer = new Timer(options);
```

### Options

| Option    | Type                   | Default | Notes                                                |
| --------- | ---------------------- | ------- | ---------------------------------------------------- |
| `tick`    | `number`               | `1`     | Tick interval in seconds.                            |
| `onstart` | `(ms: number) => void` | `null`  | Called when the timer starts. Receives remaining ms. |
| `ontick`  | `(ms: number) => void` | `null`  | Called on each tick. Receives remaining ms.          |
| `onpause` | `() => void`           | `null`  | Called when the timer pauses.                        |
| `onstop`  | `() => void`           | `null`  | Called when the timer stops.                         |
| `onend`   | `() => void`           | `null`  | Called when the timer ends naturally.                |

### Events

You can register handlers with `on()` using either `"event"` or `"onevent"` names.

- `start` or `onstart`
- `tick` or `ontick`
- `pause` or `onpause`
- `stop` or `onstop`
- `end` or `onend`

### Methods

- `start(durationSeconds?)` Start a countdown in seconds. If omitted, it reuses the last duration (e.g., resume after pause).
- `pause()` Pause a running timer and preserve remaining time (no-op if not started).
- `stop()` Stop the timer and reset remaining time to 0 (no-op if not started or paused).
- `getDuration()` Return remaining time in milliseconds.
- `getStatus()` Return `"initialized" | "started" | "paused" | "stopped"`.
- `options(options | key, value)` Set one or many options.
- `on(event, handler)` Register an event handler.
- `off(event)` Remove handlers; `"all"` resets everything to defaults.
- `measureStart(label?)` Start a measurement timer (label optional).
- `measureStop(label?)` Stop the measurement and return elapsed ms.

### Behavior notes

- `start()` without a valid duration only works when resuming a paused timer.
- `start()` while already started is a no-op.
- `getDuration()` returns `0` when the timer is not started or paused.

## TypeScript

Type definitions are shipped in `dist/timer.d.ts`.

```ts
import Timer from "timer.js";

const timer = new Timer({
  ontick: (ms) => console.log(ms),
});
```

## Releasing

Releases are automated via `semantic-release` on the `master` branch and require
conventional commit messages. Commit hooks are enforced via Husky + lint-staged.

## Contributing

Issues and pull requests are welcome. Feel free to create an issue or a pull request.
A minimal repro when creating an issue is much appreciated.

## License

[MIT](LICENCE)


================================================
FILE: eslint.config.mjs
================================================
import js from "@eslint/js";
import globals from "globals";
import { defineConfig } from "eslint/config";

export default defineConfig([
  {
    files: ["**/*.{js,mjs,cjs}"],
    plugins: { js },
    extends: ["js/recommended"],
    languageOptions: { globals: { ...globals.browser, ...globals.node } },
  },
]);


================================================
FILE: package.json
================================================
{
  "name": "timer.js",
  "version": "1.0.4",
  "description": "Simple and lighweight but powerfull eventdriven JavaScript timer",
  "main": "dist/timer.js",
  "types": "dist/timer.d.ts",
  "repository": {
    "type": "git",
    "url": "git@github.com:husa/timer.js.git"
  },
  "bugs": {
    "url": "http://github.com/husa/timer.js/issues"
  },
  "homepage": "https://github.com/husa/timer.js",
  "keywords": [
    "timer.js",
    "timer",
    "time",
    "measure",
    "start",
    "stop",
    "pause"
  ],
  "author": "husa",
  "license": "MIT",
  "scripts": {
    "lint": "prettier --check src && eslint src",
    "test": "node --test src/timer.test.js",
    "prepare": "husky",
    "build": "mkdir -p dist && cp src/timer.js dist/timer.js && cp src/timer.d.ts dist/timer.d.ts"
  },
  "lint-staged": {
    "*.{js,jsx,ts,tsx}": [
      "prettier --write",
      "eslint --fix"
    ],
    "*.{css,scss,md}": "prettier --write"
  },
  "commitlint": {
    "extends": [
      "@commitlint/config-conventional"
    ]
  },
  "release": {
    "branches": [
      "master"
    ],
    "plugins": [
      "@semantic-release/commit-analyzer",
      "@semantic-release/release-notes-generator",
      "@semantic-release/npm",
      "@semantic-release/github"
    ]
  },
  "devDependencies": {
    "@commitlint/cli": "^20.3.1",
    "@commitlint/config-conventional": "^20.3.1",
    "@eslint/js": "^9.39.2",
    "eslint": "^9.39.2",
    "globals": "^17.2.0",
    "husky": "^9.1.7",
    "lint-staged": "^16.2.7",
    "prettier": "^3.8.1"
  }
}


================================================
FILE: src/timer.d.ts
================================================
declare namespace Timer {
  /** Timer state values. */
  type Status = "initialized" | "started" | "paused" | "stopped";

  /** Called when the timer starts. Receives remaining duration in ms. */
  type StartHandler = (durationMs: number) => void;
  /** Called on each tick. Receives remaining duration in ms. */
  type TickHandler = (remainingMs: number) => void;
  /** Called when the timer pauses. */
  type PauseHandler = () => void;
  /** Called when the timer stops. */
  type StopHandler = () => void;
  /** Called when the timer ends naturally. */
  type EndHandler = () => void;

  interface Options {
    /** Tick interval in seconds. */
    tick?: number;
    /** Start callback. */
    onstart?: StartHandler | null;
    /** Tick callback. */
    ontick?: TickHandler | null;
    /** Pause callback. */
    onpause?: PauseHandler | null;
    /** Stop callback. */
    onstop?: StopHandler | null;
    /** End callback. */
    onend?: EndHandler | null;
  }

  /** Event names without the "on" prefix. */
  type EventName = "start" | "tick" | "pause" | "stop" | "end";
  /** Event names with the "on" prefix. */
  type EventKey = "onstart" | "ontick" | "onpause" | "onstop" | "onend";
}

/** Lightweight countdown timer. */
interface Timer {
  /**
   * Start the timer.
   * @param durationSeconds Optional duration in seconds. If omitted, reuses the last duration.
   * @example
   * const timer = Timer();
   * timer.start(5);
   */
  start(durationSeconds?: number): this;
  /** Pause the timer, preserving remaining time. */
  pause(): this;
  /** Stop the timer and reset remaining time to 0. */
  stop(): this;
  /** Remaining time in milliseconds. */
  getDuration(): number;
  /** Current lifecycle status. */
  getStatus(): Timer.Status;

  /** Get or set options. */
  options(): this;
  /** Set options in bulk. */
  options(options: Timer.Options): this;
  /** Set a single option. */
  options<K extends keyof Timer.Options>(
    option: K,
    value: Timer.Options[K],
  ): this;
  /** Set a custom option by name. */
  options(option: string, value: unknown): this;

  /** Register a start handler. */
  on(event: "start" | "onstart", handler: Timer.StartHandler): this;
  /** Register a tick handler. */
  on(event: "tick" | "ontick", handler: Timer.TickHandler): this;
  /** Register a pause handler. */
  on(event: "pause" | "onpause", handler: Timer.PauseHandler): this;
  /** Register a stop handler. */
  on(event: "stop" | "onstop", handler: Timer.StopHandler): this;
  /** Register an end handler. */
  on(event: "end" | "onend", handler: Timer.EndHandler): this;
  /** Register a handler for a custom event key. */
  on(event: string, handler: (...args: unknown[]) => void): this;

  /** Remove all handlers. */
  off(): this;
  /** Remove handlers for a specific event. */
  off(event: "all" | Timer.EventName | Timer.EventKey): this;
  /** Remove handlers for a custom event key. */
  off(event: string): this;

  /** Start a measurement timer for the given label. */
  measureStart(label?: string): this;
  /** Stop a measurement timer and return elapsed ms. */
  measureStop(label?: string): number;
}

/** Timer constructor. */
declare const Timer: {
  /** Create a new timer instance. */
  new (options?: Timer.Options): Timer;
  /** Create a new timer instance without `new`. */
  (options?: Timer.Options): Timer;
  prototype: Timer;
};

export = Timer;
export as namespace Timer;


================================================
FILE: src/timer.js
================================================
(function (root, factory) {
  "use strict";
  // eslint-disable-next-line no-undef
  if (typeof define === "function" && define.amd) define([], factory);
  else if (typeof exports === "object") module.exports = factory();
  else root.Timer = factory();
})(this, function () {
  "use strict";

  var defaultOptions = {
    tick: 1,
    onstart: null,
    ontick: null,
    onpause: null,
    onstop: null,
    onend: null,
  };

  var Timer = function (options) {
    if (!(this instanceof Timer)) return new Timer(options);
    this._ = {
      id: +new Date(),
      options: {},
      duration: 0,
      status: "initialized",
      start: 0,
      measures: [],
    };
    for (var prop in defaultOptions)
      this._.options[prop] = defaultOptions[prop];
    this.options(options);
  };

  Timer.prototype.start = function (duration) {
    // timer already running
    if (this._.timeout && this._.status === "started") return this;
    const isDurationValid = typeof duration === "number" && duration > 0;
    // no valid duration provided and timer isn't "on pause"
    if (!isDurationValid && !this._.duration) return this;
    this._.duration = duration * 1000 || this._.duration;
    this._.timeout = setTimeout(end.bind(this), this._.duration);
    if (typeof this._.options.ontick === "function")
      this._.interval = setInterval(
        function () {
          trigger.call(this, "ontick", this.getDuration());
        }.bind(this),
        +this._.options.tick * 1000,
      );
    this._.start = +new Date();
    this._.status = "started";
    trigger.call(this, "onstart", this.getDuration());
    return this;
  };

  Timer.prototype.pause = function () {
    if (this._.status !== "started") return this;
    this._.duration -= +new Date() - this._.start;
    clear.call(this, false);
    this._.status = "paused";
    trigger.call(this, "onpause");
    return this;
  };

  Timer.prototype.stop = function () {
    if (!/started|paused/.test(this._.status)) return this;
    clear.call(this, true);
    this._.status = "stopped";
    trigger.call(this, "onstop");
    return this;
  };

  Timer.prototype.getDuration = function () {
    if (this._.status === "started")
      return this._.duration - (+new Date() - this._.start);
    if (this._.status === "paused") return this._.duration;
    return 0;
  };

  Timer.prototype.getStatus = function () {
    return this._.status;
  };

  Timer.prototype.options = function (option, value) {
    if (option && value) this._.options[option] = value;
    if (!value && typeof option === "object")
      for (var prop in option)
        if (Object.prototype.hasOwnProperty.call(this._.options, prop))
          this._.options[prop] = option[prop];
    return this;
  };

  Timer.prototype.on = function (option, value) {
    if (typeof option !== "string" || typeof value !== "function") return this;
    if (!/^on/.test(option)) option = "on" + option;
    if (Object.prototype.hasOwnProperty.call(this._.options, option))
      this._.options[option] = value;
    return this;
  };

  Timer.prototype.off = function (option) {
    if (typeof option !== "string") return this;
    option = option.toLowerCase();
    if (option === "all") {
      this._.options = defaultOptions;
      return this;
    }
    if (!/^on/.test(option)) option = "on" + option;
    if (Object.prototype.hasOwnProperty.call(this._.options, option))
      this._.options[option] = defaultOptions[option];
    return this;
  };

  Timer.prototype.measureStart = function (label) {
    this._.measures[label || ""] = +new Date();
    return this;
  };

  Timer.prototype.measureStop = function (label) {
    return +new Date() - this._.measures[label || ""];
  };

  function end() {
    clear.call(this);
    this._.status = "stopped";
    trigger.call(this, "onend");
  }

  function trigger(event) {
    var callback = this._.options[event],
      args = [].slice.call(arguments, 1);
    typeof callback === "function" && callback.apply(this, args);
  }

  function clear(clearDuration) {
    clearTimeout(this._.timeout);
    clearInterval(this._.interval);
    if (clearDuration === true) this._.duration = 0;
  }

  return Timer;
});


================================================
FILE: src/timer.test.js
================================================
const assert = require("node:assert/strict");
const { describe, it, beforeEach, afterEach, mock } = require("node:test");
const Timer = require("./timer");

describe("Timer", () => {
  let timer;
  let start;
  let stop;
  let pause;
  let end;
  let tick;

  beforeEach(() => {
    timer = new Timer();
    start = mock.fn();
    pause = mock.fn();
    stop = mock.fn();
    tick = mock.fn();
    end = mock.fn();

    mock.timers.enable({
      apis: ["setTimeout", "setInterval", "Date"],
    });
    mock.timers.setTime(0);
  });

  afterEach(() => {
    mock.timers.reset();
  });

  it("should be available as global", () => {
    assert.ok(Timer);
  });

  describe("#constructor", () => {
    it('should self invoke without "new" keyword', () => {
      const timer = new Timer();
      const timer2 = Timer();

      assert.ok(timer instanceof Timer);
      assert.ok(timer2 instanceof Timer);
    });

    it("should accept object as arguments", () => {
      timer = new Timer({
        onstart: start,
        onpause: pause,
        onstop: stop,
      });

      timer.start();
      assert.equal(start.mock.callCount(), 0);
      timer.start(10);
      assert.equal(start.mock.callCount(), 1);
      timer.pause();
      assert.equal(pause.mock.callCount(), 1);
      timer.stop();
      assert.equal(stop.mock.callCount(), 1);
    });
  });

  describe("#getStatus", () => {
    it("should always be string", () => {
      assert.equal(typeof timer.getStatus(), "string");
    });

    it("should be valid status", () => {
      const match = /^(initialized|started|paused|stopped|finished)$/;
      assert.match(timer.getStatus(), match);
    });
  });

  describe("#getDuration", () => {
    it("should return 0 if timer isn't started or paused", () => {
      assert.equal(timer.getDuration(), 0);
      timer.start(10);
      assert.equal(timer.getDuration(), 10000);
      timer.pause();
      assert.equal(timer.getDuration(), 10000);
      timer.stop();
      assert.equal(timer.getDuration(), 0);
    });

    it("should return actual value", () => {
      timer.start(10);
      mock.timers.tick(100);
      assert.equal(timer.getDuration(), 9900);
      mock.timers.tick(1100);
      assert.equal(timer.getDuration(), 8800);
      timer.pause();
      mock.timers.tick(100);
      assert.equal(timer.getDuration(), 8800);
      timer.start();
      mock.timers.tick(100);
      assert.equal(timer.getDuration(), 8700);
    });
  });

  describe("#start", () => {
    it("should not change status if no arguments", () => {
      timer.start();
      assert.equal(timer.getStatus(), "initialized");
      timer.start(10);
      assert.equal(timer.getStatus(), "started");
      timer.start();
      assert.equal(timer.getStatus(), "started");
    });

    it("should ignore zero duration when no previous duration", () => {
      timer.on("start", start);
      timer.on("end", end);
      timer.start(0);
      assert.equal(timer.getStatus(), "initialized");
      assert.equal(start.mock.callCount(), 0);
      mock.timers.tick(1000);
      assert.equal(end.mock.callCount(), 0);
    });

    it("should treat negative duration the same as zero", () => {
      timer.on("start", start);
      timer.on("end", end);
      timer.start(-5);
      assert.equal(timer.getStatus(), "initialized");
      assert.equal(start.mock.callCount(), 0);
      mock.timers.tick(1000);
      assert.equal(end.mock.callCount(), 0);
    });

    it('should change status to "started" if valid arguments', () => {
      assert.equal(timer.getStatus(), "initialized");
      timer.start(10);
      assert.equal(timer.getStatus(), "started");
    });

    it('should trigger "onstart" callback', () => {
      timer.on("start", start);
      timer.start(1);
      assert.equal(start.mock.callCount(), 1);
      assert.deepEqual(start.mock.calls[0].arguments, [1000]);
    });

    it("should resume timer after pause", () => {
      timer.on("end", end);
      timer.start(5);
      mock.timers.tick(1000);
      timer.pause();
      assert.equal(timer.getStatus(), "paused");
      timer.start();
      assert.equal(timer.getStatus(), "started");
      mock.timers.tick(3900);
      assert.equal(timer.getDuration(), 100);
      assert.equal(timer.getStatus(), "started");
      mock.timers.tick(101);
      assert.equal(timer.getStatus(), "stopped");
      assert.equal(end.mock.callCount(), 1);
    });

    it("should restart timer if argument provided after pause", () => {
      timer.on("end", end);
      timer.start(5);
      mock.timers.tick(1000);
      timer.pause();
      assert.equal(timer.getStatus(), "paused");
      timer.start(10);
      assert.equal(timer.getDuration(), 10000);
      mock.timers.tick(4001);
      assert.equal(end.mock.callCount(), 0);
      assert.equal(timer.getDuration(), 5999);
      mock.timers.tick(6000);
      assert.equal(end.mock.callCount(), 1);
    });

    it('should ignore start calls if already "started"', () => {
      timer.on("start", start);
      timer.start(5);
      assert.equal(timer.getDuration(), 5000);
      assert.equal(start.mock.callCount(), 1);
      // any new .start calls are ignored
      timer.start(10);
      assert.equal(start.mock.callCount(), 1);
      assert.equal(timer.getDuration(), 5000);
      timer.start([{ what: "ever" }]);
      assert.equal(start.mock.callCount(), 1);
      assert.equal(timer.getDuration(), 5000);
    });
  });

  describe("#pause", () => {
    it("should return if timer hasn't started", () => {
      timer.on("pause", pause);
      timer.pause();
      assert.equal(timer.getStatus(), "initialized");
      assert.equal(pause.mock.callCount(), 0);
    });

    it('should change status to "paused"', () => {
      timer.start(1);
      timer.pause();
      assert.equal(timer.getStatus(), "paused");
    });

    it('should trigger "onpause" callback', () => {
      timer.on("pause", pause);
      timer.start(1);
      timer.pause();
      assert.equal(pause.mock.callCount(), 1);
      timer.pause();
      assert.equal(pause.mock.callCount(), 1);
    });
  });

  describe("#stop", () => {
    it("should return if timer hasn't started", () => {
      timer.on("stop", stop);
      timer.stop();
      assert.equal(timer.getStatus(), "initialized");
      assert.equal(stop.mock.callCount(), 0);
    });

    it('should change status to "stopped" after start', () => {
      timer.on("stop", stop);
      timer.start(1);
      timer.stop();
      assert.equal(timer.getStatus(), "stopped");
      assert.equal(stop.mock.callCount(), 1);
    });

    it('should change status to "stopped" after pause', () => {
      timer.on("stop", stop);
      timer.start(1);
      timer.pause();
      timer.stop();
      assert.equal(timer.getStatus(), "stopped");
      assert.equal(stop.mock.callCount(), 1);
    });

    it('should trigger "onstop" callback', () => {
      timer.on("stop", stop);
      timer.start(1);
      timer.stop();
      assert.equal(stop.mock.callCount(), 1);
      timer.stop();
      assert.equal(stop.mock.callCount(), 1);
    });
  });

  describe("#on", () => {
    it("should attach start callback", () => {
      timer.on("start", start);
      timer.start(1);
      assert.equal(start.mock.callCount(), 1);
    });

    it("should attach pause callback", () => {
      timer.on("pause", pause);
      timer.start(1);
      timer.pause();
      assert.equal(pause.mock.callCount(), 1);
    });

    it("should attach stop callback", () => {
      timer.on("stop", stop);
      timer.start(1);
      timer.stop();
      assert.equal(stop.mock.callCount(), 1);
    });

    it("should attach end callback", () => {
      timer.on("end", end);
      timer.start(1);
      mock.timers.tick(1001);
      assert.equal(end.mock.callCount(), 1);
    });

    it("should attach tick callback", () => {
      timer.on("tick", tick);
      timer.start(2);
      mock.timers.tick(1001);
      assert.equal(tick.mock.callCount(), 1);
    });

    it('should accept options with/without "on"', () => {
      timer.on("tick", tick);
      timer.on("onstart", start);
      timer.on("onstop", stop);
      timer.start(2);
      mock.timers.tick(1001);
      timer.stop();
      assert.equal(start.mock.callCount(), 1);
      assert.equal(tick.mock.callCount(), 1);
      assert.equal(stop.mock.callCount(), 1);
    });
  });

  describe("#off", () => {
    beforeEach(() => {
      timer.on("tick", tick);
      timer.on("onstart", start);
      timer.on("stop", stop);
    });

    it("should remove callbacks", () => {
      timer.off("tick");
      timer.off("onstart");
      timer.off("stop");
      timer.start(2);
      mock.timers.tick(1900);
      timer.stop();
      assert.equal(start.mock.callCount(), 0);
      assert.equal(tick.mock.callCount(), 0);
      assert.equal(stop.mock.callCount(), 0);
    });

    it('should remove all callbacks if "all" passed', () => {
      timer.off("all");
      timer.start(2);
      mock.timers.tick(1900);
      timer.stop();
      assert.equal(start.mock.callCount(), 0);
      assert.equal(tick.mock.callCount(), 0);
      assert.equal(stop.mock.callCount(), 0);
    });
  });

  describe("#callbacks execution", () => {
    beforeEach(() => {
      timer.options({
        onstart: start,
        ontick: tick,
        onpause: pause,
        onend: end,
        onstop: stop,
      });
    });

    it('should trigger "tick" every second', () => {
      timer.start(3);
      mock.timers.tick(1001);
      assert.equal(tick.mock.callCount(), 1);
      mock.timers.tick(1000);
      assert.equal(tick.mock.callCount(), 2);
      mock.timers.tick(1000);
      assert.equal(tick.mock.callCount(), 2);
    });

    it('should trigger "end"', () => {
      timer.start(2);
      mock.timers.tick(2001);
      assert.equal(end.mock.callCount(), 1);
    });

    it('should not trigger "end" if stopped', () => {
      timer.start(2);
      mock.timers.tick(1900);
      timer.stop();
      mock.timers.tick(1000);
      assert.equal(end.mock.callCount(), 0);
      assert.equal(stop.mock.callCount(), 1);
    });
  });

  describe("#chaining", () => {
    it("should chain any way", () => {
      assert.doesNotThrow(() => {
        timer
          .pause()
          .stop()
          .start()
          .start(20)
          .stop()
          .pause()
          .start()
          .on()
          .off()
          .options()
          .stop();
      });
    });
  });
});
Download .txt
gitextract_qg53h0nv/

├── .editorconfig
├── .github/
│   └── workflows/
│       └── ci.yml
├── .gitignore
├── .husky/
│   ├── commit-msg
│   └── pre-commit
├── LICENCE
├── README.md
├── eslint.config.mjs
├── package.json
└── src/
    ├── timer.d.ts
    ├── timer.js
    └── timer.test.js
Download .txt
SYMBOL INDEX (13 symbols across 2 files)

FILE: src/timer.d.ts
  type Status (line 3) | type Status = "initialized" | "started" | "paused" | "stopped";
  type StartHandler (line 6) | type StartHandler = (durationMs: number) => void;
  type TickHandler (line 8) | type TickHandler = (remainingMs: number) => void;
  type PauseHandler (line 10) | type PauseHandler = () => void;
  type StopHandler (line 12) | type StopHandler = () => void;
  type EndHandler (line 14) | type EndHandler = () => void;
  type Options (line 16) | interface Options {
  type EventName (line 32) | type EventName = "start" | "tick" | "pause" | "stop" | "end";
  type EventKey (line 34) | type EventKey = "onstart" | "ontick" | "onpause" | "onstop" | "onend";
  type Timer (line 38) | interface Timer {

FILE: src/timer.js
  function end (line 122) | function end() {
  function trigger (line 128) | function trigger(event) {
  function clear (line 134) | function clear(clearDuration) {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (31K chars).
[
  {
    "path": ".editorconfig",
    "chars": 174,
    "preview": "# http://editorconfig.org\n\nroot = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert"
  },
  {
    "path": ".github/workflows/ci.yml",
    "chars": 1551,
    "preview": "name: CI\n\non:\n  push:\n  pull_request:\n\njobs:\n  test:\n    name: Lint & Test (Node ${{ matrix.node-version }})\n    runs-on"
  },
  {
    "path": ".gitignore",
    "chars": 2069,
    "preview": "\n\n# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\nlerna-debug.log*\n.pnpm-debug.log*\n\n# Diagnostic repor"
  },
  {
    "path": ".husky/commit-msg",
    "chars": 38,
    "preview": "npx --no -- commitlint --edit \"${1}\"\n\n"
  },
  {
    "path": ".husky/pre-commit",
    "chars": 16,
    "preview": "npx lint-staged\n"
  },
  {
    "path": "LICENCE",
    "chars": 1084,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2013-2015 Yuriy Husnay\n\nPermission is hereby granted, free of charge, to any person"
  },
  {
    "path": "README.md",
    "chars": 3721,
    "preview": "# Timer.js\n\nSimple and lightweight, zero-dependency library to create and manage, well, _timers_.\n\n- Chainable API\n- No "
  },
  {
    "path": "eslint.config.mjs",
    "chars": 313,
    "preview": "import js from \"@eslint/js\";\nimport globals from \"globals\";\nimport { defineConfig } from \"eslint/config\";\n\nexport defaul"
  },
  {
    "path": "package.json",
    "chars": 1532,
    "preview": "{\n  \"name\": \"timer.js\",\n  \"version\": \"1.0.4\",\n  \"description\": \"Simple and lighweight but powerfull eventdriven JavaScri"
  },
  {
    "path": "src/timer.d.ts",
    "chars": 3424,
    "preview": "declare namespace Timer {\n  /** Timer state values. */\n  type Status = \"initialized\" | \"started\" | \"paused\" | \"stopped\";"
  },
  {
    "path": "src/timer.js",
    "chars": 4186,
    "preview": "(function (root, factory) {\n  \"use strict\";\n  // eslint-disable-next-line no-undef\n  if (typeof define === \"function\" &&"
  },
  {
    "path": "src/timer.test.js",
    "chars": 10455,
    "preview": "const assert = require(\"node:assert/strict\");\nconst { describe, it, beforeEach, afterEach, mock } = require(\"node:test\")"
  }
]

About this extraction

This page contains the full source code of the husa/timer.js GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (27.9 KB), approximately 7.6k tokens, and a symbol index with 13 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!