@pikku/cli 0.12.27 → 0.12.29

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 (89) hide show
  1. package/cli.schema.json +1 -1
  2. package/console-app/assets/index-ClGe-ul_.js +229 -0
  3. package/console-app/assets/{index-CQ29NRyR.css → index-DwUzVI5k.css} +1 -1
  4. package/console-app/index.html +2 -2
  5. package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
  6. package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
  7. package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
  8. package/dist/.pikku/cli/pikku-cli-channel.js +1 -1
  9. package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
  10. package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
  11. package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
  12. package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
  13. package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
  14. package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
  15. package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
  16. package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
  17. package/dist/.pikku/function/pikku-function-types.gen.d.ts +1 -1
  18. package/dist/.pikku/function/pikku-function-types.gen.js +1 -1
  19. package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
  20. package/dist/.pikku/function/pikku-functions-meta.gen.json +183 -161
  21. package/dist/.pikku/function/pikku-functions.gen.js +5 -1
  22. package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
  23. package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
  24. package/dist/.pikku/http/pikku-http-wirings-meta.gen.js +1 -1
  25. package/dist/.pikku/http/pikku-http-wirings.gen.d.ts +1 -1
  26. package/dist/.pikku/http/pikku-http-wirings.gen.js +1 -1
  27. package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
  28. package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
  29. package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
  30. package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
  31. package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
  32. package/dist/.pikku/pikku-meta-service.gen.js +1 -1
  33. package/dist/.pikku/pikku-services.gen.d.ts +1 -1
  34. package/dist/.pikku/pikku-types.gen.d.ts +1 -1
  35. package/dist/.pikku/pikku-types.gen.js +1 -1
  36. package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
  37. package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
  38. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
  39. package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.json +8 -0
  40. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
  41. package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
  42. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
  43. package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +6 -5
  44. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
  45. package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
  46. package/dist/.pikku/schemas/register.gen.js +9 -9
  47. package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
  48. package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +1 -1
  49. package/dist/.pikku/secrets/pikku-secret-types.gen.js +1 -1
  50. package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
  51. package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
  52. package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
  53. package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
  54. package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +1 -1
  55. package/dist/.pikku/variables/pikku-variable-types.gen.js +1 -1
  56. package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
  57. package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
  58. package/dist/.pikku/workflow/meta/allWorkflow.gen.json +15 -3
  59. package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +1 -1
  60. package/dist/.pikku/workflow/pikku-workflow-types.gen.js +1 -1
  61. package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
  62. package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
  63. package/dist/bin/pikku-bin.mjs +2 -2
  64. package/dist/src/fabric/functions/validate-core.js +65 -0
  65. package/dist/src/fabric/functions/validate.function.js +23 -7
  66. package/dist/src/functions/commands/dev.js +8 -8
  67. package/dist/src/functions/commands/tests-coverage.js +4 -2
  68. package/dist/src/functions/db/annotation-parser.d.ts +7 -7
  69. package/dist/src/functions/db/annotation-parser.js +61 -11
  70. package/dist/src/functions/db/db-codegen.d.ts +4 -0
  71. package/dist/src/functions/db/db-codegen.js +117 -15
  72. package/dist/src/functions/db/local-db.d.ts +14 -1
  73. package/dist/src/functions/db/local-db.js +151 -37
  74. package/dist/src/functions/db/postgres/postgres-introspector.d.ts +8 -2
  75. package/dist/src/functions/db/postgres/postgres-introspector.js +26 -14
  76. package/dist/src/functions/wirings/auth/pikku-command-auth.d.ts +1 -0
  77. package/dist/src/functions/wirings/auth/pikku-command-auth.js +22 -0
  78. package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +1 -0
  79. package/dist/src/functions/wirings/auth/serialize-auth-gen.js +115 -0
  80. package/dist/src/functions/workflows/all.workflow.js +1 -0
  81. package/dist/src/scaffold/rpc-remote.gen.js +1 -1
  82. package/dist/src/utils/pikku-cli-config.js +3 -0
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/package.json +7 -4
  85. package/skills/pikku-auth-js/SKILL.md +137 -117
  86. package/skills/pikku-middleware/SKILL.md +2 -2
  87. package/skills/pikku-services/SKILL.md +44 -7
  88. package/skills/pikku-workflow/SKILL.md +22 -0
  89. package/console-app/assets/index-BERGDBO9.js +0 -228
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/cli",
3
- "version": "0.12.27",
3
+ "version": "0.12.29",
4
4
  "author": "yasser.fadl@gmail.com",
