@sparring/tech-roles-library
Version:
Comprehensive tech roles and competencies library for 78 technical roles with 9 career levels each. Includes detailed competencies and career progression paths with complete bilingual support (EN/ES).
528 lines (481 loc) • 16.4 kB
JavaScript
/**
* Tech Roles Library
*
* A comprehensive library for managing technical roles, career levels, and competency frameworks.
* Provides 78 technical roles across 9 career levels with bilingual support (EN/ES).
*
* @module tech-roles-library
* @author 686f6c61
* @license MIT
* @version 1.0.0
* @see {@link https://github.com/686f6c61/npm-tech-roles-library}
*/
const path = require('path');
const JSONParser = require('./core/json-parser');
const CompetencyDatabase = require('./core/database');
const QueryAPI = require('./api/queries');
const FilterAPI = require('./api/filters');
const ComparisonAPI = require('./api/comparisons');
const { Validator } = require('./core/validator');
const Translator = require('./i18n/translator');
/**
* Main library class for accessing tech roles and competency data.
*
* @class TechRolesLibrary
* @example
* const TechRolesLibrary = require('tech-roles-library');
* const library = new TechRolesLibrary({ language: 'en' });
* const roles = library.getRoles();
*/
class TechRolesLibrary {
/**
* Creates a new instance of TechRolesLibrary.
*
* @param {Object} [options={}] - Configuration options
* @param {string} [options.language='es'] - Language for translations ('en' or 'es')
* @param {boolean} [options.includeComplementary=true] - Include complementary competencies
* @param {boolean} [options.includeIndicators=true] - Include level indicators
* @param {string} [options.csvPath] - Custom path to CSV data file
*/
constructor(options = {}) {
this.options = {
language: options.language || 'en',
includeComplementary: options.includeComplementary !== false,
includeIndicators: options.includeIndicators !== false,
translationsDir: options.translationsDir || null
};
this.database = null;
this.translator = null;
this.queryAPI = null;
this.filterAPI = null;
this.comparisonAPI = null;
this.loaded = false;
}
/**
* Ensures data is loaded before queries.
* Lazy loads the CSV data, database, and APIs on first use.
*
* @private
*/
ensureLoaded() {
if (this.loaded) return;
// Determine translations directory based on language
const translationsDir = this.options.translationsDir ||
path.join(__dirname, `i18n/translations/${this.options.language}`);
const parser = new JSONParser(translationsDir);
const entries = parser.parse();
this.database = new CompetencyDatabase();
this.database.load(entries);
// Initialize translator
this.translator = new Translator(this.options.language);
// Pass translator to QueryAPI
this.queryAPI = new QueryAPI(this.database, this.translator);
this.filterAPI = new FilterAPI(this.database);
this.comparisonAPI = new ComparisonAPI(this.database, this.queryAPI);
this.loaded = true;
}
// ========================================
// ROLE QUERIES
// ========================================
/**
* Get all available role names.
*
* @returns {string[]} Array of all role names (78 roles)
* @example
* const roles = library.getRoles();
* // ['Backend Developer', 'Frontend Developer', ...]
*/
getRoles() {
this.ensureLoaded();
return this.queryAPI.getRoles();
}
/**
* Get role details by code.
*
* @param {string} code - Role code (e.g., 'BE-L3', 'FE-L4')
* @returns {Object} Role entry with competencies and metadata
* @throws {RoleNotFoundError} If code doesn't exist
* @example
* const role = library.getRole('BE-L3');
* // { role: 'Backend Developer', level: 'L3', code: 'BE-L3', ... }
*/
getRole(code) {
this.ensureLoaded();
return this.queryAPI.getRoleByCode(code);
}
/**
* Get role details by name and level.
*
* @param {string} name - Role name (e.g., 'Backend Developer')
* @param {string|number} level - Level (e.g., 'L3', 3, 'Mid-Level I')
* @returns {Object} Role entry with competencies and metadata
* @throws {RoleNotFoundError} If role name doesn't exist
* @throws {LevelNotFoundError} If level doesn't exist for this role
* @example
* const role = library.getRoleByName('Backend Developer', 'L3');
*/
getRoleByName(name, level) {
this.ensureLoaded();
return this.queryAPI.getRoleByNameAndLevel(name, level);
}
/**
* Get all levels for a specific role.
*
* @param {string} roleName - Role name
* @returns {Object[]} Array of all level entries for this role (L1-L9)
* @throws {RoleNotFoundError} If role doesn't exist
* @example
* const levels = library.getLevelsForRole('Backend Developer');
* // [{ level: 'L1', ... }, { level: 'L2', ... }, ...]
*/
getLevelsForRole(roleName) {
this.ensureLoaded();
return this.queryAPI.getAllLevelsForRole(roleName);
}
// ========================================
// COMPETENCY QUERIES
// ========================================
/**
* Get competencies for a specific role and level.
*
* @param {string} roleName - Role name
* @param {string|number} level - Level identifier
* @returns {Object} Competencies object with core, complementary, and indicators
* @example
* const competencies = library.getCompetencies('Backend Developer', 'L3');
* // { role: '...', level: '...', core: [...], complementary: [...], indicators: [...] }
*/
getCompetencies(roleName, level) {
this.ensureLoaded();
return this.queryAPI.getCompetencies(roleName, level, {
includeComplementary: this.options.includeComplementary,
includeIndicators: this.options.includeIndicators
});
}
/**
* Get only core competencies for a role and level.
*
* @param {string} roleName - Role name
* @param {string|number} level - Level identifier
* @returns {string[]} Array of core competencies
* @example
* const core = library.getCoreCompetencies('Backend Developer', 'L3');
*/
getCoreCompetencies(roleName, level) {
this.ensureLoaded();
const result = this.queryAPI.getCompetencies(roleName, level, {
includeComplementary: false,
includeIndicators: false
});
return result.core;
}
/**
* Get only complementary competencies for a role and level.
*
* @param {string} roleName - Role name
* @param {string|number} level - Level identifier
* @returns {string[]} Array of complementary competencies
* @example
* const complementary = library.getComplementaryCompetencies('Backend Developer', 'L3');
*/
getComplementaryCompetencies(roleName, level) {
this.ensureLoaded();
const entry = this.queryAPI.getRoleByNameAndLevel(roleName, level);
return entry.complementaryCompetencies;
}
/**
* Get accumulated competencies from L1 to target level.
*
* @param {string} roleName - Role name
* @param {string|number} level - Target level
* @returns {Object} Accumulated competencies across all levels up to target
* @example
* const accumulated = library.getAccumulatedCompetencies('Backend Developer', 'L5');
*/
getAccumulatedCompetencies(roleName, level) {
this.ensureLoaded();
return this.queryAPI.getAccumulatedCompetencies(roleName, level);
}
/**
* Get complete career path view (mastered + current + growth).
* Perfect for HR dashboards and career planning.
*
* @param {string} roleName - Role name
* @param {string|number} currentLevel - Current level
* @returns {Object} Complete career view with mastered, current, and growth paths
* @example
* const career = library.getCareerPathComplete('Backend Developer', 'L5');
* // { masteredLevels: [...], currentLevel: {...}, growthPath: [...], summary: {...} }
*/
getCareerPathComplete(roleName, currentLevel) {
this.ensureLoaded();
return this.queryAPI.getCareerPathComplete(roleName, currentLevel);
}
// ========================================
// EXPERIENCE-BASED QUERIES
// ========================================
/**
* Get recommended level based on years of experience.
*
* @param {string} roleName - Role name
* @param {number} years - Years of experience
* @returns {Object} Recommended level entry
* @throws {Error} If years is not a positive number
* @example
* const recommended = library.getByExperience('Backend Developer', 5);
* // Returns L4 or L5 based on experience mapping
*/
getByExperience(roleName, years) {
this.ensureLoaded();
return this.queryAPI.getByExperience(roleName, years);
}
/**
* Get years range for a specific role and level.
*
* @param {string} roleName - Role name
* @param {string|number} level - Level identifier
* @returns {Object} Years range object with min and max
* @example
* const range = library.getYearsRange('Backend Developer', 'L3');
* // { min: 2, max: 4 }
*/
getYearsRange(roleName, level) {
this.ensureLoaded();
const entry = this.queryAPI.getRoleByNameAndLevel(roleName, level);
return entry.yearsRange;
}
// ========================================
// SEARCH & FILTER
// ========================================
/**
* Search for roles by name or category.
* Returns unique roles (not individual role-level entries).
*
* @param {string} query - Search term (role name or category)
* @param {Object} [options] - Search options
* @param {number} [options.limit=20] - Maximum results to return
* @returns {Object[]} Array of unique roles with match scores
* @example
* const results = library.search('fullstack', { limit: 10 });
* // Returns:
* // [
* // {
* // role: 'Full-Stack Developer',
* // category: 'Software Engineering',
* // matchScore: 10,
* // matchedIn: 'role'
* // }
* // ]
*/
search(query, options = {}) {
this.ensureLoaded();
return this.filterAPI.search(query, options);
}
/**
* Filter entries by category.
*
* @param {string} category - Category name (e.g., 'Software Engineering')
* @returns {Object[]} Array of entries in this category
* @example
* const entries = library.filterByCategory('AI/ML');
*/
filterByCategory(category) {
this.ensureLoaded();
return this.queryAPI.filterByCategory(category);
}
/**
* Filter entries by level number.
*
* @param {number} levelNumber - Level number (1-9)
* @returns {Object[]} Array of entries at this level
* @throws {Error} If level number is not between 1 and 9
* @example
* const seniorRoles = library.filterByLevel(6); // All L6 entries
*/
filterByLevel(levelNumber) {
this.ensureLoaded();
return this.queryAPI.filterByLevel(levelNumber);
}
// ========================================
// CAREER PROGRESSION
// ========================================
/**
* Get next level requirements for a role.
*
* @param {string} role - Role name
* @param {string|number} currentLevel - Current level
* @returns {Object|null} Next level details and new competencies, or null if at max level
* @example
* const next = library.getNextLevel('Backend Developer', 'L3');
* // { current: {...}, next: {...}, newCompetencies: [...], newCompetenciesCount: 5 }
*/
getNextLevel(role, currentLevel) {
this.ensureLoaded();
const normalizedLevel = Validator.normalizeLevel(currentLevel);
const currentNum = parseInt(normalizedLevel.match(/\d/)[0]);
if (currentNum >= 9) {
return null;
}
const nextNum = currentNum + 1;
const nextLevel = this.queryAPI.getRoleByNameAndLevel(role, `L${nextNum}`);
const comparison = this.comparisonAPI.compareLevels(role, normalizedLevel, nextLevel.level);
return {
current: {
level: normalizedLevel,
yearsRange: this.getYearsRange(role, normalizedLevel)
},
next: {
level: nextLevel.level,
code: nextLevel.code,
yearsRange: nextLevel.yearsRange,
coreCompetencies: nextLevel.coreCompetencies,
complementaryCompetencies: nextLevel.complementaryCompetencies,
indicators: nextLevel.indicators
},
newCompetencies: comparison.new,
newCompetenciesCount: comparison.new.length
};
}
// ========================================
// UTILITIES
// ========================================
/**
* Get all available categories.
*
* @returns {string[]} Array of category names
* @example
* const categories = library.getCategories();
* // ['Software Engineering', 'AI/ML', 'Data', 'Infrastructure', ...]
*/
getCategories() {
this.ensureLoaded();
return this.queryAPI.getCategories();
}
/**
* Get complete metadata for all 78 roles.
* Perfect for building catalogs, navigation, and dashboards.
*
* @returns {Object} Complete catalog with roles, byCategory, and summary
* @example
* const catalog = library.getAllRolesWithMetadata();
* // { roles: [...], byCategory: {...}, summary: {...} }
*/
getAllRolesWithMetadata() {
this.ensureLoaded();
return this.queryAPI.getAllRolesWithMetadata();
}
/**
* Get database statistics.
*
* @returns {Object} Statistics about roles, categories, and entries
* @example
* const stats = library.getStatistics();
* // { totalRoles: 78, totalCategories: 7, totalEntries: 702, ... }
*/
getStatistics() {
this.ensureLoaded();
return this.database.getStatistics();
}
/**
* Validate if a role name exists.
*
* @param {string} roleName - Role name to validate
* @returns {boolean} True if role exists
* @example
* const exists = library.validateRole('Backend Developer'); // true
*/
validateRole(roleName) {
this.ensureLoaded();
return this.database.indexes.byRole.has(roleName);
}
/**
* Validate if a level exists for a role.
*
* @param {string} roleName - Role name
* @param {string|number} level - Level to validate
* @returns {boolean} True if level exists for this role
* @example
* const exists = library.validateLevel('Backend Developer', 'L3'); // true
*/
validateLevel(roleName, level) {
this.ensureLoaded();
try {
this.queryAPI.getRoleByNameAndLevel(roleName, level);
return true;
} catch (error) {
return false;
}
}
/**
* Get all available levels for a role.
*
* @param {string} roleName - Role name
* @returns {string[]} Array of level identifiers
* @example
* const levels = library.getAvailableLevels('Backend Developer');
* // ['L1 - Trainee', 'L2 - Junior I', ...]
*/
getAvailableLevels(roleName) {
this.ensureLoaded();
const levels = this.queryAPI.getAllLevelsForRole(roleName);
return levels.map(l => l.level);
}
// ========================================
// EXPORT
// ========================================
/**
* Export data in different formats.
*
* @param {string} format - Export format ('json' or 'markdown')
* @param {Object} data - Data to export
* @returns {string} Formatted export string
* @throws {Error} If format is not supported
* @example
* const data = library.getCompetencies('Backend Developer', 'L3');
* const json = library.export('json', data);
* const markdown = library.export('markdown', data);
*/
export(format, data) {
if (format === 'json') {
return JSON.stringify(data, null, 2);
}
if (format === 'markdown') {
return this.toMarkdown(data);
}
throw new Error(`Unsupported export format: ${format}`);
}
/**
* Convert data to Markdown format.
*
* @param {Object} data - Data to convert
* @returns {string} Markdown formatted string
* @private
* @example
* const md = library.toMarkdown(competenciesData);
*/
toMarkdown(data) {
let md = '';
if (data.role) {
md += `# ${data.role}\n\n`;
}
if (data.level) {
md += `## ${data.level}\n\n`;
}
if (data.core && Array.isArray(data.core)) {
md += `### Core Competencies\n\n`;
data.core.forEach(comp => {
md += `- ${comp}\n`;
});
md += '\n';
}
if (data.complementary && Array.isArray(data.complementary)) {
md += `### Complementary Competencies\n\n`;
data.complementary.forEach(comp => {
md += `- ${comp}\n`;
});
md += '\n';
}
return md;
}
}
module.exports = TechRolesLibrary;