@pikku/skills 0.12.22 → 0.12.26

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +134 -0
  2. package/dist/skills.gen.js +1 -1
  3. package/package.json +1 -1
  4. package/skills/pikku-a11y/SKILL.md +59 -0
  5. package/skills/pikku-addon/SKILL.md +2 -2
  6. package/skills/pikku-agent/SKILL.md +67 -316
  7. package/skills/pikku-agent/references/agents.md +299 -0
  8. package/skills/{pikku-ai-vercel/SKILL.md → pikku-agent/references/runner-vercel.md} +2 -24
  9. package/skills/{pikku-ai-voice/SKILL.md → pikku-agent/references/voice.md} +1 -22
  10. package/skills/pikku-architect/SKILL.md +265 -0
  11. package/skills/pikku-auth/SKILL.md +89 -0
  12. package/skills/{pikku-better-auth/SKILL.md → pikku-auth/references/better-auth.md} +126 -34
  13. package/skills/{pikku-jose/SKILL.md → pikku-auth/references/jose.md} +1 -23
  14. package/skills/{pikku-machine-auth/SKILL.md → pikku-auth/references/machine-auth.md} +0 -23
  15. package/skills/pikku-auth/references/permissions.md +261 -0
  16. package/skills/{pikku-security/SKILL.md → pikku-auth/references/sessions.md} +0 -20
  17. package/skills/pikku-build/SKILL.md +88 -0
  18. package/skills/{pikku-build-app/SKILL.md → pikku-build/references/app.md} +76 -24
  19. package/skills/{pikku-feature/SKILL.md → pikku-build/references/feature.md} +1 -8
  20. package/skills/{pikku-build-app → pikku-build}/references/multi-app.md +56 -1
  21. package/skills/{pikku-build-platform/SKILL.md → pikku-build/references/platform.md} +22 -37
  22. package/skills/{pikku-template-clone/SKILL.md → pikku-build/references/post-clone.md} +0 -6
  23. package/skills/{pikku-build-quick/SKILL.md → pikku-build/references/quick.md} +4 -20
  24. package/skills/{pikku-build-app → pikku-build}/references/ship.md +9 -3
  25. package/skills/pikku-concepts/SKILL.md +72 -7
  26. package/skills/pikku-concepts/references/concept-mapping.md +8 -8
  27. package/skills/pikku-deploy/SKILL.md +158 -0
  28. package/skills/{pikku-deploy-azure/SKILL.md → pikku-deploy/references/azure.md} +18 -50
  29. package/skills/pikku-deploy/references/cloudflare.md +104 -0
  30. package/skills/pikku-deploy/references/express.md +92 -0
  31. package/skills/{pikku-deploy-fastify/SKILL.md → pikku-deploy/references/fastify.md} +8 -32
  32. package/skills/{pikku-deploy-lambda/SKILL.md → pikku-deploy/references/lambda.md} +6 -27
  33. package/skills/{pikku-deploy-nextjs/SKILL.md → pikku-deploy/references/nextjs.md} +9 -33
  34. package/skills/pikku-deploy/references/uws.md +72 -0
  35. package/skills/pikku-deploy/references/ws.md +75 -0
  36. package/skills/pikku-emails/SKILL.md +3 -2
  37. package/skills/pikku-fabric/SKILL.md +47 -20
  38. package/skills/{pikku-fabric-debug/SKILL.md → pikku-fabric/references/debugging.md} +0 -6
  39. package/skills/pikku-i18n/SKILL.md +62 -207
  40. package/skills/{pikku-paraglide/SKILL.md → pikku-i18n/references/enum-labels.md} +0 -6
  41. package/skills/pikku-i18n/references/messages.md +218 -0
  42. package/skills/{pikku-rtl/SKILL.md → pikku-i18n/references/rtl.md} +3 -9
  43. package/skills/pikku-knowledge/SKILL.md +15 -0
  44. package/skills/pikku-kysely/SKILL.md +13 -13
  45. package/skills/pikku-list-query/SKILL.md +163 -0
  46. package/skills/pikku-meta/SKILL.md +58 -130
  47. package/skills/{pikku-deps/SKILL.md → pikku-meta/references/audit.md} +1 -17
  48. package/skills/pikku-meta/references/meta.md +114 -0
  49. package/skills/{pikku-versioning/SKILL.md → pikku-meta/references/versioning.md} +0 -26
  50. package/skills/pikku-middleware/SKILL.md +5 -5
  51. package/skills/pikku-n8n-import/SKILL.md +0 -1
  52. package/skills/pikku-permissions/SKILL.md +75 -229
  53. package/skills/pikku-react/SKILL.md +50 -298
  54. package/skills/pikku-react/references/client.md +313 -0
  55. package/skills/{pikku-react-query/SKILL.md → pikku-react/references/react-query.md} +2 -22
  56. package/skills/{pikku-workflows-client/SKILL.md → pikku-react/references/workflows.md} +1 -22
  57. package/skills/pikku-realtime/SKILL.md +110 -251
  58. package/skills/pikku-scenario/SKILL.md +60 -45
  59. package/skills/pikku-scenario/references/persona-run.md +148 -0
  60. package/skills/pikku-seo/SKILL.md +133 -0
  61. package/skills/pikku-service-backends/SKILL.md +154 -0
  62. package/skills/pikku-service-backends/references/aws.md +106 -0
  63. package/skills/pikku-service-backends/references/backblaze.md +57 -0
  64. package/skills/pikku-service-backends/references/mongodb.md +90 -0
  65. package/skills/pikku-service-backends/references/redis.md +75 -0
  66. package/skills/pikku-service-backends/references/schema.md +63 -0
  67. package/skills/pikku-services/SKILL.md +68 -291
  68. package/skills/{pikku-audit/SKILL.md → pikku-services/references/audit.md} +0 -22
  69. package/skills/{pikku-config/SKILL.md → pikku-services/references/config.md} +1 -25
  70. package/skills/{pikku-pino/SKILL.md → pikku-services/references/pino.md} +0 -20
  71. package/skills/pikku-services/references/services.md +272 -0
  72. package/skills/pikku-software-archaeology/README.md +5 -1
  73. package/skills/pikku-software-archaeology/SKILL.md +16 -2
  74. package/skills/{pikku-product-second-opinion/example/sample-report.md → pikku-software-archaeology/example/second-opinion-sample-report.md} +1 -1
  75. package/skills/pikku-software-archaeology/references/blueprint.schema.json +1 -1
  76. package/skills/pikku-software-archaeology/references/pikku-mapping.md +3 -3
  77. package/skills/{pikku-product-second-opinion/SKILL.md → pikku-software-archaeology/references/second-opinion.md} +4 -9
  78. package/skills/pikku-webhook/SKILL.md +224 -0
  79. package/skills/pikku-wiring/SKILL.md +180 -0
  80. package/skills/{pikku-websocket/SKILL.md → pikku-wiring/references/channel.md} +2 -35
  81. package/skills/{pikku-cli/SKILL.md → pikku-wiring/references/cli.md} +1 -33
  82. package/skills/{pikku-gateway-slack/SKILL.md → pikku-wiring/references/gateway-slack.md} +0 -23
  83. package/skills/{pikku-http/SKILL.md → pikku-wiring/references/http.md} +3 -39
  84. package/skills/{pikku-mcp/SKILL.md → pikku-wiring/references/mcp.md} +0 -33
  85. package/skills/{pikku-queue/SKILL.md → pikku-wiring/references/queue.md} +1 -33
  86. package/skills/pikku-wiring/references/realtime.md +265 -0
  87. package/skills/{pikku-rpc/SKILL.md → pikku-wiring/references/rpc.md} +0 -32
  88. package/skills/{pikku-schedule/SKILL.md → pikku-wiring/references/scheduler.md} +1 -35
  89. package/skills/{pikku-trigger/SKILL.md → pikku-wiring/references/trigger.md} +0 -43
  90. package/skills/pikku-workflow/SKILL.md +39 -2
  91. package/skills/pikku-aws/SKILL.md +0 -161
  92. package/skills/pikku-backblaze/SKILL.md +0 -104
  93. package/skills/pikku-deploy-cloudflare/SKILL.md +0 -123
  94. package/skills/pikku-deploy-express/SKILL.md +0 -122
  95. package/skills/pikku-deploy-uws/SKILL.md +0 -144
  96. package/skills/pikku-mongodb/SKILL.md +0 -113
  97. package/skills/pikku-product-second-opinion/README.md +0 -43
  98. package/skills/pikku-redis/SKILL.md +0 -99
  99. package/skills/pikku-schema-ajv/SKILL.md +0 -83
  100. package/skills/pikku-schema-cfworker/SKILL.md +0 -82
  101. package/skills/pikku-ws/SKILL.md +0 -87
  102. /package/skills/{pikku-build-app → pikku-build}/references/theming.md +0 -0
  103. /package/skills/{pikku-product-second-opinion/references/report-template.md → pikku-software-archaeology/references/second-opinion-report-template.md} +0 -0
  104. /package/skills/{pikku-cli/references/complete-example.md → pikku-wiring/references/cli-complete-example.md} +0 -0
  105. /package/skills/{pikku-http → pikku-wiring}/references/http-options.md +0 -0
  106. /package/skills/{pikku-realtime/references/other-routes.md → pikku-wiring/references/realtime-other-routes.md} +0 -0