5
5
  "license": "BUSL-1.1",
6
6
  "imports": {
@@ -26,11 +26,11 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "@openapi-contrib/json-schema-to-openapi-schema": "^4.3.1",
29
- "@pikku/core": "^0.12.26",
29
+ "@pikku/core": "^0.12.27",
30
30
  "@pikku/deploy-cloudflare": "^0.12.3",
31
31
  "@pikku/fetch": "^0.12.2",
32
- "@pikku/inspector": "^0.12.13",
33
- "@pikku/kysely": "^0.12.13",
32
+ "@pikku/inspector": "^0.12.15",
33
+ "@pikku/kysely": "^0.12.14",
34
34
  "@pikku/kysely-node-sqlite": "^0.12.1",
35
35
  "@pikku/node-http-server": "^0.12.1",
36
36
  "@pikku/openapi-parser": "^0.12.10",
@@ -67,5 +67,8 @@
67
67
  ],
68
68
  "engines": {
69
69
  "node": ">=18"
70
+ },
71
+ "exports": {
72
+ ".": "./dist/bin/pikku.js"
70
73
  }
71
74
  }
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: pikku-auth-js
3
- description: 'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthRoutes, authJsSession middleware, Credentials provider, JWT callbacks, and session mapping.
4
- TRIGGER when: code uses createAuthRoutes, authJsSession, createAuthHandler, user asks about Auth.js, NextAuth, OAuth providers, login/logout, or @pikku/auth-js.
3
+ description: 'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers wireAuth, authJsSession middleware, OAuth providers, Credentials provider, JWT callbacks, and session mapping.
4
+ TRIGGER when: code uses wireAuth, createAuthRoutes, authJsSession, createAuthHandler, user asks about Auth.js, NextAuth, OAuth providers, login/logout, or @pikku/auth-js.
5
5
  TRIGGER when: user asks about ANY form of authentication, login, logout, sessions, or user identity — always answer with this skill.
6
6
  DO NOT TRIGGER when: user asks about JWT middleware (use pikku-security) or custom session services (use pikku-services).'
7
7
  ---
@@ -45,101 +45,62 @@ yarn add @pikku/auth-js @auth/core
45
45
 
46
46
  Auth.js in Pikku has two independent concerns:
47
47
 
48
- 1. **Route wiring** (`createAuthRoutes`) — mounts the Auth.js signin/signout/callback endpoints into Pikku's HTTP router.
48
+ 1. **Route wiring** (`wireAuth`) — mounts the Auth.js signin/signout/callback endpoints into Pikku's HTTP router. The CLI generates `auth.gen.ts` with provider imports, secret wires, and route setup.
49
49
  2. **Session middleware** (`authJsSession`) — reads the Auth.js JWT cookie on every request and populates the Pikku session object.
50
50
 
51
- Both must be present and must share the same `secret`.
51
+ Both must be present and must share the same `AUTH_SECRET`.
52
52
 
53
53
  ---
54
54
 
55
- ## Standard Setup (Credentials Provider)
55
+ ## Standard Setup (OAuth Providers)
56
56
 
57
57
  ### 1. Auth wiring — `wirings/auth.wiring.ts`
58
58
 
