@pikku/schedule 0.11.1 → 0.12.1

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/CHANGELOG.md CHANGED
@@ -1,3 +1,29 @@
1
+ ## 0.12.0
2
+
3
+ ## 0.12.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e04531f: Code quality improvements: resolve oxlint warnings and apply autofixes across the codebase (unused bindings, unnecessary constructors, prefer `const` over `let`, etc.). No behaviour changes.
8
+ - Updated dependencies [62a8725]
9
+ - Updated dependencies [a3bdb0d]
10
+ - Updated dependencies [e0349ff]
11
+ - Updated dependencies [62a8725]
12
+ - Updated dependencies [e04531f]
13
+ - Updated dependencies [62a8725]
14
+ - Updated dependencies [a83efb8]
15
+ - Updated dependencies [8eed717]
16
+ - Updated dependencies [62a8725]
17
+ - Updated dependencies [62a8725]
18
+ - Updated dependencies [62a8725]
19
+ - @pikku/core@0.12.1
20
+
21
+ ### New Features
22
+
23
+ - `InMemorySchedulerService` replacing `PikkuTaskScheduler` with full `SchedulerService` interface
24
+ - Recurring cron task support via `node-cron`
25
+ - `setServices()`, `start()`, `stop()` lifecycle methods
26
+
1
27
  ## 0.11.0
2
28
 
3
29
  ## 0.11.1
