@pikku/cli 0.12.54 → 0.12.56
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/cli.schema.json +1 -1
- package/console-app/assets/{index-DYnbceYg.js → index-xN8LW0II.js} +155 -155
- package/console-app/index.html +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +3 -3
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.d.ts +6 -6
- package/dist/.pikku/cli/pikku-cli-channel.js +11 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +14 -0
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +29 -0
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +8 -31
- package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +1420 -1384
- package/dist/.pikku/function/pikku-functions.gen.js +3 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +4 -2
- package/dist/.pikku/pikku-services.gen.js +2 -0
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +0 -248
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +69 -67
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +191 -185
- package/dist/.pikku/schemas/schemas/FabricAddonVerifyInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/FabricAddonVerifyOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuAuthInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +9 -3
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/src/cli.wiring.js +15 -1
- package/dist/src/deploy/analyzer/analyzer.d.ts +6 -0
- package/dist/src/deploy/analyzer/analyzer.js +5 -4
- package/dist/src/deploy/build-pipeline.d.ts +5 -1
- package/dist/src/deploy/build-pipeline.js +5 -5
- package/dist/src/deploy/bundler/bun-bundler.d.ts +14 -0
- package/dist/src/deploy/bundler/bun-bundler.js +121 -0
- package/dist/src/deploy/bundler/bundler.d.ts +25 -30
- package/dist/src/deploy/bundler/bundler.interface.d.ts +54 -0
- package/dist/src/deploy/bundler/bundler.interface.js +11 -0
- package/dist/src/deploy/bundler/bundler.js +120 -190
- package/dist/src/deploy/bundler/dep-extractor.d.ts +11 -3
- package/dist/src/deploy/bundler/dep-extractor.js +12 -6
- package/dist/src/deploy/bundler/index.d.ts +5 -2
- package/dist/src/deploy/bundler/index.js +4 -2
- package/dist/src/deploy/bundler/node-bundler.d.ts +13 -0
- package/dist/src/deploy/bundler/node-bundler.js +80 -0
- package/dist/src/deploy/provider-adapter.d.ts +11 -0
- package/dist/src/deploy/server-entry.js +3 -1
- package/dist/src/fabric/fabric-commands.d.ts +109 -72
- package/dist/src/fabric/fabric-commands.js +8 -0
- package/dist/src/fabric/functions/add.function.d.ts +3 -3
- package/dist/src/fabric/functions/addon-verify.function.d.ts +54 -0
- package/dist/src/fabric/functions/addon-verify.function.js +153 -0
- package/dist/src/fabric/functions/db-schema.function.d.ts +3 -3
- package/dist/src/fabric/functions/deploy-list.function.d.ts +3 -3
- package/dist/src/fabric/functions/deploy-units.function.d.ts +3 -3
- package/dist/src/fabric/functions/deploy.function.d.ts +6 -6
- package/dist/src/fabric/functions/domains-add.function.d.ts +3 -3
- package/dist/src/fabric/functions/domains-list.function.d.ts +3 -3
- package/dist/src/fabric/functions/domains-remove.function.d.ts +3 -3
- package/dist/src/fabric/functions/errors.function.d.ts +3 -3
- package/dist/src/fabric/functions/init.function.d.ts +3 -3
- package/dist/src/fabric/functions/link.function.d.ts +3 -3
- package/dist/src/fabric/functions/llm-key.function.d.ts +3 -3
- package/dist/src/fabric/functions/llm-key.function.js +1 -1
- package/dist/src/fabric/functions/login.function.d.ts +3 -3
- package/dist/src/fabric/functions/logs.function.d.ts +3 -3
- package/dist/src/fabric/functions/metrics.function.d.ts +3 -3
- package/dist/src/fabric/functions/publish.function.d.ts +3 -3
- package/dist/src/fabric/functions/publish.function.js +8 -3
- package/dist/src/fabric/functions/rollback.function.d.ts +3 -3
- package/dist/src/fabric/functions/secrets-list.function.d.ts +3 -3
- package/dist/src/fabric/functions/secrets-set.function.d.ts +3 -3
- package/dist/src/fabric/functions/smoke.function.d.ts +3 -3
- package/dist/src/fabric/functions/status.function.d.ts +3 -3
- package/dist/src/fabric/functions/trace.function.d.ts +3 -3
- package/dist/src/fabric/functions/validate.function.d.ts +3 -3
- package/dist/src/functions/commands/all.d.ts +1 -1
- package/dist/src/functions/commands/all.js +19 -2
- package/dist/src/functions/commands/binary.d.ts +3 -3
- package/dist/src/functions/commands/bootstrap.d.ts +1 -1
- package/dist/src/functions/commands/bootstrap.js +3 -0
- package/dist/src/functions/commands/console.d.ts +3 -3
- package/dist/src/functions/commands/db-audit.d.ts +1 -1
- package/dist/src/functions/commands/db-generate.d.ts +1 -1
- package/dist/src/functions/commands/db-migrate.d.ts +1 -1
- package/dist/src/functions/commands/db-reset.d.ts +1 -1
- package/dist/src/functions/commands/db-seed.d.ts +1 -1
- package/dist/src/functions/commands/deploy-apply.d.ts +3 -3
- package/dist/src/functions/commands/deploy-apply.js +32 -1
- package/dist/src/functions/commands/deploy-info.d.ts +1 -1
- package/dist/src/functions/commands/deploy-plan.d.ts +3 -3
- package/dist/src/functions/commands/deploy-plan.js +3 -1
- package/dist/src/functions/commands/dev.d.ts +3 -3
- package/dist/src/functions/commands/dev.js +17 -45
- package/dist/src/functions/commands/emails-init.d.ts +1 -1
- package/dist/src/functions/commands/enable.d.ts +6 -5
- package/dist/src/functions/commands/enable.js +4 -0
- package/dist/src/functions/commands/info.d.ts +4 -4
- package/dist/src/functions/commands/login.d.ts +7 -7
- package/dist/src/functions/commands/meta.d.ts +31 -31
- package/dist/src/functions/commands/new-addon.d.ts +3 -3
- package/dist/src/functions/commands/new-function.d.ts +3 -3
- package/dist/src/functions/commands/new-middleware.d.ts +3 -3
- package/dist/src/functions/commands/new-permission.d.ts +3 -3
- package/dist/src/functions/commands/new-wiring.d.ts +3 -3
- package/dist/src/functions/commands/pikku-command-bootstrap.d.ts +1 -1
- package/dist/src/functions/commands/pikku-command-summary.d.ts +1 -1
- package/dist/src/functions/commands/pikku-command-summary.js +6 -1
- package/dist/src/functions/commands/skills.d.ts +6 -6
- package/dist/src/functions/commands/tests-coverage.d.ts +3 -3
- package/dist/src/functions/commands/tests-init.d.ts +3 -3
- package/dist/src/functions/commands/versions-check.d.ts +1 -1
- package/dist/src/functions/commands/versions-init.d.ts +3 -3
- package/dist/src/functions/commands/versions-update.d.ts +1 -1
- package/dist/src/functions/commands/watch.d.ts +3 -3
- package/dist/src/functions/commands/workspace-validate.d.ts +3 -3
- package/dist/src/functions/db/db-codegen.js +14 -0
- package/dist/src/functions/db/sqlite/sqlite-runtime-bun.js +10 -0
- package/dist/src/functions/runtimes/fetch/index.d.ts +1 -1
- package/dist/src/functions/runtimes/nextjs/pikku-command-nextjs.d.ts +1 -1
- package/dist/src/functions/runtimes/tanstack-start/pikku-command-tanstack-start.d.ts +1 -1
- package/dist/src/functions/runtimes/websocket/pikku-command-websocket-typed.d.ts +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent-types.d.ts +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-ai-agent.d.ts +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.d.ts +1 -1
- package/dist/src/functions/wirings/auth/pikku-command-auth.d.ts +7 -1
- package/dist/src/functions/wirings/auth/pikku-command-auth.js +14 -2
- package/dist/src/functions/wirings/auth/serialize-auth-types.d.ts +10 -0
- package/dist/src/functions/wirings/auth/serialize-auth-types.js +15 -0
- package/dist/src/functions/wirings/channels/pikku-channels.d.ts +1 -1
- package/dist/src/functions/wirings/channels/pikku-command-channel-types.d.ts +1 -1
- package/dist/src/functions/wirings/channels/pikku-command-channels-map.d.ts +1 -1
- package/dist/src/functions/wirings/channels/pikku-command-channels.d.ts +1 -1
- package/dist/src/functions/wirings/cli/pikku-command-cli-entry.d.ts +1 -1
- package/dist/src/functions/wirings/cli/pikku-command-cli-types.d.ts +1 -1
- package/dist/src/functions/wirings/cli/pikku-command-cli.d.ts +1 -1
- package/dist/src/functions/wirings/console/pikku-command-console-functions.d.ts +1 -1
- package/dist/src/functions/wirings/console/pikku-command-node-types.d.ts +1 -1
- package/dist/src/functions/wirings/console/pikku-command-nodes-meta.d.ts +1 -1
- package/dist/src/functions/wirings/credentials/pikku-command-credentials.d.ts +1 -1
- package/dist/src/functions/wirings/emails/pikku-command-emails.d.ts +1 -1
- package/dist/src/functions/wirings/functions/pikku-command-addon-types.d.ts +1 -1
- package/dist/src/functions/wirings/functions/pikku-command-function-types-split.d.ts +3 -3
- package/dist/src/functions/wirings/functions/pikku-command-function-types.d.ts +3 -3
- package/dist/src/functions/wirings/functions/pikku-command-function-types.js +10 -3
- package/dist/src/functions/wirings/functions/pikku-command-functions.d.ts +1 -1
- package/dist/src/functions/wirings/functions/pikku-command-services.d.ts +1 -1
- package/dist/src/functions/wirings/functions/schemas.d.ts +1 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +6 -29
- package/dist/src/functions/wirings/gateway/pikku-command-gateway.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-command-http-map.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-command-http-routes.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-command-http-types.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-command-openapi.d.ts +1 -1
- package/dist/src/functions/wirings/http/pikku-http-routes.d.ts +1 -1
- package/dist/src/functions/wirings/mcp/pikku-command-mcp-json.d.ts +1 -1
- package/dist/src/functions/wirings/mcp/pikku-command-mcp-types.d.ts +1 -1
- package/dist/src/functions/wirings/mcp/pikku-command-mcp.d.ts +1 -1
- package/dist/src/functions/wirings/middleware/pikku-command-middleware.d.ts +1 -1
- package/dist/src/functions/wirings/package/pikku-command-package-types.d.ts +2 -2
- package/dist/src/functions/wirings/package/pikku-command-package.d.ts +1 -1
- package/dist/src/functions/wirings/permissions/pikku-command-permissions.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue-map.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue-service.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue-types.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-command-queue.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-queue-map.d.ts +1 -1
- package/dist/src/functions/wirings/queue/pikku-queue.d.ts +1 -1
- package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.d.ts +1 -1
- package/dist/src/functions/wirings/realtime/pikku-command-realtime.d.ts +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.d.ts +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-react-query.d.ts +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-remote-rpc.d.ts +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc-client.d.ts +1 -1
- package/dist/src/functions/wirings/rpc/pikku-command-rpc-map.d.ts +2 -2
- package/dist/src/functions/wirings/rpc/pikku-command-rpc.d.ts +1 -1
- package/dist/src/functions/wirings/scheduler/pikku-command-scheduler-types.d.ts +1 -1
- package/dist/src/functions/wirings/scheduler/pikku-command-scheduler.d.ts +1 -1
- package/dist/src/functions/wirings/secrets/pikku-command-secrets.d.ts +1 -1
- package/dist/src/functions/wirings/triggers/pikku-command-trigger-types.d.ts +3 -3
- package/dist/src/functions/wirings/triggers/pikku-command-trigger.d.ts +1 -1
- package/dist/src/functions/wirings/variables/pikku-command-variables.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.d.ts +1 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.d.ts +1 -1
- package/dist/src/functions/workflows/all.workflow.js +6 -1
- package/dist/src/scaffold/rpc-remote.gen.d.ts +3 -3
- package/dist/src/scaffold/rpc-remote.gen.js +1 -1
- package/dist/src/server/bun-server-runner.d.ts +17 -0
- package/dist/src/server/bun-server-runner.js +25 -0
- package/dist/src/server/dev-server-runner.interface.d.ts +31 -0
- package/dist/src/server/dev-server-runner.interface.js +11 -0
- package/dist/src/server/node-server-runner.d.ts +12 -0
- package/dist/src/server/node-server-runner.js +30 -0
- package/dist/src/services/cli-logger.service.js +7 -1
- package/dist/src/services.js +18 -0
- package/dist/src/utils/detect-better-auth.d.ts +7 -0
- package/dist/src/utils/detect-better-auth.js +29 -0
- package/dist/src/utils/parse-cli-filters.d.ts +1 -0
- package/dist/src/utils/parse-cli-filters.js +1 -0
- package/dist/src/utils/pikku-cli-config.js +1 -1
- package/dist/src/utils/serialize-schemas.js +5 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +4 -4
- package/skills/pikku-addon/SKILL.md +25 -117
- package/skills/pikku-addon/references/addon-package-manifest.md +63 -0
- package/skills/pikku-cli/SKILL.md +7 -93
- package/skills/pikku-cli/references/complete-example.md +82 -0
- package/skills/pikku-concepts/SKILL.md +17 -69
- package/skills/pikku-concepts/references/concept-mapping.md +37 -13
- package/skills/pikku-concepts/references/packages.md +29 -0
- package/skills/pikku-http/SKILL.md +14 -105
- package/skills/pikku-http/references/http-options.md +57 -0
- package/skills/pikku-middleware/SKILL.md +11 -68
- package/skills/pikku-middleware/references/middleware-patterns.md +61 -0
- package/skills/pikku-realtime/SKILL.md +56 -105
- package/skills/pikku-realtime/references/other-routes.md +23 -0
- package/skills/pikku-services/SKILL.md +25 -108
- package/skills/pikku-services/references/audit-wire-service.md +34 -0
- package/skills/pikku-testing/SKILL.md +51 -359
- package/skills/pikku-testing/references/cucumber-bdd-testing.md +176 -0
- package/skills/pikku-workflow/SKILL.md +93 -259
- package/skills/pikku-workflow/references/workflow-reference.md +63 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Cucumber / BDD Testing with `@pikku/cucumber`
|
|
2
|
+
|
|
3
|
+
## Personas and Named Data — Never Inline JSON
|
|
4
|
+
|
|
5
|
+
**Never put JSON, inline tables, or raw values inside `.feature` files.** Feature files are for human-readable scenarios; all test data belongs in typed maps that step definitions look up by name.
|
|
6
|
+
|
|
7
|
+
`@pikku/cucumber` exports `PersonaData<T>` — a typed map that throws a clear error when a name is missing.
|
|
8
|
+
|
|
9
|
+
### Personas
|
|
10
|
+
|
|
11
|
+
A **persona** is a named user: login credentials plus the session held after authenticating. Define all personas in one file:
|
|
12
|
+
|
|
13
|
+
```ts
|
|
14
|
+
// tests/tests/support/personas.ts
|
|
15
|
+
import { PersonaData } from '@pikku/cucumber'
|
|
16
|
+
|
|
17
|
+
export const logins = new PersonaData({
|
|
18
|
+
yasser: { email: 'yasser@example.com', password: 'hunter2' },
|
|
19
|
+
guest: { email: 'guest@example.com', password: 'guest123' },
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
A persona step logs in and stores the session in the world so every subsequent call by that persona carries it automatically:
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
// tests/tests/support/steps/auth.steps.ts
|
|
27
|
+
import { Given } from '@cucumber/cucumber'
|
|
28
|
+
import { logins } from '../personas.js'
|
|
29
|
+
|
|
30
|
+
Given('{string} logs in', async function (name: string) {
|
|
31
|
+
await this.call(name, 'auth:login', logins.get(name))
|
|
32
|
+
const { token } = this.lastResult as { token: string }
|
|
33
|
+
this.setSession(name, { token })
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Named Domain Data
|
|
38
|
+
|
|
39
|
+
Use a separate `PersonaData` map per domain concept. Name entries after real-world meaning, not technical fields:
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
// tests/tests/support/data/cards.ts
|
|
43
|
+
import { PersonaData } from '@pikku/cucumber'
|
|
44
|
+
|
|
45
|
+
export const cards = new PersonaData({
|
|
46
|
+
'writing a blog post': { title: 'Writing a blog post', columnId: 'backlog' },
|
|
47
|
+
'fix the login bug': { title: 'Fix the login bug', columnId: 'in-progress' },
|
|
48
|
+
})
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Steps resolve the name and make the call — the feature file never sees raw data:
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
// tests/tests/support/steps/card.steps.ts
|
|
55
|
+
import { When, Then } from '@cucumber/cucumber'
|
|
56
|
+
import assert from 'node:assert/strict'
|
|
57
|
+
import { cards } from '../data/cards.js'
|
|
58
|
+
|
|
59
|
+
When('{string} creates a card for {string}', async function (persona: string, cardName: string) {
|
|
60
|
+
await this.call(persona, 'kanban:createCard', cards.get(cardName))
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
When('{string} gets the card {string}', async function (persona: string, cardName: string) {
|
|
64
|
+
const { title } = cards.get(cardName)
|
|
65
|
+
await this.call(persona, 'kanban:getCard', { title })
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
// "the newly created card" — checks the live result against the data map entry
|
|
69
|
+
// AND any server-assigned fields (id, createdAt) are present
|
|
70
|
+
Then('the result is the newly created card {string}', function (cardName: string) {
|
|
71
|
+
const expected = cards.get(cardName)
|
|
72
|
+
const result = this.lastResult as typeof expected & { id: string; createdAt: string }
|
|
73
|
+
assert.equal(result.title, expected.title)
|
|
74
|
+
assert.equal(result.columnId, expected.columnId)
|
|
75
|
+
assert.ok(result.id, 'expected server-assigned id')
|
|
76
|
+
assert.ok(result.createdAt, 'expected server-assigned createdAt')
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
The feature file reads naturally:
|
|
81
|
+
|
|
82
|
+
```gherkin
|
|
83
|
+
Feature: Card management
|
|
84
|
+
|
|
85
|
+
Scenario: Create and retrieve a card
|
|
86
|
+
Given 'yasser' logs in
|
|
87
|
+
When 'yasser' creates a card for 'writing a blog post'
|
|
88
|
+
And 'yasser' gets the card 'writing a blog post'
|
|
89
|
+
Then the result is the newly created card 'writing a blog post'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### File layout
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
tests/tests/support/
|
|
96
|
+
personas.ts ← logins PersonaData (one per project)
|
|
97
|
+
data/
|
|
98
|
+
cards.ts ← cards PersonaData
|
|
99
|
+
users.ts ← users PersonaData
|
|
100
|
+
steps/
|
|
101
|
+
auth.steps.ts ← login / logout steps
|
|
102
|
+
card.steps.ts ← card CRUD steps
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Keep one `PersonaData` instance per domain concept. Steps import only what they need — no cross-domain coupling.
|
|
106
|
+
|
|
107
|
+
## Anti-Patterns
|
|
108
|
+
|
|
109
|
+
### Inline data in feature files
|
|
110
|
+
|
|
111
|
+
Raw values/JSON in `.feature` files make scenarios brittle and unreadable. Use named references resolved by step definitions instead.
|
|
112
|
+
|
|
113
|
+
```gherkin
|
|
114
|
+
# Wrong
|
|
115
|
+
When I call 'kanban:createCard' with {"title": "My card", "columnId": "backlog"}
|
|
116
|
+
Then the result title is "My card"
|
|
117
|
+
|
|
118
|
+
# Right
|
|
119
|
+
When 'yasser' creates a card for 'writing a blog post'
|
|
120
|
+
Then the result is the newly created card 'writing a blog post'
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Feature-coupled step definitions
|
|
124
|
+
|
|
125
|
+
Steps tied to one feature can't be reused and cause duplication. Organise by **domain concept**, not by feature. Name step files after the domain they cover — a login step belongs in `auth.steps.ts` regardless of which feature needs it.
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
Wrong: Right:
|
|
129
|
+
steps/ steps/
|
|
130
|
+
edit_work_experience.ts auth.steps.ts
|
|
131
|
+
edit_languages.ts profile.steps.ts
|
|
132
|
+
edit_education.ts card.steps.ts
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Conjunction steps
|
|
136
|
+
|
|
137
|
+
Don't combine multiple actions into a single step — it makes reuse impossible. Use `And` / `But`: each step does exactly one thing.
|
|
138
|
+
|
|
139
|
+
```gherkin
|
|
140
|
+
# Wrong — two actions in one step
|
|
141
|
+
Given 'yasser' is logged in and has created a card
|
|
142
|
+
|
|
143
|
+
# Right — atomic, composable
|
|
144
|
+
Given 'yasser' logs in
|
|
145
|
+
And 'yasser' creates a card for 'writing a blog post'
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Asserting in When steps
|
|
149
|
+
|
|
150
|
+
`When` steps perform actions; `Then` steps assert outcomes. Mixing them hides intent.
|
|
151
|
+
|
|
152
|
+
```gherkin
|
|
153
|
+
# Wrong
|
|
154
|
+
When 'yasser' creates a card and the title is 'writing a blog post'
|
|
155
|
+
|
|
156
|
+
# Right
|
|
157
|
+
When 'yasser' creates a card for 'writing a blog post'
|
|
158
|
+
Then the call succeeds
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Hard-coding persona data in step definitions
|
|
162
|
+
|
|
163
|
+
Credentials/test inputs embedded in step code can't be reused and break when data changes — look them up from `PersonaData`.
|
|
164
|
+
|
|
165
|
+
```ts
|
|
166
|
+
// Wrong
|
|
167
|
+
Given('{string} logs in', async function (name: string) {
|
|
168
|
+
await this.call(name, 'auth:login', { email: 'yasser@example.com', password: 'hunter2' })
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
// Right
|
|
172
|
+
Given('{string} logs in', async function (name: string) {
|
|
173
|
+
await this.call(name, 'auth:login', logins.get(name))
|
|
174
|
+
this.setSession(name, (this.lastResult as { token: string }))
|
|
175
|
+
})
|
|
176
|
+
```
|
|
@@ -12,247 +12,129 @@ installGroups: [core]
|
|
|
12
12
|
|
|
13
13
|
Use this skill as an execution checklist, not reference material.
|
|
14
14
|
|
|
15
|
-
1.
|
|
16
|
-
2.
|
|
17
|
-
3.
|
|
18
|
-
4.
|
|
19
|
-
5.
|
|
20
|
-
|
|
21
|
-
Build durable, multi-step workflows with automatic retry, sleep, suspend/resume, and parallel execution. Steps are cached for replay safety.
|
|
22
|
-
|
|
23
|
-
## Before You Start
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
pikku info functions --verbose # See existing functions that can be workflow steps
|
|
27
|
-
pikku info tags --verbose # Understand project organization
|
|
28
|
-
```
|
|
15
|
+
1. Capture baseline. Run `pikku-verify` (or `pikku all`) BEFORE writing code; note existing errors — only NEW errors are yours to fix.
|
|
16
|
+
2. Discover before editing. Prefer `pikku-meta` / `pikku info functions --verbose` and `pikku info tags --verbose` to see functions usable as steps and project organization; inspect only the focused output you need.
|
|
17
|
+
3. Identify the source files that own the behavior. Do not start from generated output, `.pikku`, `node_modules`, vendored packages, or build artifacts.
|
|
18
|
+
4. Make the smallest source change. Keep generated files generated — never hand-edit SDKs, schema output, or typegen to paper over errors; fix the source cause.
|
|
19
|
+
5. Validate with the narrowest relevant command, then re-run `pikku-verify`. If only files you did not touch still error, those are pre-existing — leave them unless asked.
|
|
20
|
+
6. Call `pikku-workflow-view` only when `pikku-verify` fully passes (codegen AND type check both green) — never after a partial pass.
|
|
29
21
|
|
|
30
22
|
See `pikku-concepts` for the core mental model.
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
Build durable, multi-step workflows with automatic retry, sleep, suspend/resume, and parallel execution. Steps are cached for replay safety.
|
|
33
25
|
|
|
34
|
-
|
|
26
|
+
## Choosing the right factory
|
|
35
27
|
|
|
36
|
-
|
|
37
|
-
|
|
28
|
+
| Factory | When to use | Step-graph view? |
|
|
29
|
+
|---|---|---|
|
|
30
|
+
| `pikkuWorkflowFunc` | **Default for all new workflows.** Sequential + conditional logic; DSL mode (serialisable, replay-safe). ALL `const`/`let` declarations must be at the top level of the function body (not inside blocks). | ✅ Yes |
|
|
31
|
+
| `pikkuWorkflowGraph` | DAG / fan-out with nodes and typed refs between them. | ✅ Yes |
|
|
32
|
+
| `pikkuWorkflowComplexFunc` | Escape hatch only — arbitrary TypeScript, no top-level restriction (e.g. dynamic inline functions the DSL extractor cannot handle). | ❌ No (loses step-graph view) |
|
|
38
33
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return result
|
|
43
|
-
}
|
|
44
|
-
)
|
|
45
|
-
```
|
|
34
|
+
**Default to `pikkuWorkflowFunc`.** Use `pikkuWorkflowGraph` ONLY with explicit user approval AND only for a genuine cyclic dependency or Node.js-only import DSL cannot express. Use `pikkuWorkflowComplexFunc` ONLY with explicit user approval — a last-resort escape hatch. Never switch to either just to dodge a PKU641 error; restructure the code instead.
|
|
35
|
+
|
|
36
|
+
### PKU641 — DSL static analysis error
|
|
46
37
|
|
|
47
|
-
|
|
38
|
+
`pikkuWorkflowFunc` statically analyzes the body: **every `const`/`let` must be top-level, not inside any block (`if`, `for`, `while`, …).** Assignments inside blocks are fine — only declarations trigger it.
|
|
48
39
|
|
|
49
40
|
```typescript
|
|
50
|
-
//
|
|
51
|
-
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
'createUserProfile',
|
|
55
|
-
{
|
|
56
|
-
email: data.email,
|
|
57
|
-
},
|
|
58
|
-
{ retries: 3, retryDelay: '1s' }
|
|
59
|
-
)
|
|
41
|
+
// ❌ PKU641 — declaration inside block
|
|
42
|
+
if (priority === 'high') {
|
|
43
|
+
const bugCard = await workflow.do(...)
|
|
44
|
+
}
|
|
60
45
|
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
46
|
+
// ✅ hoist the declaration, assign inside the block
|
|
47
|
+
let bugCard: Awaited<ReturnType<typeof workflow.do>>
|
|
48
|
+
if (priority === 'high') {
|
|
49
|
+
bugCard = await workflow.do(...)
|
|
50
|
+
}
|
|
66
51
|
```
|
|
67
52
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
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 `inline` flag.
|
|
71
|
-
|
|
72
|
-
- **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.
|
|
73
|
-
- **`inline: false` opts a function out.** Set `inline: false` 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. It is **not** a `workflow.do(...)` option (those are only `retries`/`retryDelay`/`description`).
|
|
74
|
-
|
|
75
|
-
The rule (`dispatchStep`):
|
|
76
|
-
|
|
77
|
-
| Function `inline` | `queueService` present? | Result |
|
|
78
|
-
|---|---|---|
|
|
79
|
-
| default / `true` | any | **inline** |
|
|
80
|
-
| `false` | yes | **queued** (own worker) |
|
|
81
|
-
| `false` | no | **inline + a `logger.warn`** (misconfiguration: can't dispatch) |
|
|
53
|
+
## Import path
|
|
82
54
|
|
|
83
55
|
```typescript
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
inline: false, // dispatch via queue instead of running inline
|
|
87
|
-
input: ReportInput,
|
|
88
|
-
output: ReportOutput,
|
|
89
|
-
func: async (services, data) => { /* ... */ },
|
|
90
|
-
})
|
|
91
|
-
```
|
|
56
|
+
// CORRECT — workflow factories come from the generated types file
|
|
57
|
+
import { pikkuWorkflowFunc, pikkuWorkflowGraph, pikkuWorkflowComplexFunc } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
92
58
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
// Sleep — durable pause (duration: '5min', '1h', '30s', '1d')
|
|
97
|
-
await workflow.sleep('Wait 5 minutes', '5min')
|
|
98
|
-
|
|
99
|
-
// Suspend — pause until externally resumed
|
|
100
|
-
await workflow.suspend('Awaiting approval')
|
|
59
|
+
// WRONG — '#pikku' does not re-export them (TS2305)
|
|
60
|
+
import { pikkuWorkflowFunc } from '#pikku'
|
|
101
61
|
```
|
|
102
62
|
|
|
103
|
-
|
|
63
|
+
## Defining a workflow
|
|
104
64
|
|
|
105
|
-
|
|
106
|
-
|---|---|
|
|
107
|
-
| `pikkuWorkflowFunc` | **Default.** Use for all new workflows. DSL mode — serialisable, replay-safe. |
|
|
108
|
-
| `pikkuWorkflowComplexFunc` | **Only with explicit user approval.** For workflows with patterns the DSL extractor cannot handle (e.g. dynamic inline functions). Not a general escape hatch — restructure first. |
|
|
109
|
-
| `pikkuWorkflowGraph` | **Only with explicit user approval.** For genuine DAGs where there is a cyclic dependency between nodes or a Node.js-only import DSL cannot express. |
|
|
110
|
-
|
|
111
|
-
**Conditional results** — the correct DSL pattern when a step only runs under some condition:
|
|
65
|
+
Declare input/output as Zod schemas (like any function) — never TypeScript generic params (no `pikkuWorkflowFunc<In, Out>(...)`; that skips runtime validation). `data` is typed from the input schema.
|
|
112
66
|
|
|
113
67
|
```typescript
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
68
|
+
import { z } from 'zod'
|
|
69
|
+
import { pikkuWorkflowFunc } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
70
|
+
|
|
71
|
+
const ProcessOrderInput = z.object({ orderId: z.string(), amount: z.number() })
|
|
72
|
+
const ProcessOrderOutput = z.object({ status: z.string(), discount: z.number().optional() })
|
|
73
|
+
|
|
74
|
+
export const processOrder = pikkuWorkflowFunc({
|
|
75
|
+
description: 'Process an order through payment and fulfillment',
|
|
76
|
+
tags: ['orders'],
|
|
77
|
+
input: ProcessOrderInput,
|
|
78
|
+
output: ProcessOrderOutput,
|
|
79
|
+
func: async (services, data, { workflow }) => {
|
|
80
|
+
// Declare ALL variables at top level — even those only assigned in branches (PKU641)
|
|
81
|
+
let discount: number | undefined
|
|
82
|
+
let status: string
|
|
83
|
+
|
|
84
|
+
if (data.amount > 1000) {
|
|
85
|
+
const d = await workflow.do('Apply bulk discount', 'calcDiscount', { amount: data.amount })
|
|
86
|
+
discount = d.discountPercent
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const payment = await workflow.do('Charge', 'chargePayment', {
|
|
90
|
+
orderId: data.orderId,
|
|
91
|
+
amount: discount ? data.amount * (1 - discount / 100) : data.amount,
|
|
92
|
+
})
|
|
126
93
|
|
|
127
|
-
|
|
128
|
-
|
|
94
|
+
if (payment.success) {
|
|
95
|
+
await workflow.do('Fulfill', 'fulfillOrder', { orderId: data.orderId })
|
|
96
|
+
status = 'fulfilled'
|
|
97
|
+
} else {
|
|
98
|
+
status = 'payment-failed'
|
|
99
|
+
}
|
|
129
100
|
|
|
130
|
-
|
|
131
|
-
description: 'Onboard a new user',
|
|
132
|
-
nodes: {
|
|
133
|
-
createProfile: 'createUserProfile', // nodeName → Pikku function name
|
|
134
|
-
sendWelcome: 'sendEmail',
|
|
135
|
-
},
|
|
136
|
-
config: {
|
|
137
|
-
createProfile: {
|
|
138
|
-
next: ['sendWelcome'], // Nodes to run after this one (parallel)
|
|
139
|
-
},
|
|
140
|
-
sendWelcome: {
|
|
141
|
-
input: (ref) => ({
|
|
142
|
-
// Transform input using refs to prior node outputs
|
|
143
|
-
to: ref('createProfile', 'email'),
|
|
144
|
-
subject: 'Welcome!',
|
|
145
|
-
}),
|
|
146
|
-
},
|
|
101
|
+
return { status, discount }
|
|
147
102
|
},
|
|
148
103
|
})
|
|
149
104
|
```
|
|
150
105
|
|
|
151
|
-
###
|
|
106
|
+
### Workflow step types
|
|
152
107
|
|
|
153
108
|
```typescript
|
|
154
|
-
//
|
|
155
|
-
|
|
156
|
-
method: 'post',
|
|
157
|
-
route: '/workflow/onboard',
|
|
158
|
-
func: workflowStart('workflowName'),
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// Execute workflow steps (called by orchestrator)
|
|
162
|
-
wireHTTP({
|
|
163
|
-
method: 'post',
|
|
164
|
-
route: '/workflow/onboard/run',
|
|
165
|
-
func: workflow('workflowName'),
|
|
166
|
-
})
|
|
167
|
-
|
|
168
|
-
// Check workflow status
|
|
169
|
-
wireHTTP({
|
|
170
|
-
method: 'get',
|
|
171
|
-
route: '/workflow/status/:runId',
|
|
172
|
-
func: workflowStatus('workflowName'),
|
|
173
|
-
})
|
|
174
|
-
```
|
|
109
|
+
// RPC step — run a registered Pikku function as a step (opts: retries, retryDelay, description)
|
|
110
|
+
const result = await workflow.do('Step name', 'rpcFunctionName', { ...data }, { retries: 3, retryDelay: '1s' })
|
|
175
111
|
|
|
176
|
-
|
|
112
|
+
// Inline closure step — immediate execution, cached for replay
|
|
113
|
+
const msg = await workflow.do('Generate', async () => `Welcome, ${data.email}!`)
|
|
177
114
|
|
|
178
|
-
|
|
115
|
+
// Sleep — durable pause (duration: '30s', '5min', '1h', '1d')
|
|
116
|
+
await workflow.sleep('Wait 5 minutes', '5min')
|
|
179
117
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
{ email: string; userId: string },
|
|
183
|
-
{ success: boolean }
|
|
184
|
-
>(async ({}, data, { workflow }) => {
|
|
185
|
-
const user = await workflow.do('Create profile', 'createUserProfile', {
|
|
186
|
-
email: data.email,
|
|
187
|
-
userId: data.userId,
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
const message = await workflow.do(
|
|
191
|
-
'Generate welcome',
|
|
192
|
-
async () => `Welcome, ${data.email}!`
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
await workflow.sleep('Wait 5 minutes', '5min')
|
|
196
|
-
|
|
197
|
-
await workflow.do('Send email', 'sendEmail', {
|
|
198
|
-
to: data.email,
|
|
199
|
-
subject: 'Welcome!',
|
|
200
|
-
body: message,
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
return { success: true }
|
|
204
|
-
})
|
|
118
|
+
// Suspend — pause until externally resumed (e.g. awaiting approval), then continue
|
|
119
|
+
await workflow.suspend('Awaiting approval')
|
|
205
120
|
```
|
|
206
121
|
|
|
207
|
-
### Parallel
|
|
122
|
+
### Parallel fan-out
|
|
208
123
|
|
|
209
124
|
```typescript
|
|
210
125
|
const users = await Promise.all(
|
|
211
|
-
data.userIds.map(
|
|
212
|
-
async (userId) =>
|
|
213
|
-
await workflow.do(`Get user ${userId}`, 'userGet', { userId })
|
|
214
|
-
)
|
|
215
|
-
)
|
|
216
|
-
```
|
|
217
|
-
|
|
218
|
-
### Retry with Backoff
|
|
219
|
-
|
|
220
|
-
```typescript
|
|
221
|
-
const payment = await workflow.do(
|
|
222
|
-
'Process payment',
|
|
223
|
-
'processPayment',
|
|
224
|
-
{ amount: 100 },
|
|
225
|
-
{ retries: 3, retryDelay: '1s' }
|
|
126
|
+
data.userIds.map((userId) => workflow.do(`Fetch user ${userId}`, 'getUser', { userId }))
|
|
226
127
|
)
|
|
227
128
|
```
|
|
228
129
|
|
|
229
|
-
###
|
|
230
|
-
|
|
231
|
-
```typescript
|
|
232
|
-
if (user.plan === 'pro') {
|
|
233
|
-
await workflow.do('Apply discount', 'applyDiscount', { userId })
|
|
234
|
-
}
|
|
235
|
-
```
|
|
130
|
+
### Graph workflow (DAG)
|
|
236
131
|
|
|
237
|
-
|
|
132
|
+
`pikkuWorkflowGraph` derives types from the RPC map — no explicit `input`/`output`. Nodes map `nodeName → Pikku function name`; `config.<node>.next` lists nodes to run after it (in parallel); `config.<node>.input: (ref) => ...` transforms input using refs to prior node outputs.
|
|
238
133
|
|
|
239
134
|
```typescript
|
|
240
|
-
|
|
241
|
-
{ requestId: string },
|
|
242
|
-
{ approved: boolean }
|
|
243
|
-
>(async ({}, data, { workflow }) => {
|
|
244
|
-
await workflow.do('Submit request', 'submitRequest', data)
|
|
245
|
-
await workflow.suspend('Awaiting approval')
|
|
246
|
-
// Workflow pauses here until externally resumed
|
|
247
|
-
const result = await workflow.do('Check result', 'getApprovalResult', data)
|
|
248
|
-
return { approved: result.approved }
|
|
249
|
-
})
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
### Graph Workflow (DAG)
|
|
135
|
+
import { pikkuWorkflowGraph } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
253
136
|
|
|
254
|
-
|
|
255
|
-
const userOnboarding = pikkuWorkflowGraph({
|
|
137
|
+
export const userOnboarding = pikkuWorkflowGraph({
|
|
256
138
|
description: 'Onboard a new user',
|
|
257
139
|
nodes: {
|
|
258
140
|
createProfile: 'createUserProfile',
|
|
@@ -260,75 +142,27 @@ const userOnboarding = pikkuWorkflowGraph({
|
|
|
260
142
|
setupDefaults: 'createDefaultTodos',
|
|
261
143
|
},
|
|
262
144
|
config: {
|
|
263
|
-
createProfile: {
|
|
264
|
-
next: ['sendWelcome', 'setupDefaults'], // Run in parallel
|
|
265
|
-
},
|
|
145
|
+
createProfile: { next: ['sendWelcome', 'setupDefaults'] }, // run in parallel
|
|
266
146
|
sendWelcome: {
|
|
267
|
-
input: (ref) => ({
|
|
268
|
-
to: ref('createProfile', 'email'),
|
|
269
|
-
subject: 'Welcome!',
|
|
270
|
-
}),
|
|
147
|
+
input: (ref) => ({ to: ref('createProfile', 'email'), subject: 'Welcome!' }),
|
|
271
148
|
},
|
|
272
149
|
},
|
|
273
150
|
})
|
|
274
151
|
```
|
|
275
152
|
|
|
276
|
-
##
|
|
153
|
+
## File conventions
|
|
277
154
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
export const onboardUser = pikkuWorkflowFunc<
|
|
281
|
-
{ email: string; userId: string; plan: string },
|
|
282
|
-
{ success: boolean }
|
|
283
|
-
>(async ({}, data, { workflow }) => {
|
|
284
|
-
// Step 1: Create user profile
|
|
285
|
-
const user = await workflow.do('Create profile', 'createUserProfile', {
|
|
286
|
-
email: data.email,
|
|
287
|
-
userId: data.userId,
|
|
288
|
-
})
|
|
289
|
-
|
|
290
|
-
// Step 2: Set up defaults based on plan
|
|
291
|
-
if (data.plan === 'pro') {
|
|
292
|
-
await workflow.do('Apply pro features', 'enableProFeatures', {
|
|
293
|
-
userId: data.userId,
|
|
294
|
-
})
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// Step 3: Send welcome email
|
|
298
|
-
await workflow.do('Send welcome', 'sendEmail', {
|
|
299
|
-
to: data.email,
|
|
300
|
-
subject: 'Welcome!',
|
|
301
|
-
body: `Welcome to our platform, ${user.name}!`,
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
// Step 4: Wait then send follow-up
|
|
305
|
-
await workflow.sleep('Wait 1 day', '1d')
|
|
306
|
-
|
|
307
|
-
await workflow.do('Send follow-up', 'sendEmail', {
|
|
308
|
-
to: data.email,
|
|
309
|
-
subject: 'Getting started',
|
|
310
|
-
body: 'Here are some tips to get started...',
|
|
311
|
-
})
|
|
155
|
+
- Place workflows in `packages/functions/src/wirings/*.workflow.ts`; export the variable so the inspector discovers it (no manual registration).
|
|
156
|
+
- HTTP start/run/status routes are auto-scaffolded via `scaffold.workflow` in `pikku.config.json`.
|
|
312
157
|
|
|
313
|
-
|
|
314
|
-
})
|
|
158
|
+
## Step dispatch & HTTP wiring
|
|
315
159
|
|
|
316
|
-
|
|
317
|
-
wireHTTP({
|
|
318
|
-
method: 'post',
|
|
319
|
-
route: '/onboard',
|
|
320
|
-
func: workflowStart('onboardUser'),
|
|
321
|
-
})
|
|
160
|
+
For per-step inline-vs-queue dispatch (`inline: false` and the `dispatchStep` rules), the manual `workflowStart`/`workflow`/`workflowStatus` HTTP wirings, and a suspend/resume example, read `references/workflow-reference.md`.
|
|
322
161
|
|
|
323
|
-
|
|
324
|
-
method: 'post',
|
|
325
|
-
route: '/onboard/run',
|
|
326
|
-
func: workflow('onboardUser'),
|
|
327
|
-
})
|
|
162
|
+
## After writing
|
|
328
163
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
```
|
|
164
|
+
1. `pikku-verify` (codegen + tsc).
|
|
165
|
+
2. PKU641 → a `const`/`let` is inside a block; hoist it to the top of the function body.
|
|
166
|
+
3. Import errors → use `#pikku/workflow/pikku-workflow-types.gen.js`, not `#pikku`.
|
|
167
|
+
4. Type errors only in files you did not touch → pre-existing template errors; safe to ignore.
|
|
168
|
+
5. Both green → call `pikku-workflow-view` with the workflow name.
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Pikku Workflow Reference
|
|
2
|
+
|
|
3
|
+
## Step execution: inline vs queue dispatch
|
|
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 `inline` flag. `workflow.do(...)` options are only `retries`/`retryDelay`/`description`.
|
|
6
|
+
|
|
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
|
+
- **`inline: false` opts a function out.** Set `inline: false` 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.
|
|
9
|
+
- **Run-level `inline` is separate** and only controls whether the *whole run* executes in-process without queue infrastructure (set automatically when there is no `queueService`, or via `startWorkflow(..., { inline: true })`). It governs sleep handling, not per-step dispatch.
|
|
10
|
+
|
|
11
|
+
The rule (`dispatchStep`):
|
|
12
|
+
|
|
13
|
+
| Function `inline` | `queueService` present? | Result |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| default / `true` | any | **inline** |
|
|
16
|
+
| `false` | yes | **queued** (own worker) |
|
|
17
|
+
| `false` | no | **inline + a `logger.warn`** (misconfiguration: can't dispatch) |
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
// Push this one expensive step onto the queue; every other step stays inline:
|
|
21
|
+
export const renderLargeReport = pikkuSessionlessFunc({
|
|
22
|
+
inline: false, // dispatch via queue instead of running inline
|
|
23
|
+
input: ReportInput,
|
|
24
|
+
output: ReportOutput,
|
|
25
|
+
func: async (services, data) => { /* ... */ },
|
|
26
|
+
})
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`inline: false` requires a `queueService`; without one the step still runs (so the workflow progresses) but emits a `logger.warn` so the misconfiguration is visible.
|
|
30
|
+
|
|
31
|
+
## HTTP workflow wiring (manual)
|
|
32
|
+
|
|
33
|
+
Usually auto-scaffolded via `scaffold.workflow`. To wire by hand:
|
|
34
|
+
|
|
35
|
+
```typescript
|
|
36
|
+
// Start a workflow
|
|
37
|
+
wireHTTP({ method: 'post', route: '/onboard', func: workflowStart('onboardUser') })
|
|
38
|
+
|
|
39
|
+
// Execute workflow steps (called by the orchestrator)
|
|
40
|
+
wireHTTP({ method: 'post', route: '/onboard/run', func: workflow('onboardUser') })
|
|
41
|
+
|
|
42
|
+
// Check workflow status
|
|
43
|
+
wireHTTP({ method: 'get', route: '/onboard/status/:runId', func: workflowStatus('onboardUser') })
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Suspend / resume example
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { z } from 'zod'
|
|
50
|
+
import { pikkuWorkflowFunc } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
51
|
+
|
|
52
|
+
export const approval = pikkuWorkflowFunc({
|
|
53
|
+
description: 'Submit a request and wait for approval',
|
|
54
|
+
input: z.object({ requestId: z.string() }),
|
|
55
|
+
output: z.object({ approved: z.boolean() }),
|
|
56
|
+
func: async (services, data, { workflow }) => {
|
|
57
|
+
await workflow.do('Submit request', 'submitRequest', data)
|
|
58
|
+
await workflow.suspend('Awaiting approval') // pauses here until externally resumed
|
|
59
|
+
const result = await workflow.do('Check result', 'getApprovalResult', data)
|
|
60
|
+
return { approved: result.approved }
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
```
|