@pikku/cli 0.12.55 → 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.
Files changed (125) hide show
  1. package/cli.schema.json +1 -1
  2. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  3. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  4. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  5. package/dist/.pikku/cli/pikku-cli-channel.js +6 -1
  6. package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +1 -1
  7. package/dist/.pikku/cli/pikku-cli-client.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
  9. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
  10. package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.json +14 -0
  11. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  12. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +14 -0
  15. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  16. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  17. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  18. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  19. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  20. package/dist/.pikku/function/pikku-function-types.gen.d.ts +7 -30
  21. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  22. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  23. package/dist/.pikku/function/pikku-functions-meta.gen.json +24 -5
  24. package/dist/.pikku/function/pikku-functions.gen.js +3 -1
  25. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  27. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  28. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  29. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  30. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  31. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  32. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  33. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  34. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  36. package/dist/.pikku/pikku-services.gen.d.ts +4 -2
  37. package/dist/.pikku/pikku-services.gen.js +2 -0
  38. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  39. package/dist/.pikku/pikku-types.gen.js +1 -1
  40. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  42. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  43. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +0 -248
  44. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  45. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  46. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  47. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +1 -0
  48. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  49. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  50. package/dist/.pikku/schemas/register.gen.js +5 -1
  51. package/dist/.pikku/schemas/schemas/FabricAddonVerifyInput.schema.json +1 -0
  52. package/dist/.pikku/schemas/schemas/FabricAddonVerifyOutput.schema.json +1 -0
  53. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  54. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  55. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  56. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  57. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  58. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  59. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  60. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  61. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  62. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  63. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  64. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  65. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  66. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  67. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  68. package/dist/bin/pikku-bin.mjs +2 -2
  69. package/dist/src/deploy/analyzer/analyzer.d.ts +6 -0
  70. package/dist/src/deploy/analyzer/analyzer.js +5 -4
  71. package/dist/src/deploy/build-pipeline.d.ts +5 -1
  72. package/dist/src/deploy/build-pipeline.js +5 -5
  73. package/dist/src/deploy/bundler/bun-bundler.d.ts +14 -0
  74. package/dist/src/deploy/bundler/bun-bundler.js +121 -0
  75. package/dist/src/deploy/bundler/bundler.d.ts +25 -30
  76. package/dist/src/deploy/bundler/bundler.interface.d.ts +54 -0
  77. package/dist/src/deploy/bundler/bundler.interface.js +11 -0
  78. package/dist/src/deploy/bundler/bundler.js +120 -190
  79. package/dist/src/deploy/bundler/dep-extractor.d.ts +11 -3
  80. package/dist/src/deploy/bundler/dep-extractor.js +12 -6
  81. package/dist/src/deploy/bundler/index.d.ts +5 -2
  82. package/dist/src/deploy/bundler/index.js +4 -2
  83. package/dist/src/deploy/bundler/node-bundler.d.ts +13 -0
  84. package/dist/src/deploy/bundler/node-bundler.js +80 -0
  85. package/dist/src/fabric/fabric-commands.d.ts +37 -0
  86. package/dist/src/fabric/fabric-commands.js +8 -0
  87. package/dist/src/fabric/functions/addon-verify.function.d.ts +54 -0
  88. package/dist/src/fabric/functions/addon-verify.function.js +153 -0
  89. package/dist/src/fabric/functions/llm-key.function.js +1 -1
  90. package/dist/src/fabric/functions/publish.function.js +8 -3
  91. package/dist/src/functions/commands/deploy-apply.js +3 -1
  92. package/dist/src/functions/commands/deploy-plan.js +3 -1
  93. package/dist/src/functions/commands/dev.js +11 -45
  94. package/dist/src/functions/db/db-codegen.js +14 -0
  95. package/dist/src/functions/wirings/functions/serialize-function-types.js +6 -29
  96. package/dist/src/server/bun-server-runner.d.ts +17 -0
  97. package/dist/src/server/bun-server-runner.js +25 -0
  98. package/dist/src/server/dev-server-runner.interface.d.ts +31 -0
  99. package/dist/src/server/dev-server-runner.interface.js +11 -0
  100. package/dist/src/server/node-server-runner.d.ts +12 -0
  101. package/dist/src/server/node-server-runner.js +30 -0
  102. package/dist/src/services.js +10 -0
  103. package/dist/src/utils/parse-cli-filters.d.ts +1 -0
  104. package/dist/src/utils/parse-cli-filters.js +1 -0
  105. package/dist/tsconfig.tsbuildinfo +1 -1
  106. package/package.json +3 -3
  107. package/skills/pikku-addon/SKILL.md +25 -117
  108. package/skills/pikku-addon/references/addon-package-manifest.md +63 -0
  109. package/skills/pikku-cli/SKILL.md +7 -93
  110. package/skills/pikku-cli/references/complete-example.md +82 -0
  111. package/skills/pikku-concepts/SKILL.md +17 -69
  112. package/skills/pikku-concepts/references/concept-mapping.md +37 -13
  113. package/skills/pikku-concepts/references/packages.md +29 -0
  114. package/skills/pikku-http/SKILL.md +14 -105
  115. package/skills/pikku-http/references/http-options.md +57 -0
  116. package/skills/pikku-middleware/SKILL.md +11 -68
  117. package/skills/pikku-middleware/references/middleware-patterns.md +61 -0
  118. package/skills/pikku-realtime/SKILL.md +56 -105
  119. package/skills/pikku-realtime/references/other-routes.md +23 -0
  120. package/skills/pikku-services/SKILL.md +25 -108
  121. package/skills/pikku-services/references/audit-wire-service.md +34 -0
  122. package/skills/pikku-testing/SKILL.md +51 -359
  123. package/skills/pikku-testing/references/cucumber-bdd-testing.md +176 -0
  124. package/skills/pikku-workflow/SKILL.md +93 -259
  125. package/skills/pikku-workflow/references/workflow-reference.md +63 -0