59
- ```typescript
60
- import Credentials from '@auth/core/providers/credentials'
61
- import { createAuthRoutes } from '@pikku/auth-js'
62
- import type { AuthConfigOrFactory } from '@pikku/auth-js'
63
- import { wireHTTPRoutes } from '#pikku'
64
-
65
- const DEV_AUTH_SECRET = 'dev-insecure-auth-secret-change-me'
66
-
67
- const configFactory: AuthConfigOrFactory = async (services) => {
68
- const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
69
-
70
- return {
71
- providers: [
72
- Credentials({
73
- credentials: {
74
- email: { label: 'Email', type: 'email' },
75
- password: { label: 'Password', type: 'password' },
76
- },
77
- async authorize(credentials) {
78
- const email = (credentials?.email as string)?.toLowerCase()
79
- const password = credentials?.password as string
80
- if (!email || !password) return null
81
-
82
- // Look up user and verify password against your DB
83
- const user = await (services as any).kysely
84
- .selectFrom('appUser')
85
- .where('email', '=', email)
86
- .select(['userId', 'role', 'name', 'email', 'passwordHash'])
87
- .executeTakeFirst()
88
-
89
- if (!user || !user.passwordHash) return null
90
- // verifyPassword must be implemented in your app — use bcrypt or argon2.
91
- // See services/password.ts in seminarhof for a reference implementation.
92
- const ok = await verifyPassword(password, user.passwordHash)
93
- if (!ok) return null
94
-
95
- // Return shape is the Auth.js User — add any custom claims here
96
- return { id: user.userId, email: user.email, name: user.name, role: user.role }
97
- },
98
- }),
99
- ],
100
- // Embed custom claims into the JWT
101
- callbacks: {
102
- jwt({ token, user }: any) {
103
- if (user) token.role = user.role
104
- return token
105
- },
106
- session({ session, token }: any) {
107
- if (token) session.role = token.role
108
- return session
109
- },
110
- },
111
- session: { strategy: 'jwt' as const },
112
- secret,
113
- trustHost: true,
114
- basePath: '/auth',
115
- }
116
- }
59
+ Use `wireAuth` to declare which providers you need. The CLI reads this call and generates `auth.gen.ts` with all imports, secret declarations, and route wiring automatically.
117
60
 
118
- wireHTTPRoutes({ routes: { auth: createAuthRoutes(configFactory) as any } })
61
+ ```typescript
62
+ import { wireAuth } from '@pikku/auth-js'
63
+
64
+ wireAuth({
65
+ providers: ['github', 'google'],
66
+ callbacks: {
67
+ signIn: async (rpc, { user, account }) =>
68
+ rpc.invoke('auth:signIn', { userId: user.id, provider: account.provider }),
69
+ redirect: async (rpc, { url, baseUrl }) =>
70
+ rpc.invoke('auth:redirect', { url, baseUrl }),
71
+ },
72
+ })
119
73
  ```
120
74
 
121
75
  **Key points:**
122
- - `configFactory` is async and receives singleton servicesuse it to read `AUTH_SECRET` from the secrets service.
123
- - Always provide a `DEV_AUTH_SECRET` fallback with the same literal in both the wiring and the middleware (see below) so sign and verify agree during local dev without env vars.
124
- - The `jwt` + `session` callbacks are how you embed custom fields (e.g. `role`) into the token. Without them, only the standard Auth.js claims (`sub`, `name`, `email`) are available.
125
- - `trustHost: true` is required in non-Next.js deployments.
126
- - `basePath: '/auth'` must match the path your frontend hits.
76
+ - `providers` must be an array of string literals the CLI inspector reads them statically and generates the `auth.gen.ts` file.
77
+ - `callbacks` are standard Auth.js callbacks but receive `rpc` as a first argument. Use `rpc.invoke('funcName', data)` to delegate to typed pikku functions that have access to services and sessions.
78
+ - The generated `auth.gen.ts` file handles provider imports, Zod schemas, `wireSecret` declarations for all credentials and `AUTH_SECRET`, and the `createAuthRoutes` + `wireHTTPRoutes` call.
79
+ - Do NOT edit `auth.gen.ts` — re-run `pikku auth` (or `pikku all`) to regenerate.
127
80
 
128
- ### 2. Middleware `wirings/middleware.ts`
81
+ **Supported providers:** `github`, `google`, `discord`, `twitter`, `apple`, `facebook`, `linkedin`, `slack`, `spotify`, `twitch`, `gitlab`, `auth0`, `azure-ad`, `okta`
82
+
83
+ ### 2. Configure `pikku.config.json`
84
+
85
+ Add `authFile` pointing to where `auth.gen.ts` should be written (must be within `srcDirectories`):
86
+
87
+ ```json
88
+ {
89
+ "srcDirectories": ["src"],
90
+ "authFile": "src/wirings/auth.gen.ts"
91
+ }
92
+ ```
93
+
94
+ ### 3. Middleware — `wirings/middleware.ts`
129
95
 
130
96
  ```typescript
131
97
  import { addHTTPMiddleware } from '#pikku'
132
98
  import { authJsSession } from '@pikku/auth-js'
133
- import { sessionCookieMiddleware } from '../middleware/session-cookie.js'
134
99
 
135
- // Order is load-bearing: sessionCookieMiddleware MUST run before authJsSession.
136
- // If you have a custom DB session middleware it must go first, otherwise
137
- // authJsSession's post-check throws when the session is set inside next().
138
100
  addHTTPMiddleware('*', [
139
- sessionCookieMiddleware, // custom session (if present) — always first
140
101
  authJsSession({
141
102
  secretId: 'AUTH_SECRET',
142
- mapSession: (claims) => ({ userId: claims.sub as string, role: claims.role as string }),
103
+ mapSession: (claims) => ({ userId: claims.sub as string }),
143
104
  }),
144
105
  ])
