npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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-core

Quick 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 item

Maturity 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); // true

Slug 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 hyphen

API 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 failed
  • DUPLICATE_SLUG - Slug already exists
  • NAME_TOO_LONG - Name exceeds 100 characters
  • INVALID_COLOR - Color is not valid hex format (#RRGGBB)
  • INVALID_DISPLAY_ORDER - Display order is not a non-negative integer
  • NOT_FOUND - Type with specified ID not found
  • CANNOT_DELETE_SYSTEM_TYPE - Attempted to delete a system type
  • ALREADY_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

License

Copyright (c) 2025 Bernier LLC. All rights reserved.