/**
* Schema.org Mapper Service
*
* Maps codebase entities to Schema.org types and properties.
* Based on Ludwig neurosymbolic system with 74+ bidirectional property mappings.
*/
import { getDatabase } from './database.js';
import type { Entity, SchemaAnnotation } from './schema.js';
import { SchemaOrgType } from './schema.js';
/**
* Schema.org property mappings
* Based on Ludwig system's 74+ bidirectional mappings
*/
const PROPERTY_MAPPINGS: Record<string, string> = {
// Software properties
dependencies: 'softwareRequirements',
version: 'softwareVersion',
authors: 'author',
author: 'author',
maintainers: 'maintainer',
license: 'license',
description: 'description',
homepage: 'url',
repository: 'codeRepository',
bugs: 'discussionUrl',
keywords: 'keywords',
// Application properties
applicationCategory: 'applicationCategory',
operatingSystem: 'operatingSystem',
permissions: 'permissions',
softwareHelp: 'softwareHelp',
availableOnDevice: 'availableOnDevice',
// API properties
documentation: 'documentation',
endpointURL: 'url',
contentType: 'encodingFormat',
httpMethod: 'httpMethod',
// Code properties
programmingLanguage: 'programmingLanguage',
runtimePlatform: 'runtimePlatform',
targetProduct: 'targetProduct',
codeRepository: 'codeRepository',
codeSampleType: 'codeSampleType',
// Creative work properties
dateCreated: 'dateCreated',
dateModified: 'dateModified',
datePublished: 'datePublished',
creator: 'creator',
contributor: 'contributor',
publisher: 'publisher',
// Additional mappings
name: 'name',
alternateName: 'alternateName',
identifier: 'identifier',
sameAs: 'sameAs',
isPartOf: 'isPartOf',
hasPart: 'hasPart',
};
/**
* Codebase type to Schema.org type mappings
*/
const TYPE_MAPPINGS: Record<string, SchemaOrgType | string> = {
// Web applications
'web-app': SchemaOrgType.WEB_APPLICATION,
'webapp': SchemaOrgType.WEB_APPLICATION,
'website': SchemaOrgType.WEB_APPLICATION,
// API servers
'api': SchemaOrgType.WEB_API,
'rest-api': SchemaOrgType.WEB_API,
'graphql-api': SchemaOrgType.WEB_API,
'api-server': SchemaOrgType.WEB_API,
// Mobile apps
'mobile-app': 'MobileApplication',
'ios-app': 'MobileApplication',
'android-app': 'MobileApplication',
// Libraries and packages
'library': 'SoftwareLibrary',
'package': 'SoftwareLibrary',
'npm-package': 'SoftwareLibrary',
'module': 'SoftwareLibrary',
// Source code
'source': SchemaOrgType.SOFTWARE_SOURCE_CODE,
'repository': SchemaOrgType.SOFTWARE_SOURCE_CODE,
'codebase': SchemaOrgType.SOFTWARE_SOURCE_CODE,
// Software applications
'application': SchemaOrgType.SOFTWARE_APPLICATION,
'app': SchemaOrgType.SOFTWARE_APPLICATION,
'software': SchemaOrgType.SOFTWARE_APPLICATION,
// Programming languages
'language': SchemaOrgType.COMPUTER_LANGUAGE,
'programming-language': SchemaOrgType.COMPUTER_LANGUAGE,
// Documentation
'docs': SchemaOrgType.TECH_ARTICLE,
'documentation': SchemaOrgType.TECH_ARTICLE,
'guide': SchemaOrgType.HOW_TO,
'tutorial': SchemaOrgType.HOW_TO,
// API documentation
'api-docs': SchemaOrgType.API_REFERENCE,
'api-reference': SchemaOrgType.API_REFERENCE,
};
/**
* Schema.org mapper for codebase entities
*/
export class SchemaMapper {
/**
* Map codebase entity to Schema.org type
*/
mapToSchemaType(entityType: string): SchemaOrgType | string {
const normalized = entityType.toLowerCase().replace(/_/g, '-');
return TYPE_MAPPINGS[normalized] || SchemaOrgType.SOFTWARE_APPLICATION;
}
/**
* Map codebase properties to Schema.org properties
*/
mapProperties(properties: Record<string, any>): Record<string, any> {
const mapped: Record<string, any> = {};
for (const [key, value] of Object.entries(properties)) {
const schemaProperty = PROPERTY_MAPPINGS[key] || key;
mapped[schemaProperty] = value;
}
return mapped;
}
/**
* Create Schema.org annotation for entity
*/
async annotateEntity(entity: Entity): Promise<SchemaAnnotation> {
const schemaType = this.mapToSchemaType(entity.type);
const properties = this.mapProperties(entity.metadata || {});
// Add entity name
properties.name = entity.name;
// Add identifier
properties.identifier = entity.name;
// Add file reference if available
if (entity.source_file) {
properties.codeRepository = entity.source_file;
}
return {
entity_id: entity.id!,
schema_type: schemaType,
properties,
context_url: 'https://schema.org',
};
}
/**
* Detect codebase type from analysis results
*/
detectCodebaseType(analysis: any): SchemaOrgType | string {
// Check package.json indicators
if (analysis.package?.type === 'module') {
return SchemaOrgType.SOFTWARE_SOURCE_CODE;
}
// Check for web frameworks
if (this.hasFramework(analysis, ['react', 'vue', 'angular', 'svelte'])) {
return SchemaOrgType.WEB_APPLICATION;
}
// Check for API frameworks
if (this.hasFramework(analysis, ['express', 'fastify', 'koa', 'nestjs'])) {
return SchemaOrgType.WEB_API;
}
// Check for mobile frameworks
if (this.hasFramework(analysis, ['react-native', 'flutter', 'ionic'])) {
return 'MobileApplication';
}
// Check for CLI tools
if (analysis.package?.bin) {
return 'SoftwareApplication';
}
// Default to generic software application
return SchemaOrgType.SOFTWARE_APPLICATION;
}
/**
* Extract Schema.org properties from codebase analysis
*/
extractProperties(analysis: any): Record<string, any> {
const properties: Record<string, any> = {};
// Extract from package.json
if (analysis.package) {
const pkg = analysis.package;
if (pkg.name) properties.name = pkg.name;
if (pkg.version) properties.softwareVersion = pkg.version;
if (pkg.description) properties.description = pkg.description;
if (pkg.author) properties.author = this.parseAuthor(pkg.author);
if (pkg.license) properties.license = pkg.license;
if (pkg.homepage) properties.url = pkg.homepage;
if (pkg.repository) properties.codeRepository = this.parseRepository(pkg.repository);
if (pkg.keywords) properties.keywords = pkg.keywords;
// Dependencies as software requirements
if (pkg.dependencies) {
properties.softwareRequirements = Object.keys(pkg.dependencies);
}
}
// Extract programming languages
if (analysis.languages) {
const langs = Object.keys(analysis.languages);
if (langs.length > 0) {
properties.programmingLanguage = langs;
}
}
// Extract entry points
if (analysis.entry_points && analysis.entry_points.length > 0) {
properties.hasPart = analysis.entry_points.map((ep: string) => ({
'@type': 'SoftwareSourceCode',
name: ep,
}));
}
return properties;
}
/**
* Check if codebase has specific framework
*/
private hasFramework(analysis: any, frameworks: string[]): boolean {
const deps = analysis.package?.dependencies || {};
const devDeps = analysis.package?.devDependencies || {};
const allDeps = { ...deps, ...devDeps };
return frameworks.some((framework) => framework in allDeps);
}
/**
* Parse author field (can be string or object)
*/
private parseAuthor(author: any): any {
if (typeof author === 'string') {
return { '@type': 'Person', name: author };
}
if (typeof author === 'object') {
const parsed: any = { '@type': 'Person' };
if (author.name) parsed.name = author.name;
if (author.email) parsed.email = author.email;
if (author.url) parsed.url = author.url;
return parsed;
}
return author;
}
/**
* Parse repository field (can be string or object)
*/
private parseRepository(repo: any): string {
if (typeof repo === 'string') {
return repo;
}
if (typeof repo === 'object' && repo.url) {
return repo.url;
}
return '';
}
/**
* Generate JSON-LD context
*/
generateJSONLD(annotation: SchemaAnnotation): any {
return {
'@context': annotation.context_url || 'https://schema.org',
'@type': annotation.schema_type,
...annotation.properties,
};
}
/**
* Save annotation to database
*/
async saveAnnotation(annotation: SchemaAnnotation): Promise<void> {
const db = await getDatabase();
await db.upsertSchemaAnnotation(annotation);
}
/**
* Get annotation for entity
*/
async getAnnotation(entityId: number): Promise<SchemaAnnotation | null> {
const db = await getDatabase();
return db.findSchemaAnnotationByEntityId(entityId);
}
}
/**
* Singleton instance
*/
let mapperInstance: SchemaMapper | null = null;
/**
* Get or create Schema mapper instance
*/
export function getSchemaMapper(): SchemaMapper {
if (!mapperInstance) {
mapperInstance = new SchemaMapper();
}
return mapperInstance;
}