@pikku/schedule 0.12.9 → 0.12.10

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,23 @@
1
+ ## 0.12.10
2
+
3
+ ### Patch Changes
4
+
5
+ - de9af6a: RPCs for Pikku Studio's migrated screens. Runtime state that must be visible on a deployed app lives in `@pikku/addon-admin`; local-copy tools stay in `@pikku/addon-console`.
6
+
7
+ `@pikku/addon-admin`:
8
+
9
+ - `admin:getSchedulerHistory` returns each scheduled task's last run, next run and recent runs, from a new `SchedulerService.getTaskRuns()` (empty by default). `InMemorySchedulerService` records the last 50 runs per cron task with status, duration and error. Scope `admin:schedules:read`.
10
+ - `admin:getQueueHistory({ queueNames?, limit? })` returns live counts and recent jobs per queue, from a new optional `QueueService.inspect(queueName, { limit })`, implemented by `InMemoryQueueService` and `BullQueueService` and forwarded by `SignedQueueService`. Scope `admin:queues:read`.
11
+ - `admin:listIncomingWebhookReceipts` / `admin:getIncomingWebhookReceipt` expose received webhooks, backed by a new `IncomingWebhookService.getReceipt(receiptId)` (null by default; implemented by `KyselyIncomingWebhookService`). Scope `admin:webhooks:read`.
12
+
13
+ `@pikku/addon-console`:
14
+
15
+ - `console:variableGet` / `console:variableSet` read and write a project variable, gated on `pikku:console:variables:read` / `:write`.
16
+ - `console:runScenarios({ names?, environment? })` starts `pikku scenario run` as a detached child process and returns `{ runId }` to follow through `listScenarioRuns` / `getScenarioRun`, gated on the new `pikku:console:scenarios:run` scope. `pikku scenario run` takes `--run-id <id>` to record under a caller-chosen id.
17
+
18
+ - Updated dependencies [de9af6a]
19
+ - @pikku/core@0.12.143
20
+
1
21
  ## 0.12.9
2
22
 
3
23
  ### Patch Changes
@@ -1,5 +1,6 @@
1
1
  import type { CoreUserSession } from '@pikku/core/types';
2
2
  import type { ScheduledTaskInfo, ScheduledTaskSummary } from '@pikku/core/services';
3
+ import type { ScheduledTaskRuns } from '@pikku/core/scheduler';
3
4
  import { SchedulerService } from '@pikku/core/services';
4
5
  /**
5
6
  * In-memory SchedulerService implementation.
@@ -8,6 +9,7 @@ import { SchedulerService } from '@pikku/core/services';
8
9
  export declare class InMemorySchedulerService extends SchedulerService {
9
10
  private cronJobs;
10
11
  private delayedTasks;
12
+ private taskRuns;
11
13
  private idCounter;
12
14
  init(): Promise<void>;
13
15
  /**
@@ -17,6 +19,7 @@ export declare class InMemorySchedulerService extends SchedulerService {
17
19
  unschedule(taskId: string): Promise<boolean>;
18
20
  getTask(taskId: string): Promise<ScheduledTaskInfo | null>;
19
21
  getAllTasks(): Promise<ScheduledTaskSummary[]>;
22
+ getTaskRuns(): Promise<Record<string, ScheduledTaskRuns>>;
20
23
  close(): Promise<void>;
21
24
  /**
22
25
  * Start recurring scheduled tasks.
@@ -27,4 +30,5 @@ export declare class InMemorySchedulerService extends SchedulerService {
27
30
  */
28
31
  stop(): Promise<void>;
29
32
  private startCronJob;
33
+ private recordRun;
30
34
  }
@@ -5,6 +5,7 @@ import { pikkuState, getSingletonServices } from '@pikku/core/state';
5
5
  import { runScheduledTask } from '@pikku/core/scheduler';
6
6
  import { getScheduledTasks } from '@pikku/core/scheduler';
7
7
  import { rpcService } from '@pikku/core/rpc';
8
+ const RUN_HISTORY_LIMIT = 50;
8
9
  const getLogger = () => {
9
10
  return pikkuState(null, 'package', 'singletonServices').logger;
10
11
  };
