@pikku/skills 0.12.59 → 0.12.61

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pikku/skills",
3
- "version": "0.12.59",
3
+ "version": "0.12.61",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/pikkujs/pikku.git",
@@ -49,7 +49,7 @@ wireAddon({
49
49
  rpcEndpoint?: string, // Optional remote RPC endpoint for distributed execution
50
50
  auth?: boolean, // Require a session for every function in the addon
51
51
  mcp?: boolean | string[], // true: every function the addon declared mcp: true; a list: the tools this app offers, typed against the addon's function names
52
- expose?: boolean | string[], // what rpc.exposed / POST /rpc may reach: unset/true = the addon's own expose: true, false = none, a list = exactly those (typed; PKU343 on unknown names)
52
+ expose?: boolean | string[], // opt-in: what rpc.exposed / POST /rpc may reach. true = every function the addon declared expose: true, a list = those of them named (typed; PKU343 on unknown or undeclared names), unset/false = none
53
53
  tags?: string[], // Tags applied to all addon functions
54
54
  scopes?: string[], // Required of every function, on top of its own
55
55
  secretOverrides?: Record<string, string>, // Remap secret names (and grant them)
@@ -279,6 +279,45 @@ everything else. Leave it out and the addon installs fine and fails to typecheck
279
279
  in every app that depends on it, which is what `pikku validate` is there to
280
280
  catch before you publish.
281
281
 
282
+ ### Remote functions and the SDK
283
+
284
+ A `remote: true` function is called over the wire by apps that use
285
+ `wireRemoteAddon`. Those apps build against the addon's SDK, not its source, so
286
+ **a remote function's input and output must be exported zod schemas**. If it
287
+ uses generics (`pikkuSessionlessFunc<In, Out>`) or returns a type declared in
288
+ `src/`, codegen fails with PKU922. Functions that are not remote can keep their
289
+ generics.
290
+
291
+ ```typescript
292
+ // src/functions/listNotes.schemas.ts
293
+ import { z } from 'zod'
294
+
295
+ export const NoteRows = z.array(z.object({ id: z.string(), body: z.string() }))
296
+ ```
297
+
298
+ ```typescript
299
+ // src/functions/listNotes.function.ts
300
+ import { pikkuSessionlessFunc } from '#pikku/addon/function'
301
+ import { NoteRows } from './listNotes.schemas.js'
302
+
303
+ export const listNotes = pikkuSessionlessFunc({
304
+ output: NoteRows,
305
+ func: async ({ kysely }) =>
306
+ kysely.selectFrom('notes').select(['id', 'body']).execute(),
307
+ remote: true,
308
+ })
309
+ ```
310
+
311
+ ```bash
312
+ yarn pikku all # also writes .pikku/sdk/ + <scope>-<name>-<version>.sdk.tgz
313
+ yarn pikku versions check # fails on a remote contract changed without a new version
314
+ ```
315
+
316
+ In an addon, `pikku all` packs the SDK on every run, at the package.json
317
+ version. The SDK carries only the remote RPCs, the `expose: true` workflows and
318
+ the types they reach. Changing a published `remote: true` contract needs a new
319
+ `version` on the function, which `pikku versions check` enforces.
320
+
282
321
  ### Database tables
283
322
 
284
323
  An addon may **ship** tables. It must never **create** them: it runs inside the
@@ -361,6 +400,13 @@ covered, and writes only the delta after an addon upgrade. An addon wired with
361
400
  `wireRemoteAddon` contributes no schema at all: its tables belong to the
362
401
  deployment that runs its functions.
363
402
 
403
+ A `wireRemoteAddon` whose `package` carries a range (`'@scope/name@^1.4.0'`)
404
+ is not installed at all: `pikku all` downloads its SDK into
405
+ `.pikku/remote` and pins it in `pikku-addons.lock` (commit the lock). If
406
+ codegen reports PKU338 for one, run `pikku all`, not an install; to move
407
+ to a newer SDK, edit the range by hand (`pikku update` prints the file:line
408
+ and the new literal; it never rewrites source), then run `pikku all`.
409
+
364
410
  ### Call via RPC
365
411
 
366
412
  ```typescript
@@ -381,9 +427,10 @@ and the frontend calls `rpc.invoke('todos:listTodos', …)` (over HTTP,
381
427
  Use `ref('todos:listTodos')` on an HTTP or MCP wiring only when the addon needs
382
428
  a route of its own. Don't write an app function that calls `rpc.invoke` and
383
429
  returns the result unchanged: it's a second name and a second schema for the
384
- same thing, and it drifts. `expose: true` exposes only what the addon itself
385
- declared `expose: true` — an OpenAPI-generated addon declares none, so list
386
- the names.
430
+ same thing, and it drifts. Nothing in an addon is reachable over `/rpc` until
431
+ the wiring opts in, and it can only open what the addon itself declared
432
+ `expose: true` — an OpenAPI-generated addon declares every operation, so list
433
+ the ones the frontend needs.
387
434
 
388
435
  Write your own function when the app needs the data narrowed, typed, or
389
436
  combined (a flag the upstream sends as `"0"`, a total summed from several
@@ -5,6 +5,7 @@
5
5
  - Package Structure
6
6
  - pikku.config.json
7
7
  - package.json (key fields)
8
+ - Platform service contributors (`pikku.contributors`)
8
9
  - tsconfig.json (key fields)
9
10
 
10
11
  `npx pikku new addon` scaffolds these files. You rarely hand-edit them — consult this when wiring exports or config by hand.
@@ -98,6 +99,58 @@ live in a file that never imports `#pikku` — see "Functions" in the skill.
98
99
  writes `@my-org/addon-todos/.pikku/rpc/...`, exactly as it would in an
99
100
  application, and the leaf stays the package's own business.
100
101
 
102
+ ## Platform service contributors (`pikku.contributors`)
103
+
104
+ An addon can ship code that runs in the **host's** generated
105
+ `createPlatformServices` — a log source over the host's database, a client
106
+ built from a host secret. `wireAddon` has no field for it; the package declares
107
+ it in its own `package.json`, the same shape as the host's `dev.contributors`:
108
+
109
+ ```json
110
+ {
111
+ "exports": {
112
+ "./contributors": {
113
+ "types": "./dist/src/contributors.d.ts",
114
+ "import": "./dist/src/contributors.js"
115
+ }
116
+ },
117
+ "pikku": {
118
+ "contributors": [
119
+ { "module": "@my-org/addon-logs/contributors", "export": "logSources" }
120
+ ]
121
+ }
122
+ }
123
+ ```
124
+
125
+ `module` resolves from the addon's package directory. Every app that wires the
126
+ addon (not `wireRemoteAddon`) picks the contributors up: `pikku all` emits them
127
+ into `.pikku/pikku-dev-platform-services.gen.ts` for `pikku dev`, and
128
+ `pikku deploy plan` / `apply` hands them to the provider adapter, after the
129
+ app's own `deploy.contributors`.
130
+
131
+ Declare what a contributor reads and sets, rather than relying on list order:
132
+
133
+ ```ts
134
+ export const logSources: PlatformServiceContributor = {
135
+ name: 'log-sources',
136
+ needs: ['kysely'], // runtime.kysely, or a contributor's services.kysely
137
+ provides: ['logSources', 'logSources.workspace'],
138
+ imports: [`import { kyselyLogSource } from '@my-org/addon-logs/log-source'`],
139
+ emit: () => [
140
+ ` services.logSources = { workspace: kyselyLogSource(runtime.kysely) }`,
141
+ ],
142
+ }
143
+ ```
144
+
145
+ Contributors are sorted so each runs after every contributor providing one of
146
+ its `needs`; a dotted need (`logSources.stage`) is met by a provider of the key
147
+ or of its prefix. Emitted lines read services the entry built before them as
148
+ `runtime.<name>` — `pikku dev` has logger, variables, secrets, schema and (with
149
+ `db`) kysely; the standalone entry has logger, kysely (with a database) and its
150
+ scheduler/queue/workflow/trigger services and event hub. Codegen **fails** on a
151
+ need nothing meets, on two contributors providing the same key, and on a cycle,
152
+ naming the contributors involved.
153
+
101
154
  ## tsconfig.json (key fields)
102
155
 
103
156
  ```json
@@ -179,8 +179,12 @@ in the repo, green or red on every future run.
179
179
  }
180
180
  ```
181
181
 
182
- The module exports a `PlatformServiceContributor` (`{ name, imports?, emit }`, the
183
- type `@pikku/deploy` takes). `pikku all` writes `.pikku/pikku-dev-platform-services.gen.ts`
182
+ The module exports a `PlatformServiceContributor` (`{ name, needs?, provides?, imports?, emit }`, the
183
+ type `@pikku/deploy` takes). Addons the project wires add their own, declared in their
184
+ package.json as `pikku.contributors` (see the pikku-addon skill's package manifest
185
+ reference). Contributors run in the order their `needs`/`provides` require, and the
186
+ generated `createPlatformServices(env, runtime)` gets the local services as `runtime`;
187
+ an unmet need, a key provided twice or a cycle fails `pikku all`. `pikku all` writes `.pikku/pikku-dev-platform-services.gen.ts`
184
188
  from them, and `pikku dev` merges what it returns over the local services before
185
189
  `createSingletonServices` runs, so a contributed `emailService` beats
186
190
  `LocalEmailService` (and the test-mode spy). Nothing is written without
@@ -60,7 +60,7 @@ Most surfaces are switched on by the CLI rather than hand-wired:
60
60
  bunx --bun pikku enable workflow # workflow workers
61
61
  bunx --bun pikku enable agent # public agent endpoints
62
62
  bunx --bun pikku enable events # realtime events channel + SSE stream
63
- bunx --bun pikku enable remote-rpc # internal RPC queue worker + HTTP endpoint
63
+ bunx --bun pikku enable mesh-rpc # mesh RPC queue worker + HTTP endpoint
64
64
  bunx --bun pikku enable webhook # outgoing webhook delivery queue worker
65
65
  bunx --bun pikku enable scenarios # scenario instrumentation
66
66
  bunx --bun pikku enable rpc # public RPC endpoint
@@ -25,6 +25,13 @@ with Node, or a self-contained executable compiled with `bun build --compile`.
25
25
  factories the deploy codegen expects, and hand-rolling an `ExportedHandler` is
26
26
  how a worker deploy fails at runtime instead of at build.
27
27
 
28
+ Platform services a host injects into the bundle come from contributors:
29
+ `deploy.contributors` in `pikku.config.json` (`[{ "module", "export"? }]`, the
30
+ same shape as `dev.contributors`) plus the `pikku.contributors` every wired addon
31
+ declares in its package.json. `plan` and `apply` pass them all to the adapter,
32
+ which orders them by `needs`/`provides` and, on standalone, runs them after the
33
+ database is open, with `runtime.logger` and `runtime.kysely` in reach.
34
+
28
35
  Always run `plan` before `apply`, and read it. It names what will be created,
29
36
  updated and deleted — the deletions are the reason to look.
30
37
 
@@ -67,7 +74,7 @@ builds anyway (`config`, `logger`, `variables`, `schema`, `secrets`, and the
67
74
  per-request `rpc`/`mcp`/`channel`/`userSession`). Units are named for that set —
68
75
  `svc-todo-store`, `svc-event-hub-todo-store`, `svc-base` for functions needing
69
76
  nothing else — and each records it as `servicesKey` in the manifest. On the
70
- `templates/functions` app it turns 44 units into 10.
77
+ `recipes/functions` app it turns 44 units into 10.
71
78
 
72
79
  The deploy target is part of the key, so a `server` unit is named `-server` and
73
80
  can never merge with its serverless twin. That is what keeps `services` from
@@ -124,6 +124,13 @@ Config lives in `pikku.config.json`:
124
124
  }
125
125
  ```
126
126
 
127
+ A config can `extends` another (`"extends": "./pikku.config.json"`) and state only
128
+ what differs. The child merges into the parent: objects merge key by key,
129
+ `srcDirectories` is the parent's list plus the child's new entries, every other
130
+ array and value the child sets replaces the parent's, and `null` removes a
131
+ parent value. Each file's paths are relative to that file, and `rootDir` is
132
+ inherited unless the child sets its own.
133
+
127
134
  ## Project Structure Convention
128
135
 
129
136
  ```text
@@ -13,14 +13,14 @@
13
13
  | Method | Purpose |
14
14
  | -------------------------------- | ----------------------------------------- |
15
15
  | `rpc.invoke(name, data)` | Internal call to any wired function |
16
- | `rpc.remote(name, data)` | Remote call via DeploymentService |
16
+ | `rpc.mesh(name, data)` | Mesh call via DeploymentService |
17
17
  | `rpc.exposed(name, data)` | Call functions marked with `expose: true` |
18
18
  | `rpc.startWorkflow(name, input)` | Start a workflow (see `pikku-workflow`) |
19
19
  | `rpc.agent.run/stream(...)` | Run an AI agent (see `pikku-agent`) |
20
20
  | `rpc.agent.resume/approve(...)` | Answer a tool-approval interrupt |
21
21
  | `rpc.agent.interrupt(runId)` | Stop an in-flight run |
22
22
 
23
- `rpc.invoke`, `rpc.remote` and `rpc.startWorkflow` are typed off the generated
23
+ `rpc.invoke`, `rpc.mesh` and `rpc.startWorkflow` are typed off the generated
24
24
  RPC map, so the name and the payload are checked. `rpc.exposed` is deliberately
25
25
  `(name: string, data: any) => Promise<any>` — it exists to dispatch a name that
26
26
  arrived from outside, which by definition cannot be checked at compile time.