packages/cli/skills/pikku-queue/SKILL.md
Use when adding background job processing, async task queues, or distributed workers to a Pikku app. Covers wireQueueWorker, job enqueuing, progress tracking, retries, BullMQ and PgBoss adapters. TRIGGER when: code uses wireQueueWorker, user asks about background jobs, task queues, async processing, BullMQ, PgBoss, or job retries. DO NOT TRIGGER when: user asks about scheduled cron tasks (use pikku-cron) or event-driven triggers (use pikku-trigger).
npx skillsauth add pikkujs/pikku pikku-queueInstall 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.
Use this skill as an execution checklist, not reference material.
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need..pikku, node_modules, vendored packages, or broad build artifacts.pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.Wire Pikku functions as background queue workers. Supports job control (progress, retry, discard), configurable concurrency, and type-safe job publishing.
pikku info functions --verbose # See existing functions and their types
pikku info tags --verbose # Understand project organization
See pikku-concepts for the core mental model.
wireQueueWorker(config)import { wireQueueWorker } from '@pikku/core/queue'
wireQueueWorker({
name: string, // Queue name (unique identifier)
func: PikkuFunc, // Worker function
config?: {
batchSize?: number, // Process N jobs at once
removeOnComplete?: number | boolean, // Clean up completed jobs
},
})
wire.queue)Inside queue worker functions:
wire.queue.updateProgress(percent: number) // Report progress (0-100)
wire.queue.discard(reason: string) // Silently discard job
wire.queue.fail(reason: string) // Mark job as failed
const jobId = await queue.add(queueName, data, options?)
Options:
{
priority?: number, // Higher = processed first
delay?: number, // Delay in ms before processing
attempts?: number, // Max retry attempts
backoff?: {
type: 'exponential' | 'fixed',
delay: number, // Base delay in ms
},
}
const processReminder = pikkuSessionlessFunc({
title: 'Process Reminder',
func: async ({ db, emailService }, { todoId, userId }) => {
const todo = await db.getTodo(todoId)
await emailService.sendReminder(userId, todo)
return { sent: true }
},
})
wireQueueWorker({
name: 'todo-reminders',
func: processReminder,
})
const processReminder = pikkuSessionlessFunc({
title: 'Process Reminder',
func: async ({ db }, { todoId }, wire) => {
await wire.queue.updateProgress(25)
const todo = await db.getTodo(todoId)
if (!todo) {
await wire.queue.discard('Todo not found')
return
}
if (todo.completed) {
await wire.queue.fail('Todo already completed')
return
}
await wire.queue.updateProgress(100)
return { sent: true }
},
})
wireQueueWorker({
name: 'todo-reminders',
func: processReminder,
config: {
batchSize: 5,
removeOnComplete: 100,
},
})
// Enqueue with retry options
const jobId = await queue.add(
'todo-reminders',
{
todoId: 'abc-123',
userId: 'user-456',
},
{
priority: 10,
delay: 5000,
attempts: 3,
backoff: { type: 'exponential', delay: 1000 },
}
)
After npx pikku prebuild:
import { PikkuQueue } from '.pikku/pikku-queue.gen.js'
const queue = new PikkuQueue(queueService)
const jobId = await queue.add('todo-reminders', {
todoId: 'abc-123',
userId: 'user-456',
})
const job = await queue.getJob('todo-reminders', jobId)
const status = await job.status() // 'waiting' | 'active' | 'completed' | 'failed'
const result = await job.waitForCompletion(30_000)
BullMQ (Redis-based):
import { BullMQQueueService } from '@pikku/queue-bullmq'
const queueService = new BullMQQueueService({
connection: { host: 'localhost', port: 6379 },
})
PgBoss (PostgreSQL-based):
import { PgBossQueueService } from '@pikku/queue-pg-boss'
const queueService = new PgBossQueueService({
connectionString: 'postgres://...',
})
// functions/email.functions.ts
export const sendWelcomeEmail = pikkuSessionlessFunc({
title: 'Send Welcome Email',
func: async ({ emailService, db }, { userId }, wire) => {
await wire.queue.updateProgress(10)
const user = await db.getUser(userId)
if (!user) {
await wire.queue.discard('User not found')
return
}
await wire.queue.updateProgress(50)
await emailService.send({
to: user.email,
subject: 'Welcome!',
template: 'welcome',
data: { name: user.name },
})
await wire.queue.updateProgress(100)
return { sent: true, email: user.email }
},
})
// wirings/queue.wiring.ts
wireQueueWorker({
name: 'welcome-emails',
func: sendWelcomeEmail,
config: { removeOnComplete: 100 },
})
// Enqueue from another function
export const registerUser = pikkuSessionlessFunc({
title: 'Register User',
func: async ({ db, queue }, { email, name }) => {
const user = await db.createUser({ email, name })
await queue.add('welcome-emails', { userId: user.id })
return { user }
},
})
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.