@@ -16,16 +16,13 @@ Use this skill as an execution checklist, not reference material.
16
16
  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.
17
17
  5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
18
18
 
19
- Most realtime UI is just typed pub/sub: a server pushes `todo-created`, the
20
- client renders it. Pikku's realtime feature ships exactly that, two ways:
19
+ Most realtime UI is just typed pub/sub: a server pushes `todo-created`, the client
20
+ renders it. Pikku ships exactly that, two ways — both use the same `EventHubService`
21
+ and the same publish call, so choose by transport, not by code shape:
21
22
 
22
23
  - **WebSocket** at `/events` — one connection, many topic subscriptions.
23
- - **SSE** at `GET /events/:topic` — one connection per topic, auto-cleanup
24
- on disconnect. Good for environments where WebSocket is blocked or for
25
- trivially streaming one topic.
26
-
27
- Both transports use the same `EventHubService` and the same publish call.
28
- Choose by transport, not by code shape.
24
+ - **SSE** at `GET /events/:topic` — one connection per topic, auto-cleanup on
25
+ disconnect. Good when WebSocket is blocked or for trivially streaming one topic.
29
26
 
30
27
  ## 1. Declare your topics
31
28
 
@@ -41,28 +38,24 @@ export type EventHubTopics = {
41
38
  }
42
39
  ```
43
40
 
44
- Reference it in `application-types.d.ts`:
41
+ Reference it in `application-types.d.ts` and instantiate it in `services.ts`:
45
42
 
46
43
  ```ts
44
+ // application-types.d.ts
47
45
  import type { EventHubService } from '@pikku/core/channel'
48
46
  import type { EventHubTopics } from './eventhub-topics.js'
49
47
 
50
48
  export interface SingletonServices extends CoreSingletonServices<Config> {
51
49
  eventHub?: EventHubService<EventHubTopics>
52
- // ...
53
50
  }
54
- ```
55
-
56
- And instantiate it in `services.ts`:
57
51
 
58
- ```ts
52
+ // services.ts
59
53
  import { LocalEventHubService } from '@pikku/core/channel'
60
- // ...
61
54
  const eventHub = new LocalEventHubService<EventHubTopics>()
