269 lines
6.8 KiB
TypeScript
269 lines
6.8 KiB
TypeScript
/**
|
|
* @file versioning.ts
|
|
* @description Version management interfaces
|
|
* Semantic versioning, change tracking, branches, and diff comparison
|
|
*/
|
|
|
|
import type {
|
|
TVersionType,
|
|
TChangeOperation,
|
|
TBranchStatus,
|
|
} from './types.js';
|
|
|
|
// ============================================================================
|
|
// SEMANTIC VERSION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Semantic version following major.minor.patch pattern
|
|
* For contracts: major = substantial changes, minor = clause additions, patch = wording fixes
|
|
*/
|
|
export interface ISemanticVersion {
|
|
major: number;
|
|
minor: number;
|
|
patch: number;
|
|
label?: 'alpha' | 'beta' | 'rc' | 'final';
|
|
}
|
|
|
|
/**
|
|
* Version string type
|
|
*/
|
|
export type TVersionString = `${number}.${number}.${number}` | `${number}.${number}.${number}-${string}`;
|
|
|
|
// ============================================================================
|
|
// VERSION CHANGES
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Represents a single atomic change within a version
|
|
*/
|
|
export interface IVersionChange {
|
|
id: string;
|
|
operation: TChangeOperation;
|
|
targetPath: string;
|
|
targetParagraphId?: string;
|
|
previousValue?: string | object;
|
|
newValue?: string | object;
|
|
characterRange?: {
|
|
start: number;
|
|
end: number;
|
|
};
|
|
timestamp: number;
|
|
userId: string;
|
|
userDisplayName?: string;
|
|
changeReason?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// AMENDMENT
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Amendment-specific information
|
|
*/
|
|
export interface IAmendmentDetails {
|
|
amendmentNumber: number;
|
|
amendmentType: 'modification' | 'addendum' | 'rider' | 'waiver' | 'extension';
|
|
effectiveDate: number;
|
|
supersedes: string[];
|
|
requiresResigning: boolean;
|
|
affectedSections: string[];
|
|
}
|
|
|
|
// ============================================================================
|
|
// VERSION
|
|
// ============================================================================
|
|
|
|
/**
|
|
* A complete version snapshot
|
|
*/
|
|
export interface IVersion {
|
|
id: string;
|
|
version: ISemanticVersion;
|
|
versionString: TVersionString;
|
|
type: TVersionType;
|
|
createdAt: number;
|
|
createdBy: string;
|
|
createdByDisplayName?: string;
|
|
previousVersionId?: string;
|
|
changes: IVersionChange[];
|
|
contentHash: string;
|
|
parentHash?: string;
|
|
title?: string;
|
|
description?: string;
|
|
tags: string[];
|
|
isAmendment: boolean;
|
|
amendmentDetails?: IAmendmentDetails;
|
|
}
|
|
|
|
// ============================================================================
|
|
// VERSION BRANCH
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Branch for parallel version development
|
|
*/
|
|
export interface IVersionBranch {
|
|
id: string;
|
|
name: string;
|
|
description?: string;
|
|
createdAt: number;
|
|
createdBy: string;
|
|
baseVersionId: string;
|
|
headVersionId: string;
|
|
status: TBranchStatus;
|
|
mergedIntoVersionId?: string;
|
|
}
|
|
|
|
// ============================================================================
|
|
// VERSION HISTORY
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Complete version history for a contract
|
|
*/
|
|
export interface IVersionHistory {
|
|
contractId: string;
|
|
currentVersionId: string;
|
|
currentVersion: ISemanticVersion;
|
|
versions: IVersion[];
|
|
branches: IVersionBranch[];
|
|
mainBranchId: string;
|
|
publishedVersions: string[];
|
|
amendmentVersions: string[];
|
|
}
|
|
|
|
// ============================================================================
|
|
// VERSION DIFF
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Diff summary statistics
|
|
*/
|
|
export interface IDiffSummary {
|
|
insertions: number;
|
|
deletions: number;
|
|
modifications: number;
|
|
paragraphsAffected: string[];
|
|
}
|
|
|
|
/**
|
|
* Version comparison result
|
|
*/
|
|
export interface IVersionDiff {
|
|
fromVersionId: string;
|
|
toVersionId: string;
|
|
fromVersion: ISemanticVersion;
|
|
toVersion: ISemanticVersion;
|
|
changes: IVersionChange[];
|
|
summary: IDiffSummary;
|
|
}
|
|
|
|
// ============================================================================
|
|
// ROLLBACK
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Rollback request
|
|
*/
|
|
export interface IRollbackRequest {
|
|
targetVersionId: string;
|
|
reason: string;
|
|
requestedBy: string;
|
|
requestedAt: number;
|
|
createNewVersion: boolean;
|
|
}
|
|
|
|
// ============================================================================
|
|
// FACTORY FUNCTIONS
|
|
// ============================================================================
|
|
|
|
/**
|
|
* Create initial version
|
|
*/
|
|
export function createInitialVersion(userId: string, userDisplayName?: string): IVersion {
|
|
const now = Date.now();
|
|
return {
|
|
id: crypto.randomUUID(),
|
|
version: { major: 0, minor: 1, patch: 0 },
|
|
versionString: '0.1.0',
|
|
type: 'draft',
|
|
createdAt: now,
|
|
createdBy: userId,
|
|
createdByDisplayName: userDisplayName,
|
|
changes: [],
|
|
contentHash: '',
|
|
tags: [],
|
|
isAmendment: false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Create empty version history
|
|
*/
|
|
export function createEmptyVersionHistory(contractId: string, initialVersion: IVersion): IVersionHistory {
|
|
const mainBranchId = crypto.randomUUID();
|
|
return {
|
|
contractId,
|
|
currentVersionId: initialVersion.id,
|
|
currentVersion: initialVersion.version,
|
|
versions: [initialVersion],
|
|
branches: [
|
|
{
|
|
id: mainBranchId,
|
|
name: 'main',
|
|
createdAt: initialVersion.createdAt,
|
|
createdBy: initialVersion.createdBy,
|
|
baseVersionId: initialVersion.id,
|
|
headVersionId: initialVersion.id,
|
|
status: 'active',
|
|
},
|
|
],
|
|
mainBranchId,
|
|
publishedVersions: [],
|
|
amendmentVersions: [],
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Increment semantic version
|
|
*/
|
|
export function incrementVersion(
|
|
current: ISemanticVersion,
|
|
incrementType: 'major' | 'minor' | 'patch'
|
|
): ISemanticVersion {
|
|
switch (incrementType) {
|
|
case 'major':
|
|
return { major: current.major + 1, minor: 0, patch: 0 };
|
|
case 'minor':
|
|
return { major: current.major, minor: current.minor + 1, patch: 0 };
|
|
case 'patch':
|
|
return { major: current.major, minor: current.minor, patch: current.patch + 1 };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Convert semantic version to string
|
|
*/
|
|
export function versionToString(version: ISemanticVersion): TVersionString {
|
|
const base = `${version.major}.${version.minor}.${version.patch}` as TVersionString;
|
|
if (version.label && version.label !== 'final') {
|
|
return `${base}-${version.label}` as TVersionString;
|
|
}
|
|
return base;
|
|
}
|
|
|
|
/**
|
|
* Parse version string to semantic version
|
|
*/
|
|
export function parseVersionString(versionString: string): ISemanticVersion {
|
|
const [base, label] = versionString.split('-');
|
|
const [major, minor, patch] = base.split('.').map(Number);
|
|
return {
|
|
major: major || 0,
|
|
minor: minor || 0,
|
|
patch: patch || 0,
|
|
label: label as ISemanticVersion['label'],
|
|
};
|
|
}
|