@roastery/pantry

Shared utilities, use cases, DTOs, and domain abstractions for the Roastery CMS ecosystem.


Keywords
hoyasumii, roastery, @roastery, seedbed, @roastery/seedbed, roastery-seedbed
License
MIT
Install
npm install @roastery/pantry@0.1.0

Documentation

@roastery/seedbed

Shared utilities, use cases, DTOs, and domain abstractions for the Roastery CMS ecosystem — reusable building blocks for entity lookup, pagination, slug validation, and plugin extensibility.

Checked with Biome

Overview

seedbed provides cross-cutting application concerns shared across Roastery services:

  • FindEntityByTypeUseCase — Generic use case that resolves an entity by UUID or slug, auto-detecting the input type.
  • GetNumberOfPagesService — Pagination calculator with configurable items per page.
  • SlugUniquenessCheckerService — Domain service to verify slug availability.
  • DTOs — Schema-validated Data Transfer Objects for common query parameters (ID/slug lookup, pagination).
  • Plugin interface — Generic contract for extending services with plugins.
  • Constants — Shared configuration values for pagination limits and cache expiration.

Technologies

Tool Purpose
@roastery/terroir Schema validation, exception hierarchy, and TypeBox re-exports
@roastery/beans Entity abstractions, UUID utilities, and collection schemas
tsup Bundling to ESM + CJS with .d.ts generation
Bun Runtime, test runner, and package manager
Knip Unused exports and dependency detection
Husky + commitlint Git hooks and conventional commit enforcement

Installation

Install the package and its peer dependencies:

bun add @roastery/seedbed @roastery/terroir @roastery/beans typescript

Local development (link)

If you're developing seedbed alongside another project, you can link it locally:

# Inside the seedbed directory
bun run setup  # builds and registers the link

# Inside your consuming project
bun link @roastery/seedbed

FindEntityByTypeUseCase

Generic use case that resolves an entity by its identifier. It auto-detects whether the input is a UUID or slug and delegates to the appropriate repository method.

import { FindEntityByTypeUseCase } from "@roastery/seedbed/application/use-cases";
import type { ICanReadId, ICanReadSlug } from "@roastery/seedbed/domain/types/repositories";

class PostRepository implements ICanReadId<PostDTO, Post>, ICanReadSlug<PostDTO, Post> {
  async findById(id: string): Promise<Post | null> { /* ... */ }
  async findBySlug(slug: string): Promise<Post | null> { /* ... */ }
}

const repository = new PostRepository();
const findPost = new FindEntityByTypeUseCase(repository);

// Resolves by UUID
const post = await findPost.run("550e8400-e29b-41d4-a716-446655440000", "Post");

// Resolves by slug
const post = await findPost.run("my-first-post", "Post");

// Throws ResourceNotFoundException if not found

GetNumberOfPagesService

Calculates the total number of pages for a paginated result set.

import { GetNumberOfPagesService } from "@roastery/seedbed/application/services";

GetNumberOfPagesService.run(100, 10); // 10
GetNumberOfPagesService.run(101, 10); // 11
GetNumberOfPagesService.run(100);     // 8 (default: 14 items per page)
GetNumberOfPagesService.run(0, 10);   // 0

SlugUniquenessCheckerService

Domain service that checks whether a slug is available for use.

import { SlugUniquenessCheckerService } from "@roastery/seedbed/domain/services";

const checker = new SlugUniquenessCheckerService(repository);

await checker.run("my-post");       // true (available)
await checker.run("existing-post"); // false (taken)

DTOs

Schema-validated Data Transfer Objects for common query parameters.

IdOrSlugDTO

Accepts either a UUID or a slug as a path/query parameter:

import { IdOrSlugDTO } from "@roastery/seedbed/presentation/dtos";

// { "id-or-slug": "550e8400-e29b-41d4-a716-446655440000" }
// { "id-or-slug": "my-cool-post" }

PaginationDTO

Pagination parameter with a minimum value of 1:

import { PaginationDTO } from "@roastery/seedbed/presentation/dtos";

// { page: 1 }

Plugin interface

Generic contract for building extensible plugin systems:

import type { IPlugin } from "@roastery/seedbed/presentation/types";

interface MyPluginValue {
  handler: () => void;
}

const myPlugin: IPlugin<MyPluginValue> = {
  name: "my-plugin",
  value: { handler: () => console.log("Hello!") },
};

Constants

import { MAX_ITEMS_PER_QUERY, CACHE_EXPIRATION_TIME } from "@roastery/seedbed/constants";
Constant Value Description
MAX_ITEMS_PER_QUERY 14 Default items per page for pagination
CACHE_EXPIRATION_TIME.SAFE 3600 1 hour (seconds)
CACHE_EXPIRATION_TIME.LOW_UPDATES 86400 24 hours (seconds)
CACHE_EXPIRATION_TIME.HIGH_UPDATES 900 15 minutes (seconds)

Utilities

detectEntry

Detects whether a string is a UUID or a slug:

import { detectEntry } from "@roastery/seedbed/application/utils";

detectEntry("550e8400-e29b-41d4-a716-446655440000"); // "UUID"
detectEntry("my-cool-post");                          // "SLUG"

Repository interfaces

Granular interfaces for read operations, allowing repositories to implement only what they need:

import type { ICanReadId, ICanReadSlug } from "@roastery/seedbed/domain/types/repositories";

// Find by UUID
interface ICanReadId<SchemaType, EntityType> {
  findById(id: string): Promise<EntityType | null>;
}

// Find by slug
interface ICanReadSlug<SchemaType, EntityType> {
  findBySlug(slug: string): Promise<EntityType | null>;
}

Exports reference

// Application — Use Cases
import { FindEntityByTypeUseCase } from "@roastery/seedbed/application/use-cases";

// Application — Services
import { GetNumberOfPagesService } from "@roastery/seedbed/application/services";

// Application — Utilities
import { detectEntry } from "@roastery/seedbed/application/utils";

// Application — Types
import type { EntryActions, ValueTypes, ICountItems } from "@roastery/seedbed/application/types";

// Domain — Repository Interfaces
import type { ICanReadId, ICanReadSlug } from "@roastery/seedbed/domain/types/repositories";

// Domain — Services
import { SlugUniquenessCheckerService } from "@roastery/seedbed/domain/services";

// Presentation — DTOs
import { IdOrSlugDTO, PaginationDTO } from "@roastery/seedbed/presentation/dtos";

// Presentation — Types
import type { IPlugin } from "@roastery/seedbed/presentation/types";

// Constants
import { MAX_ITEMS_PER_QUERY, CACHE_EXPIRATION_TIME } from "@roastery/seedbed/constants";

Development

# Run tests
bun run test:unit

# Run tests with coverage
bun run test:coverage

# Build for distribution
bun run build

# Check for unused exports and dependencies
bun run knip

# Full setup (build + bun link)
bun run setup

License

MIT