@@ -15,6 +16,7 @@ const getLogger = () => {
15
16
  export class InMemorySchedulerService extends SchedulerService {
16
17
  cronJobs = new Map();
17
18
  delayedTasks = new Map();
19
+ taskRuns = new Map();
18
20
  idCounter = 0;
19
21
  async init() { }
20
22
  /**
@@ -92,6 +94,18 @@ export class InMemorySchedulerService extends SchedulerService {
92
94
  scheduledFor: t.scheduledFor,
93
95
  }));
94
96
  }
97
+ async getTaskRuns() {
98
+ const runs = {};
99
+ for (const [name, job] of this.cronJobs) {
100
+ const history = this.taskRuns.get(name) ?? [];
101
+ runs[name] = {
102
+ nextRun: job.isActive ? job.nextDate().toJSDate() : null,
103
+ lastRun: history[0] ?? null,
104
+ history: [...history],
105
+ };
106
+ }
107
+ return runs;
108
+ }
95
109
  async close() {
96
110
  await this.stop();
97
111
  // Clear delayed tasks
@@ -120,15 +134,32 @@ export class InMemorySchedulerService extends SchedulerService {
120
134
  }
121
135
  startCronJob(name, schedule) {
122
136
  const job = new CronJob(schedule, async () => {
137
+ const startedAt = new Date();
123
138
  try {
124
139
  getLogger().info(`Running scheduled task: ${name}`);
125
140
  await runScheduledTask({ name });
126
141
  getLogger().debug(`Completed scheduled task: ${name}`);
142
+ this.recordRun(name, startedAt, 'completed', null);
127
143
  }
128
144
  catch (err) {
129
- getLogger().error(`Scheduled task '${name}' failed: ${err}`);
145
+ const skipped = err instanceof Error && err.name === 'ScheduledTaskSkippedError';
146
+ this.recordRun(name, startedAt, skipped ? 'skipped' : 'failed', err instanceof Error ? err.message : String(err));
147
+ if (!skipped) {
148
+ getLogger().error(`Scheduled task '${name}' failed: ${err}`);
149
+ }
130
150
  }
131
151
  }, null, true);
132
152
  this.cronJobs.set(name, job);
133
153
  }
154
+ recordRun(name, startedAt, status, error) {
155
+ const history = this.taskRuns.get(name) ?? [];
156
+ history.unshift({
157
+ startedAt,
158
+ status,
159
+ durationMs: Date.now() - startedAt.getTime(),
160
+ error,
161
+ });
162
+ history.length = Math.min(history.length, RUN_HISTORY_LIMIT);
163
+ this.taskRuns.set(name, history);
164
+ }
134
165
  }
@@ -0,0 +1,13 @@
1
+ ---
2
+ type: overview
3
+ title: Knowledge
4
+ description: Questions raised by the schedule package's in-memory scheduler
5
+ ---
6
+
7
+ # Knowledge
8
+
9
+ Questions about how the schedule package runs delayed work, where the code does not say what is intended.
10
+
11
+ <!-- pikku:knowledge-index -->
12
+ - [questions](questions/index.md) — something asked and not yet answered
13
+ <!-- /pikku:knowledge-index -->
@@ -0,0 +1,13 @@
1
+ ---
2
+ type: overview
3
+ title: Questions
4
+ description: Things asked of the schedule package and not yet answered
5
+ ---
6
+
7
+ # Questions
8
+
9
+ Something asked and not yet answered. The choice stays, the doubt gets recorded.
10
+
11
+ <!-- pikku:knowledge-index -->
12
+ - [Should a delayed RPC run as the session it was scheduled with?](should-a-delayed-rpc-run-as-the-session-it-was-scheduled-with.md) — The in-memory scheduler stores the session with the task and returns it from getTask, but the timer runs the RPC without it
13
+ <!-- /pikku:knowledge-index -->
@@ -0,0 +1,22 @@
1
+ ---
2
+ type: question
3
+ title: Should a delayed RPC run as the session it was scheduled with?
4
+ description: The in-memory scheduler stores the session with the task and returns it from getTask, but the timer runs the RPC without it
5
+ tags: [schedule, sessions, rpc]
6
+ ---
7
+
8
+ # Should a delayed RPC run as the session it was scheduled with?
9
+
10
+ `scheduleRPC` in `packages/schedule/src/in-memory-scheduler-service.ts` takes an optional `session`
11
+ (line 45). It stores it on the task (lines 85-92), and `getTask` returns it (line 115).
12
+
13
+ The timer callback `run` (line 52) calls `rpcService.getContextRPCService(services, {}, false)` (lines 69-74)
14
+ and then `rpc.invoke(rpcName, data)` (line 75). The `session` is not passed to either call and is not in
15
+ scope inside `run`.
16
+
17
+ The comment at lines 67-68 says the BullMQ and pg-boss schedulers "enqueue `{ rpcName, data, session }`
18
+ and invoke the RPC with it". This file does not do that for the session.
19
+
20
+ Question: should the in-memory scheduler run the RPC as the captured session, as the comment says the other
21
+ schedulers do, or is a delayed RPC meant to run with no user? The code does not settle it, and no note in
22
+ this package records the choice.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/schedule",
3
- "version": "0.12.9",
3
+ "version": "0.12.10",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -24,7 +24,7 @@
24
24
  "prepublishOnly": "bun run build"
25
25
  },
26
26
  "peerDependencies": {
27
- "@pikku/core": "^0.12.138"
27
+ "@pikku/core": "^0.12.143"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@types/node": "^24.13.3"