.claude/skills/pikku-kysely/SKILL.md
Use when setting up SQL database services with Kysely in a Pikku app. Covers @pikku/kysely (base), @pikku/kysely-postgres, @pikku/kysely-mysql, @pikku/kysely-sqlite — channel stores, workflow services, secret services, AI storage, agent runs, and deployment services. TRIGGER when: code uses Kysely, PikkuKysely, KyselyChannelStore, KyselyWorkflowService, KyselySecretService, or user asks about SQL database setup, Postgres/MySQL/SQLite with Pikku. DO NOT TRIGGER when: user asks about MongoDB (use pikku-mongodb) or Redis (use pikku-redis).
npx skillsauth add pikkujs/pikku pikku-kyselyInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Pikku provides SQL database services through four packages:
@pikku/kysely — Base service implementations (database-agnostic)@pikku/kysely-postgres — PostgreSQL-specific implementations + PikkuKysely connection wrapper@pikku/kysely-mysql — MySQL-specific implementations@pikku/kysely-sqlite — SQLite-specific implementations + createSQLiteKysely factoryAll implement standard Pikku interfaces from @pikku/core.
# Pick your database
yarn add @pikku/kysely @pikku/kysely-postgres # PostgreSQL
yarn add @pikku/kysely @pikku/kysely-mysql # MySQL
yarn add @pikku/kysely @pikku/kysely-sqlite # SQLite
PikkuKyselyimport { PikkuKysely } from '@pikku/kysely-postgres'
const db = new PikkuKysely<DB>(
logger: Logger,
connectionOrConfig: postgres.Sql | postgres.Options | string,
defaultSchemaName?: string
)
await db.init()
db.kysely // Kysely<DB> instance for queries
await db.close()
createSQLiteKyselyimport { createSQLiteKysely } from '@pikku/kysely-sqlite'
const kysely = createSQLiteKysely(database: SqliteDatabase | (() => Promise<SqliteDatabase>))
Each database variant exports these services with a prefix (Pg, MySQL, SQLite, or base Kysely):
| Service | Interface | Purpose |
|---------|-----------|---------|
| *ChannelStore | ChannelStore | WebSocket channel state persistence |
| *EventHubStore | EventHubStore | Event hub state persistence |
| *WorkflowService | PikkuWorkflowService | Workflow definition storage |
| *WorkflowRunService | WorkflowRunService | Workflow execution tracking |
| *DeploymentService | DeploymentService | Deployment state management |
| *AIStorageService | AIStorageService, AIRunStateService | AI conversation/run storage |
| *AgentRunService | AgentRunService | Agent execution tracking |
| *SecretService | SecretService | Encrypted secret storage (envelope encryption) |
All services take a Kysely<KyselyPikkuDB> instance in their constructor and have an init() method that creates tables if needed.
import { PgKyselySecretService } from '@pikku/kysely-postgres'
const secrets = new PgKyselySecretService(db.kysely, {
kekSecret: 'your-key-encryption-key',
salt: 'your-salt',
})
await secrets.init()
await secrets.setSecretJSON('api-key', { key: 'sk-...' })
const value = await secrets.getSecretJSON<{ key: string }>('api-key')
await secrets.rotateKEK() // Re-encrypt all secrets with new KEK
import { PikkuKysely, PgKyselyChannelStore, PgKyselyWorkflowService } from '@pikku/kysely-postgres'
const createSingletonServices = pikkuServices(async (config) => {
const logger = new PinoLogger()
const db = new PikkuKysely(logger, config.databaseUrl)
await db.init()
const channelStore = new PgKyselyChannelStore(db.kysely)
await channelStore.init()
const workflowService = new PgKyselyWorkflowService(db.kysely)
await workflowService.init()
return { config, logger, database: db, channelStore, workflowService }
})
import { createSQLiteKysely, SQLiteKyselyChannelStore } from '@pikku/kysely-sqlite'
import Database from 'better-sqlite3'
const kysely = createSQLiteKysely(new Database('app.db'))
const channelStore = new SQLiteKyselyChannelStore(kysely)
await channelStore.init()
import { MySQLKyselyWorkflowService } from '@pikku/kysely-mysql'
const workflowService = new MySQLKyselyWorkflowService(kyselyInstance)
await workflowService.init()
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.
development
Make a Pikku frontend work in both English (LTR) and Arabic / right-to-left languages. Direction is derived from the active locale, applied once at the document root, and the layout mirrors itself — but only if styling is written flow-relative (margin-inline-start, text-align: start, Mantine ms/me) instead of left/right. TRIGGER when: adding Arabic (or Hebrew/Farsi/Urdu), asked to "support RTL / right-to-left / bidi / mirror the layout", or writing layout styles in an app that may run RTL. Builds on pikku-i18n (an RTL language is just another locale file). DO NOT TRIGGER for backend functions or for LTR-only copy changes.
development
Wire i18n into a Pikku frontend (Vite SPA, Vite SSR, or Next.js app-router) with react-i18next + i18next. English by default, every user-facing string goes through a `t()` token, and additional languages are served under `/de` `/es` URL prefixes. TRIGGER when: scaffolding or editing a frontend and writing user-facing text, adding a second language, or asked to "make this translatable / use tokens / add i18n". DO NOT TRIGGER for backend functions, error messages thrown from functions, or log output.
development
Use when translating an n8n Code node body into a real Pikku function body. Triggered when the user opens or points at a stub generated by @pikku/n8n-import (look for `STUB — generated from n8n Code node` in the file's JSDoc), or when the user says 'translate this n8n code', 'port this n8n code node', 'finish the codeStub__... function', etc. The stub file is a `pikkuSessionlessFunc` with a Zod input/output, a JSDoc preserving the original n8n JavaScript verbatim, and a `throw new Error('… — implement me')` body.