@pikku/schedule 0.12.4 → 0.12.6

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,46 @@
1
+ ## 0.12.6
2
+
3
+ ### Patch Changes
4
+
5
+ - 6794681: Publish the ecosystem surface as per-area sub-barrels under `@pikku/core/ecosystem/*`, and point generated code, the CLI, the inspector and the runtime adapters at them.
6
+
7
+ 348 names that only generated code, the toolchain or a runtime adapter ever imports now have a second home on `@pikku/core/ecosystem/<area>` — one sub-barrel per area, matching how core already publishes its entrypoints, so no single barrel grows without bound and a consumer only pulls in the area it uses.
8
+
9
+ This step is additive: every name is still exported from the entrypoint it was published from before, so nothing downstream breaks. Removing them from the app-facing barrels is a later change, and needs a release carrying `./ecosystem/*` first.
10
+
11
+ - Updated dependencies [7406bfe]
12
+ - Updated dependencies [6794681]
13
+ - @pikku/core@0.12.84
14
+
15
+ ## 0.12.5
16
+
17
+ ### Patch Changes
18
+
19
+ - dbff6ae: **A delayed RPC now actually runs.** `InMemorySchedulerService.scheduleRPC`
20
+ dispatched through `runScheduledTask`, which resolves names against the wired
21
+ _cron task_ registry — a registry no internally scheduled RPC is ever in. Every
22
+ such call died as `ScheduledTaskNotFoundError` and was swallowed into a log
23
+ line, and the payload captured alongside it was never forwarded. The one that
24
+ mattered was `pikkuWorkflowSleeper`: it is what wakes a run from
25
+ `workflow.sleep`, so an async workflow containing a sleep stopped at the first
26
+ one and stayed `running` forever. It now invokes the RPC with its data, matching
27
+ what the BullMQ and pg-boss schedulers already do.
28
+ - Updated dependencies [41c1a95]
29
+ - Updated dependencies [ce96383]
30
+ - Updated dependencies [7e60867]
31
+ - Updated dependencies [f8f1244]
32
+ - Updated dependencies [dcf20cb]
33
+ - Updated dependencies [6512384]
34
+ - Updated dependencies [e3b4c14]
35
+ - Updated dependencies [efd0ed1]
36
+ - Updated dependencies [cba98fb]
37
+ - Updated dependencies [ce96383]
38
+ - Updated dependencies [f8f1244]
39
+ - Updated dependencies [f8f1244]
40
+ - Updated dependencies [6e93a35]
41
+ - Updated dependencies [6dada45]
42
+ - @pikku/core@0.12.80
43
+
1
44
  ## 0.12.4
2
45
 
3
46
  ### Patch Changes
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2021 - present Yasser Fadl and Pikku contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # @pikku/scheduler
1
+ # @pikku/schedule
2
2
 