145
106
  ```
@@ -163,7 +124,56 @@ cors({
163
124
  })
164
125
  ```
165
126
 
166
- ### 3. Auth-protected functions
127
+ ---
128
+
129
+ ## Credentials Provider (Username/Password)
130
+
131
+ Use `wireAuth` with the `credentials` option. The `authorize` callback receives `rpc` as a first argument so you can delegate to a typed Pikku function:
132
+
133
+ ```typescript
134
+ import { wireAuth } from '@pikku/auth-js'
135
+
136
+ wireAuth({
137
+ credentials: {
138
+ fields: {
139
+ email: { label: 'Email', type: 'email' },
140
+ password: { label: 'Password', type: 'password' },
141
+ },
142
+ authorize: async (rpc, { email, password }) =>
143
+ rpc.invoke('auth:login', { email, password }),
144
+ },
145
+ callbacks: {
146
+ jwt: async (_rpc, { token, user }) => {
147
+ if (user) token.role = user.role
148
+ return token
149
+ },
150
+ },
151
+ })
152
+ ```
153
+
154
+ The `auth:login` function handles password verification and returns the Auth.js `User` shape (with `id` required), or `null` to reject the credentials:
155
+
156
+ ```typescript
157
+ export const login = pikkuSessionlessFunc({
158
+ func: async ({ kysely }, { email, password }) => {
159
+ const user = await kysely
160
+ .selectFrom('appUser')
161
+ .where('email', '=', email.toLowerCase())
162
+ .select(['userId', 'role', 'name', 'email', 'passwordHash'])
163
+ .executeTakeFirst()
164
+
165
+ if (!user || !user.passwordHash) return null
166
+ const ok = await verifyPassword(password, user.passwordHash)
167
+ if (!ok) return null
168
+
169
+ return { id: user.userId, email: user.email, name: user.name, role: user.role }
170
+ },
171
+ })
172
+ ```
173
+
174
+ ---
175
+
176
+ ## Auth-Protected Functions
167
177
 
168
178
  Functions that require a session use `pikkuFunc` — anonymous callers are rejected automatically:
169
179
 
@@ -245,23 +255,58 @@ The Pikku SDK does **not** wrap these — call them directly or use `@auth/core`
245
255
 
246
256
  ## Secret Management
247
257
 
248
- Both the auth config factory and `authJsSession` must use the same `AUTH_SECRET` value they resolve it through the secrets service in both cases.
258
+ All auth secrets are managed through the secrets service. `wireAuth` reads `AUTH_SECRET` and each provider's credentials object at request time using `services.secrets.getSecrets(keys)`.
249
259
 
250
- **In `auth.wiring.ts`** — read via the services factory (falls back to a dev literal if the secret is absent):
251
- ```typescript
252
- const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
253
- ```
260
+ **`AUTH_SECRET`** — a random string used to sign all JWT session tokens. Required.
261
+
262
+ **Provider credentials** each provider (e.g. `GITHUB_OAUTH`, `GOOGLE_OAUTH`) stores a JSON object with `clientId` and `clientSecret`.
263
+
264
+ Both are registered in `auth.gen.ts` via `wireSecret`, which makes them visible in the Pikku console for secret management.
254
265
 
255
266
  **In `middleware.ts`** — use `secretId`, resolved from the secrets service at request time:
256
267
  ```typescript
257
268
  authJsSession({ secretId: 'AUTH_SECRET', mapSession: ... })
258
269
  ```
259
270
 
260
- Do **not** pass `secret: process.env.AUTH_SECRET` or any string value directly to `authJsSession`. The `secret` option no longer exists — `secretId` is the only accepted form. This ensures the secret is always fetched through the secrets service rather than leaked into the process environment.
271
+ Do **not** pass `secret: process.env.AUTH_SECRET` or any string value directly to `authJsSession`. The `secret` option no longer exists — `secretId` is the only accepted form.
261
272
 
262
273
  ---
263
274
 
