Two functions. Everything wired.
pikkuServices creates singletons at startup. pikkuWireServices creates per-request services. That's the whole model.
Created once at startup, shared across all requests. Database pools, loggers, JWT, third-party clients. Receives (config, existingServices).
Created fresh per HTTP request, queue job, CLI command, or WebSocket lifetime. Sessions, transactions, audit contexts. Receives (singletonServices, wire).
Functions declare dependencies explicitly. Pikku merges singleton + wire services so your function sees one flat object. Only pull what you use.
export const createSingletonServices = pikkuServices(
async (config, existingServices) => {
const variables =
existingServices?.variables ??
new TypedVariablesService(new LocalVariablesService());
const secrets =
existingServices?.secrets ??
new TypedSecretService(new LocalSecretService(variables));
const logger =
existingServices?.logger ?? new JsonConsoleLogger();
const schema =
existingServices?.schema ?? new CFWorkerSchemaService(logger);
const paymentService =
existingServices?.paymentService ?? new FakePaymentService();
// In CF Workers DATABASE_URL is injected by Fabric.
// In local dev (pikku dev) existingServices.kysely is the node:sqlite instance.
// In tests existingServices.kysely is provided by the test support layer.
let kysely: Kysely<DB> | undefined;
if (existingServices?.kysely) {
kysely = existingServices.kysely as Kysely<DB>;
} else {
const databaseUrl = await variables.get('DATABASE_URL');
if (databaseUrl) {
if (/^postgres(ql)?:\/\//.test(databaseUrl)) {
const [{ PostgresJSDialect }, postgres] =
await Promise.all([
import('kysely-postgres-js'),
import('postgres'),
]);
kysely = new Kysely<DB>({
dialect: new PostgresJSDialect({
postgres: postgres.default(databaseUrl),
}),
plugins: [new CamelCasePlugin()],
});
} else {
kysely = new Kysely<DB>({
dialect: new LibsqlWebDialect({ url: databaseUrl }),
plugins: [new CamelCasePlugin()],
});
}
} else {
throw new Error(
'kysely not provided: set DATABASE_URL or pass kysely via existingServices',
);
}
}
return {
...(existingServices ?? {}),
config,
variables,
secrets,
logger,
schema,
kysely,
paymentService,
audit: existingServices?.audit ?? new NoopAuditService(),
};
},
);
import { pikkuWireServices } from '../.pikku/pikku-types.gen.js';
export const createWireServices = pikkuWireServices(
async (_singletonServices, _wire) => {
// Created fresh per HTTP request, queue job, CLI command, or WebSocket lifetime.
// Pikku merges these with singletons so functions see one flat object.
// Add per-request services here — e.g. scoped loggers, transactions, etc.
return {};
},
);
Only load what you actually use.
Pikku's CLI generates requiredSingletonServices — a map of which services your filtered functions need. Pair it with dynamic import() calls to skip everything else.
Pikku analyzes which services each function, middleware, and permission actually destructures from the services parameter.
A boolean map marking each service true or false. Plus a RequiredSingletonServices type that narrows your factory's return type.
Guard heavy imports with if (requiredSingletonServices.x). When a service isn't needed, the import is never executed — zero cold-start overhead.
// .pikku/pikku-services.gen.ts (auto-generated)
export const requiredSingletonServices = {
'kysely': true, // used by getItem, createOrder, etc.
'paymentService': true, // used by processPayment
'logger': true, // used everywhere
'secrets': false, // not used by any wired function
} as const
export type RequiredSingletonServices =
Pick<SingletonServices, 'kysely' | 'paymentService' | 'logger'>
& Partial<Omit<SingletonServices, 'kysely' | 'paymentService' | 'logger'>>
import { requiredSingletonServices } from '../.pikku/pikku-services.gen.js';
// requiredSingletonServices is a map of booleans generated by pikku.
// If a service is false, no wired function needs it — safe to skip.
if (requiredSingletonServices.kysely) {
// Dynamic import only when actually needed — skip on cold starts that don't use DB
const { Kysely, CamelCasePlugin } = await import('kysely');
(void Kysely, CamelCasePlugin); // referenced to satisfy linter in snippet context
}
Start building in 5 minutes.
Scaffold a project with services pre-configured. Add your own, destructure what you need, and deploy anywhere.
MIT Licensed · Works with Express, Fastify, Lambda & Cloudflare