xiaohongshu
xiaohongshu
create-utility-service
Create a utility service for cross-cutting concerns. Use when creating services for authentication, authorization, email, notifications, or other shared functionality that doesn't directly map to a domain entity.
Full skill instructions
Creates a service for cross-cutting concerns or specialized functionality. Unlike resource services, utility services don't extend BaseService and typically don't inject repositories.
Location: src/services/{service-name}.service.ts
Naming: Descriptive, kebab-case (e.g., authentication.service.ts, email.service.ts)
Use this skill when creating services that:
Examples: AuthenticationService, AuthorizationService, EmailService, NotificationService
Services that communicate with external systems.
import { env } from "@/env";
import { ServiceUnavailableError, UnauthenticatedError } from "@/errors";
import { responseSchema, type ResponseType } from "@/schemas/response.schema";
export class ExternalApiService {
private readonly baseUrl: string;
constructor() {
this.baseUrl = env.EXTERNAL_SERVICE_URL;
if (!this.baseUrl) {
throw new ServiceUnavailableError(
"External service is not properly configured.",
);
}
}
async fetchData(token: string): Promise<ResponseType> {
try {
const response = await fetch(`${this.baseUrl}/endpoint`, {
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
});
if (!response.ok) {
this.handleHttpError(response.status);
}
const rawData = await response.json();
return this.validateResponse(rawData);
} catch (error) {
this.handleError(error);
}
}
private handleHttpError(status: number): never {
if (status === 401 || status === 403) {
throw new UnauthenticatedError("Invalid authentication token");
}
throw new ServiceUnavailableError(`External service error: ${status}`);
}
private validateResponse(data: unknown): ResponseType {
const parsed = responseSchema.safeParse(data);
if (!parsed.success) {
console.error("Invalid response format:", parsed.error.format());
throw new ServiceUnavailableError("Invalid response format");
}
return parsed.data;
}
private handleError(error: unknown): never {
// Re-throw known domain errors
if (
error instanceof UnauthenticatedError ||
error instanceof ServiceUnavailableError
) {
throw error;
}
console.error("External service error:", error);
throw new ServiceUnavailableError("External service unavailable");
}
}
Key patterns:
@/env (never process.env directly)@/errorsServices that provide permission logic.
import type { AuthenticatedUserContextType } from "@/schemas/user.schemas";
import type { {Entity}Type } from "@/schemas/{entity}.schema";
export class AuthorizationService {
isAdmin(user: AuthenticatedUserContextType): boolean {
return user.globalRole === "admin";
}
// --- {Entity} Permissions ---
async canView{Entity}(
user: AuthenticatedUserContextType,
{entity}: {Entity}Type,
): Promise<boolean> {
if (this.isAdmin(user)) return true;
if ({entity}.createdBy === user.userId) return true;
return false;
}
async canCreate{Entity}(user: AuthenticatedUserContextType): Promise<boolean> {
if (this.isAdmin(user)) return true;
if (user.globalRole === "user") return true;
return false;
}
async canUpdate{Entity}(
user: AuthenticatedUserContextType,
{entity}: {Entity}Type,
): Promise<boolean> {
if (this.isAdmin(user)) return true;
if ({entity}.createdBy === user.userId) return true;
return false;
}
async canDelete{Entity}(
user: AuthenticatedUserContextType,
{entity}: {Entity}Type,
): Promise<boolean> {
if (this.isAdmin(user)) return true;
if ({entity}.createdBy === user.userId) return true;
return false;
}
// --- Event Permissions ---
async canReceive{Entity}Event(
user: AuthenticatedUserContextType,
{entity}Data: { createdBy: string; [key: string]: unknown },
): Promise<boolean> {
// Apply same rules as viewing
if (this.isAdmin(user)) return true;
if ({entity}Data.createdBy === user.userId) return true;
return false;
}
}
Key patterns:
async for consistency (even if currently sync)boolean not throw errors (let caller decide)Services that send notifications, emails, or messages.
import { env } from "@/env";
import { ServiceUnavailableError } from "@/errors";
export interface EmailOptions {
to: string;
subject: string;
body: string;
html?: boolean;
}
export class EmailService {
private readonly apiKey: string;
private readonly fromAddress: string;
constructor() {
this.apiKey = env.EMAIL_API_KEY;
this.fromAddress = env.EMAIL_FROM_ADDRESS;
if (!this.apiKey || !this.fromAddress) {
throw new ServiceUnavailableError(
"Email service is not properly configured.",
);
}
}
async send(options: EmailOptions): Promise<boolean> {
try {
// External API call implementation
return true;
} catch (error) {
console.error("Email service error:", error);
throw new ServiceUnavailableError("Email service unavailable");
}
}
}
Utility services are standalone classes - don't extend BaseService:
// Correct
export class AuthenticationService {
// ...
}
// Wrong - BaseService is for resource services
export class AuthenticationService extends BaseService {
// ...
}
Utility services don't directly access data:
// Correct - calls external API or provides logic
export class AuthenticationService {
async authenticate(token: string) {
return fetch(`${this.authUrl}/auth/me`, ...);
}
}
// Wrong - use resource service for data access
export class AuthenticationService {
constructor(private userRepository: IUserRepository) {}
}
When you need to support multiple providers (e.g., different email services, notification channels, or payment gateways), create an interface and provide multiple implementations:
// Interface in src/services/email.service.ts
export interface IEmailService {
send(options: EmailOptions): Promise<EmailResult>;
sendTemplate(to: string, templateId: string, variables: Record<string, string>): Promise<EmailResult>;
}
// SendGrid implementation in src/services/sendgrid-email.service.ts
export class SendGridEmailService implements IEmailService {
async send(options: EmailOptions): Promise<EmailResult> {
// SendGrid-specific implementation
}
async sendTemplate(...): Promise<EmailResult> {
// SendGrid-specific implementation
}
}
// Mailgun implementation in src/services/mailgun-email.service.ts
export class MailgunEmailService implements IEmailService {
async send(options: EmailOptions): Promise<EmailResult> {
// Mailgun-specific implementation
}
async sendTemplate(...): Promise<EmailResult> {
// Mailgun-specific implementation
}
}
Then inject the interface in dependent services:
export class NotificationService {
constructor(private emailService: IEmailService) {}
async notifyUser(userId: string, message: string) {
await this.emailService.send({
to: userEmail,
subject: "Notification",
body: message,
});
}
}
// Usage - choose provider based on config
const emailService =
env.EMAIL_PROVIDER === "sendgrid"
? new SendGridEmailService()
: new MailgunEmailService();
const notificationService = new NotificationService(emailService);
When to use this pattern:
Use domain errors from @/errors:
import {
ServiceUnavailableError,
UnauthenticatedError,
UnauthorizedError,
} from "@/errors";
// Throw appropriate domain errors
if (!response.ok) {
throw new ServiceUnavailableError("Service unavailable");
}
// Re-throw known errors, wrap unknown ones
if (error instanceof ServiceUnavailableError) {
throw error;
}
throw new ServiceUnavailableError("Unknown error");
Always read from validated env:
import { env } from "@/env";
// Correct
const apiUrl = env.API_URL;
// Wrong - bypasses validation
const apiUrl = process.env.API_URL;
Always validate external data with Zod:
const rawData = await response.json();
const parsed = schema.safeParse(rawData);
if (!parsed.success) {
console.error("Invalid format:", parsed.error.format());
throw new ServiceUnavailableError("Invalid response format");
}
return parsed.data;
See REFERENCE.md for complete examples:
AuthenticationService - External API integrationAuthorizationService - Permission logicBaseService (that's for resource services)process.env directly (use @/env)create-service - Guide for choosing service typecreate-resource-service - CRUD services for domain entitiesadd-env-variable - Adding environment variables for service configurationxiaohongshu
technical spec
product ux expert
database patterns
Conduct multi-agent task orchestration and workflow coordination.
Initialize project with Conductor artifacts (product definition,
Expert in web animations, transitions, and motion design using Framer Motion and CSS
Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...
PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.
Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...
Display project status, active tracks, and next actions
Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.
Generate, edit, and beat-sync AI video with leading models in one workspace.
The world's fastest calendar for remote work
Transform Your Design with AI Designer by ImgCreator.ai
Revolutionizing Video Production with AI-Powered Creativity
Extend an image past the frame and let AI fill the new aspect ratio.
Discover your celebrity doppelgänger with StarByFace!
ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.
Opus.ai: Revolutionize Your Web Experience