@@ -0,0 +1,265 @@
1
+ # Pikku Realtime
2
+
3
+ ## 1. Declare your topics
4
+
5
+ In your project's types file (e.g. `types/eventhub-topics.d.ts`):
6
+
7
+ ```ts
8
+ import type { Todo } from '../src/schemas.js'
9
+
10
+ export type EventHubTopics = {
11
+ 'todo-created': { todo: Todo }
12
+ 'todo-updated': { todo: Todo }
13
+ 'todo-deleted': { todoId: string }
14
+ }
15
+ ```
16
+
17
+ Reference it in `application-types.d.ts` and instantiate it in `services.ts`:
18
+
19
+ ```ts
20
+ // application-types.d.ts
21
+ import type { EventHubService } from '@pikku/core/channel'
22
+ import type { EventHubTopics } from './eventhub-topics.js'
23
+
24
+ export interface SingletonServices extends CoreSingletonServices<Config> {
25
+ // `CoreSingletonServices` declares eventHub optional; re-declare it required
26
+ // so functions can use it without a `if (eventHub)` guard on every publish.
27
+ eventHub: EventHubService<EventHubTopics>
28
+ }
29
+
30
+ // services.ts
31
+ import { LocalEventHubService } from '@pikku/core/channel'
32
+ const eventHub = new LocalEventHubService<EventHubTopics>()
33
+ ```
34
+
35
+ For multi-instance deployments use `CloudflareEventHubService` /
36
+ `LambdaEventHubService` / `UWSEventHubService` instead — same interface.
37
+
38
+ If a deployment genuinely has no eventHub, that belongs in `services.ts` (don't
39
+ create the service there), not as an optional type every function has to guard —
40
+ see `pikku-services`.
41
+
42
+ ## 2. Enable the server side
43
+
44
+ ```bash
45
+ yarn pikku enable events
46
+ ```
47
+
48
+ This sets `scaffold.events` in `pikku.config.json`. The next `pikku all` generates
49
+ `events.gen.ts` in your scaffold dir, wiring (using whatever `eventHub` is in your
50
+ singletons — you write neither by hand):
51
+
52
+ - A WebSocket channel at `/events` handling `{action: 'subscribe' | 'unsubscribe', topic}` messages.
53
+ - An SSE handler at `GET /events/:topic`.
54
+
55
+ ## 3. Generate the typed client
56
+
57
+ Add to `pikku.config.json`:
58
+
59
+ ```jsonc
60
+ {
61
+ "clientFiles": {
62
+ "realtimeFile": "packages/sdk/src/pikku/realtime.gen.ts",
63
+ // Optional: full type inference for subscribe/unsubscribe
64
+ "realtimeEventHubTopicsImport": "../../../functions/types/eventhub-topics.js#EventHubTopics",
65
+ },
66
+ }
67
+ ```
68
+
69
+ Run `pikku all` (or `pikku realtime` to regenerate just this file). Everything is
70
+ on one class — both transports are methods, so switching from WebSocket to SSE is
71
+ a one-word change, not a different import:
72
+
73
+ ```ts
74
+ export class PikkuRealtime {
75
+ constructor(options?: {
76
+ reconnect?: boolean
77
+ reconnectDelayMs?: number
78
+ reconnectMaxDelayMs?: number
79
+ })
80
+ setPikkuFetch(fetch: PikkuFetch): void // server URL + auth come from here, not the constructor
81
+
82
+ // WebSocket at /events — many topics on one connection
83
+ subscribe<K extends keyof EventHubTopics>(
84
+ topic: K,
85
+ handler: (data: EventHubTopics[K]) => void
86
+ ): () => void
87
+ unsubscribe<K extends keyof EventHubTopics>(
88
+ topic: K,
89
+ handler?: (data: EventHubTopics[K]) => void
90
+ ): void
91
+
92
+ // SSE at GET /events/:topic — one EventSource per topic
93
+ subscribeToTopic<K extends keyof EventHubTopics>(
94
+ topic: K,
95
+ handler: (data: EventHubTopics[K]) => void
96
+ ): { close: () => void }
97
+
98
+ // generic escape hatches — see realtime-other-routes.md
99
+ subscribeToSSE<T>(
100
+ path: string,
101
+ handler: (data: T) => void
102
+ ): { close: () => void }
103
+ connectToChannel(
104
+ channelRoute: string,
105
+ protocols?: string | string[]
106
+ ): WebSocket
107
+
108
+ close(): void
109
+ }
110
+ ```
111
+
112
+ Without `realtimeEventHubTopicsImport`, the client falls back to
113
+ `Record<string, unknown>` — usable but untyped. Set the import for full typed
114
+ subscribe/unsubscribe.
115
+
116
+ ## 4. Publish events from a function
117
+
118
+ The `/events` channel listens for client subscriptions; the eventHub fans out
119
+ publishes:
120
+
121
+ ```ts
122
+ publish(topic, channelId: string | null, data, isBinary?)
123
+ ```
124
+
125
+ The middle argument is the channel to **skip**, not the one to send to — pass
126
+ `null` to reach every subscriber, or the current `channel.channelId` when the
127
+ originating connection has already applied the change locally and would otherwise
128
+ render it twice.
129
+
130
+ Envelope the payload as `{ topic, data }`: the generated client dispatches on the
131
+ `topic` field, so a bare payload arrives but no handler fires.
132
+
133
+ ```ts
134
+ import { pikkuFunc } from '#pikku/function'
135
+
136
+ export const createTodo = pikkuFunc({
137
+ input: CreateTodoInput,
138
+ output: CreateTodoOutput,
139
+ func: async ({ kysely, eventHub }, data) => {
140
+ const todo = await kysely
141
+ .insertInto('todos')
142
+ .values(data)
143
+ .returningAll()
144
+ .executeTakeFirstOrThrow()
145
+
146
+ await eventHub.publish('todo-created', null, {
147
+ topic: 'todo-created',
148
+ data: { todo },
149
+ })
150
+ return { id: todo.id }
151
+ },
152
+ })
153
+ ```
154
+
155
+ A thin helper removes the duplication:
156
+
157
+ ```ts
158
+ async function publishEvent<K extends keyof EventHubTopics>(
159
+ hub: EventHubService<EventHubTopics>,
160
+ topic: K,
161
+ data: EventHubTopics[K]
162
+ ) {
163
+ return hub.publish(topic, null, { topic, data })
164
+ }
165
+ // usage: await publishEvent(eventHub, 'todo-created', { todo })
166
+ ```
167
+
168
+ ## 5. Wire it up — share fetch with PikkuRPC
169
+
170
+ `PikkuRealtime` mirrors `PikkuRPC`: it wraps the same `PikkuFetch`, so server URL +
171
+ auth are configured **once** and shared across HTTP, RPC, and realtime transports.
172
+
173
+ ```tsx
174
+ import { createPikku, PikkuProvider } from '@pikku/react'
175
+ import { PikkuFetch } from './pikku/pikku-fetch.gen'
176
+ import { PikkuRPC } from './pikku/pikku-rpc.gen'
177
+ import { PikkuRealtime } from './pikku/realtime.gen'
178
+
179
+ const pikku = createPikku(
180
+ PikkuFetch,
181
+ PikkuRPC,
182
+ PikkuRealtime, // pass the realtime class as the third arg
183
+ { serverUrl: apiUrl() } // shared env helper — see pikku-react
184
+ )
185
+ // pikku.fetch / pikku.rpc / pikku.realtime — all share the same fetch.
186
+
187
+ createRoot(document.getElementById('root')!).render(
188
+ <PikkuProvider pikku={pikku}>
189
+ <App />
190
+ </PikkuProvider>
191
+ )
192
+ ```
193
+
194
+ Or wire manually:
195
+
196
+ ```ts
197
+ const realtime = new PikkuRealtime()
198
+ realtime.setPikkuFetch(pikku.fetch) // inherits serverUrl + auth
199
+ ```
200
+
201
+ ## 6. Subscribe from React
202
+
203
+ Subscribe inside `useEffect` (never the render path, or you create a subscription
204
+ per render). `subscribe` returns an unsubscribe function; SSE's `subscribeToTopic`
205
+ returns a handle with `close()`:
206
+
207
+ ```tsx
208
+ import { useEffect, useState } from 'react'
209
+
210
+ function TodoList() {
211
+ const { realtime } = usePikku() // a hook over your context
212
+ const [todos, setTodos] = useState<Todo[]>([])
213
+
214
+ useEffect(() => {
215
+ // WebSocket multi-topic:
216
+ const off = realtime.subscribe('todo-created', ({ todo }) =>
217
+ setTodos((prev) => [...prev, todo])
218
+ )
219
+ return off
220
+
221
+ // Single-topic SSE (auto-cleanup on close) instead:
222
+ // const sub = realtime.subscribeToTopic('todo-created', ({ todo }) =>
223
+ // setTodos((prev) => [...prev, todo]))
224
+ // return () => sub.close()
225
+ }, [realtime])
226
+
227
+ return (
228
+ <ul>
229
+ {todos.map((t) => (
230
+ <li key={t.id}>{t.title}</li>
231
+ ))}
232
+ </ul>
233
+ )
234
+ }
235
+ ```
236
+
237
+ ## Other SSE / WebSocket routes
238
+
239
+ The same client also subscribes to generic `sse: true` routes and raw `wireChannel`
240
+ sockets (`subscribeToSSE`, `connectToChannel`). See
241
+ [realtime-other-routes.md](realtime-other-routes.md).
242
+
243
+ ## When to pick which transport
244
+
245
+ | Need | Use |
246
+ | ------------------------------------------ | --------------------------- |
247
+ | Many topics in one connection | `realtime.subscribe` |
248
+ | Single live stream, simple cleanup | `realtime.subscribeToTopic` |
249
+ | Bidirectional (client also sends messages) | `realtime.subscribe` |
250
+ | WebSockets blocked by infra | `realtime.subscribeToTopic` |
251
+
252
+ Both auto-clean on the server (the eventHub's `onChannelClosed` hook unsubscribes
253
+ all topics for the dead channel id). Don't write manual cleanup unless you're
254
+ unsubscribing partway through a session.
255
+
256
+ ## What NOT to do
257
+
258
+ - Don't call `eventHub.publish(topic, ..., rawData)` without the `{topic, data}`
259
+ envelope — clients use `topic` to dispatch handlers.
260
+ - Don't create your own `/events` channel by hand — `pikku enable events` already
261
+ does it correctly with disconnect cleanup.
262
+ - Don't subscribe inside the render path — use `useEffect`.
263
+ - Don't subscribe to topics that don't exist in `EventHubTopics`. The generated
264
+ client's types prevent it; if you reach for `as any` to subscribe to a string,
265
+ declare the topic first.
@@ -1,37 +1,5 @@
1
- ---
2
- name: pikku-rpc
3
- description: >-
4
- Use when making internal function-to-function calls within a Pikku app, composing functions, or
5
- exposing RPC endpoints. Covers rpc.invoke, rpc.remote, rpc.exposed, and generated RPC client.
6
- TRIGGER when: code uses wire.rpc or expose: true, user asks about calling one Pikku function
7
- from another, function composition, or RPC endpoints. DO NOT TRIGGER when: user asks about HTTP
8
- routes (use pikku-http) or addon cross-package calls (use pikku-addon).
9
- installGroups: [fabric]
10
- ---
11
-
12
1
  # Pikku RPC Wiring
