@pikku/skills 0.12.9 → 0.12.11
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/CHANGELOG.md +768 -0
- package/dist/skills.gen.js +1 -1
- package/package.json +4 -4
- package/skills/pikku-addon/SKILL.md +20 -14
- package/skills/pikku-addon/references/addon-package-manifest.md +2 -2
- package/skills/{pikku-ai-agent → pikku-agent}/SKILL.md +24 -24
- package/skills/pikku-ai-vercel/SKILL.md +18 -18
- package/skills/pikku-ai-voice/SKILL.md +15 -15
- package/skills/pikku-audit/SKILL.md +28 -13
- package/skills/pikku-aws/SKILL.md +2 -2
- package/skills/pikku-better-auth/SKILL.md +97 -17
- package/skills/pikku-build-app/SKILL.md +621 -0
- package/skills/pikku-build-app/references/multi-app.md +117 -0
- package/skills/pikku-build-app/references/ship.md +98 -0
- package/skills/pikku-build-app/references/theming.md +70 -0
- package/skills/pikku-build-platform/SKILL.md +239 -0
- package/skills/pikku-build-quick/SKILL.md +238 -0
- package/skills/pikku-cli/SKILL.md +7 -7
- package/skills/pikku-cli/references/complete-example.md +1 -1
- package/skills/pikku-concepts/SKILL.md +10 -7
- package/skills/pikku-concepts/references/concept-mapping.md +1 -1
- package/skills/pikku-config/SKILL.md +5 -3
- package/skills/pikku-deploy-azure/SKILL.md +5 -4
- package/skills/pikku-deploy-cloudflare/SKILL.md +9 -9
- package/skills/pikku-deploy-uws/SKILL.md +5 -2
- package/skills/pikku-deps/SKILL.md +42 -3
- package/skills/pikku-emails/SKILL.md +5 -5
- package/skills/pikku-fabric/SKILL.md +27 -3
- package/skills/pikku-fabric-debug/SKILL.md +1 -1
- package/skills/pikku-feature/SKILL.md +5 -4
- package/skills/pikku-http/SKILL.md +4 -4
- package/skills/pikku-http/references/http-options.md +13 -13
- package/skills/pikku-i18n/SKILL.md +2 -1
- package/skills/pikku-info/SKILL.md +1 -1
- package/skills/pikku-knowledge/SKILL.md +13 -13
- package/skills/pikku-kysely/SKILL.md +68 -41
- package/skills/pikku-machine-auth/SKILL.md +10 -10
- package/skills/pikku-mcp/SKILL.md +23 -20
- package/skills/pikku-middleware/SKILL.md +19 -12
- package/skills/pikku-middleware/references/middleware-patterns.md +14 -8
- package/skills/pikku-mongodb/SKILL.md +11 -11
- package/skills/pikku-n8n-import/SKILL.md +12 -12
- package/skills/pikku-n8n-import/SPEC.md +3 -0
- package/skills/pikku-n8n-import/references/addon-mapping.md +14 -8
- package/skills/pikku-n8n-import/references/code-translation.md +26 -22
- package/skills/pikku-n8n-import/references/loops-and-control.md +7 -7
- package/skills/pikku-paraglide/SKILL.md +11 -6
- package/skills/pikku-permissions/SKILL.md +19 -15
- package/skills/pikku-product-second-opinion/README.md +3 -3
- package/skills/pikku-product-second-opinion/SKILL.md +83 -73
- package/skills/pikku-product-second-opinion/example/sample-report.md +15 -12
- package/skills/pikku-product-second-opinion/references/report-template.md +10 -7
- package/skills/pikku-queue/SKILL.md +1 -1
- package/skills/pikku-react/SKILL.md +53 -13
- package/skills/pikku-realtime/SKILL.md +51 -19
- package/skills/pikku-rpc/SKILL.md +1 -1
- package/skills/pikku-rtl/SKILL.md +1 -1
- package/skills/pikku-scenario/SKILL.md +164 -44
- package/skills/pikku-schedule/SKILL.md +6 -1
- package/skills/pikku-schema-ajv/SKILL.md +2 -2
- package/skills/pikku-schema-cfworker/SKILL.md +1 -1
- package/skills/pikku-security/SKILL.md +9 -5
- package/skills/pikku-services/SKILL.md +27 -18
- package/skills/pikku-services/references/audit-wire-service.md +14 -8
- package/skills/pikku-software-archaeology/SKILL.md +27 -23
- package/skills/pikku-software-archaeology/references/blueprint.schema.json +580 -102
- package/skills/pikku-software-archaeology/scripts/validate.mjs +146 -69
- package/skills/pikku-tag-middleware/SKILL.md +1 -0
- package/skills/pikku-template-clone/SKILL.md +2 -1
- package/skills/pikku-trigger/SKILL.md +3 -3
- package/skills/pikku-versioning/SKILL.md +87 -3
- package/skills/pikku-websocket/SKILL.md +4 -3
- package/skills/pikku-workflow/SKILL.md +2 -2
- package/skills/pikku-workflow/references/workflow-reference.md +24 -10
- package/skills/pikku-ws/SKILL.md +5 -2
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pikku-build-quick
|
|
3
|
+
description: >-
|
|
4
|
+
Build a working app on open-source Pikku fast — scaffold to running screens, skipping the
|
|
5
|
+
knowledge base and the milestone ladder. For spikes, throwaway demos, and ideas nobody has
|
|
6
|
+
committed to yet. TRIGGER when: the user asked for something quick, a prototype, a spike, a
|
|
7
|
+
demo of an idea, or "just get it running", or picked "Quick" from the build-mode question. DO
|
|
8
|
+
NOT TRIGGER when: the request is an unqualified "build me an X on Pikku" with no signal of
|
|
9
|
+
speed or throwaway-ness — App is the default and small or toy-sounding apps do not change that
|
|
10
|
+
(use pikku-build-app); the user wants a real product someone else will pick up (use
|
|
11
|
+
pikku-build-app); the user wants a demo of Pikku itself — one that shows off surfaces like
|
|
12
|
+
workflows, queues, realtime or i18n (use pikku-build-platform); or the user is adding a feature
|
|
13
|
+
to an app that already exists rather than building one from a fresh scaffold (use
|
|
14
|
+
pikku-feature).
|
|
15
|
+
installGroups: [core]
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Build an app on Pikku, fast
|
|
19
|
+
|
|
20
|
+
You have a scaffolded project with skills installed. Get it to working, seeded,
|
|
21
|
+
signed-in screens in as few steps as possible.
|
|
22
|
+
|
|
23
|
+
**What this mode deliberately skips**, and what that costs:
|
|
24
|
+
|
|
25
|
+
| Skipped | Cost |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `knowledge/` | Another agent — or you next week — cannot resume this. Nothing records *why*. |
|
|
28
|
+
| Milestone planning | No build order, no per-piece proof. Fine at this size, painful past it. |
|
|
29
|
+
| Design direction | It will look like the template. |
|
|
30
|
+
| Refusal scenarios | Access control is asserted, not proven. |
|
|
31
|
+
|
|
32
|
+
**Say this out loud to the user, once, when you finish.** A quick build that gets
|
|
33
|
+
mistaken for a real one is the only way this mode does damage. §6 is the way out.
|
|
34
|
+
|
|
35
|
+
## Agent Operating Procedure
|
|
36
|
+
|
|
37
|
+
1. Read `AGENTS.md` at the project root before your first screen — routing slots,
|
|
38
|
+
`useNavItems()`, and the shipped component kit.
|
|
39
|
+
2. Keep generated files generated. Never hand-edit `.pikku/`, `*.gen.*`, or the SDK.
|
|
40
|
+
3. Run `pikku all` after touching functions, wirings or schemas. It is the gate,
|
|
41
|
+
and its criticals are real.
|
|
42
|
+
|
|
43
|
+
## 1. One question, then build
|
|
44
|
+
|
|
45
|
+
Ask **one** thing, and only if the original request left it open: **who uses
|
|
46
|
+
it — one kind of person, or several?** Everything else you decide yourself.
|
|
47
|
+
|
|
48
|
+
- **One kind** — no roles to declare. The rule is ownership: you see yours, not
|
|
49
|
+
theirs.
|
|
50
|
+
- **Several** — declare a role each in §2 and keep the count honest. An invented
|
|
51
|
+
role becomes invented screens.
|
|
52
|
+
|
|
53
|
+
Do not ask about design, deployment, or scope. This is the quick mode; the
|
|
54
|
+
defaults are the point.
|
|
55
|
+
|
|
56
|
+
## 2. Personas — 60 seconds, not optional
|
|
57
|
+
|
|
58
|
+
`packages/functions/src/personas.ts` ships with a `visitor`. Add one persona per
|
|
59
|
+
kind of person, plus **a second one of the primary kind** — that is what makes
|
|
60
|
+
"you see yours, not theirs" observable when you click around.
|
|
61
|
+
|
|
62
|
+
**One kind of person** — no `defineSystemRole` at all. Ownership is the only
|
|
63
|
+
rule, and it lives in each function's `permissions`, not in a role:
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
|
|
67
|
+
|
|
68
|
+
definePersonas({
|
|
69
|
+
visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
|
|
70
|
+
amina: { name: 'Amina', jobTitle: 'Gardener', account: {} },
|
|
71
|
+
bilal: { name: 'Bilal', jobTitle: 'Gardener', account: {} },
|
|
72
|
+
})
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Several kinds** — one role each, and only for the kinds the user actually
|
|
76
|
+
named:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
import { definePersonas } from '#pikku/scopes/pikku-personas.gen.js'
|
|
80
|
+
import { defineSystemRole } from '#pikku'
|
|
81
|
+
|
|
82
|
+
defineSystemRole({
|
|
83
|
+
owner: { displayName: 'Owner', description: 'Sees only their own rows', scopes: [] },
|
|
84
|
+
tenant: { displayName: 'Tenant', description: 'Sees only their own tenancy', scopes: [] },
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
definePersonas({
|
|
88
|
+
visitor: { name: 'Visitor', jobTitle: 'Synthetic health-check user', account: {} },
|
|
89
|
+
amina: { name: 'Amina', jobTitle: 'Owner', roles: ['owner'], account: {} },
|
|
90
|
+
bilal: { name: 'Bilal', jobTitle: 'Owner', roles: ['owner'], account: {} },
|
|
91
|
+
chidi: { name: 'Chidi', jobTitle: 'Tenant', roles: ['tenant'], account: {} },
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Two owners in both examples, deliberately: one owner cannot demonstrate that
|
|
96
|
+
owners are separated from each other.
|
|
97
|
+
|
|
98
|
+
- **Keep `visitor`.** The shipped scenarios name `actors.visitor`; removing it
|
|
99
|
+
fails `pikku all` and nothing you write after that registers.
|
|
100
|
+
- **One `definePersonas` call for the whole project.**
|
|
101
|
+
- **Never write an email address** — each is derived from the persona id and
|
|
102
|
+
`scenarios.emailDomain` in `pikku.config.json`.
|
|
103
|
+
- `roles` is typechecked against `defineSystemRole`; an undeclared role is a
|
|
104
|
+
build error.
|
|
105
|
+
|
|
106
|
+
## 3. Build
|
|
107
|
+
|
|
108
|
+
Run `bunx --bun pikku bootstrap` once first. It wires the `#pikku` import alias
|
|
109
|
+
codegen depends on; without it your first `db migrate` fails with
|
|
110
|
+
`Cannot find package '#pikku'`.
|
|
111
|
+
|
|
112
|
+
Then, in this order — it is the order codegen depends on:
|
|
113
|
+
|
|
114
|
+
1. **Migration** — SQL in `db/sqlite/`, numbered on from what is there. Apply
|
|
115
|
+
with `bunx --bun pikku db migrate`, which regenerates the Kysely types.
|
|
116
|
+
2. **Seed** — rows in `db/sqlite-dev-seed.sql`. There is no seed command:
|
|
117
|
+
`bunx --bun pikku db reset` wipes, migrates and seeds in one go, and is the
|
|
118
|
+
only thing that applies the file. It always starts from a wiped database, so
|
|
119
|
+
the file is plain `INSERT`s — no `ON CONFLICT DO NOTHING`. **Be generous, and
|
|
120
|
+
seed rows for both personas.** An empty app demos badly, and you cannot see a
|
|
121
|
+
layout break against zero rows.
|
|
122
|
+
3. **Functions** — one `pikkuFunc` per `*.function.ts`, `expose: true`. Pikku
|
|
123
|
+
generates the typed RPC client and React Query hooks; you do NOT write HTTP
|
|
124
|
+
routes. `wireHTTP` only for a real REST shape (a third-party webhook).
|
|
125
|
+
4. `bunx --bun pikku all`
|
|
126
|
+
5. **UI** — pages in `apps/app/src/pages/`, one route file each in
|
|
127
|
+
`apps/app/src/routes/`, calling `usePikkuQuery` / `usePikkuMutation` from
|
|
128
|
+
`@project/functions-sdk/pikku/api.gen`. Compose `@/components/<Name>` —
|
|
129
|
+
`PageHeader`, `Panel`, `StatGrid`, `DataTable` — rather than hand-rolling.
|
|
130
|
+
Register each screen in `useNavItems()`; that one file feeds the desktop
|
|
131
|
+
sidebar and the phone navigation.
|
|
132
|
+
|
|
133
|
+
**Aim for two or three real entities and three screens** — a working surface, a
|
|
134
|
+
detail view, and somewhere to land. One table with a form on it is not an app,
|
|
135
|
+
and it is not faster to build.
|
|
136
|
+
|
|
137
|
+
Rules that stay non-negotiable even here, because breaking them costs more time
|
|
138
|
+
than they save:
|
|
139
|
+
|
|
140
|
+
- Input and output types come from `input:`/`output:` zod schemas. Never generic
|
|
141
|
+
type params, never an inline return type. The schema is the type.
|
|
142
|
+
- Permission checks go in the `permissions` field, never the function body. An
|
|
143
|
+
exposed function with no session and no permission is reachable by anyone over
|
|
144
|
+
`POST /rpc/:rpcName` (PKU574).
|
|
145
|
+
- No `process.env` inside a function — use the injected `variables` / `secrets`
|
|
146
|
+
services.
|
|
147
|
+
- A `z.date()` **input** arrives over RPC as an ISO string, not a `Date`.
|
|
148
|
+
`new Date(value)` before calling date methods, or it throws
|
|
149
|
+
`.getTime is not a function` at runtime.
|
|
150
|
+
- On SQLite, `db/annotations.ts` is where a `DATETIME` becomes a `Date` and a
|
|
151
|
+
`JSON` column becomes a typed object. Without an entry they are `string` and
|
|
152
|
+
`unknown` (PKU481). Add the annotation rather than casting.
|
|
153
|
+
- Surface errors inline next to the control that failed. No empty catch.
|
|
154
|
+
- Every user-facing string is a translation key, not a literal. It is one extra
|
|
155
|
+
keystroke now and a rewrite later.
|
|
156
|
+
- Never hardcode a host or port — the API base resolves to same-origin `/api`.
|
|
157
|
+
|
|
158
|
+
Then run it:
|
|
159
|
+
|
|
160
|
+
```sh
|
|
161
|
+
bun run prebuild && bun run dev
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
API on :3000, app on the port vite prints. A frontend against a dead API looks
|
|
165
|
+
exactly like an app bug, so if every request fails, check both came up.
|
|
166
|
+
|
|
167
|
+
## 4. Look at it — actually
|
|
168
|
+
|
|
169
|
+
Sign up, click every screen. **HTTP 200 is not evidence:** pages are
|
|
170
|
+
client-rendered, so the server returns 200 with an empty shell and a page whose
|
|
171
|
+
component throws still looks fine to `curl`. Open a browser, or drive it
|
|
172
|
+
headlessly and assert on rendered text.
|
|
173
|
+
|
|
174
|
+
**Screenshot at 390px too.** A layout that is fine at 1440 routinely breaks on a
|
|
175
|
+
phone — an overflowing table, a row of buttons wrapped into a pile, a modal
|
|
176
|
+
taller than the viewport. It is the most likely width your demo gets opened at.
|
|
177
|
+
|
|
178
|
+
If you have five spare minutes, `npx impeccable install` (Node 22.18+) scores
|
|
179
|
+
each screen against interaction heuristics and names what is wrong. Feed it
|
|
180
|
+
screenshots, not source. It will polish the default look; it will not give the
|
|
181
|
+
app a look — that is `pikku-build-app` §8a.
|
|
182
|
+
|
|
183
|
+
## 5. One smoke scenario
|
|
184
|
+
|
|
185
|
+
Not the full ladder — one journey, end to end, as a real persona, so the app has
|
|
186
|
+
at least one thing that stays true.
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { pikkuScenario } from '#pikku/workflow/pikku-workflow-types.gen.js'
|
|
190
|
+
|
|
191
|
+
export const ownerCreatesAndSeesItScenario = pikkuScenario<void, { id: string }>({
|
|
192
|
+
title: 'An owner creates a thing and sees it',
|
|
193
|
+
tags: ['scenario', 'smoke'],
|
|
194
|
+
func: async (_services, _data, { scenario, actors }) => {
|
|
195
|
+
const row = await scenario.do('creates', 'createThing', { name: 'first' }, { actor: actors.amina })
|
|
196
|
+
await scenario.then('sees it listed', 'thingShowsInList', { id: row.id }, { actor: actors.amina })
|
|
197
|
+
return { id: row.id }
|
|
198
|
+
},
|
|
199
|
+
})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
- **`do` takes an RPC name; `given`/`when`/`then` take a declared
|
|
203
|
+
`pikkuScenarioStep`.** An RPC name in a `then` will not resolve.
|
|
204
|
+
- **Every scenario must assert.** A ladder with no `then` is a PKU680 critical —
|
|
205
|
+
it fails `pikku all`, stopping codegen rather than a test.
|
|
206
|
+
- **Add `SCENARIO_ACTOR_SECRET` to `.env`.** `bun run dev` writes that file with
|
|
207
|
+
only a `BETTER_AUTH_SECRET`; without the actor secret
|
|
208
|
+
`/api/auth/sign-in/actor` is disabled and every scenario fails at sign-in, for
|
|
209
|
+
a reason that reads like an auth bug.
|
|
210
|
+
- **There is no state reset** — scope what you create to unique ids.
|
|
211
|
+
|
|
212
|
+
Keep the three shipped scenarios in `packages/functions/test/scenarios/` green.
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
bunx --bun pikku scenario run local --spawn
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 6. Hand it over honestly
|
|
219
|
+
|
|
220
|
+
Tell the user, in one short paragraph: what runs, what it is seeded with, and
|
|
221
|
+
that this is a quick build — no knowledge base, no milestones, no design pass,
|
|
222
|
+
access control clicked-through rather than proven.
|
|
223
|
+
|
|
224
|
+
**Upgrading to a real build is additive, not a rewrite.** If they want it, switch
|
|
225
|
+
to `pikku-build-app` and do this, in order:
|
|
226
|
+
|
|
227
|
+
1. Write `knowledge/` for what already exists — `entities/` for what you built,
|
|
228
|
+
`decisions/` for what you chose silently, `questions/` for what you guessed
|
|
229
|
+
at. Then `pikku knowledge index && pikku knowledge validate`.
|
|
230
|
+
2. Backfill a milestone note per screen you built, at `status: built`, each with
|
|
231
|
+
its gherkin block.
|
|
232
|
+
3. Write the refusal scenarios — the ones proving one persona cannot reach
|
|
233
|
+
another's rows. This is the gap that matters most.
|
|
234
|
+
4. Then pick up `pikku-build-app` at its §4 (apps) or §5 (milestones) for
|
|
235
|
+
anything new.
|
|
236
|
+
|
|
237
|
+
Nothing built here has to be thrown away to do that — which is the whole reason
|
|
238
|
+
this mode is allowed to skip those steps in the first place.
|
|
@@ -41,7 +41,7 @@ All three factories come from `#pikku` (the generated types re-export
|
|
|
41
41
|
loses your project's service and middleware types.
|
|
42
42
|
|
|
43
43
|
```typescript
|
|
44
|
-
import { wireCLI } from '#pikku'
|
|
44
|
+
import { wireCLI } from '#pikku/cli'
|
|
45
45
|
|
|
46
46
|
wireCLI({
|
|
47
47
|
program: string, // Program name (e.g. 'todos')
|
|
@@ -65,7 +65,7 @@ wireCLI({
|
|
|
65
65
|
### `pikkuCLICommand(config)`
|
|
66
66
|
|
|
67
67
|
```typescript
|
|
68
|
-
import { pikkuCLICommand } from '#pikku'
|
|
68
|
+
import { pikkuCLICommand } from '#pikku/cli'
|
|
69
69
|
|
|
70
70
|
pikkuCLICommand({
|
|
71
71
|
parameters?: string, // Positional args (e.g. '<text>', '<username> <email>')
|
|
@@ -111,7 +111,7 @@ How the parser reads them, which is worth knowing before you name one:
|
|
|
111
111
|
### `pikkuCLIRender(fn)`
|
|
112
112
|
|
|
113
113
|
```typescript
|
|
114
|
-
import { pikkuCLIRender } from '#pikku'
|
|
114
|
+
import { pikkuCLIRender } from '#pikku/cli'
|
|
115
115
|
|
|
116
116
|
const renderer = pikkuCLIRender<OutputType>((services, data) => {
|
|
117
117
|
// Format and print output to terminal
|
|
@@ -122,10 +122,10 @@ const renderer = pikkuCLIRender<OutputType>((services, data) => {
|
|
|
122
122
|
### Wire object (`wire.cli`)
|
|
123
123
|
|
|
124
124
|
```typescript
|
|
125
|
-
wire.cli.program
|
|
126
|
-
wire.cli.command
|
|
127
|
-
wire.cli.data
|
|
128
|
-
wire.cli.channel
|
|
125
|
+
wire.cli.program // program name
|
|
126
|
+
wire.cli.command // string[] — the resolved command path
|
|
127
|
+
wire.cli.data // all positionals and options, merged
|
|
128
|
+
wire.cli.channel // the channel when served remotely (see below)
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
## Usage Patterns
|
|
@@ -32,7 +32,7 @@ export const deleteUser = pikkuFunc({
|
|
|
32
32
|
})
|
|
33
33
|
|
|
34
34
|
// wirings/cli.wiring.ts
|
|
35
|
-
import { wireCLI, pikkuCLICommand, pikkuCLIRender } from '#pikku'
|
|
35
|
+
import { wireCLI, pikkuCLICommand, pikkuCLIRender } from '#pikku/cli'
|
|
36
36
|
|
|
37
37
|
const userRenderer = pikkuCLIRender<{ user: User }>((_services, { user }) => {
|
|
38
38
|
console.log(`Created user: ${user.username} (${user.email}) [${user.role}]`)
|
|
@@ -27,7 +27,7 @@ Pikku is a TypeScript framework that separates business logic from transport mec
|
|
|
27
27
|
|
|
28
28
|
For deep-dive on each topic, see the dedicated skills:
|
|
29
29
|
|
|
30
|
-
- **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-
|
|
30
|
+
- **Wiring**: `pikku-http`, `pikku-websocket`, `pikku-rpc`, `pikku-mcp`, `pikku-queue`, `pikku-cron`, `pikku-trigger`, `pikku-cli`, `pikku-agent`, `pikku-workflow`
|
|
31
31
|
- **Authorization**: `pikku-security` (authentication/sessions), `pikku-permissions` (permission checks, scopes), `pikku-middleware` (global/tag/route middleware)
|
|
32
32
|
- **Infrastructure**: `pikku-services`, `pikku-config`
|
|
33
33
|
- **Project introspection**: `pikku-info`
|
|
@@ -44,7 +44,7 @@ pikkuFunc (pure business logic)
|
|
|
44
44
|
├── wireMCPTool → Model Context Protocol (AI tools)
|
|
45
45
|
├── wireCLI → CLI commands
|
|
46
46
|
├── wireTrigger → Event-driven (Redis pub/sub, PG LISTEN/NOTIFY)
|
|
47
|
-
├──
|
|
47
|
+
├── pikkuAgent → AI agents / chatbots
|
|
48
48
|
├── pikkuWorkflow → Multi-step durable workflows
|
|
49
49
|
└── wire.rpc → Internal function-to-function calls
|
|
50
50
|
```
|
|
@@ -122,7 +122,7 @@ pikkuFunc({
|
|
|
122
122
|
permissionsInBody?: boolean, // Last resort; needs allow.permissionsInBody in config
|
|
123
123
|
middleware?: PikkuMiddleware[], // See pikku-middleware
|
|
124
124
|
|
|
125
|
-
// Agent tooling — see pikku-
|
|
125
|
+
// Agent tooling — see pikku-agent
|
|
126
126
|
approvalRequired?: boolean,
|
|
127
127
|
approvalDescription?: (services, data) => Promise<string>,
|
|
128
128
|
|
|
@@ -144,7 +144,7 @@ reject every caller it exists to serve. Gate those with `permissions`, which
|
|
|
144
144
|
receive the optional session and may pass anonymous.
|
|
145
145
|
|
|
146
146
|
**Generics XOR `input`/`output` — never both.** A function's data and return
|
|
147
|
-
types come from
|
|
147
|
+
types come from _one_ source: either the `input`/`output` schemas (preferred —
|
|
148
148
|
they double as runtime validation and OpenAPI) or type generics
|
|
149
149
|
(`pikkuFunc<In, Out>({ ... })`). Passing both makes the two disagree and forces
|
|
150
150
|
`as any` casts. Do not annotate the `func` return type inline either — let the
|
|
@@ -230,13 +230,15 @@ await server.start()
|
|
|
230
230
|
|
|
231
231
|
**Lifecycle hooks do not run on this path** — only `pikku dev` and `pikku serve` invoke them. Do your startup work directly in the entrypoint instead.
|
|
232
232
|
|
|
233
|
-
`pikku validate` warns when a project starts a server by hand
|
|
233
|
+
`pikku validate` warns when a project starts a server by hand _and_ depends on no runtime adapter, since that combination means path 1 was available and unused. Silence it with `"lint": { "customServerBootstrap": "off" }` in `pikku.config.json`.
|
|
234
234
|
|
|
235
235
|
## Code Generation
|
|
236
236
|
|
|
237
237
|
Run `npx pikku all` to generate:
|
|
238
238
|
|
|
239
|
-
-
|
|
239
|
+
- one directory per wiring (`function/`, `http/`, `workflow/`, …), each with an
|
|
240
|
+
`index.ts` reached as `#pikku/<name>` — typed function factories and wiring
|
|
241
|
+
functions, split so an app pulls in only the wirings it uses
|
|
240
242
|
- `pikku-fetch.gen.ts` — Type-safe HTTP client
|
|
241
243
|
- `pikku-websocket.gen.ts` — Type-safe WebSocket client
|
|
242
244
|
- `pikku-bootstrap.gen.ts` — Runtime initialization (auto-imports all wirings)
|
|
@@ -271,7 +273,8 @@ src/
|
|
|
271
273
|
├── middleware.ts # Middleware definitions (see pikku-security)
|
|
272
274
|
├── permissions.ts # Permission definitions (see pikku-security)
|
|
273
275
|
└── .pikku/ # Generated (gitignored)
|
|
274
|
-
├── pikku
|
|
276
|
+
├── function/ # #pikku/function
|
|
277
|
+
├── http/ # #pikku/http
|
|
275
278
|
├── pikku-fetch.gen.ts
|
|
276
279
|
└── pikku-bootstrap.gen.ts
|
|
277
280
|
```
|
|
@@ -472,7 +472,7 @@ if (!canEdit(user, todo)) {
|
|
|
472
472
|
**Pikku:**
|
|
473
473
|
|
|
474
474
|
```typescript
|
|
475
|
-
import { NotFoundError, ForbiddenError } from '
|
|
475
|
+
import { NotFoundError, ForbiddenError } from '#pikku/error'
|
|
476
476
|
|
|
477
477
|
const updateTodo = pikkuFunc(async (services, { id, title }, wire) => {
|
|
478
478
|
const todo = services.todoStore.getTodo(id)
|
|
@@ -75,7 +75,9 @@ greppable. Call it at the point the value reaches the thing that needs it:
|
|
|
75
75
|
```typescript
|
|
76
76
|
// services.ts — allowed
|
|
77
77
|
const createSingletonServices = pikkuServices(async (config, { secrets }) => ({
|
|
78
|
-
stripe: new StripeService(
|
|
78
|
+
stripe: new StripeService(
|
|
79
|
+
(await secrets.getSecret('STRIPE_CONFIG')).reveal()
|
|
80
|
+
),
|
|
79
81
|
}))
|
|
80
82
|
|
|
81
83
|
// functions/*.ts — ask the service, never the secret store
|
|
@@ -162,7 +164,7 @@ defineCredential({
|
|
|
162
164
|
|
|
163
165
|
### Usage
|
|
164
166
|
|
|
165
|
-
|
|
167
|
+
````typescript
|
|
166
168
|
// Per-user API key — no oauth2 block
|
|
167
169
|
defineCredential({
|
|
168
170
|
name: 'stripe',
|
|
@@ -208,7 +210,7 @@ export const createWireServices = pikkuWireServices(async (_services, wire) => {
|
|
|
208
210
|
export const postMessage = pikkuFunc({
|
|
209
211
|
func: async ({ slack }, { channel, text }) => slack.postMessage(channel, text),
|
|
210
212
|
})
|
|
211
|
-
|
|
213
|
+
````
|
|
212
214
|
|
|
213
215
|
A `wire` credential resolves per user, so an unconnected user hits
|
|
214
216
|
`MissingCredentialError` rather than silently acting as someone else; a
|
|
@@ -59,10 +59,11 @@ import { createAzureHandler } from '@pikku/azure-functions'
|
|
|
59
59
|
import { createConfig, createSingletonServices } from './services.js'
|
|
60
60
|
import './.pikku/pikku-bootstrap.gen.js'
|
|
61
61
|
|
|
62
|
-
const handlers = createAzureHandler(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
const handlers = createAzureHandler({ createConfig, createSingletonServices }, [
|
|
63
|
+
'fetch',
|
|
64
|
+
'queue',
|
|
65
|
+
'scheduled',
|
|
66
|
+
])
|
|
66
67
|
|
|
67
68
|
app.http('api', {
|
|
68
69
|
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
|
|
@@ -42,14 +42,14 @@ export default createCloudflareHandler(
|
|
|
42
42
|
)
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
| Factory
|
|
46
|
-
|
|
|
47
|
-
| `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled`
|
|
48
|
-
| `createCloudflareWorkerHandler(factories)`
|
|
49
|
-
| `createCloudflareCronHandler(factories)`
|
|
50
|
-
| `createCloudflareQueueHandler(factories)`
|
|
51
|
-
| `createCloudflareMCPHandler(factories)`
|
|
52
|
-
| `createCloudflareWebSocketHandler(factories)`
|
|
45
|
+
| Factory | For |
|
|
46
|
+
| -------------------------------------------------- | ------------------------------------------------ |
|
|
47
|
+
| `createCloudflareHandler(factories, handlerTypes)` | combined `fetch`/`queue`/`scheduled` |
|
|
48
|
+
| `createCloudflareWorkerHandler(factories)` | HTTP / agent / RPC / workflow-orchestrator units |
|
|
49
|
+
| `createCloudflareCronHandler(factories)` | cron units |
|
|
50
|
+
| `createCloudflareQueueHandler(factories)` | queue-consumer and workflow-step units |
|
|
51
|
+
| `createCloudflareMCPHandler(factories)` | MCP units (same HTTP transport) |
|
|
52
|
+
| `createCloudflareWebSocketHandler(factories)` | channel units, delegating to the DO |
|
|
53
53
|
|
|
54
54
|
`factories` is `{ createConfig, createSingletonServices, createPlatformServices? }`.
|
|
55
55
|
|
|
@@ -71,7 +71,7 @@ const services = await setupServices(env, {
|
|
|
71
71
|
**Do not hand-roll this.** Beyond building `LocalVariablesService` /
|
|
72
72
|
`LocalSecretService` and caching the result, it calls `setSingletonServices()` —
|
|
73
73
|
and the core runners (`fetchData`, `runQueueJob`, `runScheduled`) resolve
|
|
74
|
-
services through that global slot,
|
|
74
|
+
services through that global slot, _not_ through the value you were returned. A
|
|
75
75
|
setup function that only returns the services leaves every request throwing
|
|
76
76
|
"Singleton services not initialized" as a CF `1101`. It also stashes the env via
|
|
77
77
|
`setCloudflareEnv`, which `getCloudflareEnv()` reads for bindings.
|
|
@@ -102,14 +102,17 @@ yarn add @pikku/ws
|
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
```typescript
|
|
105
|
-
import { pikkuWebsocketHandler } from '@pikku/ws'
|
|
105
|
+
import { DEFAULT_WS_MAX_PAYLOAD, pikkuWebsocketHandler } from '@pikku/ws'
|
|
106
106
|
import { stopSingletonServices } from '@pikku/core'
|
|
107
107
|
import { Server } from 'http'
|
|
108
108
|
import { WebSocketServer } from 'ws'
|
|
109
109
|
import './.pikku/pikku-bootstrap.gen.js'
|
|
110
110
|
|
|
111
111
|
const server = new Server()
|
|
112
|
-
const wss = new WebSocketServer({
|
|
112
|
+
const wss = new WebSocketServer({
|
|
113
|
+
noServer: true,
|
|
114
|
+
maxPayload: DEFAULT_WS_MAX_PAYLOAD,
|
|
115
|
+
})
|
|
113
116
|
|
|
114
117
|
pikkuWebsocketHandler({
|
|
115
118
|
server,
|
|
@@ -4,8 +4,11 @@ description: >-
|
|
|
4
4
|
Use for the Pikku dependency security audit: the `pikku audit` CLI command, the
|
|
5
5
|
`.pikku/audit.json` artifact, the `SecurityAuditReport` type in @pikku/core, and the console
|
|
6
6
|
Security screen (getSecurityAudit / runSecurityAudit / updateDependency + SecurityAuditView).
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Also covers `pikku update`, which moves the @pikku/* dependency set forward and reports the
|
|
8
|
+
peers those versions need.
|
|
9
|
+
TRIGGER when: user asks about `pikku audit` or `pikku update`, dependency
|
|
10
|
+
vulnerabilities/advisories, outdated dependencies, upgrading Pikku itself, peer dependency
|
|
11
|
+
conflicts, the Security screen/page in the console, updating a vulnerable dependency, or
|
|
9
12
|
reading/rendering audit.json. DO NOT TRIGGER when: user asks about authentication/sessions/JWT
|
|
10
13
|
(use pikku-security), permissions (use pikku-permissions), or secrets/env vars (use
|
|
11
14
|
pikku-config).
|
|
@@ -43,11 +46,47 @@ installGroups: [core]
|
|
|
43
46
|
`bun outdated`, normalised into one `SecurityAuditReport` with per-severity /
|
|
44
47
|
per-update-level counts). Other PMs are detected but **stubbed** with a `note`
|
|
45
48
|
field until their shapes are normalised — issues/updates come back empty.
|
|
46
|
-
- `bun audit` exits non-zero when it
|
|
49
|
+
- `bun audit` exits non-zero when it _finds_ advisories but still writes the
|
|
47
50
|
payload to stdout, so a non-zero exit **with output** is data. A non-zero exit
|
|
48
51
|
with **no** output — or a launch failure, timeout, or a blown 32MB buffer —
|
|
49
52
|
throws, precisely so a failed run can't masquerade as "0 advisories".
|
|
50
53
|
|
|
54
|
+
## The `pikku update` command
|
|
55
|
+
|
|
56
|
+
Narrower than `audit --outdated`, and the only one that writes: it moves the
|
|
57
|
+
**@pikku/\* set** forward and reports the peers those versions need. Use `audit`
|
|
58
|
+
to learn a dependency is vulnerable; use `update` to move Pikku itself.
|
|
59
|
+
|
|
60
|
+
- `pikku update` — reports only. Nothing is written without `--update`.
|
|
61
|
+
- `pikku update --update` — writes the new ranges into every covered
|
|
62
|
+
package.json, then runs an install. `--no-install` writes and stops.
|
|
63
|
+
- `pikku update --update-peers` — implies `--update` and additionally writes the
|
|
64
|
+
ranges unsatisfied peers require, **for peers the project already declares**.
|
|
65
|
+
A peer it does not declare is reported and never added — adding a dependency
|
|
66
|
+
is not an update. Separate from `--update` because a peer bump can cross a
|
|
67
|
+
**major** of a third-party package (`ai` 5 → 6), which is not a call to make
|
|
68
|
+
on the user's behalf.
|
|
69
|
+
- `--tag <dist-tag>` (default `latest`) reads each package's own dist-tag, so
|
|
70
|
+
`--tag next` moves the whole set onto prereleases. `--registry <url>` defaults
|
|
71
|
+
to `npm_config_registry`.
|
|
72
|
+
- Coverage is the nearest package.json walking up from the project root, plus
|
|
73
|
+
every workspace it declares — a monorepo updates in one pass. All four
|
|
74
|
+
dependency fields are read, `peerDependencies` included, so an addon's own
|
|
75
|
+
declared peer range moves with it.
|
|
76
|
+
|
|
77
|
+
Statuses, per dependency: `outdated` (the range floor is behind latest — this is
|
|
78
|
+
what `--update` writes), `stale-install` (the range already admits latest but
|
|
79
|
+
node_modules is behind — an install fixes it, no edit needed), `linked` (a
|
|
80
|
+
`workspace:`/`file:`/`link:`/`portal:` range — a deliberate local checkout,
|
|
81
|
+
counted but never listed), `manual` (a registry range we refuse to substitute
|
|
82
|
+
into: a union, an x-range, a `*`), `unresolved` (the registry had no such tag —
|
|
83
|
+
this must **never** read as "current", the same rule as a failed audit).
|
|
84
|
+
|
|
85
|
+
Peers are read off the version the run **lands on**, not the one installed —
|
|
86
|
+
the point is what the target needs. An @pikku peer the same run already brings
|
|
87
|
+
forward is not reported, and an unsatisfied _optional_ peer the project never
|
|
88
|
+
declared is skipped.
|
|
89
|
+
|
|
51
90
|
## Console integration (@pikku/addon-console)
|
|
52
91
|
|
|
53
92
|
Three RPCs, all reading/writing the same artifact via the meta service. Shared
|
|
@@ -36,8 +36,8 @@ generated output is never edited by hand.
|
|
|
36
36
|
```jsonc
|
|
37
37
|
// pikku.config.json
|
|
38
38
|
{
|
|
39
|
-
"emailTemplatesDir": "emails",
|
|
40
|
-
"outDir": ".pikku"
|
|
39
|
+
"emailTemplatesDir": "emails", // relative to rootDir; omit to disable emails
|
|
40
|
+
"outDir": ".pikku", // gen lands in <outDir>/email/
|
|
41
41
|
}
|
|
42
42
|
```
|
|
43
43
|
|
|
@@ -121,9 +121,9 @@ render with sample data and read the result rather than trusting that it compile
|
|
|
121
121
|
|
|
122
122
|
```ts
|
|
123
123
|
const rendered = renderEmailTemplate({
|
|
124
|
-
name: 'verify-email',
|
|
125
|
-
locale: 'en',
|
|
126
|
-
data: { verifyUrl: url },
|
|
124
|
+
name: 'verify-email', // EmailTemplateName (autocompleted)
|
|
125
|
+
locale: 'en', // optional, defaults to 'en'
|
|
126
|
+
data: { verifyUrl: url }, // EmailTemplateVariables<'verify-email'>
|
|
127
127
|
})
|
|
128
128
|
// rendered: { name, locale, subject, html, text?, variables, hash }
|
|
129
129
|
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-fabric
|
|
3
|
-
description: 'Build and convert apps for the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `fabric.config.json`, and the pikku-verify workflow. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, or asking about Fabric deployment, database, or project conventions. DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy-cloudflare, pikku-deploy-fastify, etc. instead.'
|
|
3
|
+
description: 'Build and convert apps for the Pikku Fabric platform. Covers SQLite/libSQL database setup with Kysely, fabric project layout, deploy provider config, `fabric.config.json`, and the pikku-verify workflow. TRIGGER when: user is working on a Fabric-hosted Pikku project, converting an app to Fabric format, or asking about Fabric deployment, database, or project conventions. TRIGGER when: user asks about a `pikku fabric validate` finding, including app-missing-actor-quick-login. DO NOT TRIGGER when: user is working on a generic (non-Fabric) Pikku deployment — use pikku-deploy-cloudflare, pikku-deploy-fastify, etc. instead.'
|
|
4
4
|
installGroups: [fabric]
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -115,7 +115,7 @@ and refuses a database outside the runtime directory. Anything a real environmen
|
|
|
115
115
|
needs — accounts, role grants — is provisioning, not seeding, and belongs in
|
|
116
116
|
`pikku persona sync` or a migration.
|
|
117
117
|
|
|
118
|
-
A Better Auth app has a second constraint: the plugins you enable (`
|
|
118
|
+
A Better Auth app has a second constraint: the plugins you enable (`ban()`,
|
|
119
119
|
`actor()`, …) each declare columns, and `pikku db migrate` refuses to run while
|
|
120
120
|
the applied schema is missing any of them. `pikku db generate` writes the
|
|
121
121
|
migration that closes the gap.
|
|
@@ -311,13 +311,37 @@ Always call the `pikku-verify` tool after modifying functions, wirings, or schem
|
|
|
311
311
|
|
|
312
312
|
The output card shows whether any breaking changes were detected.
|
|
313
313
|
|
|
314
|
+
### `app-missing-actor-quick-login-<app>`
|
|
315
|
+
|
|
316
|
+
The `fabric validate` finding people most often misread. It fires when an app has
|
|
317
|
+
a **login screen** but no dev actor switcher, and it is not a style nit: a sandbox
|
|
318
|
+
reviewer has no seed password, so without the control they are locked out of the
|
|
319
|
+
app they were asked to look at.
|
|
320
|
+
|
|
321
|
+
Satisfy it with `<DevActorSwitcher />` from `@pikku/mantine/dev`, or with your
|
|
322
|
+
own UI built on `useDevActors()` from `@pikku/react` — validate accepts either
|
|
323
|
+
call site as evidence, so custom rendering passes. See **pikku-react** for the
|
|
324
|
+
props and **pikku-scenario** for where the actor list comes from.
|
|
325
|
+
|
|
326
|
+
The validator also accepts the shapes that predate the package — a hand-rolled
|
|
327
|
+
`signInAsActor()` or a literal `POST /auth/sign-in/actor` — so an older app does
|
|
328
|
+
not fail the build. **Treat that as a grace period, not the target: migrate those
|
|
329
|
+
to `<DevActorSwitcher />`.** The hand-copied version is exactly the duplication
|
|
330
|
+
the package exists to remove, and the copies drift — the ones that prompted this
|
|
331
|
+
had already diverged on the `import.meta.env.DEV` gate that keeps the shared
|
|
332
|
+
secret out of production bundles.
|
|
333
|
+
|
|
334
|
+
Do **not** satisfy it with Better Auth's `/dev/quick-login`. That is a different
|
|
335
|
+
endpoint with a different purpose — one fixed admin, not the declared personas —
|
|
336
|
+
and it does not clear this rule.
|
|
337
|
+
|
|
314
338
|
## Hard rules
|
|
315
339
|
|
|
316
340
|
These apply in every Fabric app:
|
|
317
341
|
|
|
318
342
|
- **No `process.env`** — use `variables.get('NAME')` and `secrets.getSecret('NAME')`. Declare with `defineVariable` / `defineSecret`.
|
|
319
343
|
- **No `as any`** — fix types properly.
|
|
320
|
-
- **No generic `Error`** — throw `NotFoundError`, `ConflictError`, `BadRequestError`, `UnauthorizedError` from
|
|
344
|
+
- **No generic `Error`** — throw `NotFoundError`, `ConflictError`, `BadRequestError`, `UnauthorizedError` from `#pikku/error`.
|
|
321
345
|
- **No auth checks in function bodies** — use `permissions:` field on the function config with a `pikkuPermission` factory.
|
|
322
346
|
- **No hand-editing `.pikku/db/schema.gen.ts`** — write a migration and re-run `pikku db migrate`.
|
|
323
347
|
- **One runtime unit per file** — never define multiple functions/workflows in a single source file.
|
|
@@ -87,7 +87,7 @@ without it, even though the flag reads as optional.
|
|
|
87
87
|
pikku fabric status # active + in-flight deployment, per stage, with gitSha
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Check this
|
|
90
|
+
Check this _before_ deep-diving. A stage still serving an older `gitSha`, or a
|
|
91
91
|
deploy stuck in flight, explains a whole class of "my fix did nothing".
|
|
92
92
|
|
|
93
93
|
## Known gaps — do not misread these as bugs in your app
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pikku-feature
|
|
3
|
-
description: 'Drive create-a-feature work
|
|
3
|
+
description: 'Drive create-a-feature work inside a Pikku project that already exists: discover project context, work on a feature branch, implement + verify + commit, and ask the user to review via the diff. TRIGGER when: the user asks to "create a feature", "add X to my Pikku project", "wire up a new endpoint", or anything that implies turning a natural-language request into Pikku functions/wirings/migrations within a working app. DO NOT TRIGGER when: the user asks for a one-off code edit in an existing function, asks about Pikku concepts (use pikku-concepts), or is building a whole app from a fresh scaffold rather than extending one (use pikku-build-app, or pikku-build-quick / pikku-build-platform).'
|
|
4
4
|
installGroups: [core]
|
|
5
5
|
allowed-tools: Bash(yarn pikku meta *), Bash(yarn pikku all *), Bash(yarn tsc), Bash(git status *), Bash(git diff *), Bash(git switch *), Bash(git checkout *), Bash(git checkout -b *), Bash(git add *), Bash(git commit *), Bash(git log *), Bash(git branch *)
|
|
6
6
|
argument-hint: '<feature description>'
|
|
@@ -121,7 +121,7 @@ in-app features don't.
|
|
|
121
121
|
queue-dispatched.
|
|
122
122
|
- **Auth checks belong on the function or wiring**, not in function bodies.
|
|
123
123
|
Use the `permissions` field with a `pikkuPermission` factory.
|
|
124
|
-
- **Throw typed errors** from
|
|
124
|
+
- **Throw typed errors** from `#pikku/error` — `NotFoundError`,
|
|
125
125
|
`ConflictError`, `BadRequestError`. Never bare `Error`.
|
|
126
126
|
- **Migrations are inline SQL files** in the project's migrations dir
|
|
127
127
|
(typically `sql/`). Use a numbered prefix matching existing files.
|
|
@@ -141,8 +141,9 @@ Some patterns vary by project; **read a neighbour file before writing**:
|
|
|
141
141
|
`CreateTodoOutput`) passed to `input`/`output` on the func config — vs
|
|
142
142
|
generic-typed config. Schema name **must match codegen expectations** (the
|
|
143
143
|
exported const name = the schema name in generated `.gen.json`).
|
|
144
|
-
- **Imports**:
|
|
145
|
-
|
|
144
|
+
- **Imports**: `#pikku` is a namespace, not a module — one subpath per wiring.
|
|
145
|
+
`pikkuFunc` / `pikkuSessionlessFunc` come from `'#pikku/function'`, `wireHTTP`
|
|
146
|
+
from `'#pikku/http'`. Copy what neighbours do.
|
|
146
147
|
- **Service usage**: e.g. `kysely`, `redis`. Look at how an existing function
|
|
147
148
|
destructures services from its first arg. **Check `application-types.d.ts`**
|
|
148
149
|
to see whether services like `kysely` are typed (`Kysely<DB>`) or untyped
|