@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
|
@@ -53,42 +53,32 @@ The function never imports Express, never reads `req.body`, never touches `ws.se
|
|
|
53
53
|
|
|
54
54
|
## Concept Mapping: Generic Backend → Pikku
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
| --------------------------------------- | --------------------------------------------------------------- | ----------------- |
|
|
58
|
-
| **Controller / Route Handler** | `pikkuFunc` / `pikkuSessionlessFunc` | (this skill) |
|
|
59
|
-
| **Route definition** (`GET /users/:id`) | `wireHTTP({ route, method, func })` | `pikku-http` |
|
|
60
|
-
| **Middleware** (Express/Koa-style) | `pikkuMiddleware` | `pikku-security` |
|
|
61
|
-
| **Auth Guard / Auth Middleware** | `authBearer()` / `authCookie()` / `authApiKey()` | `pikku-security` |
|
|
62
|
-
| **Authorization / Permissions** | `pikkuPermission` / `pikkuAuth` | `pikku-security` |
|
|
63
|
-
| **DTO / Request Validation** | Standard Schema (Zod, Valibot, ArkType) | (this skill) |
|
|
64
|
-
| **Dependency Injection** | `pikkuServices` (singleton) + `pikkuWireServices` (per-request) | `pikku-services` |
|
|
65
|
-
| **WebSocket handlers** | `wireChannel` | `pikku-websocket` |
|
|
66
|
-
| **Job Queue workers** | `wireQueueWorker` | `pikku-queue` |
|
|
67
|
-
| **Cron / Scheduled tasks** | `wireScheduler` | `pikku-cron` |
|
|
68
|
-
| **Module / Feature grouping** | Tags + wiring files | (this skill) |
|
|
69
|
-
| **Error handling** | Throw typed errors (`NotFoundError`, `ForbiddenError`) | (this skill) |
|
|
70
|
-
| **Type-safe API client** | `npx pikku prebuild` generates clients | (this skill) |
|
|
71
|
-
| **Secrets / Config** | `wireSecret`, `wireVariable`, `services.variables` | `pikku-config` |
|
|
56
|
+
Controllers/routes → `pikkuFunc`; middleware/auth/permissions → `pikku-security`; DI → `pikku-services`; transports (HTTP/WS/queue/cron) → their `wire*` + skill. For the full Generic Backend → Pikku mapping table (with side-by-side code examples), read `references/concept-mapping.md`.
|
|
72
57
|
|
|
73
58
|
## Functions
|
|
74
59
|
|
|
75
60
|
Three main function types:
|
|
76
61
|
|
|
77
62
|
```typescript
|
|
78
|
-
// Requires authentication — receives session in wire context
|
|
79
|
-
|
|
80
|
-
|
|
63
|
+
// Requires authentication — receives session in wire context.
|
|
64
|
+
// input/output are Zod schemas; the data + return types are inferred from them.
|
|
65
|
+
const updateTodo = pikkuFunc({
|
|
66
|
+
input: UpdateTodoInput,
|
|
67
|
+
output: TodoOutput,
|
|
68
|
+
func: async (services, data, wire) => {
|
|
81
69
|
const session = await wire.session.get()
|
|
82
70
|
return services.todoStore.update(data.id, data)
|
|
83
|
-
}
|
|
84
|
-
)
|
|
71
|
+
},
|
|
72
|
+
})
|
|
85
73
|
|
|
86
74
|
// No authentication required
|
|
87
|
-
const listTodos = pikkuSessionlessFunc
|
|
88
|
-
|
|
75
|
+
const listTodos = pikkuSessionlessFunc({
|
|
76
|
+
input: ListTodosInput,
|
|
77
|
+
output: TodoListOutput,
|
|
78
|
+
func: async (services, data) => {
|
|
89
79
|
return { todos: services.todoStore.list(data.filters) }
|
|
90
|
-
}
|
|
91
|
-
)
|
|
80
|
+
},
|
|
81
|
+
})
|
|
92
82
|
|
|
93
83
|
// No input or output (for scheduled tasks, lifecycle hooks)
|
|
94
84
|
const cleanup = pikkuVoidFunc(async (services) => {
|
|
@@ -96,23 +86,7 @@ const cleanup = pikkuVoidFunc(async (services) => {
|
|
|
96
86
|
})
|
|
97
87
|
```
|
|
98
88
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
```typescript
|
|
102
|
-
const createTodo = pikkuSessionlessFunc({
|
|
103
|
-
title: 'Create Todo',
|
|
104
|
-
description: 'Create a new todo item',
|
|
105
|
-
input: CreateTodoInputSchema,
|
|
106
|
-
output: CreateTodoOutputSchema,
|
|
107
|
-
func: async ({ logger, todoStore }, { title, priority }) => {
|
|
108
|
-
const todo = todoStore.createTodo(title, priority)
|
|
109
|
-
logger.info(`Created todo: ${todo.id}`)
|
|
110
|
-
return { todo }
|
|
111
|
-
},
|
|
112
|
-
})
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Full config options:
|
|
89
|
+
Services can be destructured inline in the `func` signature (e.g. `async ({ logger, todoStore }, { title }) => ...`). Full config options:
|
|
116
90
|
|
|
117
91
|
```typescript
|
|
118
92
|
pikkuFunc({
|
|
@@ -243,33 +217,7 @@ expect(result.todos).toHaveLength(3)
|
|
|
243
217
|
|
|
244
218
|
## Available Packages
|
|
245
219
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
| Package | Use Case |
|
|
249
|
-
| ----------------------------- | ------------------------------------- |
|
|
250
|
-
| `@pikku/express-server` | Express standalone server |
|
|
251
|
-
| `@pikku/express-middleware` | Express as middleware in existing app |
|
|
252
|
-
| `@pikku/fastify-server` | Fastify standalone |
|
|
253
|
-
| `@pikku/fastify-plugin` | Fastify plugin |
|
|
254
|
-
| `@pikku/next` | Next.js API routes |
|
|
255
|
-
| `@pikku/aws-lambda` | AWS Lambda handlers |
|
|
256
|
-
| `@pikku/cloudflare` | Cloudflare Workers |
|
|
257
|
-
| `@pikku/uws-server` | uWebSockets.js (high perf) |
|
|
258
|
-
| `@pikku/modelcontextprotocol` | MCP server |
|
|
259
|
-
|
|
260
|
-
### Service Packages
|
|
261
|
-
|
|
262
|
-
| Package | Provides |
|
|
263
|
-
| ------------------------ | ---------------------------------------------------- |
|
|
264
|
-
| `@pikku/jose` | JWT (sign/verify) via jose library |
|
|
265
|
-
| `@pikku/schema-ajv` | Schema validation via AJV |
|
|
266
|
-
| `@pikku/schema-cfworker` | Schema validation for Cloudflare |
|
|
267
|
-
| `@pikku/pino` | Structured logging via Pino |
|
|
268
|
-
| `@pikku/kysely` | Type-safe SQL via Kysely (PostgreSQL, SQLite, MySQL) |
|
|
269
|
-
| `@pikku/redis` | Redis client |
|
|
270
|
-
| `@pikku/queue-bullmq` | Job queues via BullMQ |
|
|
271
|
-
| `@pikku/queue-pg-boss` | Job queues via PgBoss |
|
|
272
|
-
| `@pikku/aws-services` | AWS SDK (SQS, DynamoDB, etc.) |
|
|
220
|
+
Pikku ships runtime adapters (`@pikku/express-server`, `@pikku/fastify-server`, `@pikku/next`, `@pikku/aws-lambda`, `@pikku/cloudflare`, `@pikku/uws-server`, `@pikku/modelcontextprotocol`, ...) and service packages (`@pikku/jose`, `@pikku/schema-ajv`, `@pikku/pino`, `@pikku/kysely`, `@pikku/redis`, `@pikku/queue-bullmq`, `@pikku/queue-pg-boss`, ...). For the full list with use cases, read `references/packages.md`.
|
|
273
221
|
|
|
274
222
|
## Key Differences from Traditional Frameworks
|
|
275
223
|
|
|
@@ -1,6 +1,25 @@
|
|
|
1
1
|
# Concept Mapping: Generic Backend → Pikku
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Authoritative mapping table plus side-by-side code examples showing how common backend patterns translate to Pikku.
|
|
4
|
+
|
|
5
|
+
## Quick Reference Table
|
|
6
|
+
|
|
7
|
+
| Generic Backend Concept | Pikku Equivalent | Skill |
|
|
8
|
+
| --------------------------------------- | --------------------------------------------------------------- | ----------------- |
|
|
9
|
+
| **Controller / Route Handler** | `pikkuFunc` / `pikkuSessionlessFunc` | `pikku-concepts` |
|
|
10
|
+
| **Route definition** (`GET /users/:id`) | `wireHTTP({ route, method, func })` | `pikku-http` |
|
|
11
|
+
| **Middleware** (Express/Koa-style) | `pikkuMiddleware` | `pikku-security` |
|
|
12
|
+
| **Auth Guard / Auth Middleware** | `authBearer()` / `authCookie()` / `authApiKey()` | `pikku-security` |
|
|
13
|
+
| **Authorization / Permissions** | `pikkuPermission` / `pikkuAuth` | `pikku-security` |
|
|
14
|
+
| **DTO / Request Validation** | Standard Schema (Zod, Valibot, ArkType) | `pikku-concepts` |
|
|
15
|
+
| **Dependency Injection** | `pikkuServices` (singleton) + `pikkuWireServices` (per-request) | `pikku-services` |
|
|
16
|
+
| **WebSocket handlers** | `wireChannel` | `pikku-websocket` |
|
|
17
|
+
| **Job Queue workers** | `wireQueueWorker` | `pikku-queue` |
|
|
18
|
+
| **Cron / Scheduled tasks** | `wireScheduler` | `pikku-cron` |
|
|
19
|
+
| **Module / Feature grouping** | Tags + wiring files | `pikku-concepts` |
|
|
20
|
+
| **Error handling** | Throw typed errors (`NotFoundError`, `ForbiddenError`) | `pikku-concepts` |
|
|
21
|
+
| **Type-safe API client** | `npx pikku prebuild` generates clients | `pikku-concepts` |
|
|
22
|
+
| **Secrets / Config** | `wireSecret`, `wireVariable`, `services.variables` | `pikku-config` |
|
|
4
23
|
|
|
5
24
|
## Route Handler / Controller → pikkuFunc
|
|
6
25
|
|
|
@@ -21,14 +40,17 @@ class TodoController {
|
|
|
21
40
|
**Pikku:**
|
|
22
41
|
|
|
23
42
|
```typescript
|
|
24
|
-
// Function knows nothing about HTTP
|
|
25
|
-
|
|
26
|
-
|
|
43
|
+
// Function knows nothing about HTTP.
|
|
44
|
+
// input/output are Zod schemas; the data + return types are inferred from them.
|
|
45
|
+
const createTodo = pikkuSessionlessFunc({
|
|
46
|
+
input: CreateTodoInput,
|
|
47
|
+
output: TodoOutput,
|
|
48
|
+
func: async ({ todoStore, logger }, { title, priority }) => {
|
|
27
49
|
const todo = todoStore.createTodo(title, priority)
|
|
28
50
|
logger.info(`Created todo: ${todo.id}`)
|
|
29
51
|
return { todo }
|
|
30
|
-
}
|
|
31
|
-
)
|
|
52
|
+
},
|
|
53
|
+
})
|
|
32
54
|
|
|
33
55
|
// Wiring (separate file) - grouped with defineHTTPRoutes
|
|
34
56
|
export const todoRoutes = defineHTTPRoutes({
|
|
@@ -68,13 +90,15 @@ async getUser(req: Request, res: Response) {
|
|
|
68
90
|
**Pikku:**
|
|
69
91
|
|
|
70
92
|
```typescript
|
|
71
|
-
// All sources merged into a single typed `data` object
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
// All sources merged into a single typed `data` object.
|
|
94
|
+
// input/output are Zod schemas; the data + return types are inferred from them.
|
|
95
|
+
const getUser = pikkuSessionlessFunc({
|
|
96
|
+
input: z.object({ id: z.string(), fields: z.string().optional() }),
|
|
97
|
+
output: UserOutput,
|
|
98
|
+
func: async (services, { id, fields }) => {
|
|
99
|
+
// id comes from route param, fields from query - function doesn't care
|
|
100
|
+
return { user: await services.db.getUser(id, fields) }
|
|
101
|
+
},
|
|
78
102
|
})
|
|
79
103
|
|
|
80
104
|
wireHTTP({ method: 'get', route: '/users/:id', func: getUser, auth: false })
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Available Pikku Packages
|
|
2
|
+
|
|
3
|
+
## Runtime Adapters
|
|
4
|
+
|
|
5
|
+
| Package | Use Case |
|
|
6
|
+
| ----------------------------- | ------------------------------------- |
|
|
7
|
+
| `@pikku/express-server` | Express standalone server |
|
|
8
|
+
| `@pikku/express-middleware` | Express as middleware in existing app |
|
|
9
|
+
| `@pikku/fastify-server` | Fastify standalone |
|
|
10
|
+
| `@pikku/fastify-plugin` | Fastify plugin |
|
|
11
|
+
| `@pikku/next` | Next.js API routes |
|
|
12
|
+
| `@pikku/aws-lambda` | AWS Lambda handlers |
|
|
13
|
+
| `@pikku/cloudflare` | Cloudflare Workers |
|
|
14
|
+
| `@pikku/uws-server` | uWebSockets.js (high perf) |
|
|
15
|
+
| `@pikku/modelcontextprotocol` | MCP server |
|
|
16
|
+
|
|
17
|
+
## Service Packages
|
|
18
|
+
|
|
19
|
+
| Package | Provides |
|
|
20
|
+
| ------------------------ | ---------------------------------------------------- |
|
|
21
|
+
| `@pikku/jose` | JWT (sign/verify) via jose library |
|
|
22
|
+
| `@pikku/schema-ajv` | Schema validation via AJV |
|
|
23
|
+
| `@pikku/schema-cfworker` | Schema validation for Cloudflare |
|
|
24
|
+
| `@pikku/pino` | Structured logging via Pino |
|
|
25
|
+
| `@pikku/kysely` | Type-safe SQL via Kysely (PostgreSQL, SQLite, MySQL) |
|
|
26
|
+
| `@pikku/redis` | Redis client |
|
|
27
|
+
| `@pikku/queue-bullmq` | Job queues via BullMQ |
|
|
28
|
+
| `@pikku/queue-pg-boss` | Job queues via PgBoss |
|
|
29
|
+
| `@pikku/aws-services` | AWS SDK (SQS, DynamoDB, etc.) |
|
|
@@ -34,67 +34,14 @@ Follow existing patterns you find (naming, tag usage, file organization). See `p
|
|
|
34
34
|
|
|
35
35
|
## API Reference
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
- `wireHTTP(config)` (from `@pikku/core/http`) — wire one function to one endpoint.
|
|
38
|
+
- `defineHTTPRoutes(config)` + `wireHTTPRoutes(config)` (from `.pikku/pikku-types.gen.js`) — group routes with shared config; composable/nestable.
|
|
38
39
|
|
|
39
|
-
|
|
40
|
+
Function input/output types come from the function's own `input:`/`output:` zod schemas — never declared in the wiring. Route `:params`, query params, and body are merged into the function's `data` arg (see Data Flow).
|
|
40
41
|
|
|
41
|
-
|
|
42
|
-
import { wireHTTP } from '@pikku/core/http'
|
|
43
|
-
|
|
44
|
-
wireHTTP({
|
|
45
|
-
method: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head',
|
|
46
|
-
route: string, // e.g. '/books/:bookId' — :params become data fields
|
|
47
|
-
func: PikkuFunc, // The function to call
|
|
48
|
-
auth?: boolean, // Override default auth (true = require session)
|
|
49
|
-
tags?: string[], // For grouping, middleware targeting
|
|
50
|
-
permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
|
|
51
|
-
middleware?: PikkuMiddleware[],
|
|
52
|
-
sse?: boolean, // Enable Server-Sent Events
|
|
53
|
-
contentType?: 'xml' | 'json', // Response content type
|
|
54
|
-
timeout?: number, // Request timeout in ms
|
|
55
|
-
headers?: HTTPHeadersSchema, // Expected headers schema
|
|
56
|
-
docs?: HTTPRouteDocsConfig, // OpenAPI docs config
|
|
57
|
-
})
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### `defineHTTPRoutes(config)` + `wireHTTPRoutes(config)`
|
|
42
|
+
Config cascading across groups: `basePath` concatenates down the chain, `tags` merge (union), `auth` child overrides parent.
|
|
61
43
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
import { defineHTTPRoutes, wireHTTPRoutes } from '.pikku/pikku-types.gen.js'
|
|
66
|
-
|
|
67
|
-
const routes = defineHTTPRoutes({
|
|
68
|
-
basePath?: string, // Prepended to all route paths
|
|
69
|
-
tags?: string[], // Applied to all routes in group
|
|
70
|
-
auth?: boolean, // Default auth for all routes (overridable per-route)
|
|
71
|
-
middleware?: PikkuMiddleware[],
|
|
72
|
-
routes: {
|
|
73
|
-
[key: string]: {
|
|
74
|
-
method: string,
|
|
75
|
-
route: string,
|
|
76
|
-
func: PikkuFunc,
|
|
77
|
-
auth?: boolean, // Override group auth
|
|
78
|
-
permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
|
|
79
|
-
middleware?: PikkuMiddleware[],
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
wireHTTPRoutes({
|
|
85
|
-
basePath?: string, // Top-level prefix (e.g. '/api/v1')
|
|
86
|
-
middleware?: PikkuMiddleware[],
|
|
87
|
-
routes: {
|
|
88
|
-
[key: string]: ReturnType<typeof defineHTTPRoutes>,
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
Config cascading rules:
|
|
94
|
-
|
|
95
|
-
- `basePath` — concatenates down the chain
|
|
96
|
-
- `tags` — merge (union)
|
|
97
|
-
- `auth` — child overrides parent
|
|
44
|
+
For the full option tables (every `wireHTTP` field, the `defineHTTPRoutes`/`wireHTTPRoutes` config shape), read `references/http-options.md`.
|
|
98
45
|
|
|
99
46
|
### `addHTTPMiddleware(pattern, middlewares)`
|
|
100
47
|
|
|
@@ -137,7 +84,7 @@ wireHTTP({
|
|
|
137
84
|
const booksRoutes = defineHTTPRoutes({
|
|
138
85
|
tags: ['books'],
|
|
139
86
|
routes: {
|
|
140
|
-
list: { method: 'get', route: '/books', func: listBooks, auth: false },
|
|
87
|
+
list: { method: 'get', route: '/books', func: listBooks, auth: false }, // per-route override
|
|
141
88
|
get: { method: 'get', route: '/books/:bookId', func: getBook },
|
|
142
89
|
create: { method: 'post', route: '/books', func: createBook },
|
|
143
90
|
delete: { method: 'delete', route: '/books/:bookId', func: deleteBook },
|
|
@@ -145,24 +92,19 @@ const booksRoutes = defineHTTPRoutes({
|
|
|
145
92
|
})
|
|
146
93
|
|
|
147
94
|
const todosRoutes = defineHTTPRoutes({
|
|
148
|
-
auth: false,
|
|
95
|
+
auth: false, // group-level default, overridable per-route
|
|
149
96
|
tags: ['todos'],
|
|
150
97
|
routes: {
|
|
151
98
|
list: { method: 'get', route: '/todos', func: listTodos },
|
|
152
|
-
create: { method: 'post', route: '/todos', func: createTodo },
|
|
153
|
-
get: { method: 'get', route: '/todos/:id', func: getTodo },
|
|
154
99
|
},
|
|
155
100
|
})
|
|
156
101
|
|
|
157
102
|
wireHTTPRoutes({
|
|
158
103
|
basePath: '/api/v1',
|
|
159
104
|
middleware: [cors()],
|
|
160
|
-
routes: {
|
|
161
|
-
books: booksRoutes,
|
|
162
|
-
todos: todosRoutes,
|
|
163
|
-
},
|
|
105
|
+
routes: { books: booksRoutes, todos: todosRoutes },
|
|
164
106
|
})
|
|
165
|
-
// Results in: GET /api/v1/books, POST /api/v1/books, etc.
|
|
107
|
+
// Results in: GET /api/v1/books, POST /api/v1/books, GET /api/v1/todos, etc.
|
|
166
108
|
```
|
|
167
109
|
|
|
168
110
|
### Auth & Permissions
|
|
@@ -258,60 +200,27 @@ const deleted = await pikkuFetch.delete('/api/v1/books/:bookId', {
|
|
|
258
200
|
|
|
259
201
|
## Complete Example
|
|
260
202
|
|
|
203
|
+
Functions live in their own files (one per file) and supply behavior + `permissions`; the wiring file imports them and wires routes. Sessionless funcs need no session; `pikkuFunc` does.
|
|
204
|
+
|
|
261
205
|
```typescript
|
|
262
206
|
// functions/books.functions.ts
|
|
263
207
|
import { pikkuFunc, pikkuSessionlessFunc } from '#pikku'
|
|
264
208
|
|
|
265
209
|
export const listBooks = pikkuSessionlessFunc({
|
|
266
210
|
title: 'List Books',
|
|
267
|
-
func: async ({ db }, { limit }) => {
|
|
268
|
-
return { books: await db.listBooks(limit) }
|
|
269
|
-
},
|
|
211
|
+
func: async ({ db }, { limit }) => ({ books: await db.listBooks(limit) }),
|
|
270
212
|
})
|
|
271
213
|
|
|
272
214
|
export const getBook = pikkuFunc({
|
|
273
215
|
title: 'Get Book',
|
|
274
216
|
description: 'Retrieve a book by ID',
|
|
275
|
-
func: async ({ db }, { bookId }) =>
|
|
276
|
-
return await db.getBook(bookId)
|
|
277
|
-
},
|
|
217
|
+
func: async ({ db }, { bookId }) => await db.getBook(bookId),
|
|
278
218
|
permissions: { user: isAuthenticated },
|
|
279
219
|
})
|
|
280
220
|
|
|
281
|
-
|
|
282
|
-
title: 'Create Book',
|
|
283
|
-
func: async ({ db }, { title, author }) => {
|
|
284
|
-
return await db.createBook({ title, author })
|
|
285
|
-
},
|
|
286
|
-
})
|
|
287
|
-
|
|
288
|
-
export const deleteBook = pikkuFunc({
|
|
289
|
-
title: 'Delete Book',
|
|
290
|
-
func: async ({ db }, { bookId }) => {
|
|
291
|
-
await db.deleteBook(bookId)
|
|
292
|
-
return { deleted: true }
|
|
293
|
-
},
|
|
294
|
-
})
|
|
295
|
-
|
|
296
|
-
// wirings/books.http.ts
|
|
297
|
-
import { defineHTTPRoutes, wireHTTPRoutes } from '.pikku/pikku-types.gen.js'
|
|
221
|
+
// wirings/books.http.ts — same defineHTTPRoutes/wireHTTPRoutes shape as the Route Groups example above
|
|
298
222
|
import { addHTTPMiddleware } from '@pikku/core/http'
|
|
299
223
|
import { cors, authBearer } from '@pikku/core/middleware'
|
|
300
224
|
|
|
301
|
-
const booksRoutes = defineHTTPRoutes({
|
|
302
|
-
tags: ['books'],
|
|
303
|
-
routes: {
|
|
304
|
-
list: { method: 'get', route: '/books', func: listBooks, auth: false },
|
|
305
|
-
get: { method: 'get', route: '/books/:bookId', func: getBook },
|
|
306
|
-
create: { method: 'post', route: '/books', func: createBook },
|
|
307
|
-
delete: { method: 'delete', route: '/books/:bookId', func: deleteBook },
|
|
308
|
-
},
|
|
309
|
-
})
|
|
310
|
-
|
|
311
|
-
wireHTTPRoutes({
|
|
312
|
-
basePath: '/api',
|
|
313
|
-
routes: { books: booksRoutes },
|
|
314
|
-
})
|
|
315
|
-
|
|
316
225
|
addHTTPMiddleware('*', [cors(), authBearer()])
|
|
317
226
|
```
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# wireHTTP / defineHTTPRoutes / wireHTTPRoutes — full option reference
|
|
2
|
+
|
|
3
|
+
## `wireHTTP(config)`
|
|
4
|
+
|
|
5
|
+
Wire a single function to an HTTP endpoint. Import from `@pikku/core/http`.
|
|
6
|
+
|
|
7
|
+
| Option | Type | Notes |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `method` | `'get' \| 'post' \| 'put' \| 'patch' \| 'delete' \| 'head'` | HTTP verb |
|
|
10
|
+
| `route` | `string` | e.g. `/books/:bookId` — `:params` become `data` fields |
|
|
11
|
+
| `func` | `PikkuFunc` | The function to call |
|
|
12
|
+
| `auth?` | `boolean` | Override default auth (`true` = require session) |
|
|
13
|
+
| `tags?` | `string[]` | For grouping, middleware targeting |
|
|
14
|
+
| `permissions?` | `Record<string, PikkuPermission \| PikkuPermission[]>` | Permission checks |
|
|
15
|
+
| `middleware?` | `PikkuMiddleware[]` | Per-route middleware |
|
|
16
|
+
| `sse?` | `boolean` | Enable Server-Sent Events |
|
|
17
|
+
| `contentType?` | `'xml' \| 'json'` | Response content type |
|
|
18
|
+
| `timeout?` | `number` | Request timeout in ms |
|
|
19
|
+
| `headers?` | `HTTPHeadersSchema` | Expected headers schema |
|
|
20
|
+
| `docs?` | `HTTPRouteDocsConfig` | OpenAPI docs config |
|
|
21
|
+
|
|
22
|
+
## `defineHTTPRoutes(config)` + `wireHTTPRoutes(config)`
|
|
23
|
+
|
|
24
|
+
Group routes with shared configuration. Groups are composable and nestable. Import from `.pikku/pikku-types.gen.js`.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
const routes = defineHTTPRoutes({
|
|
28
|
+
basePath?: string, // Prepended to all route paths
|
|
29
|
+
tags?: string[], // Applied to all routes in group
|
|
30
|
+
auth?: boolean, // Default auth for all routes (overridable per-route)
|
|
31
|
+
middleware?: PikkuMiddleware[],
|
|
32
|
+
routes: {
|
|
33
|
+
[key: string]: {
|
|
34
|
+
method: string,
|
|
35
|
+
route: string,
|
|
36
|
+
func: PikkuFunc,
|
|
37
|
+
auth?: boolean, // Override group auth
|
|
38
|
+
permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
|
|
39
|
+
middleware?: PikkuMiddleware[],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
wireHTTPRoutes({
|
|
45
|
+
basePath?: string, // Top-level prefix (e.g. '/api/v1')
|
|
46
|
+
middleware?: PikkuMiddleware[],
|
|
47
|
+
routes: {
|
|
48
|
+
[key: string]: ReturnType<typeof defineHTTPRoutes>,
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Config cascading rules:
|
|
54
|
+
|
|
55
|
+
- `basePath` — concatenates down the chain
|
|
56
|
+
- `tags` — merge (union)
|
|
57
|
+
- `auth` — child overrides parent
|
|
@@ -77,20 +77,17 @@ wireHTTP({
|
|
|
77
77
|
|
|
78
78
|
## Global Middleware (`addGlobalMiddleware`)
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Runs before everything else, across every wire type: HTTP, Queue, Channel, Trigger, Scheduler, Workflow, Agent, CLI, MCP. Use it for cross-cutting concerns (e.g. telemetry) that must wrap every invocation regardless of transport.
|
|
81
81
|
|
|
82
82
|
```typescript
|
|
83
83
|
import { addGlobalMiddleware } from '@pikku/core'
|
|
84
84
|
import { telemetryOuter, telemetryInner } from '@pikku/core/middleware'
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
addGlobalMiddleware([
|
|
88
|
-
|
|
89
|
-
// Inner telemetry: closest to the function body (lowest priority)
|
|
90
|
-
addGlobalMiddleware([telemetryInner({ environmentId: env.STAGE_ID })])
|
|
86
|
+
addGlobalMiddleware([telemetryOuter({ environmentId: env.STAGE_ID })]) // wraps the full call
|
|
87
|
+
addGlobalMiddleware([telemetryInner({ environmentId: env.STAGE_ID })]) // closest to the function body
|
|
91
88
|
```
|
|
92
89
|
|
|
93
|
-
`telemetryOuter` ships with `priority: 'highest'
|
|
90
|
+
`telemetryOuter` ships with `priority: 'highest'`, `telemetryInner` with `priority: 'lowest'` — so priority sorting places outer first regardless of array/call order.
|
|
94
91
|
|
|
95
92
|
## HTTP & Prefix Middleware (`addHTTPMiddleware`)
|
|
96
93
|
|
|
@@ -165,15 +162,13 @@ const earlyMiddleware = pikkuMiddleware({
|
|
|
165
162
|
})
|
|
166
163
|
```
|
|
167
164
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
## Common Patterns
|
|
165
|
+
Priority is the primary sort key; within the same level, registration order is preserved. Use priority when a middleware must run before/after others regardless of registration order (e.g. telemetry wrapping everything, session extraction before auth checks).
|
|
171
166
|
|
|
172
|
-
|
|
167
|
+
## Service-to-Service Bearer Auth (canonical pattern)
|
|
173
168
|
|
|
174
|
-
|
|
169
|
+
A server that exposes RPCs only to a trusted caller (e.g. an API calling a machine-agent). Auth lives in a tag middleware — NOT in the function body. Authorization/permission checks belong in the `permissions` field (see `pikku-permissions`), never inside `func`.
|
|
175
170
|
|
|
176
|
-
**On the server (the service being called):**
|
|
171
|
+
**On the server (the service being called):** tag the function, register a `pikkuMiddleware` that reads the `Authorization` header on that tag.
|
|
177
172
|
|
|
178
173
|
```typescript
|
|
179
174
|
// lib/host-token.ts
|
|
@@ -217,63 +212,11 @@ export const myFunc = pikkuSessionlessFunc({
|
|
|
217
212
|
})
|
|
218
213
|
```
|
|
219
214
|
|
|
220
|
-
**On the client (the caller):**
|
|
221
|
-
|
|
222
|
-
Use the generated `RPCInvoke` type from `.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts` — never hand-write the input/output types:
|
|
223
|
-
|
|
224
|
-
```typescript
|
|
225
|
-
import type { RPCInvoke } from '../../backends/my-service/.pikku/rpc/pikku-rpc-wirings-map.gen.d.js'
|
|
226
|
-
|
|
227
|
-
export function getServiceRPC(baseUrl: string, token: string): RPCInvoke {
|
|
228
|
-
return async (name: string, data?: unknown) => {
|
|
229
|
-
const res = await fetch(`${baseUrl}/rpc/${String(name)}`, {
|
|
230
|
-
method: 'POST',
|
|
231
|
-
headers: {
|
|
232
|
-
'Content-Type': 'application/json',
|
|
233
|
-
Authorization: `Bearer ${token}`,
|
|
234
|
-
},
|
|
235
|
-
body: JSON.stringify({ data: data ?? {} }),
|
|
236
|
-
})
|
|
237
|
-
if (!res.ok) {
|
|
238
|
-
const text = await res.text().catch(() => '')
|
|
239
|
-
throw new Error(`rpc ${String(name)} failed: ${res.status} ${text}`)
|
|
240
|
-
}
|
|
241
|
-
return res.json()
|
|
242
|
-
} as RPCInvoke
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
### Session-Setting Middleware
|
|
247
|
-
|
|
248
|
-
```typescript
|
|
249
|
-
const apiKeyAuth = pikkuMiddleware(async ({ kysely }, { http, setSession, session }, next) => {
|
|
250
|
-
if (session) return next() // already authenticated
|
|
251
|
-
|
|
252
|
-
const header = http?.request?.header?.('x-api-key')
|
|
253
|
-
if (!header) return next()
|
|
254
|
-
|
|
255
|
-
const row = await kysely.selectFrom('apiKey').select('userId').where('key', '=', header).executeTakeFirst()
|
|
256
|
-
if (row) setSession?.({ userId: row.userId })
|
|
257
|
-
|
|
258
|
-
return next()
|
|
259
|
-
})
|
|
260
|
-
|
|
261
|
-
addTagMiddleware('api-key-auth', [apiKeyAuth])
|
|
262
|
-
```
|
|
263
|
-
|
|
264
|
-
Functions tagged `'api-key-auth'` with `auth: true` reject requests without a valid key; those with `auth: false` can inspect the session but won't reject.
|
|
215
|
+
**On the client (the caller):** use the generated `RPCInvoke` type — never hand-write a `fetch` wrapper's types. See `references/middleware-patterns.md`.
|
|
265
216
|
|
|
266
|
-
|
|
217
|
+
## More patterns
|
|
267
218
|
|
|
268
|
-
|
|
269
|
-
const auditLog = pikkuMiddleware(async ({ logger, db }, wire, next) => {
|
|
270
|
-
const start = Date.now()
|
|
271
|
-
await next()
|
|
272
|
-
await db.createAuditLog({ duration: Date.now() - start })
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
addHTTPMiddleware('/admin/*', [auditLog])
|
|
276
|
-
```
|
|
219
|
+
`references/middleware-patterns.md` covers the client-side `RPCInvoke` caller, session-setting middleware (set a session from an API key), and request logging / audit middleware.
|
|
277
220
|
|
|
278
221
|
## After Changes
|
|
279
222
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# Middleware Patterns (extended)
|
|
2
|
+
|
|
3
|
+
Detailed, less-common middleware recipes. The common-path bearer-auth pattern lives inline in SKILL.md; this file holds the client-side caller, session-setting, and audit recipes.
|
|
4
|
+
|
|
5
|
+
## Service-to-Service: the client (caller) side
|
|
6
|
+
|
|
7
|
+
Use the generated `RPCInvoke` type from `.pikku/rpc/pikku-rpc-wirings-map.gen.d.ts` — never hand-write the input/output types:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import type { RPCInvoke } from '../../backends/my-service/.pikku/rpc/pikku-rpc-wirings-map.gen.d.js'
|
|
11
|
+
|
|
12
|
+
export function getServiceRPC(baseUrl: string, token: string): RPCInvoke {
|
|
13
|
+
return async (name: string, data?: unknown) => {
|
|
14
|
+
const res = await fetch(`${baseUrl}/rpc/${String(name)}`, {
|
|
15
|
+
method: 'POST',
|
|
16
|
+
headers: {
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
Authorization: `Bearer ${token}`,
|
|
19
|
+
},
|
|
20
|
+
body: JSON.stringify({ data: data ?? {} }),
|
|
21
|
+
})
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
const text = await res.text().catch(() => '')
|
|
24
|
+
throw new Error(`rpc ${String(name)} failed: ${res.status} ${text}`)
|
|
25
|
+
}
|
|
26
|
+
return res.json()
|
|
27
|
+
} as RPCInvoke
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Session-Setting Middleware
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
const apiKeyAuth = pikkuMiddleware(async ({ kysely }, { http, setSession, session }, next) => {
|
|
35
|
+
if (session) return next() // already authenticated
|
|
36
|
+
|
|
37
|
+
const header = http?.request?.header?.('x-api-key')
|
|
38
|
+
if (!header) return next()
|
|
39
|
+
|
|
40
|
+
const row = await kysely.selectFrom('apiKey').select('userId').where('key', '=', header).executeTakeFirst()
|
|
41
|
+
if (row) setSession?.({ userId: row.userId })
|
|
42
|
+
|
|
43
|
+
return next()
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
addTagMiddleware('api-key-auth', [apiKeyAuth])
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Functions tagged `'api-key-auth'` with `auth: true` reject requests without a valid key; those with `auth: false` can inspect the session but won't reject.
|
|
50
|
+
|
|
51
|
+
## Request Logging / Audit
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const auditLog = pikkuMiddleware(async ({ logger, db }, wire, next) => {
|
|
55
|
+
const start = Date.now()
|
|
56
|
+
await next()
|
|
57
|
+
await db.createAuditLog({ duration: Date.now() - start })
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
addHTTPMiddleware('/admin/*', [auditLog])
|
|
61
|
+
```
|