@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.
- package/cli.schema.json +1 -1
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.js +6 -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 +14 -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 +7 -30
- 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 +24 -5
- 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 +1 -0
- 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 +5 -1
- 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/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/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/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/fabric/fabric-commands.d.ts +37 -0
- package/dist/src/fabric/fabric-commands.js +8 -0
- 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/llm-key.function.js +1 -1
- package/dist/src/fabric/functions/publish.function.js +8 -3
- package/dist/src/functions/commands/deploy-apply.js +3 -1
- package/dist/src/functions/commands/deploy-plan.js +3 -1
- package/dist/src/functions/commands/dev.js +11 -45
- package/dist/src/functions/db/db-codegen.js +14 -0
- package/dist/src/functions/wirings/functions/serialize-function-types.js +6 -29
- 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.js +10 -0
- package/dist/src/utils/parse-cli-filters.d.ts +1 -0
- package/dist/src/utils/parse-cli-filters.js +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- 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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
52
|
+
// services.ts
|
|
59
53
|
import { LocalEventHubService } from '@pikku/core/channel'
|
|
60
|
-
// ...
|
|
61
54
|
const eventHub = new LocalEventHubService<EventHubTopics>()
|
|
62
55
|
```
|
|
63
56
|
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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() //
|
|
189
|
+
const { realtime } = usePikku() // a hook over your context
|
|
208
190
|
const [todos, setTodos] = useState<Todo[]>([])
|
|
209
191
|
|
|
210
192
|
useEffect(() => {
|
|
211
|
-
|
|
212
|
-
|
|
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
|
-
|
|
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
|
-
|
|
260
|
-
|
|
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
|
-
|
|
273
|
-
|
|
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
|
-
|
|
279
|
-
- Don't create your own `/events` channel by hand — `pikku enable events`
|
|
280
|
-
|
|
281
|
-
- Don't subscribe inside the render path — use `useEffect`.
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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:
|
|
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
|
-
|
|
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`
|
|
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
|
|
251
|
-
|
|
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.
|