@bernierllc/project-types-core
v1.0.4
Published
Configurable type system for planning entities (project types, work types, maturity levels, statuses)
Readme
@bernierllc/project-types-core
Configurable type system for planning entities (project types, work types, maturity levels, statuses).
Installation
npm install @bernierllc/project-types-coreQuick Start
import { ProjectTypeManager, MaturityLevelManager, WorkItemStatusManager } from '@bernierllc/project-types-core';
// Implement storage interface
class MyStorage implements TypeStorage<ProjectType> {
// Your storage implementation (database, file system, etc.)
}
const storage = new MyStorage();
const manager = new ProjectTypeManager('project_type', storage);
// Create a project type
const result = await manager.create({
name: 'Feature Development',
slug: 'feature-development',
icon: '🚀',
color: '#4CAF50',
description: 'Building new product features'
});
if (result.success) {
console.log('Created:', result.data);
}Core Concepts
Configurable Types
This package provides runtime-configurable type systems for:
- ProjectType - Categories of projects (Feature, Research, Infrastructure, etc.)
- WorkType - Types of work items (Package, Bug Fix, Documentation, etc.)
- MaturityLevel - Progression stages with ordering (Intention, Idea, Planned, Ready, Completed)
- WorkItemStatus - Workflow states (Open, In Progress, Blocked, Completed, Cancelled)
System vs. Custom Types
- System types (
is_system: true) - Cannot be deleted, provides baseline taxonomy - Custom types (
is_system: false) - User-defined, can be created and deleted
Display Ordering
All types have a display_order property for consistent sorting and UI presentation:
// Create types with specific order
await manager.create({ name: 'High Priority', slug: 'high', display_order: 1 });
await manager.create({ name: 'Normal', slug: 'normal', display_order: 2 });
await manager.create({ name: 'Low Priority', slug: 'low', display_order: 3 });
// Or let the system auto-assign
await manager.create({ name: 'Backlog', slug: 'backlog' }); // Gets display_order: 4
// Reorder later
await manager.moveUp(lowPriorityId); // Swap with previous item
await manager.moveDown(highPriorityId); // Swap with next itemMaturity Progression
Maturity levels support forward progression validation:
const maturityManager = new MaturityLevelManager('maturity_level', storage);
// Create ordered maturity levels
await maturityManager.create({ name: 'Intention', slug: 'intention', display_order: 1 });
await maturityManager.create({ name: 'Idea', slug: 'idea', display_order: 2 });
await maturityManager.create({ name: 'Planned', slug: 'planned', display_order: 3 });
await maturityManager.create({ name: 'Ready', slug: 'ready', display_order: 4 });
await maturityManager.create({ name: 'Completed', slug: 'completed', display_order: 10 });
// Get next level in progression
const current = await maturityManager.getBySlug('planned');
const next = await maturityManager.getNextMaturity(current.data!.id);
console.log('Next:', next.data?.name); // "Ready"
// Validate progression (prevents going backward)
const validation = await maturityManager.validateProgression(
readyId,
intentionId
);
console.log(validation.isValid); // false - cannot regress
// Get full progression path
const path = await maturityManager.getProgressionPath(intentionId, completedId);
console.log(path.data?.map(m => m.name));
// ["Intention", "Idea", "Planned", "Ready", "Completed"]Status Categories
Work item statuses support closed/open categorization:
const statusManager = new WorkItemStatusManager('work_item_status', storage);
// Create statuses with closed flag
await statusManager.create({
name: 'Open',
slug: 'open',
is_closed_status: false
});
await statusManager.create({
name: 'Completed',
slug: 'completed',
is_closed_status: true
});
// Query by category
const openStatuses = await statusManager.getOpenStatuses();
const closedStatuses = await statusManager.getClosedStatuses();
// Check individual status
const isCompleted = await statusManager.isClosedStatus(completedId);
console.log(isCompleted.data); // trueSlug Validation
Slugs must be URL-safe identifiers:
- Lowercase only
- Alphanumeric and hyphens
- Cannot start/end with hyphen
- 1-50 characters
- Unique within type category
// Valid slugs
'feature-development'
'bug-fix'
'high-priority-123'
// Invalid slugs
'Feature Development' // uppercase
'feature_development' // underscore
'-invalid' // starts with hyphen
'invalid-' // ends with hyphenAPI Reference
TypeManager
Generic manager for all configurable types.
Methods
create(data: CreateTypeInput): Promise<PackageResult>
Creates a new type with validation.
const result = await manager.create({
name: 'Feature Development',
slug: 'feature-development',
description: 'Building new features',
icon: '🚀',
color: '#4CAF50',
is_system: false,
display_order: 1, // Optional - auto-assigned if omitted
metadata: { custom: 'data' } // Optional extensible metadata
});get(id: string): Promise<PackageResult>
Retrieves a type by ID.
getBySlug(slug: string): Promise<PackageResult>
Retrieves a type by slug.
list(filters?: TypeFilters): Promise<PackageResult<T[]>>
Lists types with optional filtering.
// Filter by system/custom
const systemTypes = await manager.list({ is_system: true });
const customTypes = await manager.list({ is_system: false });
// Search by name
const matching = await manager.list({ name_contains: 'feature' });
// Filter by slugs
const specific = await manager.list({ slugs: ['bug-fix', 'feature'] });update(id: string, updates: TypeUpdates): Promise<PackageResult>
Updates an existing type.
await manager.update(typeId, {
name: 'Updated Name',
description: 'New description',
color: '#FF5722'
});delete(id: string): Promise<PackageResult>
Deletes a type (system types cannot be deleted).
reorder(idOrderMap: Record<string, number>): Promise<PackageResult>
Reorders multiple types at once.
await manager.reorder({
[id1]: 3,
[id2]: 1,
[id3]: 2
});moveUp(id: string): Promise<PackageResult>
Moves a type up one position in display order.
moveDown(id: string): Promise<PackageResult>
Moves a type down one position in display order.
getSystemTypes(): Promise<PackageResult<T[]>>
Returns all system types.
getCustomTypes(): Promise<PackageResult<T[]>>
Returns all custom types.
MaturityLevelManager
Specialized manager for maturity levels with progression support.
Additional Methods
getByProgression(order: number): Promise<PackageResult>
Gets maturity level by display order position.
getNextMaturity(currentId: string): Promise<PackageResult<MaturityLevel | null>>
Gets the next maturity level in progression (returns null if at maximum).
getPreviousMaturity(currentId: string): Promise<PackageResult<MaturityLevel | null>>
Gets the previous maturity level (returns null if at minimum).
validateProgression(fromId: string, toId: string): Promise
Validates that progression from one level to another is forward (not backward).
getProgressionPath(fromId: string, toId: string): Promise<PackageResult<MaturityLevel[]>>
Gets all maturity levels between two points in the progression.
WorkItemStatusManager
Specialized manager for work item statuses with closed/open categorization.
Additional Methods
getClosedStatuses(): Promise<PackageResult<WorkItemStatus[]>>
Returns all statuses with is_closed_status: true.
getOpenStatuses(): Promise<PackageResult<WorkItemStatus[]>>
Returns all statuses with is_closed_status: false.
isClosedStatus(id: string): Promise<PackageResult>
Checks if a specific status is a closed status.
Storage Implementation
You must implement the TypeStorage interface for your data persistence layer:
import { TypeStorage, ConfigurableType, TypeFilters } from '@bernierllc/project-types-core';
class MyStorage<T extends ConfigurableType> implements TypeStorage<T> {
async save(type: T): Promise<void> {
// Save or update type in your database
}
async get(id: string): Promise<T | null> {
// Retrieve type by ID
}
async getBySlug(slug: string): Promise<T | null> {
// Retrieve type by slug
}
async list(filters?: TypeFilters): Promise<T[]> {
// List types with optional filtering
// Must return types sorted by display_order ascending
}
async delete(id: string): Promise<void> {
// Delete type from storage
}
async getMaxDisplayOrder(): Promise<number> {
// Return highest display_order value (0 if no types exist)
}
}Example with Prisma
import { PrismaClient } from '@prisma/client';
import { TypeStorage, ProjectType, TypeFilters } from '@bernierllc/project-types-core';
class PrismaProjectTypeStorage implements TypeStorage<ProjectType> {
constructor(private prisma: PrismaClient) {}
async save(type: ProjectType): Promise<void> {
await this.prisma.projectType.upsert({
where: { id: type.id },
create: type,
update: type
});
}
async get(id: string): Promise<ProjectType | null> {
return await this.prisma.projectType.findUnique({
where: { id }
});
}
async getBySlug(slug: string): Promise<ProjectType | null> {
return await this.prisma.projectType.findUnique({
where: { slug }
});
}
async list(filters?: TypeFilters): Promise<ProjectType[]> {
return await this.prisma.projectType.findMany({
where: {
is_system: filters?.is_system,
name: filters?.name_contains ? {
contains: filters.name_contains,
mode: 'insensitive'
} : undefined,
slug: filters?.slugs ? { in: filters.slugs } : undefined
},
orderBy: { display_order: 'asc' }
});
}
async delete(id: string): Promise<void> {
await this.prisma.projectType.delete({
where: { id }
});
}
async getMaxDisplayOrder(): Promise<number> {
const result = await this.prisma.projectType.aggregate({
_max: { display_order: true }
});
return result._max.display_order || 0;
}
}Error Handling
All operations return PackageResult<T> with success/failure indication:
interface PackageResult<T> {
success: boolean;
data?: T;
error?: {
code: string;
message: string;
details?: any;
};
}Common Error Codes
INVALID_SLUG- Slug format validation failedDUPLICATE_SLUG- Slug already existsNAME_TOO_LONG- Name exceeds 100 charactersINVALID_COLOR- Color is not valid hex format (#RRGGBB)INVALID_DISPLAY_ORDER- Display order is not a non-negative integerNOT_FOUND- Type with specified ID not foundCANNOT_DELETE_SYSTEM_TYPE- Attempted to delete a system typeALREADY_FIRST- Cannot move up (already first)ALREADY_LAST- Cannot move down (already last)INVALID_PROGRESSION- Attempted backward maturity progression
Examples
Complete Project Type System
import { ProjectTypeManager } from '@bernierllc/project-types-core';
// Initialize
const storage = new MyStorage();
const manager = new ProjectTypeManager('project_type', storage);
// Seed system types
await manager.create({
name: 'Feature Development',
slug: 'feature',
icon: '🚀',
is_system: true,
display_order: 1
});
await manager.create({
name: 'Bug Fix',
slug: 'bug-fix',
icon: '🐛',
is_system: true,
display_order: 2
});
// Add custom type
await manager.create({
name: 'Research',
slug: 'research',
icon: '🔬',
is_system: false
});
// List all types
const allTypes = await manager.list();
allTypes.data?.forEach(type => {
console.log(`${type.icon} ${type.name} (${type.is_system ? 'system' : 'custom'})`);
});Work Item Lifecycle Management
import { MaturityLevelManager, WorkItemStatusManager } from '@bernierllc/project-types-core';
// Setup maturity levels
const maturityManager = new MaturityLevelManager('maturity', maturityStorage);
const statusManager = new WorkItemStatusManager('status', statusStorage);
// Define maturity progression
const intention = await maturityManager.create({
name: 'Intention',
slug: 'intention',
display_order: 1
});
const idea = await maturityManager.create({
name: 'Idea',
slug: 'idea',
display_order: 2
});
const completed = await maturityManager.create({
name: 'Completed',
slug: 'completed',
display_order: 10
});
// Define statuses
await statusManager.create({
name: 'Open',
slug: 'open',
is_closed_status: false
});
await statusManager.create({
name: 'Done',
slug: 'done',
is_closed_status: true
});
// Work item progression
let currentMaturity = intention.data!.id;
// Progress to next maturity
const next = await maturityManager.getNextMaturity(currentMaturity);
if (next.success && next.data) {
console.log(`Progressing to: ${next.data.name}`);
currentMaturity = next.data.id;
}
// Check if at final maturity
const atEnd = await maturityManager.getNextMaturity(currentMaturity);
if (!atEnd.data) {
console.log('Reached final maturity level');
}Integration Status
- Logger: Optional - Pass logger instance to constructors for operation logging
- Docs-Suite: Ready - Complete TypeDoc documentation on all public APIs
- NeverHub: Not Applicable - Pure data structure management without event publishing
See Also
- @bernierllc/work-item-core - Uses this package for type lookups and validation
- @bernierllc/task-queue - Uses maturity level filtering for build queues
License
Copyright (c) 2025 Bernier LLC. All rights reserved.
