@gjsify/timers 0.53.0 → 0.54.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 CHANGED
@@ -26,6 +26,45 @@ await delay(1000);
26
26
  console.log('1 second later');
27
27
  ```
28
28
 
29
+ ## The returned handle is Node-compatible
30
+
31
+ `setTimeout` / `setInterval` return a handle carrying the full `NodeJS.Timeout`
32
+ surface, so the cast third-party code used to need is gone:
33
+
34
+ ```typescript
35
+ const sweep = setInterval(() => rooms.sweep(), 30_000);
36
+ sweep.unref(); // no cast, no `as unknown as { unref?: () => void }`
37
+ sweep.hasRef(); // → boolean
38
+ sweep.refresh(); // reschedule from now, returns the same handle
39
+ clearInterval(sweep); // or `sweep.close()`, or `+sweep` for the numeric ID
40
+ ```
41
+
42
+ Members: `ref()`, `unref()`, `hasRef()`, `refresh()`, `close()`,
43
+ `[Symbol.toPrimitive]()` (→ the numeric timer ID, for libraries that key a Map
44
+ on it), `[Symbol.dispose]()` and `_onTimeout()` (what `@types/node` declares, and
45
+ what libraries call directly when they drive a handle themselves).
46
+ `clearTimeout` / `clearInterval` accept the handle itself, a foreign Node
47
+ handle, or a bare number — the check is structural, so it survives a duplicated
48
+ copy of this module in a bundle.
49
+
50
+ ### What `unref()` means on GJS
51
+
52
+ **On Node, `unref()` really releases the event loop** and the process may exit
53
+ while the timer is pending. On GJS it **cannot**, and this package does not
54
+ pretend otherwise:
55
+
56
+ - `Gtk.Application` / `GLib.MainLoop` own the loop — not the JS heap. Nothing a
57
+ timer does decides whether the app keeps running.
58
+ - The only GLib operation that releases a source's hold on the loop
59
+ (`g_source_unref`) also destroys the source. That is the double-unref
60
+ SIGSEGV this package exists to avoid, so the timers are backed by
61
+ `GLib.timeout_add` and numeric source IDs for exactly this reason.
62
+
63
+ So on GJS `unref()` is **recorded and reported**: `hasRef()` returns `false`
64
+ after it, and the timer keeps firing. Code that calls `unref()` to mean "don't
65
+ let this timer hold my process open" is correct on both runtimes — it simply
66
+ has no work to do on GJS, because the app, not the timer, decides that.
67
+
29
68
  ## License
30
69
 
31
70
  MIT
package/lib/esm/index.js CHANGED
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";import{Immediate as e,Timeout as t}from"./timeout.js";function _setTimeout(e,n=0,...r){return new t(e,n,r,!1)}function _clearTimeout(e){e instanceof t?e.close():e!=null&&clearTimeout(e)}function _setInterval(e,n=0,...r){return new t(e,n,r,!0)}function _clearInterval(e){e instanceof t?e.close():e!=null&&clearInterval(e)}function _setImmediate(t,...n){return new e(t,n)}function _clearImmediate(t){t instanceof e?t.close():t!=null&&clearTimeout(t)}var n={setTimeout:_setTimeout,clearTimeout:_clearTimeout,setInterval:_setInterval,clearInterval:_clearInterval,setImmediate:_setImmediate,clearImmediate:_clearImmediate,Timeout:t,Immediate:e};export{e as Immediate,t as Timeout,_clearImmediate as clearImmediate,_clearInterval as clearInterval,_clearTimeout as clearTimeout,n as default,_setImmediate as setImmediate,_setInterval as setInterval,_setTimeout as setTimeout};
1
+ import"./_virtual/_rolldown/runtime.js";import{Immediate as e,Timeout as t}from"./timeout.js";function isNodeTimerHandle(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.unref==`function`&&typeof t.hasRef==`function`&&typeof t.close==`function`}function _clearTimeout(e){isNodeTimerHandle(e)?e.close():e!=null&&clearTimeout(e)}function _setTimeout(e,n=0,...r){return new t(e,n,r,!1)}function _setInterval(e,n=0,...r){return new t(e,n,r,!0)}function _clearInterval(e){isNodeTimerHandle(e)?e.close():e!=null&&clearInterval(e)}function _setImmediate(t,...n){return new e(t,n)}function _clearImmediate(e){isNodeTimerHandle(e)?e.close():e!=null&&clearTimeout(e)}var n={setTimeout:_setTimeout,clearTimeout:_clearTimeout,setInterval:_setInterval,clearInterval:_clearInterval,setImmediate:_setImmediate,clearImmediate:_clearImmediate,Timeout:t,Immediate:e};export{e as Immediate,t as Timeout,_clearImmediate as clearImmediate,_clearInterval as clearInterval,_clearTimeout as clearTimeout,n as default,_setImmediate as setImmediate,_setInterval as setInterval,_setTimeout as setTimeout};
@@ -1 +1 @@
1
- import"./_virtual/_rolldown/runtime.js";var Timeout=class{_id;_ref=!0;_callback;_delay;_args;_isInterval;constructor(e,t,n,r){this._callback=e,this._delay=t,this._args=n,this._isInterval=r,r?this._id=setInterval(e,t,...n):this._id=setTimeout(e,t,...n)}ref(){return this._ref=!0,this}unref(){return this._ref=!1,this}hasRef(){return this._ref}refresh(){return this._id!=null&&(this._isInterval?(clearInterval(this._id),this._id=setInterval(this._callback,this._delay,...this._args)):(clearTimeout(this._id),this._id=setTimeout(this._callback,this._delay,...this._args))),this}close(){this._id!=null&&(this._isInterval?clearInterval(this._id):clearTimeout(this._id),this._id=null)}[Symbol.toPrimitive](){return this._id}},Immediate=class{_id=null;_ref=!0;_cancelled=!1;constructor(e,t){Promise.resolve().then(()=>{this._cancelled||e(...t)})}ref(){return this._ref=!0,this}unref(){return this._ref=!1,this}hasRef(){return this._ref}close(){this._cancelled=!0,this._id!=null&&(clearTimeout(this._id),this._id=null)}[Symbol.toPrimitive](){return this._id}};export{Immediate,Timeout};
1
+ import"./_virtual/_rolldown/runtime.js";function canDelegateRef(e){if(typeof e!=`object`||!e)return!1;let t=e;return typeof t.unref==`function`&&typeof t.ref==`function`&&typeof t.hasRef==`function`&&typeof t.refresh==`function`}var Timeout=class{_id;_ref=!0;_callback;_delay;_args;_isInterval;_numericId;constructor(e,t,n,r){this._callback=e,this._delay=t,this._args=n,this._isInterval=r,r?this._id=setInterval(e,t,...n):this._id=setTimeout(e,t,...n),this._numericId=Number(this._id)}ref(){return this._ref=!0,this._id!=null&&canDelegateRef(this._id)&&this._id.ref(),this}unref(){return this._ref=!1,this._id!=null&&canDelegateRef(this._id)&&this._id.unref(),this}hasRef(){return this._ref}refresh(){return this._id!=null&&(this._isInterval?(clearInterval(this._id),this._id=setInterval(this._callback,this._delay,...this._args)):(clearTimeout(this._id),this._id=setTimeout(this._callback,this._delay,...this._args)),this._numericId=Number(this._id),!this._ref&&canDelegateRef(this._id)&&this._id.unref()),this}close(){return this._id!=null&&(this._isInterval?clearInterval(this._id):clearTimeout(this._id),this._id=null),this}_onTimeout(...e){this._callback(...e)}[Symbol.dispose](){this.close()}[Symbol.toPrimitive](){return this._numericId}},Immediate=class{_cancelled=!1;_ref=!0;_callback;_args;constructor(e,t){this._callback=e,this._args=t,Promise.resolve().then(()=>{this._cancelled||this._onImmediate(...this._args)})}ref(){return this._ref=!0,this}unref(){return this._ref=!1,this}hasRef(){return this._ref}close(){return this._cancelled=!0,this}_onImmediate(...e){this._callback(...e)}[Symbol.dispose](){this.close()}};export{Immediate,Timeout};
@@ -1,32 +1,72 @@
1
1
  import { Timeout, Immediate } from './timeout.js';
2
2
  export { Timeout, Immediate };
3
3
  /**
4
- * Schedule a callback to be called after `delay` milliseconds.
5
- * Returns a Timeout object with ref/unref/refresh methods.
4
+ * Structural shape of a Node timer handle, as `@types/node` declares it for
5
+ * `NodeJS.Timeout`.
6
+ *
7
+ * This is what makes the return value usable WITHOUT a cast: a consumer's
8
+ * `setInterval(...).unref()` type-checks because the object we return satisfies
9
+ * this interface, which is why the globals register path and this module agree
10
+ * on the same members.
11
+ *
12
+ * `close(): unknown` because `@types/node` declares `close(): void` while ours
13
+ * returns `this` for chaining — a `this` return satisfies a `void` one.
6
14
  */
7
- declare function _setTimeout<T extends unknown[]>(callback: (...args: T) => void, delay?: number, ...args: T): Timeout;
15
+ export interface NodeTimerHandle {
16
+ ref(): unknown;
17
+ unref(): unknown;
18
+ hasRef(): boolean;
19
+ refresh(): unknown;
20
+ close(): unknown;
21
+ [Symbol.toPrimitive](): number;
22
+ [Symbol.dispose](): void;
23
+ }
24
+ /** A Node `Immediate` has no `refresh`/`toPrimitive`, so it gets its own shape. */
25
+ export interface NodeImmediateHandle {
26
+ ref(): unknown;
27
+ unref(): unknown;
28
+ hasRef(): boolean;
29
+ [Symbol.dispose](): void;
30
+ }
8
31
  /**
9
32
  * Cancel a timeout created by setTimeout.
33
+ *
34
+ * Accepts the Node spellings, not just our own instance: `setTimeout` may have
35
+ * come from a third-party module, from a `worker_threads` realm, or as a bare
36
+ * number (a browser handle). Anything with the `unref`/`hasRef`/`close` trio is
37
+ * structurally a Node `Timeout`, so duck-type it rather than `instanceof` — the
38
+ * check has to survive a duplicate copy of this module in the bundle, which is
39
+ * exactly the situation that made consumers cast in the first place.
40
+ */
41
+ declare function _clearTimeout(timeout: NodeTimerHandle | number | undefined): void;
42
+ /**
43
+ * Schedule a callback to be called after `delay` milliseconds.
44
+ *
45
+ * The returned handle satisfies {@link NodeTimerHandle}, so a consumer typed
46
+ * against `@types/node` calls `.unref()` on it without a cast.
10
47
  */
11
- declare function _clearTimeout(timeout: Timeout | number | undefined): void;
48
+ declare function _setTimeout<T extends unknown[]>(callback: (...args: T) => void, delay?: number, ...args: T): Timeout;
12
49
  /**
13
50
  * Schedule a callback to be called repeatedly every `delay` milliseconds.
14
- * Returns a Timeout object with ref/unref/refresh methods.
51
+ * Returns the same handle shape as {@link _setTimeout}.
15
52
  */
16
53
  declare function _setInterval<T extends unknown[]>(callback: (...args: T) => void, delay?: number, ...args: T): Timeout;
17
54
  /**
18
- * Cancel an interval created by setInterval.
55
+ * Cancel an interval created by setInterval. Accepts the same shapes as
56
+ * {@link _clearTimeout}.
19
57
  */
20
- declare function _clearInterval(timeout: Timeout | number | undefined): void;
58
+ declare function _clearInterval(timeout: NodeTimerHandle | number | undefined): void;
21
59
  /**
22
60
  * Schedule a callback to be called on the next iteration of the event loop.
23
61
  * Returns an Immediate object with ref/unref methods.
24
62
  */
25
63
  declare function _setImmediate<T extends unknown[]>(callback: (...args: T) => void, ...args: T): Immediate;
26
64
  /**
27
- * Cancel an immediate created by setImmediate.
65
+ * Cancel an immediate created by setImmediate. Accepts a foreign Node `Immediate`
66
+ * through the same duck-type as {@link _clearTimeout}, because `instanceof` fails
67
+ * across a duplicated module and callers hold whatever `setImmediate` gave them.
28
68
  */
29
- declare function _clearImmediate(immediate: Immediate | number | undefined): void;
69
+ declare function _clearImmediate(immediate: Immediate | NodeImmediateHandle | number | undefined): void;
30
70
  export { _setTimeout as setTimeout, _clearTimeout as clearTimeout, _setInterval as setInterval, _clearInterval as clearInterval, _setImmediate as setImmediate, _clearImmediate as clearImmediate, };
31
71
  declare const _default: {
32
72
  setTimeout: typeof _setTimeout;
@@ -1,3 +1,4 @@
1
+ type TimerCallback = (...args: unknown[]) => void;
1
2
  export declare class Timeout {
2
3
  private _id;
3
4
  private _ref;
@@ -5,36 +6,70 @@ export declare class Timeout {
5
6
  private _delay;
6
7
  private _args;
7
8
  private _isInterval;
8
- constructor(callback: (...args: unknown[]) => void, delay: number, args: unknown[], isInterval: boolean);
9
+ /**
10
+ * Retained so `+timeout` still yields a number after `close()`, where the
11
+ * host handle is gone. Node keeps its id readable for the object's life.
12
+ */
13
+ private _numericId;
14
+ constructor(callback: TimerCallback, delay: number, args: unknown[], isInterval: boolean);
9
15
  /**
10
16
  * Mark this timeout as referenced (default).
11
- * Referenced timers keep the event loop alive.
17
+ *
18
+ * Delegated to the host where the host is a Node-shaped handle (see
19
+ * {@link canDelegateRef}); recorded only otherwise.
12
20
  */
13
21
  ref(): this;
14
22
  /**
15
- * Mark this timeout as unreferenced.
16
- * Unreferenced timers do not keep the event loop alive.
23
+ * Mark this timeout as unreferenced — the caller asks that it not require
24
+ * the host loop to stay alive.
25
+ *
26
+ * Honoured on Node (delegated to libuv). On GJS it is RECORDED ONLY and the
27
+ * timer keeps firing: the app owns the main loop, and releasing liveness
28
+ * there would mean destroying the GLib source. `hasRef()` reports the
29
+ * recorded flag on both runtimes.
17
30
  */
18
31
  unref(): this;
19
32
  /** Whether this timeout is referenced. */
20
33
  hasRef(): boolean;
21
34
  /**
22
35
  * Reset the timer's start time to now and reschedule.
36
+ *
37
+ * The ref state is carried over: rescheduling creates a NEW host handle,
38
+ * which arrives refed, so an `unref()`ed timer that is refreshed would
39
+ * silently start holding the event loop open again — on Node that is the
40
+ * difference between a process that exits and one that hangs.
23
41
  */
24
42
  refresh(): this;
25
- /** Close/clear this timer. */
26
- close(): void;
43
+ /** Close/clear this timer. Returns `this`, matching `@types/node`. */
44
+ close(): this;
45
+ /**
46
+ * Node libraries call this directly rather than going through the scheduled
47
+ * dispatch (`@types/node` declares it on `NodeJS.Timeout`).
48
+ */
49
+ _onTimeout(...args: unknown[]): void;
50
+ [Symbol.dispose](): void;
27
51
  /** Get the underlying timer ID (for clearTimeout/clearInterval). */
28
52
  [Symbol.toPrimitive](): number;
29
53
  }
30
54
  export declare class Immediate {
31
- private _id;
32
- private _ref;
33
55
  private _cancelled;
34
- constructor(callback: (...args: unknown[]) => void, args: unknown[]);
56
+ private _ref;
57
+ readonly _callback: TimerCallback;
58
+ readonly _args: unknown[];
59
+ constructor(callback: TimerCallback, args: unknown[]);
60
+ /**
61
+ * Recorded only, never delegated — an `Immediate` here is a microtask, not a
62
+ * host handle, so there is nothing to release. See {@link Timeout.unref} for
63
+ * why that is the honest answer on GJS.
64
+ */
35
65
  ref(): this;
66
+ /** Recorded only — see {@link Timeout.unref}. */
36
67
  unref(): this;
37
68
  hasRef(): boolean;
38
- close(): void;
39
- [Symbol.toPrimitive](): number;
69
+ /** Cancels the immediate. Returns `this` for the same reason `Timeout.close` does. */
70
+ close(): this;
71
+ /** Node libraries call this directly (`@types/node` declares it). */
72
+ _onImmediate(...args: unknown[]): void;
73
+ [Symbol.dispose](): void;
40
74
  }
75
+ export {};
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
@@ -0,0 +1,2 @@
1
+ declare const _default: () => Promise<void>;
2
+ export default _default;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/timers",
3
- "version": "0.53.0",
3
+ "version": "0.54.0",
4
4
  "description": "Node.js timers module for Gjs",
5
5
  "type": "module",
6
6
  "module": "lib/esm/index.js",
@@ -40,8 +40,8 @@
40
40
  "timers"
41
41
  ],
42
42
  "devDependencies": {
43
- "@gjsify/cli": "^0.53.0",
44
- "@gjsify/unit": "^0.53.0",
43
+ "@gjsify/cli": "^0.54.0",
44
+ "@gjsify/unit": "^0.54.0",
45
45
  "@types/node": "^25.9.2",
46
46
  "typescript": "^6.0.3"
47
47
  },