@@ -0,0 +1,29 @@
1
+ import type { CoreUserSession, ScheduledTaskInfo, ScheduledTaskSummary } from '@pikku/core';
2
+ import { SchedulerService } from '@pikku/core';
3
+ /**
4
+ * In-memory SchedulerService implementation.
5
+ * Uses CronJob for recurring tasks and setTimeout for delayed RPCs.
6
+ */
7
+ export declare class InMemorySchedulerService extends SchedulerService {
8
+ private cronJobs;
9
+ private delayedTasks;
10
+ private idCounter;
11
+ init(): Promise<void>;
12
+ /**
13
+ * Schedule a one-off delayed RPC call via setTimeout
14
+ */
15
+ scheduleRPC(delay: number | string, rpcName: string, data?: any, session?: CoreUserSession): Promise<string>;
16
+ unschedule(taskId: string): Promise<boolean>;
17
+ getTask(taskId: string): Promise<ScheduledTaskInfo | null>;
18
+ getAllTasks(): Promise<ScheduledTaskSummary[]>;
19
+ close(): Promise<void>;
20
+ /**
21
+ * Start recurring scheduled tasks.
22
+ */
23
+ start(): Promise<void>;
24
+ /**
25
+ * Stop all recurring CronJobs.
26
+ */
27
+ stop(): Promise<void>;
28
+ private startCronJob;
29
+ }
@@ -0,0 +1,106 @@
1
+ import { CronJob } from 'cron';
2
+ import { SchedulerService, parseDurationString } from '@pikku/core';
3
+ import { pikkuState } from '@pikku/core/internal';
4
+ import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler';
5
+ const getLogger = () => {
6
+ return pikkuState(null, 'package', 'singletonServices').logger;
7
+ };
8
+ /**
9
+ * In-memory SchedulerService implementation.
10
+ * Uses CronJob for recurring tasks and setTimeout for delayed RPCs.
11
+ */
12
+ export class InMemorySchedulerService extends SchedulerService {
13
+ cronJobs = new Map();
14
+ delayedTasks = new Map();
15
+ idCounter = 0;
16
+ async init() { }
17
+ /**
18
+ * Schedule a one-off delayed RPC call via setTimeout
19
+ */
20
+ async scheduleRPC(delay, rpcName, data, session) {
21
+ const delayMs = typeof delay === 'string' ? parseDurationString(delay) : delay;
22
+ const taskId = `inmem-${++this.idCounter}-${Date.now()}`;
23
+ const scheduledFor = new Date(Date.now() + delayMs);
24
+ const timer = setTimeout(async () => {
25
+ this.delayedTasks.delete(taskId);
26
+ try {
27
+ await runScheduledTask({ name: rpcName, session });
28
+ }
29
+ catch (err) {
30
+ getLogger().error(`Failed to execute delayed RPC '${rpcName}': ${err}`);
31
+ }
32
+ }, delayMs);
33
+ this.delayedTasks.set(taskId, {
34
+ taskId,
35
+ rpcName,
36
+ data,
37
+ session,
38
+ scheduledFor,
39
+ timer,
40
+ });
41
+ return taskId;
42
+ }
43
+ async unschedule(taskId) {
44
+ const task = this.delayedTasks.get(taskId);
45
+ if (task) {
46
+ clearTimeout(task.timer);
47
+ this.delayedTasks.delete(taskId);
48
+ return true;
49
+ }
50
+ return false;
51
+ }
52
+ async getTask(taskId) {
53
+ const task = this.delayedTasks.get(taskId);
54
+ if (!task)
55
+ return null;
56
+ return {
57
+ taskId: task.taskId,
58
+ rpcName: task.rpcName,
59
+ scheduledFor: task.scheduledFor,
60
+ data: task.data,
61
+ session: task.session,
62
+ status: 'scheduled',
63
+ };
64
+ }
65
+ async getAllTasks() {
66
+ return Array.from(this.delayedTasks.values()).map((t) => ({
67
+ taskId: t.taskId,
68
+ rpcName: t.rpcName,
69
+ scheduledFor: t.scheduledFor,
70
+ }));
71
+ }
72
+ async close() {
73
+ await this.stop();
74
+ // Clear delayed tasks
75
+ for (const [, task] of this.delayedTasks) {
76
+ clearTimeout(task.timer);
77
+ }
78
+ this.delayedTasks.clear();
79
+ }
80
+ /**
81
+ * Start recurring scheduled tasks.
82
+ */
83
+ async start() {
84
+ const scheduledTasks = getScheduledTasks();
85
+ for (const [, task] of scheduledTasks) {
86
+ this.startCronJob(task.name, task.schedule);
87
+ }
88
+ }
89
+ /**
90
+ * Stop all recurring CronJobs.
91
+ */
92
+ async stop() {
93
+ for (const [, job] of this.cronJobs) {
94
+ job.stop();
95
+ }
96
+ this.cronJobs.clear();
97
+ }
98
+ startCronJob(name, schedule) {
99
+ const job = new CronJob(schedule, async () => {
100
+ getLogger().info(`Running scheduled task: ${name}`);
101
+ await runScheduledTask({ name });
102
+ getLogger().debug(`Completed scheduled task: ${name}`);
103
+ }, null, true);
104
+ this.cronJobs.set(name, job);
105
+ }
106
+ }
package/dist/index.d.ts CHANGED
@@ -1 +1,5 @@
1
- export { PikkuTaskScheduler } from './pikku-task-scheduler.js';
1
+ export { InMemorySchedulerService } from './in-memory-scheduler-service.js';
2
+ /**
3
+ * @deprecated Use InMemorySchedulerService instead
4
+ */
5
+ export { InMemorySchedulerService as PikkuTaskScheduler } from './in-memory-scheduler-service.js';
package/dist/index.js CHANGED
@@ -1 +1,5 @@
1
- export { PikkuTaskScheduler } from './pikku-task-scheduler.js';
1
+ export { InMemorySchedulerService } from './in-memory-scheduler-service.js';
2
+ /**
3
+ * @deprecated Use InMemorySchedulerService instead
4
+ */
5
+ export { InMemorySchedulerService as PikkuTaskScheduler } from './in-memory-scheduler-service.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/schedule",
3
- "version": "0.11.1",
3
+ "version": "0.12.1",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "MIT",
6
6
  "module": "dist/index.js",
@@ -16,17 +16,17 @@
16
16
  "test:coverage": "bash run-tests.sh --coverage"
17
17
  },
18
18
  "peerDependencies": {
19
- "@pikku/core": "^0.11.1"
19
+ "@pikku/core": "^0.12.1"
20
20
  },