264
- ## `createAuthRoutes` API
275
+ ## `wireAuth` API
276
+
277
+ ```typescript
278
+ import { wireAuth } from '@pikku/auth-js'
279
+ import type { WireAuthOptions } from '@pikku/auth-js'
280
+
281
+ wireAuth({
282
+ providers: ['github', 'google'], // optional — string literals read by CLI at build time
283
+ credentials: { // optional — Credentials provider (username/password)
284
+ fields: { // optional — defines what form fields to show
285
+ email: { label: 'Email', type: 'email' },
286
+ password: { label: 'Password', type: 'password' },
287
+ },
288
+ authorize: async (rpc, credentials) =>
289
+ rpc.invoke('auth:login', { email: credentials.email, password: credentials.password }),
290
+ },
291
+ basePath: '/auth', // optional, defaults to '/auth'
292
+ callbacks: { // optional — all standard Auth.js callbacks
293
+ signIn: async (rpc, data) => rpc.invoke('auth:signIn', data),
294
+ redirect: async (rpc, { url }) => url,
295
+ session: async (rpc, data) => data,
296
+ jwt: async (rpc, data) => data,
297
+ },
298
+ })
299
+ ```
300
+
301
+ - `providers` and `credentials` are both optional — use one, both, or neither.
302
+ - `rpc.invoke(funcName, data)` calls any registered Pikku function with full service injection. The return type is typed from your function definition.
303
+ - `credentials.authorize` returns the Auth.js `User` object on success, or `null` on failure.
304
+
305
+ ---
306
+
307
+ ## `createAuthRoutes` API (low-level escape hatch)
308
+
309
+ Use this only when you need full manual control, e.g. for the Credentials provider with custom `authorize` logic.
265
310
 