3
- This package is part of @pikku/mono and is responsible for scheduling tasks in pikku.
3
+ In-memory scheduler service for Pikku cron wirings, built on `cron`.
4
+
5
+ Runs jobs in the current process, so it suits single-instance deployments and
6
+ local development. For multi-instance setups use a queue-backed scheduler such
7
+ as `@pikku/queue-bullmq` or `@pikku/queue-pg-boss`.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ npm install @pikku/schedule
13
+ ```
14
+
15
+ ## Usage
16
+
17
+ ```typescript
18
+ import { InMemorySchedulerService } from '@pikku/schedule'
19
+
20
+ const scheduler = new InMemorySchedulerService(logger)
21
+
22
+ await scheduler.init()
23
+ scheduler.startAll()
24
+ ```
25
+
26
+ ## Docs
27
+
28
+ https://pikku.dev/docs
@@ -1,4 +1,5 @@
1
- import type { CoreUserSession, ScheduledTaskInfo, ScheduledTaskSummary } from '@pikku/core';
1
+ import type { CoreUserSession } from '@pikku/core';
2
+ import type { ScheduledTaskInfo, ScheduledTaskSummary } from '@pikku/core/ecosystem/services';
2
3
  import { SchedulerService } from '@pikku/core';
3
4
  /**
4
5
  * In-memory SchedulerService implementation.
@@ -1,7 +1,10 @@
1
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';
2
+ import { SchedulerService } from '@pikku/core';
3
+ import { parseDurationString } from '@pikku/core/ecosystem/types';
4
+ import { pikkuState, getSingletonServices } from '@pikku/core/ecosystem';
5
+ import { runScheduledTask } from '@pikku/core/scheduler';
6
+ import { getScheduledTasks } from '@pikku/core/ecosystem/scheduler';
7
+ import { rpcService } from '@pikku/core/rpc';
5
8
  const getLogger = () => {
6
9
  return pikkuState(null, 'package', 'singletonServices').logger;
7
10
  };
@@ -24,7 +27,23 @@ export class InMemorySchedulerService extends SchedulerService {
24
27
  const timer = setTimeout(async () => {
25
28
  this.delayedTasks.delete(taskId);
26
29
  try {
27
- await runScheduledTask({ name: rpcName, session });
30
+ // An RPC, not a cron task. `runScheduledTask` looks the name up in the
31
+ // scheduler's task registry, which only ever holds wired cron tasks —
32
+ // so every internally-scheduled RPC died here as
33
+ // ScheduledTaskNotFoundError. The one that matters most is
34
+ // `pikkuWorkflowSleeper`: it is registered by the workflow service as a
35
+ // function, and it is what wakes a run from `workflow.sleep`. With the
36
+ // wake-up lost to a swallowed log line, an async workflow run stopped
37
+ // at its first sleep and stayed `running` forever.
38
+ //
39
+ // `data` has to be forwarded for the same reason. The sleeper is called
40
+ // with `{ runId, stepId }` and cannot identify the sleeping step
41
+ // without it — it was captured in `delayedTasks` but never passed on.
42
+ // This matches what the BullMQ and pg-boss schedulers already do:
43
+ // enqueue `{ rpcName, data, session }` and invoke the RPC with it.
44
+ const services = getSingletonServices();
45
+ const rpc = rpcService.getContextRPCService(services, {}, false);
46
+ await rpc.invoke(rpcName, data);
28
47
  }
29
48
  catch (err) {
30
49
  getLogger().error(`Failed to execute delayed RPC '${rpcName}': ${err}`);
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@pikku/schedule",
3
- "version": "0.12.4",
3
+ "version": "0.12.6",
4
+ "description": "In-memory scheduler service for Pikku cron wirings",
4
5
  "author": "yasser.fadl@gmail.com",
5
6
  "license": "MIT",
6
7
  "module": "dist/index.js",
@@ -18,7 +19,7 @@
18
19
  "prepublishOnly": "yarn build"
19
20
  },
20
21
  "peerDependencies": {
21
- "@pikku/core": "^0.12.58"
22
+ "@pikku/core": "^0.12.84"
22
23
  },
23
24
  "devDependencies": {
24
25
  "@types/node": "^24.11.0",
@@ -1,9 +1,19 @@
1
1
  import { describe, test } from 'node:test'
2
2
  import assert from 'node:assert/strict'
3
- import { pikkuState } from '@pikku/core/internal'
3
+ import { pikkuState } from '@pikku/core/ecosystem'
4
+ import { addFunction } from '@pikku/core/function'
4
5
 
5
6
  import { InMemorySchedulerService } from './in-memory-scheduler-service.js'
6
7
 
8
+ const waitFor = async (predicate: () => boolean, withinMs = 2000) => {
9
+ const deadline = Date.now() + withinMs
10
+ while (Date.now() < deadline) {
11
+ if (predicate()) return true
12
+ await new Promise((resolve) => setTimeout(resolve, 10))
13
+ }
14
+ return false
15
+ }
16
+
7
17
  describe('InMemorySchedulerService scheduleRPC', () => {
8
18
  test('schedules and executes delayed RPC', async () => {
9
19
  const logger = {
@@ -31,4 +41,54 @@ describe('InMemorySchedulerService scheduleRPC', () => {
31
41
  await scheduler.close()
32
42
  }
33
43
  })
44
+
45
+ /**
46
+ * The delay actually has to invoke the RPC, with its data.
47
+ *
48
+ * The test above only proved the task was registered, which is why the
49
+ * scheduler could dispatch through `runScheduledTask` — a lookup against the
50
+ * *cron task* registry that no scheduled RPC is ever in — and still pass.
51
+ * Every internally scheduled RPC failed with ScheduledTaskNotFoundError,
52
+ * `pikkuWorkflowSleeper` among them, so an async workflow never woke from
53
+ * `workflow.sleep`.
54
+ */
55
+ test('invokes the named RPC with its data once the delay elapses', async () => {
56
+ const errors: string[] = []
57
+ const logger = {
58
+ info: () => {},
59
+ warn: () => {},
60
+ debug: () => {},
61
+ error: (message: string) => errors.push(message),
62
+ }
63
+ pikkuState(null, 'package', 'singletonServices', { logger } as any)
64
+
65
+ const calls: unknown[] = []
66
+ addFunction(
67
+ 'delayedTestRpc',
68
+ {
69
+ func: async (_services: any, data: unknown) => calls.push(data),
70
+ } as any,
71
+ null
72
+ )
73
+ pikkuState(null, 'function', 'meta')['delayedTestRpc'] = {
74
+ name: 'delayedTestRpc',
75
+ sessionless: true,
76
+ permissions: [],
77
+ } as any
78
+
79
+ const scheduler = new InMemorySchedulerService()
80
+ try {
81
+ await scheduler.scheduleRPC(20, 'delayedTestRpc', {
82
+ runId: 'run-1',
83
+ stepId: 'step-1',
84
+ })
85
+ assert.ok(
86
+ await waitFor(() => calls.length > 0),
87
+ `RPC was never invoked; scheduler errors: ${JSON.stringify(errors)}`
88
+ )
89
+ assert.deepEqual(calls[0], { runId: 'run-1', stepId: 'step-1' })
90
+ } finally {
91
+ await scheduler.close()
92
+ }
93
+ })
34
94
  })
@@ -1,12 +1,15 @@
1
1
  import { CronJob } from 'cron'
2
+ import type { CoreUserSession } from '@pikku/core'
2
3
  import type {
3
- CoreUserSession,
4
4
  ScheduledTaskInfo,
5
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'
6
+ } from '@pikku/core/ecosystem/services'
7
+ import { SchedulerService } from '@pikku/core'
8
+ import { parseDurationString } from '@pikku/core/ecosystem/types'
9
+ import { pikkuState, getSingletonServices } from '@pikku/core/ecosystem'
10
+ import { runScheduledTask } from '@pikku/core/scheduler'
11
+ import { getScheduledTasks } from '@pikku/core/ecosystem/scheduler'
12
+ import { rpcService } from '@pikku/core/rpc'
10
13
 
11
14
  interface DelayedTask {
12
15
  taskId: string
@@ -49,7 +52,27 @@ export class InMemorySchedulerService extends SchedulerService {
49
52
  const timer = setTimeout(async () => {
50
53
  this.delayedTasks.delete(taskId)
51
54
  try {
52
- await runScheduledTask({ name: rpcName, session })
55
+ // An RPC, not a cron task. `runScheduledTask` looks the name up in the
56
+ // scheduler's task registry, which only ever holds wired cron tasks —
57
+ // so every internally-scheduled RPC died here as
58
+ // ScheduledTaskNotFoundError. The one that matters most is
59
+ // `pikkuWorkflowSleeper`: it is registered by the workflow service as a
60
+ // function, and it is what wakes a run from `workflow.sleep`. With the
61
+ // wake-up lost to a swallowed log line, an async workflow run stopped
62
+ // at its first sleep and stayed `running` forever.
63
+ //
64
+ // `data` has to be forwarded for the same reason. The sleeper is called
65
+ // with `{ runId, stepId }` and cannot identify the sleeping step
66
+ // without it — it was captured in `delayedTasks` but never passed on.
67
+ // This matches what the BullMQ and pg-boss schedulers already do:
68
+ // enqueue `{ rpcName, data, session }` and invoke the RPC with it.
69
+ const services = getSingletonServices()
70
+ const rpc = rpcService.getContextRPCService(
71
+ services as any,
72
+ {},
73
+ false
74
+ ) as { invoke: (name: string, data: unknown) => Promise<unknown> }
75
+ await rpc.invoke(rpcName, data)
53
76
  } catch (err: unknown) {
54
77
  getLogger().error(`Failed to execute delayed RPC '${rpcName}': ${err}`)
55
78
  }