tina4-nodejs 3.13.76 → 3.13.77

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/CLAUDE.md CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.76)
1
+ # CLAUDE.md - AI Developer Guide for tina4-nodejs (v3.13.77)
2
2
 
3
3
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
4
4
 
5
5
  ## What This Project Is
6
6
 
7
- Tina4 for Node.js/TypeScript v3.13.76 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.13.77 - The Intelligent Native Application 4ramework. A convention-over-configuration structural paradigm. The developer writes TypeScript; Tina4 is invisible infrastructure.
8
8
 
9
9
  The philosophy: zero ceremony, batteries included, file system as source of truth.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.13.76",
3
+ "version": "3.13.77",
4
4
  "type": "module",
5
5
  "description": "Tina4 for Node.js/TypeScript - 54 built-in features, zero dependencies",
6
6
  "keywords": [
@@ -22,6 +22,8 @@ interface BackgroundTask {
22
22
  callback: () => unknown | Promise<unknown>;
23
23
  intervalSeconds: number;
24
24
  timer: NodeJS.Timeout;
25
+ /** Set by any stop path. Checked around the await so a run in flight never re-arms. */
26
+ stopped: boolean;
25
27
  }
26
28
 
27
29
  const _tasks: BackgroundTask[] = [];
@@ -64,32 +66,42 @@ export function background(
64
66
  _bindSignalsOnce();
65
67
 
66
68
  const ms = Math.max(1, Math.round(intervalSeconds * 1000));
67
- const timer = setInterval(() => {
69
+
70
+ // A task must NEVER overlap itself. setInterval fires on a fixed schedule and
71
+ // does not wait for an async callback, so a run slower than the interval would
72
+ // have a second copy start alongside it (silent double-execution of a slow
73
+ // sweep, with every later tick piling on another). Re-arm a one-shot timer only
74
+ // AFTER the run settles — the interval is then the gap BETWEEN runs. Mirrors the
75
+ // Python master (background_tick_loop) and matches PHP/Ruby, which never overlap.
76
+ const tick = async () => {
77
+ if (task.stopped) return;
68
78
  try {
69
- const result = callback();
70
- if (result && typeof (result as Promise<unknown>).then === "function") {
71
- (result as Promise<unknown>).catch((err) => {
72
- Log.error?.(`background task error: ${err instanceof Error ? err.message : String(err)}`);
73
- });
74
- }
79
+ await callback();
75
80
  } catch (err) {
76
81
  Log.error?.(`background task error: ${err instanceof Error ? err.message : String(err)}`);
77
82
  }
78
- }, ms);
83
+ // Re-check AFTER the await: a stop during the run must not re-arm.
84
+ if (task.stopped) return;
85
+ task.timer = _arm();
86
+ };
79
87
 
80
- // Don't keep the event loop alive solely for background tasks — this matches
81
- // Python's behaviour, where background tasks live in the server's loop and
82
- // exit with it rather than blocking shutdown.
83
- if (typeof timer.unref === "function") {
84
- timer.unref();
85
- }
88
+ const _arm = () => {
89
+ const t = setTimeout(() => { void tick(); }, ms);
90
+ // Don't keep the event loop alive solely for background tasks — this matches
91
+ // Python's behaviour, where background tasks live in the server's loop and
92
+ // exit with it rather than blocking shutdown.
93
+ if (typeof t.unref === "function") t.unref();
94
+ return t;
95
+ };
86
96
 
87
- const task: BackgroundTask = { callback, intervalSeconds, timer };
97
+ const task: BackgroundTask = { callback, intervalSeconds, timer: undefined as unknown as NodeJS.Timeout, stopped: false };
98
+ task.timer = _arm();
88
99
  _tasks.push(task);
89
100
 
90
101
  return {
91
102
  stop: () => {
92
- clearInterval(task.timer);
103
+ task.stopped = true;
104
+ clearTimeout(task.timer);
93
105
  const idx = _tasks.indexOf(task);
94
106
  if (idx !== -1) _tasks.splice(idx, 1);
95
107
  },
@@ -104,7 +116,8 @@ export function background(
104
116
  export function stopAllBackgroundTasks(): void {
105
117
  while (_tasks.length > 0) {
106
118
  const task = _tasks.pop()!;
107
- clearInterval(task.timer);
119
+ task.stopped = true;
120
+ clearTimeout(task.timer);
108
121
  }
109
122
  }
110
123