62
55
  ```
63
56
 
64
- (For multi-instance deployments use `CloudflareEventHubService` /
65
- `LambdaEventHubService` / `UWSEventHubService` instead — same interface.)
57
+ For multi-instance deployments use `CloudflareEventHubService` /
58
+ `LambdaEventHubService` / `UWSEventHubService` instead — same interface.
66
59
 
67
60
  ## 2. Enable the server side
68
61
 
@@ -71,15 +64,13 @@ yarn pikku enable events # auth required by default
71
64
  yarn pikku enable events --noAuth # public events
72
65
  ```
73
66
 
74
- This sets `scaffold.events` in `pikku.config.json`. The next `pikku all`
75
- generates `events.gen.ts` in your scaffold dir, which wires:
67
+ This sets `scaffold.events` in `pikku.config.json`. The next `pikku all` generates
68
+ `events.gen.ts` in your scaffold dir, wiring (using whatever `eventHub` is in your
69
+ singletons — you write neither by hand):
76
70
 
77
71
  - A WebSocket channel at `/events` handling `{action: 'subscribe' | 'unsubscribe', topic}` messages.
78
72
  - An SSE handler at `GET /events/:topic`.
79
73
 
80
- You don't write either by hand. They use whatever `eventHub` service is
81
- in your singletons.
82
-
83
74
  ## 3. Generate the typed client
84
75
 
85
76
  Add to `pikku.config.json`:
@@ -87,7 +78,6 @@ Add to `pikku.config.json`:
87
78
  ```jsonc
88
79
  {
89
80
  "clientFiles": {
90
- // ...
91
81
  "realtimeFile": "packages/sdk/src/pikku/realtime.gen.ts",
92
82
  // Optional: full type inference for subscribe/unsubscribe
93
83
  "realtimeEventHubTopicsImport": "../../../functions/types/eventhub-topics.js#EventHubTopics",
@@ -95,8 +85,8 @@ Add to `pikku.config.json`:
95
85
  }
96
86
  ```
97
87
 
98
- Run `pikku all` (or `pikku realtime` to regenerate just this file). The
99
- generated file exports two surfaces:
88
+ Run `pikku all` (or `pikku realtime` to regenerate just this file). The generated
89
+ file exports two surfaces:
100
90
 
101
91
  ```ts
102
92
  export class PikkuRealtime {
@@ -112,13 +102,15 @@ export function subscribeToTopicViaSSE<K extends keyof EventHubTopics>(
112
102
  ```
113
103
 
114
104
  Without `realtimeEventHubTopicsImport`, the client falls back to
115
- `Record<string, unknown>` — usable but untyped. Set the import for full
116
- typed subscribe/unsubscribe.
105
+ `Record<string, unknown>` — usable but untyped. Set the import for full typed
106
+ subscribe/unsubscribe.
117
107
 
118
108
  ## 4. Publish events from a function
119
109
 
120
- The `/events` channel listens for client subscriptions; the eventHub fans
121
- out publishes. Functions publish like this:
110
+ The `/events` channel listens for client subscriptions; the eventHub fans out
111
+ publishes. Envelope the payload with `topic` so the client dispatcher works; the
112
+ `null` channelId means "broadcast to all subscribers" (pass a specific channel id
113
+ to exclude/include a single connection):
122
114
 
123
115
  ```ts
124
116
  import { pikkuFunc } from '#pikku'
@@ -128,47 +120,35 @@ export const createTodo = pikkuFunc({
128
120
  output: CreateTodoOutput,
129
121
  func: async ({ kysely, eventHub }, data) => {
130
122
  const todo = await kysely
131
- .insertInto('todos')
132
- .values(data)
133
- .returningAll()
123
+ .insertInto('todos').values(data).returningAll()
134
124
  .executeTakeFirstOrThrow()
135
125
 
136
126
  if (eventHub) {
137
- // Envelope the payload with `topic` so the client dispatcher works.
138
127
  await eventHub.publish('todo-created', null, {
139
128
  topic: 'todo-created',
140
129
  data: { todo },
141
130
  })
142
131
  }
143
-
144
132
  return { id: todo.id }
145
133
  },
146
134
  })
147
135
  ```
148
136
 
149
- The `null` channelId means "broadcast to all subscribers." Pass a
150
- specific channel id to exclude/include a single connection.
151
-
152
137
  A thin helper removes the duplication:
