meld
Version:
Meld: A template language for LLM prompts
185 lines (161 loc) • 4.77 kB
text/typescript
import type { IStateService } from './IStateService.js';
import type { StateNode } from './types.js';
import { StateFactory } from './StateFactory.js';
import { stateLogger as logger } from '@core/utils/logger.js';
/**
* Options for migrating state
*/
export interface MigrationOptions {
/**
* Whether to preserve immutability status
* @default true
*/
preserveImmutability?: boolean;
/**
* Whether to validate the migrated state
* @default true
*/
validate?: boolean;
/**
* Whether to throw on validation errors
* @default false
*/
strict?: boolean;
}
/**
* Result of state migration
*/
export interface MigrationResult {
/**
* The migrated state node
*/
state: StateNode;
/**
* Any validation warnings that occurred during migration
*/
warnings: string[];
/**
* Whether the migration was successful
*/
success: boolean;
}
/**
* Migrates an old state service instance to a new immutable state node
*/
export function migrateState(oldState: IStateService, options: MigrationOptions = {}): MigrationResult {
const {
preserveImmutability = true,
validate = true,
strict = false
} = options;
const warnings: string[] = [];
const factory = new StateFactory();
try {
// Create base state
const state = factory.createState({
source: 'migration',
filePath: oldState.getCurrentFilePath() ?? undefined
});
// Migrate variables
const text = new Map(oldState.getAllTextVars());
const data = new Map(oldState.getAllDataVars());
const path = new Map(oldState.getAllPathVars());
// Migrate commands
const commands = new Map(oldState.getAllCommands());
// Migrate imports
const imports = oldState.getImports();
// Migrate nodes
const nodes = oldState.getNodes();
// Create migrated state
const migrated = factory.updateState(state, {
variables: { text, data, path },
commands,
imports,
nodes
});
// Validate migrated state
if (validate) {
validateMigration(oldState, migrated, warnings);
if (strict && warnings.length > 0) {
throw new Error('Migration validation failed:\n' + warnings.join('\n'));
}
}
logger.debug('Migrated state', {
textVars: text.size,
dataVars: data.size,
pathVars: path.size,
commands: commands.size,
imports: imports.size,
nodes: nodes.length,
warnings: warnings.length
});
return {
state: migrated,
warnings,
success: true
};
} catch (error) {
logger.error('State migration failed', { error });
return {
state: factory.createState(),
warnings: [...warnings, String(error)],
success: false
};
}
}
/**
* Validates that the migrated state matches the original
*/
export function validateMigration(oldState: IStateService, newState: StateNode, warnings: string[]): void {
// Validate text variables
for (const [key, value] of oldState.getAllTextVars()) {
const newValue = newState.variables.text.get(key);
if (newValue !== value) {
warnings.push(`Text variable mismatch: ${key}`);
}
}
// Validate data variables
for (const [key, value] of oldState.getAllDataVars()) {
const newValue = newState.variables.data.get(key);
if (JSON.stringify(newValue) !== JSON.stringify(value)) {
warnings.push(`Data variable mismatch: ${key}`);
}
}
// Validate path variables
for (const [key, value] of oldState.getAllPathVars()) {
const newValue = newState.variables.path.get(key);
if (newValue !== value) {
warnings.push(`Path variable mismatch: ${key}`);
}
}
// Validate commands
for (const [key, value] of oldState.getAllCommands()) {
const newValue = newState.commands.get(key);
if (!newValue || JSON.stringify(newValue) !== JSON.stringify(value)) {
warnings.push(`Command mismatch: ${key}`);
}
}
// Validate imports
for (const importPath of oldState.getImports()) {
if (!newState.imports.has(importPath)) {
warnings.push(`Missing import: ${importPath}`);
}
}
// Validate nodes
const oldNodes = oldState.getNodes();
if (oldNodes.length !== newState.nodes.length) {
warnings.push(`Node count mismatch: ${oldNodes.length} vs ${newState.nodes.length}`);
} else {
for (let i = 0; i < oldNodes.length; i++) {
if (JSON.stringify(oldNodes[i]) !== JSON.stringify(newState.nodes[i])) {
warnings.push(`Node mismatch at index ${i}`);
}
}
}
// Validate file path
const oldPath = oldState.getCurrentFilePath();
const newPath = newState.filePath;
if (oldPath !== (newPath ?? null)) {
warnings.push(`File path mismatch: ${oldPath} vs ${newPath}`);
}
}