@pikku/skills 0.12.13 → 0.12.14
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 +38 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +1 -1
- package/skills/pikku-ai-vercel/SKILL.md +0 -1
- package/skills/pikku-ai-voice/SKILL.md +1 -0
- package/skills/pikku-audit/SKILL.md +0 -1
- package/skills/pikku-better-auth/SKILL.md +0 -1
- package/skills/pikku-build-app/SKILL.md +0 -1
- package/skills/pikku-build-platform/SKILL.md +3 -4
- package/skills/pikku-build-quick/SKILL.md +0 -1
- package/skills/pikku-concepts/SKILL.md +58 -16
- package/skills/pikku-concepts/references/concept-mapping.md +1 -1
- package/skills/pikku-deploy-cloudflare/SKILL.md +0 -1
- package/skills/pikku-deps/SKILL.md +0 -1
- package/skills/pikku-emails/SKILL.md +0 -1
- package/skills/pikku-fabric/SKILL.md +55 -4
- package/skills/pikku-feature/SKILL.md +0 -1
- package/skills/pikku-gateway-slack/SKILL.md +1 -0
- package/skills/pikku-i18n/SKILL.md +1 -1
- package/skills/pikku-info/SKILL.md +0 -1
- package/skills/pikku-knowledge/SKILL.md +0 -1
- package/skills/pikku-kysely/SKILL.md +0 -1
- package/skills/pikku-paraglide/SKILL.md +1 -1
- package/skills/pikku-product-second-opinion/SKILL.md +0 -1
- package/skills/pikku-queue/SKILL.md +1 -1
- package/skills/pikku-react/SKILL.md +1 -1
- package/skills/pikku-react-query/SKILL.md +1 -1
- package/skills/pikku-realtime/SKILL.md +0 -1
- package/skills/pikku-rpc/SKILL.md +0 -1
- package/skills/pikku-rtl/SKILL.md +1 -1
- package/skills/pikku-scenario/SKILL.md +29 -2
- package/skills/pikku-schedule/SKILL.md +178 -50
- package/skills/pikku-schema-ajv/SKILL.md +0 -1
- package/skills/pikku-schema-cfworker/SKILL.md +0 -1
- package/skills/pikku-security/SKILL.md +2 -2
- package/skills/pikku-software-archaeology/SKILL.md +0 -1
- package/skills/pikku-template-clone/SKILL.md +0 -1
- package/skills/pikku-trigger/SKILL.md +1 -1
- package/skills/pikku-versioning/SKILL.md +0 -1
- package/skills/pikku-workflow/SKILL.md +1 -1
- package/skills/pikku-workflows-client/SKILL.md +1 -1
- package/skills/pikku-cron/SKILL.md +0 -221
- package/skills/pikku-tag-middleware/SKILL.md +0 -14
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-schedule
|
|
3
3
|
description: >-
|
|
4
|
-
Use when
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
pikku-
|
|
4
|
+
Use when adding scheduled tasks, recurring jobs, or cron-based automation to a Pikku app. Covers
|
|
5
|
+
wireScheduler, cron expressions, the scheduled task wire object, and scheduler middleware.
|
|
6
|
+
TRIGGER when: code uses wireScheduler, user asks about cron, scheduled tasks, recurring jobs, or
|
|
7
|
+
"run every X minutes/hours". DO NOT TRIGGER when: user asks about background jobs with retries
|
|
8
|
+
(use pikku-queue) or event-driven triggers (use pikku-trigger).
|
|
9
9
|
installGroups: [core]
|
|
10
10
|
---
|
|
11
11
|
|
|
12
|
-
# Pikku
|
|
12
|
+
# Pikku Scheduled Tasks
|
|
13
13
|
|
|
14
14
|
## Agent Operating Procedure
|
|
15
15
|
|
|
@@ -21,75 +21,203 @@ Use this skill as an execution checklist, not reference material.
|
|
|
21
21
|
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
22
22
|
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
Wire Pikku functions to run on a schedule using cron expressions. Uses `pikkuVoidFunc` (no input/output).
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
`pikku dev`, `pikku serve` and the standalone deploy adapter each register a scheduler service for you, so a wired task runs without any setup. Only register one yourself when deploying somewhere those do not reach, and then take it off the queue factory (`bullFactory.getSchedulerService()`, `pgBossFactory.getSchedulerService()` — see `pikku-queue`) so it survives a restart and is shared between instances.
|
|
27
|
+
|
|
28
|
+
## Before You Start
|
|
27
29
|
|
|
28
30
|
```bash
|
|
29
|
-
|
|
31
|
+
pikku info functions --verbose # See existing functions and their types
|
|
32
|
+
pikku info tags --verbose # Understand project organization
|
|
30
33
|
```
|
|
31
34
|
|
|
35
|
+
See `pikku-concepts` for the core mental model.
|
|
36
|
+
|
|
32
37
|
## API Reference
|
|
33
38
|
|
|
34
|
-
### `
|
|
39
|
+
### `wireScheduler(config)`
|
|
35
40
|
|
|
36
41
|
```typescript
|
|
37
|
-
import {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
import { wireScheduler } from '@pikku/core/scheduler'
|
|
43
|
+
|
|
44
|
+
wireScheduler({
|
|
45
|
+
name: string, // Unique scheduler name
|
|
46
|
+
schedule: string, // Cron expression
|
|
47
|
+
func: PikkuVoidFunc, // Must be pikkuVoidFunc (no input/output)
|
|
48
|
+
tags?: string[], // Targets tag middleware — see pikku-middleware
|
|
49
|
+
middleware?: PikkuMiddleware[],
|
|
50
|
+
})
|
|
41
51
|
```
|
|
42
52
|
|
|
43
|
-
|
|
44
|
-
recurring tasks you declared with `wireScheduler` (see `pikku-cron`), and
|
|
45
|
-
`setTimeout` for one-off delayed RPCs. Both live in process memory, so nothing
|
|
46
|
-
survives a restart and nothing is shared between instances — fine for
|
|
47
|
-
development and a single-instance deployment, wrong for anything else.
|
|
48
|
-
|
|
49
|
-
`PikkuTaskScheduler` is a deprecated alias for the same class.
|
|
53
|
+
### Wire Object (`wire.scheduledTask`)
|
|
50
54
|
|
|
51
|
-
|
|
55
|
+
Inside scheduled functions:
|
|
52
56
|
|
|
53
57
|
```typescript
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
wire.scheduledTask.name // Scheduler name
|
|
59
|
+
wire.scheduledTask.schedule // Cron expression string
|
|
60
|
+
wire.scheduledTask.executionTime // Date this execution was triggered
|
|
61
|
+
wire.scheduledTask.skip(reason?) // Abort this execution — THROWS, never returns
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
**`skip()` aborts by throwing.** It reads like an early return but it is not:
|
|
65
|
+
nothing after the call runs, so there is no need to `return` afterwards. The
|
|
66
|
+
consequence that bites is in middleware — a `try/catch` around `await next()`
|
|
67
|
+
will catch a skip and report it as a failure. If your middleware distinguishes
|
|
68
|
+
success from failure, let the skip pass through rather than logging it as an
|
|
69
|
+
error.
|
|
70
|
+
|
|
71
|
+
### Cron Expression Reference
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
┌───────────── minute (0-59)
|
|
75
|
+
│ ┌───────────── hour (0-23)
|
|
76
|
+
│ │ ┌───────────── day of month (1-31)
|
|
77
|
+
│ │ │ ┌───────────── month (1-12)
|
|
78
|
+
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
|
|
79
|
+
│ │ │ │ │
|
|
80
|
+
* * * * *
|
|
63
81
|
```
|
|
64
82
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
83
|
+
Common patterns:
|
|
84
|
+
|
|
85
|
+
| Expression | Meaning |
|
|
86
|
+
| ------------- | -------------------------- |
|
|
87
|
+
| `*/5 * * * *` | Every 5 minutes |
|
|
88
|
+
| `0 9 * * *` | Daily at 9:00 AM |
|
|
89
|
+
| `0 9 * * 1` | Every Monday at 9:00 AM |
|
|
90
|
+
| `0 0 1 * *` | First of month at midnight |
|
|
91
|
+
| `0 */6 * * *` | Every 6 hours |
|
|
92
|
+
| `30 2 * * 0` | Sundays at 2:30 AM |
|
|
68
93
|
|
|
69
94
|
## Usage Patterns
|
|
70
95
|
|
|
71
|
-
### Basic
|
|
96
|
+
### Basic Scheduled Task
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const dailySummary = pikkuVoidFunc({
|
|
100
|
+
title: 'Daily Summary',
|
|
101
|
+
func: async ({ db, emailService, logger }) => {
|
|
102
|
+
logger.info('Generating daily summary')
|
|
103
|
+
const stats = await db.getDailyStats()
|
|
104
|
+
await emailService.sendSummary(stats)
|
|
105
|
+
},
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
wireScheduler({
|
|
109
|
+
name: 'dailySummary',
|
|
110
|
+
schedule: '0 9 * * *',
|
|
111
|
+
func: dailySummary,
|
|
112
|
+
})
|
|
113
|
+
```
|
|
72
114
|
|
|
73
|
-
|
|
74
|
-
it is started in your server bootstrap — declaring it without calling `start()`
|
|
75
|
-
registers no cron jobs, so nothing ever fires:
|
|
115
|
+
### Using the Wire Object
|
|
76
116
|
|
|
77
117
|
```typescript
|
|
78
|
-
|
|
79
|
-
|
|
118
|
+
const weeklyCleanup = pikkuVoidFunc({
|
|
119
|
+
title: 'Weekly Cleanup',
|
|
120
|
+
func: async ({ db, logger }, _input, wire) => {
|
|
121
|
+
logger.info(`Running: ${wire.scheduledTask.name}`)
|
|
122
|
+
logger.info(`Schedule: ${wire.scheduledTask.schedule}`)
|
|
123
|
+
logger.info(`Execution time: ${wire.scheduledTask.executionTime}`)
|
|
124
|
+
|
|
125
|
+
const staleCount = await db.countStaleTodos()
|
|
126
|
+
if (staleCount === 0) {
|
|
127
|
+
wire.scheduledTask.skip('No stale todos found') // throws — nothing below runs
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await db.deleteCompletedTodos({ olderThan: '30d' })
|
|
131
|
+
logger.info(`Cleaned ${staleCount} stale todos`)
|
|
132
|
+
},
|
|
133
|
+
})
|
|
80
134
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
135
|
+
wireScheduler({
|
|
136
|
+
name: 'weeklyCleanup',
|
|
137
|
+
schedule: '0 0 * * 0',
|
|
138
|
+
func: weeklyCleanup,
|
|
84
139
|
})
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Scheduler Middleware
|
|
85
143
|
|
|
86
|
-
|
|
87
|
-
|
|
144
|
+
```typescript
|
|
145
|
+
const schedulerMetrics = pikkuMiddleware(
|
|
146
|
+
async ({ logger }, { scheduledTask }, next) => {
|
|
147
|
+
const start = Date.now()
|
|
148
|
+
logger.info(`Task started: ${scheduledTask.name}`)
|
|
149
|
+
|
|
150
|
+
try {
|
|
151
|
+
await next()
|
|
152
|
+
logger.info(`Task completed: ${scheduledTask.name}`, {
|
|
153
|
+
duration: Date.now() - start,
|
|
154
|
+
})
|
|
155
|
+
} catch (error) {
|
|
156
|
+
logger.error(`Task failed: ${scheduledTask.name}`, {
|
|
157
|
+
error: error.message,
|
|
158
|
+
duration: Date.now() - start,
|
|
159
|
+
})
|
|
160
|
+
throw error
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
wireScheduler({
|
|
166
|
+
name: 'dailySummary',
|
|
167
|
+
schedule: '0 9 * * *',
|
|
168
|
+
func: dailySummary,
|
|
169
|
+
middleware: [schedulerMetrics],
|
|
170
|
+
})
|
|
88
171
|
```
|
|
89
172
|
|
|
90
|
-
|
|
173
|
+
## Complete Example
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// functions/scheduled.functions.ts
|
|
177
|
+
export const dailySummary = pikkuVoidFunc({
|
|
178
|
+
title: 'Daily Summary',
|
|
179
|
+
func: async ({ db, emailService, logger }) => {
|
|
180
|
+
const stats = await db.getDailyStats()
|
|
181
|
+
await emailService.sendSummary(stats)
|
|
182
|
+
logger.info('Daily summary sent', { stats })
|
|
183
|
+
},
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
export const cleanupExpired = pikkuVoidFunc({
|
|
187
|
+
title: 'Cleanup Expired',
|
|
188
|
+
func: async ({ db, logger }, _input, wire) => {
|
|
189
|
+
const count = await db.countExpiredSessions()
|
|
190
|
+
if (count === 0) {
|
|
191
|
+
wire.scheduledTask.skip('No expired sessions') // throws — nothing below runs
|
|
192
|
+
}
|
|
193
|
+
await db.deleteExpiredSessions()
|
|
194
|
+
logger.info(`Cleaned ${count} expired sessions`)
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
export const syncInventory = pikkuVoidFunc({
|
|
199
|
+
title: 'Sync Inventory',
|
|
200
|
+
func: async ({ inventoryApi, db, logger }) => {
|
|
201
|
+
const updates = await inventoryApi.getChanges()
|
|
202
|
+
await db.applyInventoryUpdates(updates)
|
|
203
|
+
logger.info(`Synced ${updates.length} inventory changes`)
|
|
204
|
+
},
|
|
205
|
+
})
|
|
91
206
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
207
|
+
// wirings/scheduler.wiring.ts
|
|
208
|
+
wireScheduler({
|
|
209
|
+
name: 'dailySummary',
|
|
210
|
+
schedule: '0 9 * * *',
|
|
211
|
+
func: dailySummary,
|
|
212
|
+
})
|
|
213
|
+
wireScheduler({
|
|
214
|
+
name: 'cleanupExpired',
|
|
215
|
+
schedule: '0 */6 * * *',
|
|
216
|
+
func: cleanupExpired,
|
|
217
|
+
})
|
|
218
|
+
wireScheduler({
|
|
219
|
+
name: 'syncInventory',
|
|
220
|
+
schedule: '*/15 * * * *',
|
|
221
|
+
func: syncInventory,
|
|
222
|
+
})
|
|
223
|
+
```
|
|
@@ -5,7 +5,6 @@ description: >-
|
|
|
5
5
|
request/response validation. TRIGGER when: code uses AjvSchemaService, user asks about AJV, JSON
|
|
6
6
|
schema validation, or @pikku/schema-ajv. DO NOT TRIGGER when: user asks about Cloudflare Workers
|
|
7
7
|
schema validation (use pikku-schema-cfworker).
|
|
8
|
-
installGroups: [core]
|
|
9
8
|
---
|
|
10
9
|
|
|
11
10
|
# Pikku Schema AJV (JSON Schema Validation)
|
|
@@ -6,7 +6,6 @@ description: >-
|
|
|
6
6
|
CFWorkerSchemaService, user asks about schema validation on Cloudflare Workers, or
|
|
7
7
|
@pikku/schema-cfworker. DO NOT TRIGGER when: user asks about AJV schema validation (use
|
|
8
8
|
pikku-schema-ajv).
|
|
9
|
-
installGroups: [core, fabric]
|
|
10
9
|
---
|
|
11
10
|
|
|
12
11
|
# Pikku Schema CFWorker (Cloudflare Workers Validation)
|
|
@@ -61,7 +61,7 @@ with the default `auth` would be rejected before its body ever ran.
|
|
|
61
61
|
Apply these via `addHTTPMiddleware` in a wirings file:
|
|
62
62
|
|
|
63
63
|
```typescript
|
|
64
|
-
import { authBearer, authCookie, authAPIKey } from '
|
|
64
|
+
import { authBearer, authCookie, authAPIKey } from '#pikku/middleware'
|
|
65
65
|
import { addHTTPMiddleware } from '#pikku/http'
|
|
66
66
|
|
|
67
67
|
// JWT bearer token — reads Authorization header
|
|
@@ -128,7 +128,7 @@ export const isVerified = pikkuAuth(
|
|
|
128
128
|
)
|
|
129
129
|
|
|
130
130
|
// wirings/auth.wiring.ts
|
|
131
|
-
import { authCookie } from '
|
|
131
|
+
import { authCookie } from '#pikku/middleware'
|
|
132
132
|
import { addHTTPMiddleware } from '#pikku/http'
|
|
133
133
|
|
|
134
134
|
addHTTPMiddleware('*', [
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-software-archaeology
|
|
3
3
|
description: 'Use when reverse-engineering an existing repository into a Product Blueprint — recovering what product an undocumented or organically-grown codebase implements so it can be rebuilt cleanly (e.g. as a Pikku app). TRIGGER when: user says "extract a blueprint", "reverse engineer this app", "what does this codebase actually do as a product", "prepare this repo for a rewrite/migration", or points at a legacy repo (any language — JS, TS, Ruby, Python, PHP, Go) and asks for its domains, workflows, business rules, or a rebuild plan. DO NOT TRIGGER for: documenting code structure, generating API docs from an already-clean codebase, or code review.'
|
|
4
|
-
installGroups: [fabric]
|
|
5
4
|
---
|
|
6
5
|
|
|
7
6
|
# Software Archaeology
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
name: pikku-template-clone
|
|
3
3
|
description: 'Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `npm create pikku`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.'
|
|
4
4
|
allowed-tools: Bash(git status *), Bash(git add *), Bash(git commit *), Bash(git rm *), Bash(git mv *), Bash(git log *)
|
|
5
|
-
installGroups: [core]
|
|
6
5
|
---
|
|
7
6
|
|
|
8
7
|
# Pikku Template Post-Clone Cleanup
|
|
@@ -5,7 +5,7 @@ description: >-
|
|
|
5
5
|
PostgreSQL LISTEN/NOTIFY, or custom event sources. Covers wireTrigger, wireTriggerSource, and
|
|
6
6
|
pikkuTriggerFunc. TRIGGER when: code uses wireTrigger/wireTriggerSource/pikkuTriggerFunc, user
|
|
7
7
|
asks about event-driven functions, Redis pub/sub, PostgreSQL LISTEN/NOTIFY, or reacting to
|
|
8
|
-
external events. DO NOT TRIGGER when: user asks about scheduled tasks (use pikku-
|
|
8
|
+
external events. DO NOT TRIGGER when: user asks about scheduled tasks (use pikku-schedule) or
|
|
9
9
|
background job queues (use pikku-queue).
|
|
10
10
|
installGroups: [core]
|
|
11
11
|
---
|
|
@@ -11,7 +11,6 @@ description: >-
|
|
|
11
11
|
CLI commands. DO NOT TRIGGER when: user asks about secrets/variables/OAuth2 (use pikku-config)
|
|
12
12
|
or general function definitions (use pikku-concepts), or about updating dependency versions
|
|
13
13
|
(use pikku-deps).
|
|
14
|
-
installGroups: [core]
|
|
15
14
|
---
|
|
16
15
|
|
|
17
16
|
# Pikku Function Versioning
|
|
@@ -6,7 +6,7 @@ description: >-
|
|
|
6
6
|
TRIGGER when: code uses pikkuWorkflowFunc/pikkuWorkflowGraph, user asks about workflows,
|
|
7
7
|
multi-step processes, durable execution, suspend/resume, or DAG orchestration. DO NOT TRIGGER
|
|
8
8
|
when: user asks about simple background jobs (use pikku-queue) or scheduled tasks (use
|
|
9
|
-
pikku-
|
|
9
|
+
pikku-schedule).
|
|
10
10
|
installGroups: [core]
|
|
11
11
|
---
|
|
12
12
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-workflows-client
|
|
3
3
|
description: 'Run Pikku workflows from a React frontend and track their progress. Covers `useRunWorkflow` (run-and-wait), `useStartWorkflow` (fire-and-poll), and `useWorkflowStatus` (live status). TRIGGER when: a React component needs to invoke or display the status of a Pikku workflow, the user mentions long-running tasks / background jobs / progress UI tied to a workflow, or asks how to start/track a workflow from the client. DO NOT TRIGGER when: the user is wiring the workflow itself (use pikku-workflow) or only making regular RPC calls (use pikku-react-query).'
|
|
4
|
-
installGroups: [
|
|
4
|
+
installGroups: [client]
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Pikku Workflows — Client Hooks
|
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-cron
|
|
3
|
-
description: >-
|
|
4
|
-
Use when adding scheduled tasks, recurring jobs, or cron-based automation to a Pikku app. Covers
|
|
5
|
-
wireScheduler, cron expressions, scheduled task wire object, and scheduler middleware. TRIGGER
|
|
6
|
-
when: code uses wireScheduler, user asks about cron, scheduled tasks, recurring jobs, or "run
|
|
7
|
-
every X minutes/hours". DO NOT TRIGGER when: user asks about background jobs with retries (use
|
|
8
|
-
pikku-queue) or event-driven triggers (use pikku-trigger).
|
|
9
|
-
installGroups: [core]
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
# Pikku Cron/Scheduler Wiring
|
|
13
|
-
|
|
14
|
-
## Agent Operating Procedure
|
|
15
|
-
|
|
16
|
-
Use this skill as an execution checklist, not reference material.
|
|
17
|
-
|
|
18
|
-
1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
|
|
19
|
-
2. Identify the source files that own the behavior. Do not start by reading generated output, `.pikku`, `node_modules`, vendored packages, or broad build artifacts.
|
|
20
|
-
3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
|
|
21
|
-
4. Validate with the narrowest relevant command first, then run `pikku-verify` or `pikku all` when functions, wirings, schemas, or generated clients may have changed.
|
|
22
|
-
5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
|
|
23
|
-
|
|
24
|
-
Wire Pikku functions to run on a schedule using cron expressions. Uses `pikkuVoidFunc` (no input/output).
|
|
25
|
-
|
|
26
|
-
## Before You Start
|
|
27
|
-
|
|
28
|
-
```bash
|
|
29
|
-
pikku info functions --verbose # See existing functions and their types
|
|
30
|
-
pikku info tags --verbose # Understand project organization
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
See `pikku-concepts` for the core mental model.
|
|
34
|
-
|
|
35
|
-
## API Reference
|
|
36
|
-
|
|
37
|
-
### `wireScheduler(config)`
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
import { wireScheduler } from '@pikku/core/scheduler'
|
|
41
|
-
|
|
42
|
-
wireScheduler({
|
|
43
|
-
name: string, // Unique scheduler name
|
|
44
|
-
schedule: string, // Cron expression
|
|
45
|
-
func: PikkuVoidFunc, // Must be pikkuVoidFunc (no input/output)
|
|
46
|
-
tags?: string[], // Targets tag middleware — see pikku-middleware
|
|
47
|
-
middleware?: PikkuMiddleware[],
|
|
48
|
-
})
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
### Wire Object (`wire.scheduledTask`)
|
|
52
|
-
|
|
53
|
-
Inside scheduled functions:
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
wire.scheduledTask.name // Scheduler name
|
|
57
|
-
wire.scheduledTask.schedule // Cron expression string
|
|
58
|
-
wire.scheduledTask.executionTime // Date this execution was triggered
|
|
59
|
-
wire.scheduledTask.skip(reason?) // Abort this execution — THROWS, never returns
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
**`skip()` aborts by throwing.** It reads like an early return but it is not:
|
|
63
|
-
nothing after the call runs, so there is no need to `return` afterwards. The
|
|
64
|
-
consequence that bites is in middleware — a `try/catch` around `await next()`
|
|
65
|
-
will catch a skip and report it as a failure. If your middleware distinguishes
|
|
66
|
-
success from failure, let the skip pass through rather than logging it as an
|
|
67
|
-
error.
|
|
68
|
-
|
|
69
|
-
### Cron Expression Reference
|
|
70
|
-
|
|
71
|
-
```
|
|
72
|
-
┌───────────── minute (0-59)
|
|
73
|
-
│ ┌───────────── hour (0-23)
|
|
74
|
-
│ │ ┌───────────── day of month (1-31)
|
|
75
|
-
│ │ │ ┌───────────── month (1-12)
|
|
76
|
-
│ │ │ │ ┌───────────── day of week (0-7, 0 and 7 = Sunday)
|
|
77
|
-
│ │ │ │ │
|
|
78
|
-
* * * * *
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
Common patterns:
|
|
82
|
-
|
|
83
|
-
| Expression | Meaning |
|
|
84
|
-
| ------------- | -------------------------- |
|
|
85
|
-
| `*/5 * * * *` | Every 5 minutes |
|
|
86
|
-
| `0 9 * * *` | Daily at 9:00 AM |
|
|
87
|
-
| `0 9 * * 1` | Every Monday at 9:00 AM |
|
|
88
|
-
| `0 0 1 * *` | First of month at midnight |
|
|
89
|
-
| `0 */6 * * *` | Every 6 hours |
|
|
90
|
-
| `30 2 * * 0` | Sundays at 2:30 AM |
|
|
91
|
-
|
|
92
|
-
## Usage Patterns
|
|
93
|
-
|
|
94
|
-
### Basic Scheduled Task
|
|
95
|
-
|
|
96
|
-
```typescript
|
|
97
|
-
const dailySummary = pikkuVoidFunc({
|
|
98
|
-
title: 'Daily Summary',
|
|
99
|
-
func: async ({ db, emailService, logger }) => {
|
|
100
|
-
logger.info('Generating daily summary')
|
|
101
|
-
const stats = await db.getDailyStats()
|
|
102
|
-
await emailService.sendSummary(stats)
|
|
103
|
-
},
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
wireScheduler({
|
|
107
|
-
name: 'dailySummary',
|
|
108
|
-
schedule: '0 9 * * *',
|
|
109
|
-
func: dailySummary,
|
|
110
|
-
})
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
### Using the Wire Object
|
|
114
|
-
|
|
115
|
-
```typescript
|
|
116
|
-
const weeklyCleanup = pikkuVoidFunc({
|
|
117
|
-
title: 'Weekly Cleanup',
|
|
118
|
-
func: async ({ db, logger }, _input, wire) => {
|
|
119
|
-
logger.info(`Running: ${wire.scheduledTask.name}`)
|
|
120
|
-
logger.info(`Schedule: ${wire.scheduledTask.schedule}`)
|
|
121
|
-
logger.info(`Execution time: ${wire.scheduledTask.executionTime}`)
|
|
122
|
-
|
|
123
|
-
const staleCount = await db.countStaleTodos()
|
|
124
|
-
if (staleCount === 0) {
|
|
125
|
-
wire.scheduledTask.skip('No stale todos found') // throws — nothing below runs
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
await db.deleteCompletedTodos({ olderThan: '30d' })
|
|
129
|
-
logger.info(`Cleaned ${staleCount} stale todos`)
|
|
130
|
-
},
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
wireScheduler({
|
|
134
|
-
name: 'weeklyCleanup',
|
|
135
|
-
schedule: '0 0 * * 0',
|
|
136
|
-
func: weeklyCleanup,
|
|
137
|
-
})
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
### Scheduler Middleware
|
|
141
|
-
|
|
142
|
-
```typescript
|
|
143
|
-
const schedulerMetrics = pikkuMiddleware(
|
|
144
|
-
async ({ logger }, { scheduledTask }, next) => {
|
|
145
|
-
const start = Date.now()
|
|
146
|
-
logger.info(`Task started: ${scheduledTask.name}`)
|
|
147
|
-
|
|
148
|
-
try {
|
|
149
|
-
await next()
|
|
150
|
-
logger.info(`Task completed: ${scheduledTask.name}`, {
|
|
151
|
-
duration: Date.now() - start,
|
|
152
|
-
})
|
|
153
|
-
} catch (error) {
|
|
154
|
-
logger.error(`Task failed: ${scheduledTask.name}`, {
|
|
155
|
-
error: error.message,
|
|
156
|
-
duration: Date.now() - start,
|
|
157
|
-
})
|
|
158
|
-
throw error
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
)
|
|
162
|
-
|
|
163
|
-
wireScheduler({
|
|
164
|
-
name: 'dailySummary',
|
|
165
|
-
schedule: '0 9 * * *',
|
|
166
|
-
func: dailySummary,
|
|
167
|
-
middleware: [schedulerMetrics],
|
|
168
|
-
})
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
## Complete Example
|
|
172
|
-
|
|
173
|
-
```typescript
|
|
174
|
-
// functions/scheduled.functions.ts
|
|
175
|
-
export const dailySummary = pikkuVoidFunc({
|
|
176
|
-
title: 'Daily Summary',
|
|
177
|
-
func: async ({ db, emailService, logger }) => {
|
|
178
|
-
const stats = await db.getDailyStats()
|
|
179
|
-
await emailService.sendSummary(stats)
|
|
180
|
-
logger.info('Daily summary sent', { stats })
|
|
181
|
-
},
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
export const cleanupExpired = pikkuVoidFunc({
|
|
185
|
-
title: 'Cleanup Expired',
|
|
186
|
-
func: async ({ db, logger }, _input, wire) => {
|
|
187
|
-
const count = await db.countExpiredSessions()
|
|
188
|
-
if (count === 0) {
|
|
189
|
-
wire.scheduledTask.skip('No expired sessions') // throws — nothing below runs
|
|
190
|
-
}
|
|
191
|
-
await db.deleteExpiredSessions()
|
|
192
|
-
logger.info(`Cleaned ${count} expired sessions`)
|
|
193
|
-
},
|
|
194
|
-
})
|
|
195
|
-
|
|
196
|
-
export const syncInventory = pikkuVoidFunc({
|
|
197
|
-
title: 'Sync Inventory',
|
|
198
|
-
func: async ({ inventoryApi, db, logger }) => {
|
|
199
|
-
const updates = await inventoryApi.getChanges()
|
|
200
|
-
await db.applyInventoryUpdates(updates)
|
|
201
|
-
logger.info(`Synced ${updates.length} inventory changes`)
|
|
202
|
-
},
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
// wirings/scheduler.wiring.ts
|
|
206
|
-
wireScheduler({
|
|
207
|
-
name: 'dailySummary',
|
|
208
|
-
schedule: '0 9 * * *',
|
|
209
|
-
func: dailySummary,
|
|
210
|
-
})
|
|
211
|
-
wireScheduler({
|
|
212
|
-
name: 'cleanupExpired',
|
|
213
|
-
schedule: '0 */6 * * *',
|
|
214
|
-
func: cleanupExpired,
|
|
215
|
-
})
|
|
216
|
-
wireScheduler({
|
|
217
|
-
name: 'syncInventory',
|
|
218
|
-
schedule: '*/15 * * * *',
|
|
219
|
-
func: syncInventory,
|
|
220
|
-
})
|
|
221
|
-
```
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: pikku-tag-middleware
|
|
3
|
-
description: 'Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.'
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Deprecated: use `pikku-middleware`
|
|
7
|
-
|
|
8
|
-
Tag middleware is covered in the **`pikku-middleware`** skill, which also covers:
|
|
9
|
-
|
|
10
|
-
- `addHTTPMiddleware` (global / prefix-based)
|
|
11
|
-
- `addTagMiddleware` (tag-scoped)
|
|
12
|
-
- Middleware execution order and priority
|
|
13
|
-
- Service-to-service bearer auth pattern
|
|
14
|
-
- Session-setting middleware pattern
|