21
21
  "devDependencies": {
22
- "@types/node": "^24.10.1",
22
+ "@types/node": "^24.10.12",
23
23
  "typescript": "^5.9"
24
24
  },
25
25
  "engines": {
26
26
  "node": ">=18"
27
27
  },
28
28
  "dependencies": {
29
- "cron": "^4.3.4",
29
+ "cron": "^4.4.0",
30
30
  "cron-schedule": "^5.0.4"
31
31
  }
32
32
  }
@@ -0,0 +1,34 @@
1
+ import { describe, test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { pikkuState } from '@pikku/core/internal'
4
+
5
+ import { InMemorySchedulerService } from './in-memory-scheduler-service.js'
6
+
7
+ describe('InMemorySchedulerService scheduleRPC', () => {
8
+ test('schedules and executes delayed RPC', async () => {
9
+ const logger = {
10
+ info: () => {},
11
+ warn: () => {},
12
+ debug: () => {},
13
+ error: () => {},
14
+ }
15
+
16
+ pikkuState(null, 'package', 'singletonServices', {
17
+ logger,
18
+ } as any)
19
+
20
+ const scheduler = new InMemorySchedulerService()
21
+ try {
22
+ const taskId = await scheduler.scheduleRPC(50, 'testRpc', { ok: true }, {
23
+ userId: 'user-1',
24
+ } as any)
25
+ assert.ok(taskId)
26
+
27
+ const task = await scheduler.getTask(taskId)
28
+ assert.ok(task)
29
+ assert.equal(task.rpcName, 'testRpc')
30
+ } finally {
31
+ await scheduler.close()
32
+ }
33
+ })
34
+ })
@@ -0,0 +1,143 @@
1
+ import { CronJob } from 'cron'
2
+ import type {
3
+ CoreUserSession,
4
+ ScheduledTaskInfo,
5
+ ScheduledTaskSummary,
6
+ } from '@pikku/core'
7
+ import { SchedulerService, parseDurationString } from '@pikku/core'
8
+ import { pikkuState } from '@pikku/core/internal'
9
+ import { runScheduledTask, getScheduledTasks } from '@pikku/core/scheduler'
10
+
11
+ interface DelayedTask {
12
+ taskId: string
13
+ rpcName: string
14
+ data?: any
15
+ session?: CoreUserSession
16
+ scheduledFor: Date
17
+ timer: ReturnType<typeof setTimeout>
18
+ }
19
+
20
+ const getLogger = () => {
21
+ return pikkuState(null, 'package', 'singletonServices')!.logger
22
+ }
23
+
24
+ /**
25
+ * In-memory SchedulerService implementation.
26
+ * Uses CronJob for recurring tasks and setTimeout for delayed RPCs.
27
+ */
28
+ export class InMemorySchedulerService extends SchedulerService {
29
+ private cronJobs = new Map<string, CronJob>()
30
+ private delayedTasks = new Map<string, DelayedTask>()
31
+ private idCounter = 0
32
+
33
+ async init(): Promise<void> {}
34
+
35
+ /**
36
+ * Schedule a one-off delayed RPC call via setTimeout
37
+ */
38
+ async scheduleRPC(
39
+ delay: number | string,
40
+ rpcName: string,
41
+ data?: any,
42
+ session?: CoreUserSession
43
+ ): Promise<string> {
44
+ const delayMs =
45
+ typeof delay === 'string' ? parseDurationString(delay) : delay
46
+ const taskId = `inmem-${++this.idCounter}-${Date.now()}`
47
+ const scheduledFor = new Date(Date.now() + delayMs)
48
+
49
+ const timer = setTimeout(async () => {
50
+ this.delayedTasks.delete(taskId)
51
+ try {
52
+ await runScheduledTask({ name: rpcName, session })
53
+ } catch (err: unknown) {
54
+ getLogger().error(`Failed to execute delayed RPC '${rpcName}': ${err}`)
55
+ }
56
+ }, delayMs)
57
+
58
+ this.delayedTasks.set(taskId, {
59
+ taskId,
60
+ rpcName,
61
+ data,
62
+ session,
63
+ scheduledFor,
64
+ timer,
65
+ })
66
+
67
+ return taskId
68
+ }
69
+
70
+ async unschedule(taskId: string): Promise<boolean> {
71
+ const task = this.delayedTasks.get(taskId)
72
+ if (task) {
73
+ clearTimeout(task.timer)
74
+ this.delayedTasks.delete(taskId)
75
+ return true
76
+ }
77
+ return false
78
+ }
79
+
80
+ async getTask(taskId: string): Promise<ScheduledTaskInfo | null> {
81
+ const task = this.delayedTasks.get(taskId)
82
+ if (!task) return null
83
+ return {
84
+ taskId: task.taskId,
85
+ rpcName: task.rpcName,
86
+ scheduledFor: task.scheduledFor,
87
+ data: task.data,
88
+ session: task.session,
89
+ status: 'scheduled',
90
+ }
91
+ }
92
+
93
+ async getAllTasks(): Promise<ScheduledTaskSummary[]> {
94
+ return Array.from(this.delayedTasks.values()).map((t) => ({
95
+ taskId: t.taskId,
96
+ rpcName: t.rpcName,
97
+ scheduledFor: t.scheduledFor,
98
+ }))
99
+ }
100
+
101
+ async close(): Promise<void> {
102
+ await this.stop()
103
+ // Clear delayed tasks
104
+ for (const [, task] of this.delayedTasks) {
105
+ clearTimeout(task.timer)
106
+ }
107
+ this.delayedTasks.clear()
108
+ }
109
+
110
+ /**
111
+ * Start recurring scheduled tasks.
112
+ */
113
+ async start(): Promise<void> {
114
+ const scheduledTasks = getScheduledTasks()
115
+ for (const [, task] of scheduledTasks) {
116
+ this.startCronJob(task.name, task.schedule)
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Stop all recurring CronJobs.
122
+ */
123
+ async stop(): Promise<void> {
124
+ for (const [, job] of this.cronJobs) {
125
+ job.stop()
126
+ }
127
+ this.cronJobs.clear()
128
+ }
129
+
130
+ private startCronJob(name: string, schedule: string) {
131
+ const job = new CronJob(
132
+ schedule,
133
+ async () => {
134
+ getLogger().info(`Running scheduled task: ${name}`)
135
+ await runScheduledTask({ name })
136
+ getLogger().debug(`Completed scheduled task: ${name}`)
137
+ },
138
+ null,
139
+ true
140
+ )
141
+ this.cronJobs.set(name, job)
142
+ }
143
+ }
package/src/index.ts CHANGED
@@ -1 +1,6 @@
1
- export { PikkuTaskScheduler } from './pikku-task-scheduler.js'
1
+ export { InMemorySchedulerService } from './in-memory-scheduler-service.js'
2
+
3
+ /**
4
+ * @deprecated Use InMemorySchedulerService instead
5
+ */
6
+ export { InMemorySchedulerService as PikkuTaskScheduler } from './in-memory-scheduler-service.js'
@@ -1 +1 @@
1
- {"root":["./src/index.ts","./src/pikku-task-scheduler.ts"],"version":"5.9.3"}
1
+ {"root":["./src/in-memory-scheduler-service.ts","./src/index.ts"],"version":"5.9.3"}
@@ -1,12 +0,0 @@
1
- import { CoreServices, CoreSingletonServices, CoreUserSession, CreateWireServices } from '@pikku/core';
2
- export declare class PikkuTaskScheduler<TaskName extends string> {
3
- private singletonServices;
4
- private createWireServices?;
5
- private jobs;
6
- constructor(singletonServices: CoreSingletonServices, createWireServices?: CreateWireServices<CoreSingletonServices, CoreServices, CoreUserSession> | undefined);
7
- startAll(): void;
8
- stopAll(): void;
9
- start(names: TaskName[]): void;
10
- stop(names: TaskName[]): void;
11
- private startJobSchedule;
12
- }
@@ -1,49 +0,0 @@
1
- import { CronJob } from 'cron';
2
- import { runScheduledTask, getScheduledTasks, } from '@pikku/core/scheduler';
3
- export class PikkuTaskScheduler {
4
- singletonServices;
5
- createWireServices;
6
- jobs = new Map();
7
- constructor(singletonServices, createWireServices) {
8
- this.singletonServices = singletonServices;
9
- this.createWireServices = createWireServices;
10
- }
11
- startAll() {
12
- const scheduledTasks = getScheduledTasks();
13
- scheduledTasks.forEach((task) => this.startJobSchedule(task));
14
- }
15
- stopAll() {
16
- this.jobs.forEach((job) => job.stop());
17
- this.jobs.clear();
18
- }
19
- start(names) {
20
- const scheduledTasks = getScheduledTasks();
21
- for (const name of names) {
22
- const task = scheduledTasks.get(name);
23
- if (task) {
24
- this.startJobSchedule(task);
25
- }
26
- }
27
- }
28
- stop(names) {
29
- for (const name of names) {
30
- const job = this.jobs.get(name);
31
- if (job) {
32
- job.stop();
33
- this.jobs.delete(name);
34
- }
35
- }
36
- }
37
- startJobSchedule(task) {
38
- const job = new CronJob(task.schedule, async () => {
39
- this.singletonServices.logger.info(`Running scheduled task: ${task.name}`);
40
- await runScheduledTask({
41
- singletonServices: this.singletonServices,
42
- createWireServices: this.createWireServices,
43
- name: task.name,
44
- });
45
- this.singletonServices.logger.debug(`Completed scheduled task: ${task.name}`);
46
- }, null, true);
47
- this.jobs.set(task.name, job);
48
- }
49
- }
@@ -1,77 +0,0 @@
1
- import { CronJob } from 'cron'
2
- import {
3
- CoreServices,
4
- CoreSingletonServices,
5
- CoreUserSession,
6
- CreateWireServices,
7
- } from '@pikku/core'
8
- import {
9
- runScheduledTask,
10
- CoreScheduledTask,
11
- getScheduledTasks,
12
- } from '@pikku/core/scheduler'
13
-
14
- export class PikkuTaskScheduler<TaskName extends string> {
15
- private jobs = new Map<string, CronJob>()
16
-
17
- constructor(
18
- private singletonServices: CoreSingletonServices,
19
- private createWireServices?: CreateWireServices<
20
- CoreSingletonServices,
21
- CoreServices,
22
- CoreUserSession
23
- >
24
- ) {}
25
-
26
- public startAll() {
27
- const scheduledTasks = getScheduledTasks()
28
- scheduledTasks.forEach((task) => this.startJobSchedule(task))
29
- }
30
-
31
- public stopAll() {
32
- this.jobs.forEach((job) => job.stop())
33
- this.jobs.clear()
34
- }
35
-
36
- public start(names: TaskName[]) {
37
- const scheduledTasks = getScheduledTasks()
38
- for (const name of names) {
39
- const task = scheduledTasks.get(name)
40
- if (task) {
41
- this.startJobSchedule(task)
42
- }
43
- }
44
- }
45
-
46
- public stop(names: TaskName[]) {
47
- for (const name of names) {
48
- const job = this.jobs.get(name)
49
- if (job) {
50
- job.stop()
51
- this.jobs.delete(name)
52
- }
53
- }
54
- }
55
-
56
- private startJobSchedule(task: CoreScheduledTask) {
57
- const job = new CronJob(
58
- task.schedule,
59
- async () => {
60
- this.singletonServices.logger.info(
61
- `Running scheduled task: ${task.name}`
62
- )
63
- await runScheduledTask({
64
- singletonServices: this.singletonServices,
65
- createWireServices: this.createWireServices as any,
66
- name: task.name,
67
- })
68
- this.singletonServices.logger.debug(
69
- `Completed scheduled task: ${task.name}`
70
- )
71
- },
72
- null,
73
- true
74
- )
75
- this.jobs.set(task.name, job)
76
- }
77
- }