@pikku/skills 0.12.39 → 0.12.41
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/dist/skills.gen.js +2 -2
- package/package.json +1 -1
- package/skills/pikku-agent/references/agents.md +7 -0
- package/skills/pikku-blueprint-to-fabric/SKILL.md +1 -1
- package/skills/pikku-build/SKILL.md +8 -12
- package/skills/pikku-build/references/app.md +4 -2
- package/skills/pikku-build/references/feature.md +4 -4
- package/skills/pikku-build/references/platform.md +2 -2
- package/skills/pikku-build/references/quick.md +1 -1
- package/skills/pikku-concepts/SKILL.md +1 -1
- package/skills/pikku-kysely/SKILL.md +4 -1
- package/skills/pikku-meta/SKILL.md +5 -5
- package/skills/pikku-meta/references/versioning.md +74 -17
- package/skills/pikku-report/SKILL.md +39 -20
- package/skills/pikku-wiring/references/trigger.md +54 -1
- package/skills/pikku-workflow/SKILL.md +54 -1
package/package.json
CHANGED
|
@@ -102,6 +102,13 @@ await rpc.agent.resume(runId, { toolCallId, approved })
|
|
|
102
102
|
await rpc.agent.interrupt(runId, 'user' | 'speech' | 'timeout')
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
+
`rpc.agent` is built from a factory that `@pikku/core/agent` registers when it
|
|
106
|
+
is imported, so the agent runtime lands only in a deployment unit that actually
|
|
107
|
+
holds an agent. A unit that declares one imports it through its own agent file
|
|
108
|
+
and needs nothing extra; a unit that reaches `rpc.agent` while holding no agent
|
|
109
|
+
throws `Agent runtime not available` rather than silently paying ~52 KB for a
|
|
110
|
+
runtime it never uses.
|
|
111
|
+
|
|
105
112
|
`context` is a string injected into the system prompt for this request only —
|
|
106
113
|
use it for upfront state (current org, project, deployment) so the agent stops
|
|
107
114
|
asking the user for identifiers it could have been handed.
|
|
@@ -313,7 +313,7 @@ Then run the slice's scenarios. All four green — validate, codegen, `tsc`, sce
|
|
|
313
313
|
|
|
314
314
|
Never batch. A rebuild verified only at the end gives you an undifferentiated pile of failures with no bisect point, and the whole reason for slicing is that each slice is a checkpoint you can trust.
|
|
315
315
|
|
|
316
|
-
New functions with `expose: true` are versioned from the start — `pikku versions` / `pikku
|
|
316
|
+
New functions with `expose: true` are versioned from the start — `pikku versions` / `pikku release` (**pikku-meta**); you're establishing v1 contracts, not migrating them.
|
|
317
317
|
|
|
318
318
|
## Stage 9 — The parity report (the deliverable)
|
|
319
319
|
|
|
@@ -115,21 +115,17 @@ generated functions through `ref()`.
|
|
|
115
115
|
|
|
116
116
|
A person who has to go hunting for the port assumes the app did not start.
|
|
117
117
|
|
|
118
|
-
##
|
|
118
|
+
## Report what pikku got wrong, as it happens
|
|
119
119
|
|
|
120
120
|
Whenever pikku or a skill costs you time — a command that failed on a fresh
|
|
121
121
|
tree, a skill that described a flag the CLI does not have, generated code you
|
|
122
|
-
had to fix by hand —
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
`"skill"` and `"passage"` when a skill misled you). The `pikku-report` skill
|
|
130
|
-
has the fields. When the CLI is not signed in to Fabric, the report is queued
|
|
131
|
-
locally rather than sent: say so, and that `pikku fabric findings flush` sends
|
|
132
|
-
the queue once they sign in. Do not retry or file it twice.
|
|
122
|
+
had to fix by hand — file it then with `pikku fabric report --stdin`. The
|
|
123
|
+
`pikku-report` skill has the fields. Leave out secrets, tokens and customer data.
|
|
124
|
+
|
|
125
|
+
Findings are held until hand-over. The last thing in the hand-over, after the
|
|
126
|
+
app runs (and is deployed, if they chose to), is `pikku fabric report`: if it
|
|
127
|
+
lists findings and asks, ask the user — Yes, No, Always or Never — and run it
|
|
128
|
+
again with `--consent <answer>`. If it says reporting is off, do not ask.
|
|
133
129
|
|
|
134
130
|
## Who you are talking to
|
|
135
131
|
|
|
@@ -515,9 +515,11 @@ Rules that are not optional:
|
|
|
515
515
|
- Surface errors. No empty catch, no swallowed promise. If a mutation can fail,
|
|
516
516
|
render the failure inline next to the control that triggered it — not a toast.
|
|
517
517
|
- An exposed function with no session and no permission is reachable by anyone
|
|
518
|
-
over `POST /rpc/:rpcName` (PKU574). Either gate it
|
|
518
|
+
over `POST /rpc/:rpcName` (PKU574). Either gate it, drop `expose: true`, or —
|
|
519
|
+
when public is the point — write `auth: false` on it to say so.
|
|
519
520
|
- A public, signed-out read (a homepage's programme, a price list) is a
|
|
520
|
-
`pikkuSessionlessFunc
|
|
521
|
+
`pikkuSessionlessFunc` with `auth: false` written out, which is what keeps
|
|
522
|
+
PKU574 quiet for it. `pikkuFunc` with `auth: false` still answers
|
|
521
523
|
`MissingSessionError` over `/rpc` to a caller with no session.
|
|
522
524
|
- Better Auth already owns the `user`, `session`, `account` and `verification`
|
|
523
525
|
tables. A domain table with one of those names — a class _session_, a drop-in
|
|
@@ -259,14 +259,14 @@ Do not push without explicit confirmation. Do not merge.
|
|
|
259
259
|
When pikku itself is what cost you time — a wrong generated type, a check that
|
|
260
260
|
passed when it should not, a skill that misled you — file it with `pikku fabric
|
|
261
261
|
report`. The `pikku-report` skill owns the ladder, the two kinds, the JSON-on-
|
|
262
|
-
stdin form and the
|
|
262
|
+
stdin form and asking the user at hand-over; read it before filing.
|
|
263
263
|
|
|
264
|
-
|
|
265
|
-
network call a build may make
|
|
264
|
+
Filing is permitted here (see **Hard constraints**), and sending what was filed
|
|
265
|
+
is the one network call a build may make, once the user agrees. Nothing is
|
|
266
|
+
written to the repo. Never patch pikku
|
|
266
267
|
itself — not `node_modules`, not a linked checkout — work around it in the app,
|
|
267
268
|
report it, and let the fix happen once.
|
|
268
269
|
|
|
269
|
-
|
|
270
270
|
## Hard constraints
|
|
271
271
|
|
|
272
272
|
The skill's `allowed-tools` does **not** permit:
|
|
@@ -167,8 +167,8 @@ bunx --bun pikku versions init
|
|
|
167
167
|
|
|
168
168
|
The CLI suggests this on every run of a project without it. Versioning a function
|
|
169
169
|
contract, then changing it, is a short milestone that shows something no
|
|
170
|
-
scaffold demonstrates on its own. `pikku
|
|
171
|
-
comparing this build's surface against
|
|
170
|
+
scaffold demonstrates on its own. `pikku release` derives the release version by
|
|
171
|
+
comparing this build's surface against the last release, then ships it.
|
|
172
172
|
|
|
173
173
|
### Addons — `pikku-addon`
|
|
174
174
|
|
|
@@ -135,7 +135,7 @@ than they save:
|
|
|
135
135
|
type params, never an inline return type. The schema is the type.
|
|
136
136
|
- Permission checks go in the `permissions` field, never the function body. An
|
|
137
137
|
exposed function with no session and no permission is reachable by anyone over
|
|
138
|
-
`POST /rpc/:rpcName` (PKU574).
|
|
138
|
+
`POST /rpc/:rpcName` (PKU574). If that is the point, write `auth: false` on it.
|
|
139
139
|
- No `process.env` inside a function — use the injected `variables` / `secrets`
|
|
140
140
|
services.
|
|
141
141
|
- A `z.date()` **input** arrives over RPC as an ISO string, not a `Date`.
|
|
@@ -115,7 +115,7 @@ flags; the "Read" column is the skill that teaches the thing, where one does.
|
|
|
115
115
|
| `doc` | The installed API surface | this skill |
|
|
116
116
|
| `meta` / `info` | What the project declares, machine- and human-readable | `pikku-meta` |
|
|
117
117
|
| `validate` | Every check that applies — app structure, an addon's published file set | `pikku-build`, `pikku-addon` |
|
|
118
|
-
| `versions` / `
|
|
118
|
+
| `versions` / `release` | Contract hashes, breaking-change detection, versioned releases | `pikku-meta` |
|
|
119
119
|
| `audit` / `update` | Advisories; which `@pikku/*` can move and what peers that needs | `pikku-meta` |
|
|
120
120
|
| `scopes` / `roles` | Declared authorization scopes; roles from `defineSystemRole` | `pikku-auth` |
|
|
121
121
|
| `knowledge` | The knowledge base — what this app is, in its users' language | `pikku-knowledge` |
|
|
@@ -293,7 +293,10 @@ const kysely = createNodeSqliteKysely<DB>({
|
|
|
293
293
|
import { createSQLiteKysely } from '@pikku/kysely-sqlite'
|
|
294
294
|
|
|
295
295
|
// Pikku's own tables — returns Kysely<KyselyPikkuDB>, not your DB
|
|
296
|
-
const pikkuDb = createSQLiteKysely(
|
|
296
|
+
const pikkuDb = createSQLiteKysely(
|
|
297
|
+
database: SqliteDatabase | (() => Promise<SqliteDatabase>),
|
|
298
|
+
{ plugins: [] } // layered ahead of the always-last SerializePlugin
|
|
299
|
+
)
|
|
297
300
|
```
|
|
298
301
|
|
|
299
302
|
These two are not interchangeable. `createSQLiteKysely` is typed to
|
|
@@ -3,8 +3,8 @@ name: pikku-meta
|
|
|
3
3
|
description: >-
|
|
4
4
|
Use to inspect or evolve a project you did not just write — `pikku meta` and `pikku info` for
|
|
5
5
|
what the project declares (functions, schemas, wires, workflows, middleware, permissions) and
|
|
6
|
-
`pikku meta apply` to change it, `pikku versions` / `pikku
|
|
7
|
-
breaking-change detection
|
|
6
|
+
`pikku meta apply` to change it, `pikku versions` / `pikku release` for contract hashes,
|
|
7
|
+
breaking-change detection, the semver a release should get and shipping it, and `pikku audit` / `pikku
|
|
8
8
|
update` for dependency advisories and moving Pikku forward. TRIGGER when: user asks what
|
|
9
9
|
functions or routes exist, wants a function's input/output shape, wants to retag a function or
|
|
10
10
|
set config on a declaration, asks about API versioning, breaking changes, what semver a release
|
|
@@ -13,7 +13,7 @@ description: >-
|
|
|
13
13
|
Pikku concepts (use pikku-concepts).
|
|
14
14
|
installGroups: [core]
|
|
15
15
|
allowed-tools: Bash(yarn pikku meta *), Bash(yarn pikku info *)
|
|
16
|
-
argument-hint: '[context|functions|schemas|workflows|middleware|permissions|wires|apply|versions|
|
|
16
|
+
argument-hint: '[context|functions|schemas|workflows|middleware|permissions|wires|apply|versions|release|audit|update]'
|
|
17
17
|
---
|
|
18
18
|
|
|
19
19
|
# Pikku Project Metadata
|
|
@@ -26,7 +26,7 @@ and change it through the write path rather than by hand.
|
|
|
26
26
|
| You are… | Read |
|
|
27
27
|
| --- | --- |
|
|
28
28
|
| Asking what exists, or setting config on a declaration | `references/meta.md` |
|
|
29
|
-
| Versioning a contract, or
|
|
29
|
+
| Versioning a contract, or versioning and shipping a release | `references/versioning.md` |
|
|
30
30
|
| Chasing a dependency advisory, or upgrading Pikku | `references/audit.md` |
|
|
31
31
|
|
|
32
32
|
## Start with `pikku meta context`
|
|
@@ -41,7 +41,7 @@ they are the same ground in two shapes.
|
|
|
41
41
|
An input is contravariant (the caller writes it) and an output is covariant (the
|
|
42
42
|
caller reads it), so the same edit is not the same event on both. Adding a
|
|
43
43
|
required field breaks an input and is compatible on an output; making a field
|
|
44
|
-
optional is the reverse. `pikku
|
|
44
|
+
optional is the reverse. `pikku release diff` reads the generated JSON Schemas with
|
|
45
45
|
that asymmetry built in, so let it decide rather than eyeballing a diff.
|
|
46
46
|
|
|
47
47
|
## What NOT to do
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
# Pikku Function Versioning
|
|
2
2
|
|
|
3
|
-
|
|
4
3
|
## Before You Start
|
|
5
4
|
|
|
6
5
|
```bash
|
|
@@ -122,28 +121,86 @@ the manifest alone. Fix the contract or bump the version, then run it again.
|
|
|
122
121
|
4. If intentional: pin the old contract as `…V1` with `version: 1`, bump the
|
|
123
122
|
live function to `version: 2`, then `pikku versions update`
|
|
124
123
|
|
|
125
|
-
## The `pikku
|
|
124
|
+
## The `pikku release` command
|
|
126
125
|
|
|
127
|
-
`versions check` and `
|
|
126
|
+
`versions check` and `release` answer different questions and share no state.
|
|
128
127
|
`check` is a within-repo gate — "you changed a contract without bumping
|
|
129
|
-
`version:`". `
|
|
130
|
-
clients of the
|
|
131
|
-
|
|
132
|
-
the previous commit.
|
|
128
|
+
`version:`". `release` is a release question — "what does this build owe the
|
|
129
|
+
clients of the last release, and how do we ship it?". The baseline is
|
|
130
|
+
`surface.pikku.json`, the snapshot committed by the previous release.
|
|
133
131
|
|
|
134
132
|
```bash
|
|
135
|
-
npx pikku
|
|
136
|
-
npx pikku
|
|
137
|
-
npx pikku
|
|
138
|
-
npx pikku
|
|
133
|
+
npx pikku release init # first run: snapshot + CHANGELOG.md
|
|
134
|
+
npx pikku release diff # vs surface.pikku.json
|
|
135
|
+
npx pikku release diff --against https://api.acme.com/surface.json # vs any baseline
|
|
136
|
+
npx pikku release diff --fail-on major # PR gate
|
|
137
|
+
npx pikku release snapshot --out surface.json # write a snapshot
|
|
138
|
+
npx pikku release prepare # bump package.json, write changelog + snapshot
|
|
139
139
|
```
|
|
140
140
|
|
|
141
141
|
`--against` takes three things and tells them apart itself: a directory is read
|
|
142
142
|
as a `.pikku` tree, an `http(s)` URL is fetched as a published snapshot, and any
|
|
143
|
-
other file is read as a snapshot.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
143
|
+
other file is read as a snapshot. `snapshot` without `--out` writes to stdout
|
|
144
|
+
_after_ the CLI banner, so a bare `> file.json` captures the banner too.
|
|
145
|
+
`pikku semver` is a deprecated alias for `release diff` / `release snapshot`.
|
|
146
|
+
|
|
147
|
+
### Shipping a release
|
|
148
|
+
|
|
149
|
+
Work lands on the trunk branch (default `staging`); production (default `main`)
|
|
150
|
+
only ever fast-forwards to a release commit. pikku works the release out and
|
|
151
|
+
writes it; it never commits, tags or pushes. Who does that is your call — you
|
|
152
|
+
with plain git, or a platform with its own credentials.
|
|
153
|
+
|
|
154
|
+
`prepare` runs on a checkout of `origin/staging` after `pikku all`. It diffs the
|
|
155
|
+
surface against `surface.pikku.json`, reads the commits production does not have
|
|
156
|
+
yet, bumps `package.json`, prepends a `CHANGELOG.md` section and rewrites the
|
|
157
|
+
snapshot. No commits since the last release means nothing to release. It
|
|
158
|
+
refuses when `main` has commits `staging` lacks (merge `main` into `staging`
|
|
159
|
+
with a merge commit first). `.pikku/release.gen.json` records the version, the
|
|
160
|
+
changelog section, the trunk sha it was prepared on and the files it wrote,
|
|
161
|
+
named from the repository root.
|
|
162
|
+
|
|
163
|
+
Shipping is then one commit on top of that trunk sha:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
npx pikku release prepare
|
|
167
|
+
git add package.json CHANGELOG.md surface.pikku.json
|
|
168
|
+
git commit -m "release: v0.4.0"
|
|
169
|
+
git tag v0.4.0
|
|
170
|
+
git push --atomic origin HEAD:staging HEAD:main v0.4.0
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The push only fast-forwards, so if `staging` moved since prepare it is rejected
|
|
174
|
+
and you prepare again.
|
|
175
|
+
|
|
176
|
+
The bump comes from the surface diff alone — there is no manual override. A
|
|
177
|
+
release whose surface did not move is a patch. Below 1.0 a breaking change
|
|
178
|
+
is a minor, like any other surface change; from 1.0 it is a major. 1.0.0 is
|
|
179
|
+
never reached by a diff: `pikku release prepare --go-live` releases it once,
|
|
180
|
+
when the app is live. The changelog lists the surface changes and nothing
|
|
181
|
+
else from the code; a behaviour change behind an unchanged API only shows up
|
|
182
|
+
if a commit carries a `Release-Note: …` trailer, which adds a line to Notes.
|
|
183
|
+
A release with neither says it holds internal changes only. Branch names are configurable in `pikku.config.json`:
|
|
184
|
+
|
|
185
|
+
```json
|
|
186
|
+
{
|
|
187
|
+
"release": {
|
|
188
|
+
"trunk": "staging",
|
|
189
|
+
"production": "main",
|
|
190
|
+
"branch": "release/next",
|
|
191
|
+
"remote": "origin"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`branch` is not used by pikku itself; it is passed through in
|
|
197
|
+
`release.gen.json` as the name a platform gives the prepared release commit
|
|
198
|
+
while it waits to ship.
|
|
199
|
+
|
|
200
|
+
Functions and wirings defined under a `scaffold/` directory or in a generated
|
|
201
|
+
`*.gen.*` file are platform plumbing (auth, console, a host's injected shims)
|
|
202
|
+
and are left out of the surface, so the same app diffs the same locally and in
|
|
203
|
+
a platform's build.
|
|
147
204
|
|
|
148
205
|
The verdict, in order:
|
|
149
206
|
|
|
@@ -179,7 +236,7 @@ the wiring level only `auth` going from absent/false to true is classified as
|
|
|
179
236
|
breaking; every other metadata change is reported as compatible, because there
|
|
180
237
|
is no general way to tell a cosmetic wiring edit from a restricting one.
|
|
181
238
|
|
|
182
|
-
|
|
239
|
+
`release diff` writes `.pikku/changes.gen.json` (override with `--out`), so it rides the
|
|
183
240
|
same meta pipeline as `audit.json`:
|
|
184
241
|
|
|
185
242
|
```json
|
|
@@ -215,7 +272,7 @@ jobs:
|
|
|
215
272
|
- run: npm ci
|
|
216
273
|
- run: npx pikku versions check
|
|
217
274
|
# Refuse to ship a breaking change to production unintentionally.
|
|
218
|
-
- run: npx pikku
|
|
275
|
+
- run: npx pikku release diff --fail-on major
|
|
219
276
|
```
|
|
220
277
|
|
|
221
278
|
## Complete Example
|
|
@@ -3,12 +3,11 @@ name: pikku-report
|
|
|
3
3
|
description: >-
|
|
4
4
|
Use when pikku itself cost you time — wrong generated types, a check that passes when it
|
|
5
5
|
should not, output that is quietly wrong, a skill that misled you — or when the user asks you
|
|
6
|
-
to report a framework bug
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
to report a framework bug or file a finding. Owns `pikku fabric report` (a finding is about
|
|
7
|
+
pikku, not the app), the product-vs-harness kinds, the workaround-first ladder, and asking the
|
|
8
|
+
user at hand-over whether to send what was filed. TRIGGER when: the framework fought you,
|
|
9
9
|
codegen produced something broken, a skill told you to run something that does not exist, the
|
|
10
|
-
user says "report this to pikku" / "file a finding"
|
|
11
|
-
was queued and never sent. DO NOT TRIGGER when: the bug is in the app you are building (fix it
|
|
10
|
+
user says "report this to pikku" / "file a finding", or you are handing over a build. DO NOT TRIGGER when: the bug is in the app you are building (fix it
|
|
12
11
|
there), or you are tempted to patch pikku's source (never do that from an app).
|
|
13
12
|
installGroups: [core]
|
|
14
13
|
---
|
|
@@ -19,9 +18,9 @@ A finding is about **pikku**, not about the app you are building. It is how the
|
|
|
19
18
|
framework learns what cost its users time — the bug, the misleading skill, the
|
|
20
19
|
silence where a check should have complained.
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
no top-level report command.
|
|
21
|
+
File each one the moment it happens. Nothing leaves the machine until the user
|
|
22
|
+
says so at hand-over, and nothing is written to the repository. The command is
|
|
23
|
+
spelled `pikku fabric report` — there is no top-level report command.
|
|
25
24
|
|
|
26
25
|
## Report at the moment it happens
|
|
27
26
|
|
|
@@ -62,7 +61,7 @@ baseline noise that was already failing before you started.
|
|
|
62
61
|
- `--kind harness` — a skill misled you: it told you to run something that does
|
|
63
62
|
not exist, described a flag that is spelled differently, or contradicted what
|
|
64
63
|
the CLI actually did. Pass `--skill <name>` and `--passage "<the line or
|
|
65
|
-
|
|
64
|
+
section>"`. This is the most useful kind to file, because it is fixable
|
|
66
65
|
immediately — so file it even when the cost was small.
|
|
67
66
|
|
|
68
67
|
## The command
|
|
@@ -102,24 +101,44 @@ before anything is sent:
|
|
|
102
101
|
Add whichever of these you actually have: `error` (the error's message line,
|
|
103
102
|
verbatim), `repro` (the shortest way to reach it again), `proposal`, `area`,
|
|
104
103
|
`surface`, `cost` (measured if you measured it — "98s vs 20s steady" ranks;
|
|
105
|
-
"slow" does not), `
|
|
106
|
-
`deployTarget`.
|
|
104
|
+
"slow" does not), `deployTarget`.
|
|
107
105
|
|
|
108
106
|
Versions, platform and package manager are read off the installed tree for you.
|
|
109
107
|
Do not pass them and do not ask the user for them.
|
|
110
108
|
|
|
111
|
-
##
|
|
109
|
+
## At hand-over
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
that succeeds, so nothing you file is lost:
|
|
111
|
+
Filing holds the finding on the machine, tied to this build by a run id the CLI
|
|
112
|
+
makes for the checkout. The terminal says `held until hand-over`; carry on.
|
|
116
113
|
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
- `pikku fabric findings clear` discards it.
|
|
114
|
+
The last thing in the hand-over — after the app runs, and is deployed if they
|
|
115
|
+
chose to — is:
|
|
120
116
|
|
|
121
|
-
|
|
122
|
-
|
|
117
|
+
```bash
|
|
118
|
+
pikku fabric report
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
With no finding, it lists what this build filed. What happens next depends on
|
|
122
|
+
what the user said before, which the CLI keeps on their machine:
|
|
123
|
+
|
|
124
|
+
- **Always** — every finding was sent the moment you filed it. Tell the user in
|
|
125
|
+
one line.
|
|
126
|
+
- **Never** — nothing was kept. Do not ask and do not mention it.
|
|
127
|
+
- **Nothing saved** — show the user the titles and ask once: _"Send these to the
|
|
128
|
+
Pikku team so they can fix them?"_ — **Yes**, **No**, **Always** or
|
|
129
|
+
**Never**. Then run it again with their answer:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
pikku fabric report --consent yes|no|always|never
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
Yes sends and No discards what is held now; Always and Never are saved and the
|
|
136
|
+
question is not asked again. If nobody answers, leave them held and say that
|
|
137
|
+
`pikku fabric report` sends them later.
|
|
138
|
+
|
|
139
|
+
Findings are anonymous: no account, no project. The receipt printed when you
|
|
140
|
+
filed each one is exactly what leaves the machine. A send that fails keeps what
|
|
141
|
+
was not sent; do not retry or file it twice.
|
|
123
142
|
|
|
124
143
|
## Never fix pikku itself
|
|
125
144
|
|
|
@@ -94,6 +94,60 @@ generated by the inspector, and it warns rather than throwing; the usual fix is
|
|
|
94
94
|
the one the warning suggests — move the wiring into its own file so codegen
|
|
95
95
|
picks it up.
|
|
96
96
|
|
|
97
|
+
## Webhook sources
|
|
98
|
+
|
|
99
|
+
When the outside world pushes to you (Stripe, GitHub, Slack), the source is an
|
|
100
|
+
HTTP route, not a listener. `wireTriggerWebhookSource` declares it; triggers
|
|
101
|
+
subscribe to its events as `<source>:<event>`:
|
|
102
|
+
|
|
103
|
+
```ts snippet:wireTriggerWebhookSource
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- The route is `POST /webhooks/<name>` unless `method`/`route` say otherwise.
|
|
108
|
+
It needs no session.
|
|
109
|
+
- `events` maps each event name to a schema. An event that fails its schema is
|
|
110
|
+
logged and dropped; so is one no `wireTrigger` listens for. Both still get a
|
|
111
|
+
`200`, so the provider does not retry forever.
|
|
112
|
+
- `receive(services, { body, headers, method, url, query })` gets the **raw
|
|
113
|
+
bytes** — verify the signature over those — and returns `{ events: [{ name,
|
|
114
|
+
id?, data }] }`, or `{ respond: { status, body } }` for a handshake. Throwing
|
|
115
|
+
rejects the request with the error's status (`UnauthorizedError` → 401).
|
|
116
|
+
Omitted, the JSON body becomes one event dispatched to a trigger named just
|
|
117
|
+
`<source>`.
|
|
118
|
+
- Every accepted event is queued on `pikku-incoming-webhooks` and run by a
|
|
119
|
+
generated worker, so the provider is answered quickly and a failing trigger is
|
|
120
|
+
retried by the queue. This needs a `queueService` and an
|
|
121
|
+
`incomingWebhookService: new IncomingWebhookService(queueService)` in your
|
|
122
|
+
singleton services. The event `id` becomes the job id.
|
|
123
|
+
- With a database, use `KyselyIncomingWebhookService(queueService, db)` from
|
|
124
|
+
`@pikku/kysely` instead (and `await service.init()`). It keeps a receipt per
|
|
125
|
+
event, so a provider's redelivery of an event already accepted is dropped
|
|
126
|
+
even on queues that ignore job ids, and records each attempt and its last
|
|
127
|
+
error. Its `webhookReceipt` table comes from `pikku db generate`; `pikku dev`
|
|
128
|
+
and `pikku serve` use it when a Kysely database is configured.
|
|
129
|
+
- `receive` sees singleton services without `secrets`: read the signing secret
|
|
130
|
+
in a service method, and declare it with `defineSecret`. Name it in `secret`
|
|
131
|
+
so deploy knows where `setup` should store it.
|
|
132
|
+
|
|
133
|
+
`check`, `setup` and `teardown` register the route with the provider. Each gets
|
|
134
|
+
`{ url, label, events, previous? }`, where `events` are only the ones some
|
|
135
|
+
trigger subscribes to. The CLI runs them:
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
pikku webhooks status --url https://api.example.com --labelPrefix shop:prod
|
|
139
|
+
pikku webhooks setup --url https://api.example.com --labelPrefix shop:prod
|
|
140
|
+
pikku webhooks teardown --url https://api.example.com --labelPrefix shop:prod --previous state.json
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
Each prints one JSON line per source (`ok`, `missing`, `drifted`, `created`,
|
|
144
|
+
`updated`, `unchanged`, `manual`, `deleted`, `absent`, `skipped`, `failed`). `setup` only
|
|
145
|
+
runs where `check` does not report `ok`, and a `setup` that returns a new signing
|
|
146
|
+
secret prints it with the `secret` name to store it under. In CI pass
|
|
147
|
+
`--secretsOut <file>`: secrets are written there (mode 600) as
|
|
148
|
+
`{ secretName: secret }` and left out of stdout. Any step can be
|
|
149
|
+
`ref('<addon>:<fn>')` to use an addon's implementation.
|
|
150
|
+
|
|
97
151
|
## Usage Patterns
|
|
98
152
|
|
|
99
153
|
### Redis Pub/Sub Source
|
|
@@ -139,7 +193,6 @@ wireTriggerSource({
|
|
|
139
193
|
})
|
|
140
194
|
```
|
|
141
195
|
|
|
142
|
-
|
|
143
196
|
## Complete Example
|
|
144
197
|
|
|
145
198
|
```typescript
|
|
@@ -159,6 +159,9 @@ export const processOrder = pikkuWorkflowFunc({
|
|
|
159
159
|
// RPC step — run a registered Pikku function as a step (opts: retries, retryDelay, description)
|
|
160
160
|
const result = await workflow.do('Step name', 'rpcFunctionName', { ...data }, { retries: 3, retryDelay: '1s' })
|
|
161
161
|
|
|
162
|
+
// Sub-workflow step — name another workflow instead of an RPC (see "Sub-workflows")
|
|
163
|
+
const onboarded = await workflow.do('Onboard', 'onboardUserWorkflow', { userId })
|
|
164
|
+
|
|
162
165
|
// Inline closure step — immediate execution, cached for replay
|
|
163
166
|
const msg = await workflow.do('Generate', async () => `Welcome, ${data.email}!`)
|
|
164
167
|
|
|
@@ -277,9 +280,59 @@ const users = await Promise.all(
|
|
|
277
280
|
)
|
|
278
281
|
```
|
|
279
282
|
|
|
283
|
+
### Sub-workflows
|
|
284
|
+
|
|
285
|
+
A step whose second argument names a **workflow** rather than an RPC starts that
|
|
286
|
+
workflow as a child run and resolves to its output. There is no separate API —
|
|
287
|
+
it is the same `workflow.do`, and the generated `TypedWorkflow` has an overload
|
|
288
|
+
keyed on `FlattenedWorkflowMap`, so the child's input and output are type-checked
|
|
289
|
+
exactly like an RPC step's.
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
export const signupWorkflow = pikkuWorkflowFunc<
|
|
293
|
+
{ email: string },
|
|
294
|
+
{ userId: string }
|
|
295
|
+
>({
|
|
296
|
+
func: async (_services, data, { workflow }) => {
|
|
297
|
+
const user = await workflow.do('Create user', 'createUser', data)
|
|
298
|
+
// runs onboardUserWorkflow as a child run; awaits its output
|
|
299
|
+
await workflow.do('Onboard', 'onboardUserWorkflow', { userId: user.id })
|
|
300
|
+
await workflow.do('Send welcome', 'sendWelcomeEmail', { userId: user.id })
|
|
301
|
+
return { userId: user.id }
|
|
302
|
+
},
|
|
303
|
+
})
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
How the child runs:
|
|
307
|
+
|
|
308
|
+
- **It is its own run.** It gets its own `runId`, its own steps and its own
|
|
309
|
+
history; the parent step records it as `childRunId` and the child's wire
|
|
310
|
+
carries `parentRunId`/`parentStepId`. Inspect the child's steps on the child
|
|
311
|
+
run, not the parent.
|
|
312
|
+
- **Identity is inherited.** The child's wire copies the parent's
|
|
313
|
+
`pikkuUserId`, so the child runs as whoever started the parent.
|
|
314
|
+
- **Inline vs queued follows the deployment.** Without a `queueService` the
|
|
315
|
+
child runs inline and the parent step returns its output directly. With one,
|
|
316
|
+
the child is queued, the parent parks on that step, and the child's
|
|
317
|
+
completion writes the parent step's result and resumes the parent.
|
|
318
|
+
- **Failure propagates.** A child that fails or is cancelled fails the parent
|
|
319
|
+
step (`'Sub-workflow failed'` / `'Sub-workflow was cancelled'` when the child
|
|
320
|
+
left no message). Inline, the step's `retries` start a fresh child run per
|
|
321
|
+
attempt; queued, the child's failure lands on the parent step once and is
|
|
322
|
+
not retried — put retries on the child's own steps instead.
|
|
323
|
+
|
|
324
|
+
Use a sub-workflow when the child is a real orchestration you also start on
|
|
325
|
+
its own, or reuse from several parents. A child that would be a single
|
|
326
|
+
`workflow.do` is a function — call the RPC directly (see the single-RPC rule
|
|
327
|
+
above).
|
|
328
|
+
|
|
329
|
+
To start a workflow **without waiting** for it, that is not a sub-workflow:
|
|
330
|
+
have an RPC step call `rpc.startWorkflow(name, input)`, which returns
|
|
331
|
+
`{ runId }` immediately, and the new run has no parent link.
|
|
332
|
+
|
|
280
333
|
### Graph workflow (DAG)
|
|
281
334
|
|
|
282
|
-
`pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name
|
|
335
|
+
`pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name` — or a workflow name, which runs that workflow as a sub-workflow node; `config.<node>.next` lists nodes to run after it (in parallel); `config.<node>.input: (ref) => ...` transforms input using refs to prior node outputs.
|
|
283
336
|
|
|
284
337
|
```typescript
|
|
285
338
|
import { pikkuWorkflowGraph } from '#pikku/workflow/pikku-workflow-types.gen.js'
|