@gjsify/timers 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @gjsify/timers
2
+
3
+ GJS implementation of the Node.js `timers` module. Provides setTimeout, setInterval, setImmediate, and their promises API.
4
+
5
+ Part of the [gjsify](https://github.com/gjsify/gjsify) project — Node.js and Web APIs for GJS (GNOME JavaScript).
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @gjsify/timers
11
+ # or
12
+ yarn add @gjsify/timers
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { setTimeout, setInterval, setImmediate } from '@gjsify/timers';
19
+ import { setTimeout as delay } from '@gjsify/timers/promises';
20
+
21
+ setTimeout(() => console.log('hello'), 1000);
22
+
23
+ await delay(1000);
24
+ console.log('1 second later');
25
+ ```
26
+
27
+ ## License
28
+
29
+ MIT
@@ -0,0 +1,52 @@
1
+ import { Timeout, Immediate } from "./timeout.js";
2
+ function _setTimeout(callback, delay = 0, ...args) {
3
+ return new Timeout(callback, delay, args, false);
4
+ }
5
+ function _clearTimeout(timeout) {
6
+ if (timeout instanceof Timeout) {
7
+ timeout.close();
8
+ } else if (timeout != null) {
9
+ clearTimeout(timeout);
10
+ }
11
+ }
12
+ function _setInterval(callback, delay = 0, ...args) {
13
+ return new Timeout(callback, delay, args, true);
14
+ }
15
+ function _clearInterval(timeout) {
16
+ if (timeout instanceof Timeout) {
17
+ timeout.close();
18
+ } else if (timeout != null) {
19
+ clearInterval(timeout);
20
+ }
21
+ }
22
+ function _setImmediate(callback, ...args) {
23
+ return new Immediate(callback, args);
24
+ }
25
+ function _clearImmediate(immediate) {
26
+ if (immediate instanceof Immediate) {
27
+ immediate.close();
28
+ } else if (immediate != null) {
29
+ clearTimeout(immediate);
30
+ }
31
+ }
32
+ var index_default = {
33
+ setTimeout: _setTimeout,
34
+ clearTimeout: _clearTimeout,
35
+ setInterval: _setInterval,
36
+ clearInterval: _clearInterval,
37
+ setImmediate: _setImmediate,
38
+ clearImmediate: _clearImmediate,
39
+ Timeout,
40
+ Immediate
41
+ };
42
+ export {
43
+ Immediate,
44
+ Timeout,
45
+ _clearImmediate as clearImmediate,
46
+ _clearInterval as clearInterval,
47
+ _clearTimeout as clearTimeout,
48
+ index_default as default,
49
+ _setImmediate as setImmediate,
50
+ _setInterval as setInterval,
51
+ _setTimeout as setTimeout
52
+ };
@@ -0,0 +1,65 @@
1
+ import { Timeout } from "./timeout.js";
2
+ function setTimeout(delay = 0, value, options) {
3
+ return new Promise((resolve, reject) => {
4
+ if (options?.signal?.aborted) {
5
+ reject(options.signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
6
+ return;
7
+ }
8
+ const timeout = new Timeout(() => {
9
+ cleanup();
10
+ resolve(value);
11
+ }, delay, [], false);
12
+ if (options?.ref === false) timeout.unref();
13
+ let onAbort;
14
+ function cleanup() {
15
+ if (onAbort && options?.signal) {
16
+ options.signal.removeEventListener("abort", onAbort);
17
+ }
18
+ }
19
+ if (options?.signal) {
20
+ onAbort = () => {
21
+ timeout.close();
22
+ reject(options.signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
23
+ };
24
+ options.signal.addEventListener("abort", onAbort, { once: true });
25
+ }
26
+ });
27
+ }
28
+ function setImmediate(value, options) {
29
+ return setTimeout(0, value, options);
30
+ }
31
+ async function* setInterval(delay = 0, value, options) {
32
+ if (options?.signal?.aborted) {
33
+ throw options.signal.reason ?? new DOMException("The operation was aborted", "AbortError");
34
+ }
35
+ while (true) {
36
+ if (options?.signal?.aborted) {
37
+ throw options.signal.reason ?? new DOMException("The operation was aborted", "AbortError");
38
+ }
39
+ yield await new Promise((resolve, reject) => {
40
+ const timeout = new Timeout(() => {
41
+ cleanup();
42
+ resolve(value);
43
+ }, delay, [], false);
44
+ if (options?.ref === false) timeout.unref();
45
+ let onAbort;
46
+ function cleanup() {
47
+ if (onAbort && options?.signal) {
48
+ options.signal.removeEventListener("abort", onAbort);
49
+ }
50
+ }
51
+ if (options?.signal) {
52
+ onAbort = () => {
53
+ timeout.close();
54
+ reject(options.signal.reason ?? new DOMException("The operation was aborted", "AbortError"));
55
+ };
56
+ options.signal.addEventListener("abort", onAbort, { once: true });
57
+ }
58
+ });
59
+ }
60
+ }
61
+ export {
62
+ setImmediate,
63
+ setInterval,
64
+ setTimeout
65
+ };
@@ -0,0 +1,106 @@
1
+ class Timeout {
2
+ _id;
3
+ _ref = true;
4
+ _callback;
5
+ _delay;
6
+ _args;
7
+ _isInterval;
8
+ constructor(callback, delay, args, isInterval) {
9
+ this._callback = callback;
10
+ this._delay = delay;
11
+ this._args = args;
12
+ this._isInterval = isInterval;
13
+ if (isInterval) {
14
+ this._id = setInterval(callback, delay, ...args);
15
+ } else {
16
+ this._id = setTimeout(callback, delay, ...args);
17
+ }
18
+ }
19
+ /**
20
+ * Mark this timeout as referenced (default).
21
+ * Referenced timers keep the event loop alive.
22
+ */
23
+ ref() {
24
+ this._ref = true;
25
+ return this;
26
+ }
27
+ /**
28
+ * Mark this timeout as unreferenced.
29
+ * Unreferenced timers do not keep the event loop alive.
30
+ */
31
+ unref() {
32
+ this._ref = false;
33
+ return this;
34
+ }
35
+ /** Whether this timeout is referenced. */
36
+ hasRef() {
37
+ return this._ref;
38
+ }
39
+ /**
40
+ * Reset the timer's start time to now and reschedule.
41
+ */
42
+ refresh() {
43
+ if (this._id != null) {
44
+ if (this._isInterval) {
45
+ clearInterval(this._id);
46
+ this._id = setInterval(this._callback, this._delay, ...this._args);
47
+ } else {
48
+ clearTimeout(this._id);
49
+ this._id = setTimeout(this._callback, this._delay, ...this._args);
50
+ }
51
+ }
52
+ return this;
53
+ }
54
+ /** Close/clear this timer. */
55
+ close() {
56
+ if (this._id != null) {
57
+ if (this._isInterval) {
58
+ clearInterval(this._id);
59
+ } else {
60
+ clearTimeout(this._id);
61
+ }
62
+ this._id = null;
63
+ }
64
+ }
65
+ /** Get the underlying timer ID (for clearTimeout/clearInterval). */
66
+ [Symbol.toPrimitive]() {
67
+ return this._id;
68
+ }
69
+ }
70
+ class Immediate {
71
+ _id = null;
72
+ _ref = true;
73
+ _cancelled = false;
74
+ constructor(callback, args) {
75
+ Promise.resolve().then(() => {
76
+ if (!this._cancelled) {
77
+ callback(...args);
78
+ }
79
+ });
80
+ }
81
+ ref() {
82
+ this._ref = true;
83
+ return this;
84
+ }
85
+ unref() {
86
+ this._ref = false;
87
+ return this;
88
+ }
89
+ hasRef() {
90
+ return this._ref;
91
+ }
92
+ close() {
93
+ this._cancelled = true;
94
+ if (this._id != null) {
95
+ clearTimeout(this._id);
96
+ this._id = null;
97
+ }
98
+ }
99
+ [Symbol.toPrimitive]() {
100
+ return this._id;
101
+ }
102
+ }
103
+ export {
104
+ Immediate,
105
+ Timeout
106
+ };
@@ -0,0 +1,41 @@
1
+ import { Timeout, Immediate } from './timeout.js';
2
+ export { Timeout, Immediate };
3
+ /**
4
+ * Schedule a callback to be called after `delay` milliseconds.
5
+ * Returns a Timeout object with ref/unref/refresh methods.
6
+ */
7
+ declare function _setTimeout<T extends any[]>(callback: (...args: T) => void, delay?: number, ...args: T): Timeout;
8
+ /**
9
+ * Cancel a timeout created by setTimeout.
10
+ */
11
+ declare function _clearTimeout(timeout: Timeout | number | undefined): void;
12
+ /**
13
+ * Schedule a callback to be called repeatedly every `delay` milliseconds.
14
+ * Returns a Timeout object with ref/unref/refresh methods.
15
+ */
16
+ declare function _setInterval<T extends any[]>(callback: (...args: T) => void, delay?: number, ...args: T): Timeout;
17
+ /**
18
+ * Cancel an interval created by setInterval.
19
+ */
20
+ declare function _clearInterval(timeout: Timeout | number | undefined): void;
21
+ /**
22
+ * Schedule a callback to be called on the next iteration of the event loop.
23
+ * Returns an Immediate object with ref/unref methods.
24
+ */
25
+ declare function _setImmediate<T extends any[]>(callback: (...args: T) => void, ...args: T): Immediate;
26
+ /**
27
+ * Cancel an immediate created by setImmediate.
28
+ */
29
+ declare function _clearImmediate(immediate: Immediate | number | undefined): void;
30
+ export { _setTimeout as setTimeout, _clearTimeout as clearTimeout, _setInterval as setInterval, _clearInterval as clearInterval, _setImmediate as setImmediate, _clearImmediate as clearImmediate, };
31
+ declare const _default: {
32
+ setTimeout: typeof _setTimeout;
33
+ clearTimeout: typeof _clearTimeout;
34
+ setInterval: typeof _setInterval;
35
+ clearInterval: typeof _clearInterval;
36
+ setImmediate: typeof _setImmediate;
37
+ clearImmediate: typeof _clearImmediate;
38
+ Timeout: typeof Timeout;
39
+ Immediate: typeof Immediate;
40
+ };
41
+ export default _default;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Returns a promise that resolves after `delay` milliseconds.
3
+ * Supports AbortSignal for cancellation.
4
+ */
5
+ export declare function setTimeout<T = void>(delay?: number, value?: T, options?: {
6
+ signal?: AbortSignal;
7
+ ref?: boolean;
8
+ }): Promise<T>;
9
+ /**
10
+ * Returns a promise that resolves on the next event loop iteration.
11
+ * Supports AbortSignal for cancellation.
12
+ */
13
+ export declare function setImmediate<T = void>(value?: T, options?: {
14
+ signal?: AbortSignal;
15
+ ref?: boolean;
16
+ }): Promise<T>;
17
+ /**
18
+ * Returns an async iterable that yields at `delay` ms intervals.
19
+ * Supports AbortSignal for cancellation.
20
+ */
21
+ export declare function setInterval<T = void>(delay?: number, value?: T, options?: {
22
+ signal?: AbortSignal;
23
+ ref?: boolean;
24
+ }): AsyncGenerator<T>;
@@ -0,0 +1,40 @@
1
+ export declare class Timeout {
2
+ private _id;
3
+ private _ref;
4
+ private _callback;
5
+ private _delay;
6
+ private _args;
7
+ private _isInterval;
8
+ constructor(callback: (...args: any[]) => void, delay: number, args: any[], isInterval: boolean);
9
+ /**
10
+ * Mark this timeout as referenced (default).
11
+ * Referenced timers keep the event loop alive.
12
+ */
13
+ ref(): this;
14
+ /**
15
+ * Mark this timeout as unreferenced.
16
+ * Unreferenced timers do not keep the event loop alive.
17
+ */
18
+ unref(): this;
19
+ /** Whether this timeout is referenced. */
20
+ hasRef(): boolean;
21
+ /**
22
+ * Reset the timer's start time to now and reschedule.
23
+ */
24
+ refresh(): this;
25
+ /** Close/clear this timer. */
26
+ close(): void;
27
+ /** Get the underlying timer ID (for clearTimeout/clearInterval). */
28
+ [Symbol.toPrimitive](): number;
29
+ }
30
+ export declare class Immediate {
31
+ private _id;
32
+ private _ref;
33
+ private _cancelled;
34
+ constructor(callback: (...args: any[]) => void, args: any[]);
35
+ ref(): this;
36
+ unref(): this;
37
+ hasRef(): boolean;
38
+ close(): void;
39
+ [Symbol.toPrimitive](): number;
40
+ }
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@gjsify/timers",
3
+ "version": "0.1.0",
4
+ "description": "Node.js timers module for Gjs",
5
+ "type": "module",
6
+ "module": "lib/esm/index.js",
7
+ "types": "lib/types/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/types/index.d.ts",
11
+ "default": "./lib/esm/index.js"
12
+ },
13
+ "./promises": {
14
+ "types": "./lib/types/promises.d.ts",
15
+ "default": "./lib/esm/promises.js"
16
+ }
17
+ },
18
+ "scripts": {
19
+ "clear": "rm -rf lib tsconfig.tsbuildinfo tsconfig.types.tsbuildinfo test.gjs.mjs test.node.mjs || exit 0",
20
+ "check": "tsc --noEmit",
21
+ "build": "yarn build:gjsify && yarn build:types",
22
+ "build:gjsify": "gjsify build --library 'src/**/*.{ts,js}' --exclude 'src/**/*.spec.{mts,ts}' 'src/test.{mts,ts}'",
23
+ "build:types": "tsc",
24
+ "build:test": "yarn build:test:gjs && yarn build:test:node",
25
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
26
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
27
+ "test": "yarn build:gjsify && yarn build:test && yarn test:node && yarn test:gjs",
28
+ "test:gjs": "gjs -m test.gjs.mjs",
29
+ "test:node": "node test.node.mjs"
30
+ },
31
+ "keywords": [
32
+ "gjs",
33
+ "node",
34
+ "timers"
35
+ ],
36
+ "devDependencies": {
37
+ "@gjsify/cli": "^0.1.0",
38
+ "@gjsify/unit": "^0.1.0",
39
+ "@types/node": "^25.5.0",
40
+ "typescript": "^6.0.2"
41
+ }
42
+ }
@@ -0,0 +1,149 @@
1
+ // Ported from refs/node-test/parallel/test-timers-*.js
2
+ // Original: MIT license, Node.js contributors
3
+
4
+ import { describe, it, expect } from '@gjsify/unit';
5
+
6
+ export default async () => {
7
+
8
+ // ===================== setTimeout deep =====================
9
+ await describe('setTimeout deep', async () => {
10
+ await it('should call callback after delay', async () => {
11
+ const start = Date.now();
12
+ await new Promise<void>((resolve) => {
13
+ setTimeout(() => {
14
+ const elapsed = Date.now() - start;
15
+ expect(elapsed >= 20).toBe(true);
16
+ resolve();
17
+ }, 30);
18
+ });
19
+ });
20
+
21
+ await it('should pass arguments to callback', async () => {
22
+ const result = await new Promise<string>((resolve) => {
23
+ setTimeout((a: string, b: string) => resolve(a + b), 10, 'hello', ' world');
24
+ });
25
+ expect(result).toBe('hello world');
26
+ });
27
+
28
+ await it('should return a timeout ID', async () => {
29
+ const id = setTimeout(() => {}, 1000);
30
+ expect(id).toBeDefined();
31
+ clearTimeout(id);
32
+ });
33
+
34
+ await it('clearTimeout should prevent callback', async () => {
35
+ let called = false;
36
+ const id = setTimeout(() => { called = true; }, 30);
37
+ clearTimeout(id);
38
+ await new Promise<void>((r) => setTimeout(r, 100));
39
+ expect(called).toBe(false);
40
+ });
41
+
42
+ await it('should handle 0 delay', async () => {
43
+ const result = await new Promise<boolean>((resolve) => {
44
+ setTimeout(() => resolve(true), 0);
45
+ });
46
+ expect(result).toBe(true);
47
+ });
48
+ });
49
+
50
+ // ===================== setInterval deep =====================
51
+ await describe('setInterval deep', async () => {
52
+ await it('should call callback repeatedly', async () => {
53
+ let count = 0;
54
+ await new Promise<void>((resolve) => {
55
+ const id = setInterval(() => {
56
+ count++;
57
+ if (count >= 3) {
58
+ clearInterval(id);
59
+ resolve();
60
+ }
61
+ }, 30);
62
+ });
63
+ expect(count).toBeGreaterThan(2);
64
+ });
65
+
66
+ await it('clearInterval should stop repetition', async () => {
67
+ let count = 0;
68
+ const id = setInterval(() => { count++; }, 20);
69
+ await new Promise<void>((r) => setTimeout(r, 60));
70
+ clearInterval(id);
71
+ const countAfterClear = count;
72
+ await new Promise<void>((r) => setTimeout(r, 60));
73
+ // Should not have increased much after clear
74
+ expect(count - countAfterClear).toBeLessThan(2);
75
+ });
76
+
77
+ await it('should pass arguments to callback', async () => {
78
+ const result = await new Promise<number>((resolve) => {
79
+ const id = setInterval((val: number) => {
80
+ clearInterval(id);
81
+ resolve(val);
82
+ }, 10, 42);
83
+ });
84
+ expect(result).toBe(42);
85
+ });
86
+ });
87
+
88
+ // ===================== setImmediate =====================
89
+ await describe('setImmediate', async () => {
90
+ await it('should call callback asynchronously', async () => {
91
+ let called = false;
92
+ setImmediate(() => { called = true; });
93
+ expect(called).toBe(false); // Should not be called synchronously
94
+ await new Promise<void>((r) => setTimeout(r, 50));
95
+ expect(called).toBe(true);
96
+ });
97
+
98
+ await it('should pass arguments', async () => {
99
+ const result = await new Promise<string>((resolve) => {
100
+ setImmediate((a: string) => resolve(a), 'immediate');
101
+ });
102
+ expect(result).toBe('immediate');
103
+ });
104
+
105
+ await it('clearImmediate should prevent callback', async () => {
106
+ let called = false;
107
+ const id = setImmediate(() => { called = true; });
108
+ clearImmediate(id);
109
+ await new Promise<void>((r) => setTimeout(r, 50));
110
+ expect(called).toBe(false);
111
+ });
112
+ });
113
+
114
+ // ===================== Ordering guarantees =====================
115
+ await describe('timer ordering', async () => {
116
+ await it('setImmediate should fire before setTimeout(0)', async () => {
117
+ const order: string[] = [];
118
+ await new Promise<void>((resolve) => {
119
+ setTimeout(() => {
120
+ order.push('timeout');
121
+ if (order.length === 2) resolve();
122
+ }, 0);
123
+ setImmediate(() => {
124
+ order.push('immediate');
125
+ if (order.length === 2) resolve();
126
+ });
127
+ });
128
+ // Both should have fired
129
+ expect(order.length).toBe(2);
130
+ expect(order).toContain('timeout');
131
+ expect(order).toContain('immediate');
132
+ });
133
+
134
+ await it('nested setTimeout should fire in order', async () => {
135
+ const order: number[] = [];
136
+ await new Promise<void>((resolve) => {
137
+ setTimeout(() => {
138
+ order.push(1);
139
+ setTimeout(() => {
140
+ order.push(2);
141
+ resolve();
142
+ }, 10);
143
+ }, 10);
144
+ });
145
+ expect(order[0]).toBe(1);
146
+ expect(order[1]).toBe(2);
147
+ });
148
+ });
149
+ };