153
138
 
154
139
  ```ts
155
140
  async function publishEvent<K extends keyof EventHubTopics>(
156
- hub: EventHubService<EventHubTopics>,
157
- topic: K,
158
- data: EventHubTopics[K]
141
+ hub: EventHubService<EventHubTopics>, topic: K, data: EventHubTopics[K]
159
142
  ) {
160
143
  return hub.publish(topic, null, { topic, data })
161
144
  }
162
-
163
- // usage:
164
- await publishEvent(eventHub, 'todo-created', { todo })
145
+ // usage: await publishEvent(eventHub, 'todo-created', { todo })
165
146
  ```
166
147
 
167
148
  ## 5. Wire it up — share fetch with PikkuRPC
168
149
 
169
- `PikkuRealtime` mirrors `PikkuRPC`: it wraps the same `PikkuFetch`, so
170
- server URL + auth are configured **once** and shared across HTTP, RPC,
171
- and realtime transports.
150
+ `PikkuRealtime` mirrors `PikkuRPC`: it wraps the same `PikkuFetch`, so server URL +
151
+ auth are configured **once** and shared across HTTP, RPC, and realtime transports.
172
152
 
173
153
  ```tsx
174
154
  import { createPikku, PikkuProvider } from '@pikku/react'
@@ -185,9 +165,7 @@ const pikku = createPikku(
185
165
  // pikku.fetch / pikku.rpc / pikku.realtime — all share the same fetch.
186
166
 
187
167
  createRoot(document.getElementById('root')!).render(
188
- <PikkuProvider pikku={pikku}>
189
- <App />
190
- </PikkuProvider>
168
+ <PikkuProvider pikku={pikku}><App /></PikkuProvider>
191
169
  )
192
170
  ```
193
171
 
@@ -200,64 +178,38 @@ realtime.setPikkuFetch(pikku.fetch) // inherits serverUrl + auth
200
178
 
201
179
  ## 6. Subscribe from React
202
180
 
181
+ Subscribe inside `useEffect` (never the render path, or you create a subscription
182
+ per render). `subscribe` returns an unsubscribe function; SSE's `subscribeToTopic`
183
+ returns a handle with `close()`:
184
+
203
185
  ```tsx
204
186
  import { useEffect, useState } from 'react'
205
187
 
206
188
  function TodoList() {
207
- const { realtime } = usePikku() // assuming you expose a hook over your context
189
+ const { realtime } = usePikku() // a hook over your context
208
190
  const [todos, setTodos] = useState<Todo[]>([])
209
191
 
210
192
  useEffect(() => {
211
- const off = realtime.subscribe('todo-created', ({ todo }) => {
212
- setTodos((prev) => [...prev, todo])
213
- })
193
+ // WebSocket multi-topic:
194
+ const off = realtime.subscribe('todo-created', ({ todo }) =>
195
+ setTodos((prev) => [...prev, todo]))
214
196
  return off
197
+
198
+ // Single-topic SSE (auto-cleanup on close) instead:
199
+ // const sub = realtime.subscribeToTopic('todo-created', ({ todo }) =>
200
+ // setTodos((prev) => [...prev, todo]))
201
+ // return () => sub.close()
215
202
  }, [realtime])
216
203
 
217
- return (
218
- <ul>
219
- {todos.map((t) => (
220
- <li key={t.id}>{t.title}</li>
221
- ))}
222
- </ul>
223
- )
204
+ return <ul>{todos.map((t) => <li key={t.id}>{t.title}</li>)}</ul>
224
205
  }
225
206
  ```
226
207
 