13
2
 
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
- Call Pikku functions from other Pikku functions internally with full type safety. Use RPC to compose business logic without importing functions directly.
25
-
26
- ## Before You Start
27
-
28
- ```bash
29
- pikku info functions --verbose # See existing functions and which could be called via RPC
30
- pikku info tags --verbose # Understand project organization
31
- ```
32
-
33
- See `pikku-concepts` for the core mental model.
34
-
35
3
  ## API Reference
36
4
 
37
5
  ### RPC Methods (on `wire.rpc`)
@@ -1,45 +1,11 @@
1
- ---
2
- name: pikku-schedule
3
- description: >-
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
- installGroups: [core]
10
- ---
11
-
12
1
  # Pikku Scheduled Tasks
13
2
 
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
- `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
29
-
30
- ```bash
31
- pikku info functions --verbose # See existing functions and their types
32
- pikku info tags --verbose # Understand project organization
33
- ```
34
-
35
- See `pikku-concepts` for the core mental model.
36
-
37
3
  ## API Reference
38
4
 
39
5
  ### `wireScheduler(config)`
40
6
 
41
7
  ```typescript
42
- import { wireScheduler } from '@pikku/core/scheduler'
8
+ import { wireScheduler } from '#pikku/scheduler'
43
9
 
44
10
  wireScheduler({
45
11
  name: string, // Unique scheduler name
@@ -1,38 +1,5 @@
1
- ---
2
- name: pikku-trigger
3
- description: >-
4
- Use when adding event-driven functions that respond to system events like Redis pub/sub,
5
- PostgreSQL LISTEN/NOTIFY, or custom event sources. Covers wireTrigger, wireTriggerSource, and
6
- pikkuTriggerFunc. TRIGGER when: code uses wireTrigger/wireTriggerSource/pikkuTriggerFunc, user
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-schedule) or
9
- background job queues (use pikku-queue).
10
- installGroups: [core]
11
- ---
12
-
13
1
  # Pikku Trigger Wiring
14
2
 
15
- ## Agent Operating Procedure
16
-
17
- Use this skill as an execution checklist, not reference material.
18
-
19
- 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
20
- 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.
21
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
22
- 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.
23
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
24
-
25
- Wire Pikku functions to fire when external events occur. Triggers connect event sources (Redis pub/sub, PostgreSQL LISTEN/NOTIFY, polling, webhooks) to Pikku functions.
26
-
27
- ## Before You Start
28
-
29
- ```bash
30
- pikku info functions --verbose # See existing functions and their types
31
- pikku info tags --verbose # Understand project organization
32
- ```
33
-
34
- See `pikku-concepts` for the core mental model.
35
-
36
3
  ## API Reference
37
4
 
38
5
  All three come from `#pikku`. A trigger is deliberately split in two: the
@@ -172,16 +139,6 @@ wireTriggerSource({
172
139
  })
173
140
  ```
174
141
 
175
- ### Triggers vs Queues
176
-
177
- | Feature | Trigger | Queue |
178
- | ----------- | ---------------------------------- | ------------------------------ |
179
- | Execution | Synchronous, in-process | Async, distributed |
180
- | Reliability | At-most-once | At-least-once (with retries) |
181
- | Use case | React to events immediately | Reliable background processing |
182
- | Source | External systems (Redis, PG, etc.) | Enqueued programmatically |
183
-
184
- Use triggers for real-time reactions. Use queues for reliable, retryable background work.
185
142
 
186
143
  ## Complete Example
187
144
 
@@ -5,8 +5,8 @@ description: >-
5
5
  Covers pikkuWorkflowFunc, workflow steps (do, sleep, suspend), graph workflows, and HTTP wiring.
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
- when: user asks about simple background jobs (use pikku-queue) or scheduled tasks (use
9
- pikku-schedule).
8
+ when: user asks about simple background jobs (use pikku-wiring) or scheduled tasks (use
9
+ pikku-wiring).
10
10
  installGroups: [core]
11
11
  ---
12
12
 
@@ -27,6 +27,43 @@ See `pikku-concepts` for the core mental model.
27
27
 
28
28
  Build durable, multi-step workflows with automatic retry, sleep, suspend/resume, and parallel execution. Steps are cached for replay safety.
29
29
 
30
+ ## Decide FIRST: should this even BE a workflow?
31
+
32
+ The deciding question is: **does any part of this cross an external boundary that can fail and MUST NOT be lost or double-run** — a payment authorised/captured through a provider, a third-party API call, an email/webhook, a wait for approval? If yes → workflow (durability, retries, restart-survival, and a visible run). If it's **a single algorithm done in one shot, purely local, and not reused elsewhere** → a plain `pikkuFunc` is correct; do NOT wrap it in a workflow.
33
+
34
+ - **Checkout WITH payment → workflow.** Get cart → compute total → **(atomic: create order + order items, deduct stock, clear cart)** → **charge payment through the provider** → send confirmation email. It's a workflow because the payment leg (and the email) are external and must be **retried, not lost, and not charged twice** across a restart — and the user benefits from seeing where the run is.
35
+ - **Checkout with NO external payment** — e.g. it just records the order and decrements stock in one transaction, nothing leaves the process — is a **single-shot algorithm**: a plain `pikkuFunc` wrapping one `kysely.transaction`. Not a workflow. A workflow here would add durability machinery for a thing that already commits atomically in one shot.
36
+ - **One durable step is NOT a workflow — it's a queue worker.** A lone side-effect that must be retried / not lost (send one email, fire one webhook, one external charge) → a **queue worker** (`pikku-queue`), enqueued fire-and-forget. A workflow adds a step graph for a thing that has no steps to orchestrate. (A single non-durable step is just a direct RPC call.)
37
+ - Also workflows: onboarding sequences, settlements/payouts, digests and batch sends, anything that waits (`sleep`/`suspend`) or fans out with retries — the common thread is **multiple** steps or a durable wait, never a single step.
38
+
39
+ **HARD RULE — never a single-RPC (one-step) workflow.** A workflow whose body is one `workflow.do('x', 'someRpc', …)` is a mislabeled durable function, not orchestration. Route by durability, NOT into a workflow:
40
+
41
+ - **Not durable** — the caller wants the result now / it can just run in-request → **call the RPC directly** (this is also the synchronous path). No workflow, no queue.
42
+ - **Durable** — must be **retried / not lost / survive a restart** (one email, one webhook, one external charge) → a **queue worker** (`wireQueueWorker` + `queueService.add(...)`). Fire-and-forget, retried by the queue.
43
+
44
+ There is no "one-step workflow is justified for the durability" exception — durability for a single step is a QUEUE. A workflow earns its name only with genuine multi-step orchestration (a `sleep`/`suspend` wait, fan-out, or a saga).
45
+
46
+ **Atomicity is a TRANSACTION, not a workflow.** All-or-nothing multi-write units (create order + items + deduct stock + clear cart) belong inside ONE `kysely.transaction(async (trx) => { … })` — a single step or a single plain `pikkuFunc` — **never split across workflow steps.** A step is a unit of RETRY and REPLAY, not a unit of atomicity: pikku opens no transaction around `workflow.do`, so a step that does three writes and throws on the third leaves the first two committed, and the retry runs them again. Spreading one logical transaction over several steps is the same failure one level up. Your writes are atomic only where YOU opened a transaction, so open one inside the step (reach for compensating/saga steps only when you truly need cross-service rollback). So a payment checkout is a workflow whose _atomic DB writes are ONE step that opens ONE transaction_, with the payment charge and email as the other durable steps around it.
47
+
48
+ **A retried step re-runs its side effects.** Replay caching only covers steps that already
49
+ returned; a step that failed — or that timed out after the provider accepted it — runs again
50
+ from the top, so a charge, an email or a webhook can fire twice. Durability is at-least-once,
51
+ not exactly-once. Pass a stable idempotency key the provider deduplicates on, derived from the
52
+ workflow's own data rather than generated inside the step:
53
+
54
+ ```typescript
55
+ await workflow.do('Charge', 'chargePayment', {
56
+ orderId: data.orderId,
57
+ amount: data.amount,
58
+ idempotencyKey: `order-${data.orderId}-charge`,
59
+ })
60
+ ```
61
+
62
+ `randomUUID()` or `Date.now()` inside the step is a different key on every attempt, which is
63
+ the double-charge. Where the provider has no such header, make the step itself idempotent —
64
+ check for the effect before performing it, or record a unique row that the second attempt
65
+ collides with.
66
+
30
67
  ## Choosing the right factory
31
68
 
32
69
  | Factory | When to use | Step-graph view? |
@@ -1,161 +0,0 @@
1
- ---
2
- name: pikku-aws
3
- description: >-
4
- Use when setting up AWS services (S3, SQS, Secrets Manager) in a Pikku app. Covers S3Content for
5
- file storage, SQSQueueService for queues, and AWSSecrets for secret management. TRIGGER when:
6
- code uses S3Content, SQSQueueService, AWSSecrets, or user asks about AWS integration, S3
7
- uploads, SQS queues, or AWS Secrets Manager with Pikku. DO NOT TRIGGER when: user asks about AWS
8
- Lambda runtime (use pikku-deploy-lambda).
9
- ---
10
-
11
- # Pikku AWS Services
12
-
13
- ## Agent Operating Procedure
14
-
15
- Use this skill as an execution checklist, not reference material.
16
-
17
- 1. Discover before editing. Run the relevant `pikku meta ... --json` command and inspect only the focused output you need.
18
- 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.
19
- 3. Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
20
- 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.
21
- 5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
22
-
23
- `@pikku/aws-services` provides AWS-backed implementations of Pikku's content, queue, and secret service interfaces.
24
-
25
- ## Installation
26
-
27
- ```bash
28
- yarn add @pikku/aws-services
29
- ```
30
-
31
- ## API Reference
32
-
33
- ### `S3Content` (File Storage)
34
-
35
- ```typescript
36
- import { S3Content } from '@pikku/aws-services'
37
-
38
- const content = new S3Content(
39
- config: { bucketName: string; region: string; endpoint?: string },
40
- logger: Logger,
41
- signConfig: { keyPairId: string; privateKey: string }
42
- )
43
- ```
44
-
45
- `endpoint` is what points the client at LocalStack or an S3-compatible store.
46
-
47
- **Methods** — every one takes a single **args object**, matching the shared
48
- `ContentService` interface. None of them are positional:
49
-
50
- - `signURL({ url, dateLessThan, dateGreaterThan? }): Promise<string>` — CloudFront-sign an absolute URL
51
- - `signContentKey({ bucket, contentKey, dateLessThan, dateGreaterThan? }): Promise<string>`
52
- - `getUploadURL({ bucket, fileKey, contentType, visibility? }): Promise<{ uploadUrl, assetKey }>` — `visibility` is ignored
53
- - `readFile({ bucket, key }): Promise<ReadableStream | NodeJS.ReadableStream>`
54
- - `readFileAsBuffer({ bucket, key }): Promise<Buffer>`
55
- - `writeFile({ bucket, key, stream }): Promise<boolean>`
56
- - `copyFile({ bucket, key, fromAbsolutePath }): Promise<boolean>`
57
- - `deleteFile({ bucket, key }): Promise<boolean>`
58
-
59
- ### One real bucket, logical buckets as prefixes
60
-
61
- The `bucket` on every call is a **logical** bucket stored as a path prefix
62
- (`${bucket}/${key}`) inside the single S3 bucket named by `bucketName`. Don't
63
- provision an S3 bucket per logical bucket — the config takes only one.
64
-
65
- ### Behaviours worth knowing before you rely on them
66
-
67
- - **`signURL` fails open.** A signing error is logged and the _unsigned_ URL is
68
- returned rather than thrown. If your CloudFront distribution is private the
69
- client then gets a 403; if it isn't, you have just handed out an unrestricted
70
- link. Check that `signConfig` is a valid CloudFront key pair at boot.
71
- - **`signContentKey` builds `https://<bucketName>/<bucket>/<contentKey>`** — it
72
- uses `bucketName` as the _host_. For signed content the value must therefore be
73
- your CloudFront domain, not a plain bucket name, which also means the same
74
- config field is doing two jobs.
75
- - **Presigned upload URLs expire after a fixed 3600s.** It is not configurable
76
- through the service.
77
- - **Write paths swallow failures.** `writeFile`, `copyFile` and `deleteFile` log
78
- and return `false` rather than throwing; the read paths throw. Check the
79
- boolean.
80
-
81
- ### `SQSQueueService` (Queue)
82
-
83
- ```typescript
84
- import { SQSQueueService } from '@pikku/aws-services'
85
-
86
- const queue = new SQSQueueService({
87
- region: string,
88
- queueUrlPrefix: string, // e.g. 'https://sqs.us-east-1.amazonaws.com/123456789/'
89
- endpoint?: string, // LocalStack or a custom SQS endpoint
90
- })
91
- ```
92
-
93
- Implements `QueueService`. Note: `supportsResults = false` — job status tracking is not supported.
94
-
95
- **Methods:**
96
-
97
- - `add<T>(queueName: string, data: T, options?: JobOptions): Promise<string>` — Enqueue a message; returns SQS's `MessageId`
98
- - `getJob()` — always **throws**. SQS is fire-and-forget; reach for BullMQ or PgBoss when you need the result back.
99
-
100
- The queue URL is `queueUrlPrefix + queueName`, so the queue name in `wireQueueWorker` has to match the SQS queue exactly.
101
-
102
- Constraints inherited from SQS, enforced in `add`:
103
-
104
- - `options.delay` is in **milliseconds** and is floored to whole seconds. Over
105
- 900_000ms (15 minutes) or negative throws before the message is sent.
106
- - Standard queues only — no FIFO, so no `MessageGroupId` and no ordering
107
- guarantee.
108
- - `data` is `JSON.stringify`d, which is where a `Date` or a `Map` quietly
109
- degrades.
110
-
111
- ### `AWSSecrets` (Secrets Manager)
112
-
113
- ```typescript
114
- import { AWSSecrets } from '@pikku/aws-services'
115
-
116
- const secrets = new AWSSecrets({ awsRegion: 'eu-west-2' })
117
- ```
118
-
119
- `AWSConfig` has one field, `awsRegion` — there is no credentials option; the SDK's
120
- default provider chain (instance role, env, profile) supplies those.
121
-
122
- **Methods:**
123
-
124
- - `getSecret<T = string>(SecretId: string): Promise<SecretValue<T>>` — a JSON secret is parsed automatically, so pass a shape as `T` (a non-JSON value comes back as the raw string). The result is a branded `SecretValue`, not a bare value — reveal it where it is used rather than passing it through logs
125
- - `getSecrets<T>(SecretIds: (keyof T & string)[]): Promise<Partial<T>>` — Batch fetch; missing keys are omitted rather than thrown
126
- - `hasSecret(SecretId: string): Promise<boolean>` — Check if secret exists
127
- - `setSecret` / `deleteSecret` — **not implemented** for `AWSSecrets`; it throws. Manage AWS secrets out of band.
128
-
129
- Every `getSecret` failure — missing secret, denied permission, a secret holding
130
- only binary — surfaces as the same `FATAL: Error finding secret: <id>`, with the
131
- real reason on the error's `cause`. Read `cause` before concluding the secret
132
- doesn't exist. `hasSecret` performs a full fetch and returns `false` for any
133
- error, so it can't distinguish "absent" from "not allowed" either.
134
-
135
- ## Usage Patterns
136
-
137
- ### S3 Content Service
138
-
139
- ```typescript
140
- const createSingletonServices = pikkuServices(async (config) => {
141
- const logger = new PinoLogger()
142
- const content = new S3Content(
143
- { bucketName: config.s3Bucket, region: config.awsRegion },
144
- logger,
145
- { keyPairId: config.cfKeyPairId, privateKey: config.cfPrivateKey }
146
- )
147
- return { config, logger, content }
148
- })
149
- ```
150
-
151
- ### SQS Queue
152
-
153
- ```typescript
154
- const createSingletonServices = pikkuServices(async (config) => {
155
- const queue = new SQSQueueService({
156
- region: config.awsRegion,
157
- queueUrlPrefix: config.sqsUrlPrefix,
158
- })
159
- return { config, queue }
160
- })
161
- ```