@pikku/skills 0.12.46 → 0.12.47
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/package.json
CHANGED
|
@@ -113,8 +113,8 @@ subscribe to its events as `<source>:<event>`:
|
|
|
113
113
|
logged and dropped; so is one no `wireTrigger` listens for. Both still get a
|
|
114
114
|
`200`, so the provider does not retry forever.
|
|
115
115
|
- `receive(services, { body, headers, method, url, query })` gets the **raw
|
|
116
|
-
bytes**
|
|
117
|
-
|
|
116
|
+
bytes** and only parses them: it returns `{ events: [{ name, id?, data }] }`,
|
|
117
|
+
or `{ respond: { status, body } }` for a handshake. Throwing
|
|
118
118
|
rejects the request with the error's status (`UnauthorizedError` → 401).
|
|
119
119
|
Omitted, the JSON body becomes one event dispatched to a trigger named just
|
|
120
120
|
`<source>`.
|
|
@@ -129,12 +129,31 @@ id?, data }] }`, or `{ respond: { status, body } }` for a handshake. Throwing
|
|
|
129
129
|
even on queues that ignore job ids, and records each attempt and its last
|
|
130
130
|
error. Its `webhookReceipt` table comes from `pikku db generate`; `pikku dev`
|
|
131
131
|
and `pikku serve` use it when a Kysely database is configured.
|
|
132
|
-
-
|
|
133
|
-
|
|
134
|
-
`
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
- Signature checks belong in `verify`, never in `receive`. pikku runs it on
|
|
133
|
+
every request against the secret in the credential
|
|
134
|
+
`<name>WebhookSecret` (camelCased: `microsoft-outlook` →
|
|
135
|
+
`microsoftOutlookWebhookSecret`; `credential` overrides it). Declaring
|
|
136
|
+
`verify` declares that credential as a singleton string, so there is no
|
|
137
|
+
`defineCredential` and no service holding the secret; `credentialDescription`
|
|
138
|
+
tells whoever sets it where to find it. Pick the declared form that matches
|
|
139
|
+
the provider:
|
|
140
|
+
- `{ hmac: { header, prefix?, algorithm, encoding, secretEncoding? } }`:
|
|
141
|
+
a signature over the raw body in one header (GitHub, Shopify, Linear).
|
|
142
|
+
- `{ token: { header, prefix? } }`: the provider echoes the shared secret
|
|
143
|
+
(GitLab, Telegram).
|
|
144
|
+
- `{ publicKey: { header, algorithm?, dsaEncoding? } }`: signed with the
|
|
145
|
+
provider's private key; the stored secret is its PEM public key (Wise).
|
|
146
|
+
- Anything else (a timestamp in the signed payload, a signature in the body
|
|
147
|
+
or query, a URL in the signed string) is a function
|
|
148
|
+
`(request, secret, services) => boolean`, built from `hmacDigest`,
|
|
149
|
+
`verifyHmacSignature`, `verifyPublicKeySignature` and
|
|
150
|
+
`timingSafeStringEqual` in `@pikku/core/hmac`.
|
|
151
|
+
|
|
152
|
+
A request with a body that fails is refused with a 401. A bodiless request
|
|
153
|
+
that fails (a HEAD probe, a validation token in the query) still reaches
|
|
154
|
+
`receive` so it can answer the handshake, but any events it returns are
|
|
155
|
+
refused. A handshake that hands over the secret (Asana) stores it with
|
|
156
|
+
`credentialService.set` under the same credential name.
|
|
138
157
|
|
|
139
158
|
`check`, `setup` and `teardown` register the route with the provider. Each gets
|
|
140
159
|
`{ url, label, events, previous? }`, where `events` are only the ones some
|
|
@@ -237,38 +237,52 @@ with `outcome: 'success' | 'denied'`, the decider under `userIdentity`, and the
|
|
|
237
237
|
run, reason and refusal in `metadata`. Wire an `audit` service to keep it; a
|
|
238
238
|
project without one records nothing and is otherwise unaffected.
|
|
239
239
|
|
|
240
|
-
###
|
|
240
|
+
### Failure: `compensate`, never try/catch
|
|
241
241
|
|
|
242
242
|
**Do not wrap steps in try/catch.** The DSL extractor serialises the body into a
|
|
243
243
|
step graph, and a `catch` block is control flow it cannot represent — so the
|
|
244
|
-
graph would no longer describe what actually runs
|
|
245
|
-
|
|
244
|
+
graph would no longer describe what actually runs. This is a settled design
|
|
245
|
+
decision, not a temporary limitation.
|
|
246
246
|
|
|
247
|
-
|
|
248
|
-
|
|
247
|
+
Declare how a function is undone **on the function itself**. When a later step
|
|
248
|
+
fails (after its retries), the engine runs the `compensate` of every earlier
|
|
249
|
+
step that completed, newest first, then the failed step's own:
|
|
249
250
|
|
|
250
251
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
}
|
|
260
|
-
)
|
|
252
|
+
export const chargePayment = pikkuFunc({
|
|
253
|
+
func: async ({ payments }, { orderId }) => payments.charge(orderId),
|
|
254
|
+
compensate: async ({ payments }, { orderId }, { workflow }) => {
|
|
255
|
+
const { ok, output } = workflow!.compensatingFor!
|
|
256
|
+
// ok: false → the charge itself failed; output is null, `error` is set
|
|
257
|
+
if (ok) await payments.refund(output.chargeId)
|
|
258
|
+
},
|
|
259
|
+
})
|
|
261
260
|
```
|
|
262
261
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
262
|
+
- `compensate` gets the **same input** as the forward call. `wire.workflow.compensatingFor`
|
|
263
|
+
is `{ ok: true, output, stepName }` or `{ ok: false, output: null, error, stepName }`.
|
|
264
|
+
- It is never callable over HTTP, MCP or RPC — only the engine runs it, as the
|
|
265
|
+
durable step `<step>:compensate` with the same retry defaults as forward steps.
|
|
266
|
+
- Opt a call site out with `{ compensate: false }` (graph nodes likewise).
|
|
267
|
+
- A run ends `compensated` (everything undone), `compensation_failed` (a
|
|
268
|
+
compensation ran out of retries; `stuckSteps` names them — steps that ran
|
|
269
|
+
before a stuck one are not undone, parallel siblings still are) or `failed`
|
|
270
|
+
(nothing needed undoing).
|
|
271
|
+
- `await workflow.milestone('paid')` bounds the unwind: steps finished before the
|
|
272
|
+
last milestone are kept, and the run ends `compensated` with `restedAt: 'paid'`.
|
|
273
|
+
- A failing child workflow unwinds itself first; a stuck child makes the parent
|
|
274
|
+
`compensation_failed`. Cancelling a run (`cancelRun`) unwinds it the same way,
|
|
275
|
+
children first.
|
|
276
|
+
|
|
277
|
+
To **recover** instead of undo, use a graph node's `recover`:
|
|
278
|
+
`recover: 'nodeId' | ['a','b'] | 'ignore'`. The failure is routed to those nodes
|
|
279
|
+
(`'ignore'` continues to `next` with a null output) and the failing step is not
|
|
280
|
+
compensated. The error arrives as `wire.graph.recoveringFrom`
|
|
281
|
+
(`{ nodeId, stepName, error }`). The DSL has no recovery — branch on a result
|
|
282
|
+
object (`{ success: false, reason }`) instead.
|
|
283
|
+
|
|
284
|
+
Full step options: `description`, `retries`, `retryDelay`, `compensate: false`
|
|
285
|
+
(plus `actor`, which is scenario-only — see `pikku-scenario`).
|
|
272
286
|
|
|
273
287
|
### Parallel fan-out
|
|
274
288
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Step execution: inline vs queue dispatch
|
|
4
4
|
|
|
5
|
-
Whether a step runs **inline** (same process/session, no queue round-trip) or is **dispatched to the queue** is decided **purely by the step's function** — there is no workflow-level or per-call dispatch flag. `workflow.do(...)` options are only `description`/`retries`/`retryDelay`/`
|
|
5
|
+
Whether a step runs **inline** (same process/session, no queue round-trip) or is **dispatched to the queue** is decided **purely by the step's function** — there is no workflow-level or per-call dispatch flag. `workflow.do(...)` options are only `description`/`retries`/`retryDelay`/`compensate: false`.
|
|
6
6
|
|
|
7
7
|
- **Steps default to inline.** Most steps don't need their own worker; running them inline avoids a queue round-trip per step, so a normally-started workflow executes its whole chain in one orchestrator pass.
|
|
8
8
|
- **`workflowQueued: true` opts a function out.** Set it on the **function config** (`pikkuFunc` / `pikkuSessionlessFunc`, same level as `auth`/`expose`) to dispatch that step via the queue — for expensive/long-running steps that deserve their own worker, retry isolation, and concurrency limits. `workflowRetries` and `workflowTimeout` sit alongside it.
|