227
- Single-topic SSE (auto-cleanup on close):
228
-
229
- ```tsx
230
- useEffect(() => {
231
- const sub = realtime.subscribeToTopic('todo-created', ({ todo }) => {
232
- setTodos((prev) => [...prev, todo])
233
- })
234
- return () => sub.close()
235
- }, [realtime])
236
- ```
237
-
238
- ## Subscribing to other SSE / WebSocket routes
239
-
240
- Same client also handles generic SSE + channel routes — use the path,
241
- the base URL is inherited from PikkuFetch:
242
-
243
- ```ts
244
- // Any sse: true HTTP route
245
- const sub = realtime.subscribeToSSE<{ progress: number }>(
246
- `/workflow-run/${runId}/stream`,
247
- (event) => setProgress(event.progress)
248
- )
249
- // later: sub.close()
250
-
251
- // Any wireChannel — open a raw socket, wrap in PikkuWebSocket for typed I/O
252
- const ws = realtime.connectToChannel('/ws/kanban')
253
- const typed = new PikkuWebSocket<'kanban-live'>(ws)
254
- typed.getRoute('command').subscribe('message', (data) => {
255
- /* ... */
256
- })
257
- ```
208
+ ## Other SSE / WebSocket routes
258
209
 
259
- Discover what's available with `pikku meta clients --json` `channels`
260
- and any HTTP `sse: true` routes are listed there.
210
+ The same client also subscribes to generic `sse: true` routes and raw `wireChannel`
211
+ sockets (`subscribeToSSE`, `connectToChannel`). See
212
+ [references/other-routes.md](references/other-routes.md).
261
213
 
262
214
  ## When to pick which transport
263
215
 
@@ -268,18 +220,17 @@ and any HTTP `sse: true` routes are listed there.
268
220
  | Bidirectional (client also sends messages) | **PikkuRealtime** |
269
221
  | WebSockets blocked by infra | **subscribeToTopicViaSSE** |
270
222
 
271
- Both auto-clean on the server (the eventHub's `onChannelClosed` hook
272
- unsubscribes all topics for the dead channel id). Don't write manual
273
- cleanup unless you're unsubscribing partway through a session.
223
+ Both auto-clean on the server (the eventHub's `onChannelClosed` hook unsubscribes
224
+ all topics for the dead channel id). Don't write manual cleanup unless you're
225
+ unsubscribing partway through a session.
274
226
 
275
227
  ## What NOT to do
276
228
 
277
- - Don't call `eventHub.publish(topic, ..., rawData)` without the
278
- `{topic, data}` envelope — clients use `topic` to dispatch handlers.
279
- - Don't create your own `/events` channel by hand — `pikku enable events`
280
- already does it correctly with disconnect cleanup.
281
- - Don't subscribe inside the render path — use `useEffect`. Otherwise
282
- you'll create a subscription per render.
283
- - Don't subscribe to topics that don't exist in `EventHubTopics`. The
284
- generated client's types prevent it; if you find yourself reaching for
285
- `as any` to subscribe to a string, declare the topic first.
229
+ - Don't call `eventHub.publish(topic, ..., rawData)` without the `{topic, data}`
230
+ envelope — clients use `topic` to dispatch handlers.
231
+ - Don't create your own `/events` channel by hand — `pikku enable events` already
232
+ does it correctly with disconnect cleanup.
233
+ - Don't subscribe inside the render path — use `useEffect`.
234
+ - Don't subscribe to topics that don't exist in `EventHubTopics`. The generated
235
+ client's types prevent it; if you reach for `as any` to subscribe to a string,
236
+ declare the topic first.
@@ -0,0 +1,23 @@
1
+ # Subscribing to other SSE / WebSocket routes
2
+
3
+ The same `PikkuRealtime` client also handles generic SSE + channel routes (not just
4
+ `/events` topics). Use the path; the base URL is inherited from `PikkuFetch`.
5
+
6
+ ```ts
7
+ // Any `sse: true` HTTP route
8
+ const sub = realtime.subscribeToSSE<{ progress: number }>(
9
+ `/workflow-run/${runId}/stream`,
10
+ (event) => setProgress(event.progress)
11
+ )
12
+ // later: sub.close()
13
+
14
+ // Any wireChannel — open a raw socket, wrap in PikkuWebSocket for typed I/O
15
+ const ws = realtime.connectToChannel('/ws/kanban')
16
+ const typed = new PikkuWebSocket<'kanban-live'>(ws)
17
+ typed.getRoute('command').subscribe('message', (data) => {
18
+ /* ... */
19
+ })
20
+ ```
21
+
22
+ Discover what's available with `pikku meta clients --json` — `channels` and any HTTP
23
+ `sse: true` routes are listed there.
@@ -18,7 +18,7 @@ Use this skill as an execution checklist, not reference material.
18
18
  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.