266
311
  ```typescript
267
312
  import { createAuthRoutes } from '@pikku/auth-js'
@@ -283,37 +328,12 @@ wireHTTPRoutes({ routes: { auth: routes as any } })
283
328
 
284
329
  ## Adding Custom Claims (e.g. `role`)
285
330
 
286
- 1. Return extra fields from `authorize()` in your Credentials provider (Auth.js `User` type is open).
287
- 2. Copy them into the JWT token in the `jwt` callback (`token.role = user.role`).
288
- 3. Expose them in `mapSession` in `authJsSession` (`role: claims.role`).
289
- 4. They are now available on every `session` object in your Pikku functions.
290
-
291
- ---
292
-
293
- ## Adding OAuth Providers (GitHub, Google, etc.)
294
-
295
- With `strategy: 'jwt'` no database adapter is needed — tokens are self-contained.
296
-
297
- ```typescript
298
- import GitHub from '@auth/core/providers/github'
299
- import Google from '@auth/core/providers/google'
300
-
301
- const configFactory: AuthConfigOrFactory = async (services) => {
302
- const secret = await services.secrets.getSecret('AUTH_SECRET').catch(() => null) ?? DEV_AUTH_SECRET
303
- const github = await services.secrets.getSecretJSON('GITHUB_OAUTH').catch(() => null)
304
- const google = await services.secrets.getSecretJSON('GOOGLE_OAUTH').catch(() => null)
305
-
306
- return {
307
- providers: [
308
- GitHub({ clientId: github?.clientId, clientSecret: github?.clientSecret }),
309
- Google({ clientId: google?.clientId, clientSecret: google?.clientSecret }),
310
- ],
311
- session: { strategy: 'jwt' as const },
312
- secret,
313
- trustHost: true,
314
- basePath: '/auth',
315
- }
316
- }
317
- ```
331
+ When using `wireAuth` with callbacks:
332
+ 1. Return extra fields from your `signIn` callback.
333
+ 2. Handle them in the `jwt` callback: `jwt: async (rpc, { token, user }) => { if (user) token.role = user.role; return token }`.
334
+ 3. Expose them in `mapSession` in `authJsSession`: `role: claims.role`.
318
335
 
319
- Each OAuth provider needs its client ID and secret registered in the secrets service. No adapter or DB changes required when using JWT sessions.
336
+ When using `createAuthRoutes` directly:
337
+ 1. Return extra fields from `authorize()` in your Credentials provider.
338
+ 2. Copy them into the JWT token in the `jwt` callback.
339
+ 3. Expose them in `mapSession` in `authJsSession`.
@@ -145,13 +145,13 @@ Call at module load time — typically in the same `wirings/*.ts` file as the `w
145
145
 
146
146
  **Scope resolution order (broadest → narrowest):**
147
147
 
148
- ```
148
+ ```text
149
149
  global → httpGroup/* → httpGroup/prefix → wiringTags → wiringMiddleware → funcTags → funcMiddleware → function body
150
150
  ```
151
151
 
152
152
  **Within each scope, sorted by priority:**
153
153
 
154
- ```
154
+ ```text
155
155
  highest → high → medium (default) → low → lowest
156
156
  ```
157
157
 
@@ -173,15 +173,52 @@ const createSingletonServices = pikkuServices(async (config) => {
173
173
  })
174
174
  ```
175
175
 
176
+ ### Audit Wire Service
177
+
178
+ `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.
179
+
180
+ Pair with `createAuditedKysely` to auto-capture every Kysely query as an audit event.
181
+
182
+ ```typescript
183
+ // services.ts
184
+ import { createInvocationAudit } from '@pikku/core/services'
185
+ import { createAuditedKysely } from '@pikku/kysely'
186
+
187
+ export const createWireServices = pikkuWireServices(async (singletonServices, wire) => {
188
+ const audit = createInvocationAudit(singletonServices.audit, wire)
189
+ const kysely = singletonServices.kysely
190
+ ? createAuditedKysely(singletonServices.kysely, { audit })
191
+ : undefined
192
+ return { audit, ...(kysely ? { kysely } : {}) }
193
+ })
194
+ ```
195
+
196
+ The `audit` wire service is typed as `AuditLog` (from `@pikku/core`). Functions that emit custom events use it directly:
197
+
198
+ ```typescript
199
+ const deleteUser = pikkuFunc({
200
+ func: async ({ audit }, { userId }) => {
201
+ await audit.audit({ type: 'user.deleted', actor_user_id: userId })
202
+ // ...
203
+ },
204
+ })
205
+ ```
206
+
207
+ `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.
208
+
209
+ > **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.
210
+
176
211
  ### Built-in Services
177
212
 
178
- | Service | Package | Purpose |
179
- | ----------------------- | ---------------------- | --------------------------- |
180
- | `ConsoleLogger` | `@pikku/core/services` | Console-based logging |
181
- | `JoseJWTService` | `@pikku/jose` | JWT sign/verify via jose |
182
- | `LocalSecretService` | `@pikku/core/services` | Local development secrets |
183
- | `LocalVariablesService` | `@pikku/core/services` | Local environment variables |
184
- | `PinoLogger` | `@pikku/pino` | Structured logging via Pino |
213
+ | Service | Package | Purpose |
214
+ | -------------------------- | ---------------------- | -------------------------------- |
215
+ | `ConsoleLogger` | `@pikku/core/services` | Console-based logging |
216
+ | `JoseJWTService` | `@pikku/jose` | JWT sign/verify via jose |
217
+ | `LocalSecretService` | `@pikku/core/services` | Local development secrets |
218
+ | `LocalVariablesService` | `@pikku/core/services` | Local environment variables |
219
+ | `PinoLogger` | `@pikku/pino` | Structured logging via Pino |
220
+ | `createInvocationAudit` | `@pikku/core/services` | Per-request audit buffer |
221
+ | `createAuditedKysely` | `@pikku/kysely` | Auto-capture DB queries as audit events |
185
222
 
186
223
  ## Complete Example
187
224
 
@@ -71,6 +71,28 @@ await workflow.sleep('Wait 5 minutes', '5min')
71
71
  await workflow.suspend('Awaiting approval')
72
72
  ```
73
73
 
74
+ ### Choosing the right wrapper
75
+
76
+ | Wrapper | When to use |
77
+ |---|---|
78
+ | `pikkuWorkflowFunc` | **Default.** Use for all new workflows. DSL mode — serialisable, replay-safe. |
79
+ | `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. |
80
+ | `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. |
81
+
82
+ **Conditional results** — the correct DSL pattern when a step only runs under some condition:
83
+
84
+ ```typescript
85
+ // ✅ Declare at top level, assign inside block
86
+ let result: { id: string } | null = null
87
+ if (input.createFoo) {
88
+ result = await workflow.do('Create foo', 'createFoo', { ... })
89
+ }
90
+ // Use result?.id downstream
91
+
92
+ // ❌ Do NOT declare const inside a block — DSL forbids block-scoped declarations
93
+ // ❌ Do NOT switch to pikkuWorkflowComplexFunc to avoid the restriction
94
+ ```
95
+
74
96
  ### `pikkuWorkflowGraph(config)` — DAG Workflows
75
97
 
76
98
  ```typescript