19
19
  5. If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
20
20
 
21
- Pikku uses factory functions for dependency injection. Singleton services are created once at startup. Wire services are created fresh per request/job/command.
21
+ Pikku uses factory functions for dependency injection. Singleton services are created once at startup; wire services are created fresh per request/job/command. See `pikku-concepts` for the core mental model.
22
22
 
23
23
  ## Before You Start
24
24
 
@@ -27,47 +27,44 @@ pikku info functions --verbose # See which services existing functions use
27
27
  pikku info tags --verbose # Understand project organization
28
28
  ```
29
29
 
30
- See `pikku-concepts` for the core mental model.
31
-
32
30
  ## API Reference
33
31
 
34
- ### `pikkuServices(factory)`
35
-
36
- Create singleton services — instantiated once at server startup.
32
+ ### `pikkuServices(factory)` — singleton services (created once at startup)
37
33
 
38
34
  ```typescript
39
35
  import { pikkuServices } from '#pikku'
36
+ import { ConsoleLogger } from '@pikku/core/services'
37
+ import { JoseJWTService } from '@pikku/jose'
40
38
 
41
- const createSingletonServices = pikkuServices(
39
+ export const createSingletonServices = pikkuServices(
42
40
  async (config, existingServices?) => {
43
41
  // config: your CoreConfig object
44
42
  // existingServices: optional, for chaining factories
45
- return {
46
- config,
47
- logger: Logger,
48
- jwt: JWTService,
49
- database: DatabasePool,
50
- // ...any custom services
51
- }
43
+ const logger = new ConsoleLogger()
44
+ const database = new DatabasePool(config.database)
45
+ await database.connect()
46
+ const jwt = new JoseJWTService(
47
+ async () => [{ id: 'my-key', value: config.jwtSecret }],
48
+ logger
49
+ )
50
+ return { config, logger, database, jwt, books: new BookService() }
52
51
  }
53
52
  )
54
53
  ```
55
54
 
56
- ### `pikkuWireServices(factory)`
57
-
58
- Create per-request services — fresh instance for each HTTP request, queue job, CLI command, etc.
55
+ ### `pikkuWireServices(factory)` — per-request services (fresh per HTTP request, queue job, CLI command, etc.)
59
56
 
60
57
  ```typescript
61
58
  import { pikkuWireServices } from '#pikku'
62
59
 
63
- const createWireServices = pikkuWireServices(
60
+ export const createWireServices = pikkuWireServices(
64
61
  async (singletonServices, wire) => {
65
62
  // singletonServices: all singleton services
66
63
  // wire: transport context (session, channel, etc.)
67
64
  // Pikku merges these with singleton services automatically
68
65
  return {
69
- userSession: UserSessionService,
70
- dbTransaction: DatabaseTransaction,
66
+ userSession: createUserSessionService(wire),
67
+ dbTransaction: new DatabaseTransaction(singletonServices.database),
71
68
  }
72
69
  }
73
70
  )
@@ -75,10 +72,9 @@ const createWireServices = pikkuWireServices(
75
72
 
76
73
  ### Auto-Generated Service Manifest
77
74
 
78
- After `npx pikku prebuild`, Pikku generates a manifest of which services are actually used:
75
+ After `npx pikku prebuild`, Pikku generates `.pikku/pikku-services.gen.ts`, a manifest of which services are actually used by wired functions:
79
76
 
80
77
  ```typescript
81
- // .pikku/pikku-services.gen.ts (auto-generated)
82
78
  export const requiredSingletonServices = {
83
79
  database: true, // used by getUser, deleteUser
84
80
  audit: true, // used by deleteUser
@@ -95,47 +91,9 @@ export type RequiredSingletonServices = Pick<
95
91
 
96
92
  ## Usage Patterns
97
93
 
98
- ### Basic Singleton Services
99
-
100
- ```typescript
101
- const createSingletonServices = pikkuServices(
102
- async (config, existingServices) => {
103
- const logger = new ConsoleLogger()
104
- const database = new DatabasePool(config.database)
105
- await database.connect()
106
-
107
- const jwt = new JoseJWTService(
108
- async () => [{ id: 'my-key', value: JWT_SECRET }],
109
- logger
110
- )
111
-
112
- return {
113
- config,
114
- logger,
115
- database,
116
- jwt,
117
- books: new BookService(),
118
- }
119
- }
120
- )
121
- ```
122
-
123
- ### Per-Request Wire Services
124
-
125
- ```typescript
126
- const createWireServices = pikkuWireServices(
127
- async (singletonServices, wire) => {
128
- return {
129
- userSession: createUserSessionService(wire),
130
- dbTransaction: new DatabaseTransaction(singletonServices.database),
131
- }
132
- }
133
- )
134
- ```
135
-
136
94
  ### Using Services in Functions
137
95
 
138
- **Every service must be declared in `SingletonServices` (or `Services`) in `application-types.d.ts`.** Never access a service via a body-level cast (`services as typeof services & { myService: MyService }`) — that means the type is missing. Add the import and the field to `SingletonServices`, then destructure inline in the function signature. The inspector emits `SERVICES_NOT_DESTRUCTURED` and tree-shaking breaks when the first param is a plain identifier rather than an object pattern.
96
+ **Every service must be declared in `SingletonServices` (or `Services`) in `application-types.d.ts`.** Never access a service via a body-level cast (`services as typeof services & { myService: MyService }`) — that means the type is missing. Add the import and the field to `SingletonServices`, then destructure inline in the function signature. The inspector emits `SERVICES_NOT_DESTRUCTURED` and tree-shaking breaks when the first param is a plain identifier rather than an object pattern. Never `new` a service inside a function — services arrive only via injection.
139
97
 
140
98
  ```typescript
141
99
  // ✅ Correct — inline destructure, no cast
@@ -143,8 +101,7 @@ const getUser = pikkuFunc({
143
101
  title: 'Get User',
144
102
  func: async ({ db, logger, jwt }, { userId }) => {
145
103
  logger.info('Fetching user', { userId })
146
- const user = await db.getUser(userId)
147
- return { user }
104
+ return { user: await db.getUser(userId) }
148
105
  },
149
106
  })
150
107
 
@@ -159,7 +116,7 @@ const getUser = pikkuFunc({
159
116
 
160
117
  ### Dynamic Import Optimization
161
118
 
162
- Use the generated manifest to conditionally import heavy dependencies:
119
+ Use the generated manifest to conditionally import heavy dependencies — only the services actually wired get instantiated:
163
120
 
164
121
  ```typescript
165
122
  import { requiredSingletonServices } from '.pikku/pikku-services.gen.js'
@@ -184,38 +141,7 @@ const createSingletonServices = pikkuServices(async (config) => {
184
141
 
185
142
  ### Audit Wire Service
186
143
 
187
- `createInvocationAudit` creates a per-request `InvocationAuditLog` that buffers audit events in memory and flushes them as a batch when the function-runner calls `closeWireServices` at the end of the request. If `singletonServices.audit` is not configured (local dev without Fabric), it returns a no-op `DisabledInvocationAudit` no crash, events are silently dropped.
188
-
189
- Pair with `createAuditedKysely` to auto-capture every Kysely query as an audit event.
190
-
191
- ```typescript
192
- // services.ts
193
- import { createInvocationAudit } from '@pikku/core/services'
194
- import { createAuditedKysely } from '@pikku/kysely'
195
-
196
- export const createWireServices = pikkuWireServices(async (singletonServices, wire) => {
197
- const audit = createInvocationAudit(singletonServices.audit, wire)
198
- const kysely = singletonServices.kysely
199
- ? createAuditedKysely(singletonServices.kysely, { audit })
200
- : undefined
201
- return { audit, ...(kysely ? { kysely } : {}) }
202
- })
203
- ```
204
-
205
- The `audit` wire service is typed as `AuditLog` (from `@pikku/core`). Functions that emit custom events use it directly:
206
-
207
- ```typescript
208
- const deleteUser = pikkuFunc({
209
- func: async ({ audit }, { userId }) => {
210
- await audit.audit({ type: 'user.deleted', actor_user_id: userId })
211
- // ...
212
- },
213
- })
214
- ```
215
-
216
- `closeWireServices` (called automatically by the function-runner) invokes `audit.close()` → `singletonServices.audit.write(batch)` → platform-specific flush (e.g. CF Queue, libsql INSERT). No manual flushing needed.
217
-
218
- > **Fabric note:** Fabric provisions the audit queue and consumer worker automatically. The audit table schema is in `db/sqlite/0003-audit.sql` (starter-template). Run `pikku fabric validate` to confirm the migration is in place.
144
+ `createInvocationAudit` + `createAuditedKysely` add per-request audit buffering that flushes on request close (no-op if `audit` is unconfigured). For the full pattern, no-op behavior, custom-event usage, and Fabric notes, read `references/audit-wire-service.md`.
219
145
 
220
146
  ### Built-in Services
221
147
 
@@ -240,22 +166,14 @@ import { JoseJWTService } from '@pikku/jose'
240
166
  // Custom service
241
167
  class TodoStore {
242
168
  private todos: Map<string, Todo> = new Map()
243
-
244
169
  async create(title: string, priority: string) {
245
170
  const todo = { id: crypto.randomUUID(), title, priority, completed: false }
246
171
  this.todos.set(todo.id, todo)
247
172
  return todo
248
173
  }
249
-
250
- async get(id: string) {
251
- return this.todos.get(id)
252
- }
253
- async list() {
254
- return [...this.todos.values()]
255
- }
256
- async delete(id: string) {
257
- this.todos.delete(id)
258
- }
174
+ async get(id: string) { return this.todos.get(id) }
175
+ async list() { return [...this.todos.values()] }
176
+ async delete(id: string) { this.todos.delete(id) }
259
177
  }
260
178
 
261
179
  export const createSingletonServices = pikkuServices(async (config) => {
@@ -264,7 +182,6 @@ export const createSingletonServices = pikkuServices(async (config) => {
264
182
  async () => [{ id: 'my-key', value: config.jwtSecret }],
265
183
  logger
266
184
  )
267
-
268
185
  return {
269
186
  config,
270
187
  logger,
@@ -0,0 +1,34 @@
1
+ # Audit Wire Service
2
+
3
+ `createInvocationAudit` creates a per-request `InvocationAuditLog` that buffers audit events in memory and flushes them as a batch when the function-runner calls `closeWireServices` at the end of the request. If `singletonServices.audit` is not configured (local dev without Fabric), it returns a no-op `DisabledInvocationAudit` — no crash, events are silently dropped.
4
+
5
+ Pair with `createAuditedKysely` to auto-capture every Kysely query as an audit event.
6
+
7
+ ```typescript
8
+ // services.ts
9
+ import { createInvocationAudit } from '@pikku/core/services'
10
+ import { createAuditedKysely } from '@pikku/kysely'
11
+
12
+ export const createWireServices = pikkuWireServices(async (singletonServices, wire) => {
13
+ const audit = createInvocationAudit(singletonServices.audit, wire)
14
+ const kysely = singletonServices.kysely
15
+ ? createAuditedKysely(singletonServices.kysely, { audit })
16
+ : undefined
17
+ return { audit, ...(kysely ? { kysely } : {}) }
18
+ })
19
+ ```
20
+
21
+ The `audit` wire service is typed as `AuditLog` (from `@pikku/core`). Functions that emit custom events use it directly:
22
+
23
+ ```typescript
24
+ const deleteUser = pikkuFunc({
25
+ func: async ({ audit }, { userId }) => {
26
+ await audit.audit({ type: 'user.deleted', actor_user_id: userId })
27
+ // ...
28
+ },
29
+ })
30
+ ```
31
+
32
+ `closeWireServices` (called automatically by the function-runner) invokes `audit.close()` → `singletonServices.audit.write(batch)` → platform-specific flush (e.g. CF Queue, libsql INSERT). No manual flushing needed.
33
+
34
+ > **Fabric note:** Fabric provisions the audit queue and consumer worker automatically. The audit table schema is in `db/sqlite/0003-audit.sql` (starter-template). Run `pikku fabric validate` to confirm the migration is in place.