@allma/core-cdk
Version:
Core AWS CDK constructs for deploying the Allma serverless AI orchestration platform.
151 lines • 7.5 kB
JavaScript
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, GetCommand } from '@aws-sdk/lib-dynamodb';
import { FlowDefinitionSchema, StepDefinitionSchema, PromptTemplateSchema, PermanentStepError, SYSTEM_STEP_DEFINITIONS, } from '@allma/core-types';
import { log_error, log_info, deepMerge } from '@allma/core-sdk';
const CONFIG_TABLE_NAME = process.env.ALLMA_CONFIG_TABLE_NAME;
const ddbDocClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
/**
* Retrieves the published version number for a versioned item (Flow or Prompt).
* @param itemId The unique ID of the item (e.g., flowId, promptId).
* @param itemType The type of item, used to determine the DynamoDB PK prefix.
* @param correlationId Optional ID for logging.
* @returns The published version number.
* @throws An error if the item or its published version is not found.
*/
async function getPublishedVersion(itemId, itemType, correlationId) {
const pkPrefix = itemType === 'FLOW_DEF' ? 'FLOW_DEF#' : 'PROMPT_TEMPLATE#';
const descriptiveName = itemType === 'FLOW_DEF' ? 'flow definition' : 'prompt template';
log_info(`Resolving LATEST_PUBLISHED version for ${itemType}`, { itemId }, correlationId);
const getParams = {
TableName: CONFIG_TABLE_NAME,
Key: { PK: `${pkPrefix}${itemId}`, SK: 'METADATA' },
};
try {
const { Item } = await ddbDocClient.send(new GetCommand(getParams));
if (!Item || typeof Item.publishedVersion !== 'number') {
throw new PermanentStepError(`No published version found for ${descriptiveName} '${itemId}'. Check METADATA item.`);
}
log_info(`Resolved LATEST_PUBLISHED to version ${Item.publishedVersion}`, { itemId }, correlationId);
return Item.publishedVersion;
}
catch (e) {
log_error(`Failed to resolve LATEST_PUBLISHED version for ${itemType}`, { error: e.message, params: getParams }, correlationId);
throw e;
}
}
export async function loadFlowDefinition(flowDefinitionId, version, correlationId) {
log_info('Loading Flow Definition', { flowDefinitionId, version, tableName: CONFIG_TABLE_NAME }, correlationId);
let versionToFetch = version;
if (version === 'LATEST_PUBLISHED') {
versionToFetch = await getPublishedVersion(flowDefinitionId, 'FLOW_DEF', correlationId);
}
const getParams = {
TableName: CONFIG_TABLE_NAME,
Key: { PK: `FLOW_DEF#${flowDefinitionId}`, SK: `VERSION#${versionToFetch}` },
};
try {
const { Item } = await ddbDocClient.send(new GetCommand(getParams));
if (!Item) {
throw new PermanentStepError(`Flow Definition not found for id: ${flowDefinitionId}, version: ${versionToFetch}`);
}
// NEW HYDRATION LOGIC STARTS HERE
const rawFlowDef = Item; // Cast to work with it
for (const stepId in rawFlowDef.steps) {
const stepInstance = rawFlowDef.steps[stepId];
if (stepInstance.stepDefinitionId) {
const baseStepDef = await loadStepDefinition(stepInstance.stepDefinitionId, correlationId);
// Deep merge base definition with instance-specific overrides
const hydratedStep = deepMerge(baseStepDef, stepInstance);
rawFlowDef.steps[stepId] = hydratedStep;
}
}
// HYDRATION LOGIC ENDS
// Now, parse the FULLY HYDRATED flow definition
return FlowDefinitionSchema.parse(rawFlowDef);
}
catch (e) {
if (e instanceof PermanentStepError)
throw e;
log_error('Failed to load or parse Flow Definition from DynamoDB', { error: e.message, params: getParams }, correlationId);
throw e; // Re-throw to fail the execution
}
}
export async function loadStepDefinition(stepDefinitionId, correlationId) {
log_info('Loading Step Definition', { stepDefinitionId, tableName: CONFIG_TABLE_NAME }, correlationId);
if (stepDefinitionId.startsWith('system-') || stepDefinitionId.startsWith('system/')) {
const systemStep = SYSTEM_STEP_DEFINITIONS.find(s => s.id === stepDefinitionId);
if (systemStep) {
log_info(`Found system step definition for '${stepDefinitionId}'. Hydrating from constant.`, {}, correlationId);
const now = new Date().toISOString();
const constructedDef = {
...systemStep,
createdAt: now,
updatedAt: now,
version: 1,
isPublished: true,
tags: ['system'],
};
// The object is not a complete StepDefinition yet, so we return it without validation.
// Final validation happens after it's merged with the instance config.
return constructedDef;
}
else {
throw new PermanentStepError(`System step definition with id '${stepDefinitionId}' not found.`);
}
}
const getParams = {
TableName: CONFIG_TABLE_NAME,
// Assuming steps are not versioned for now, but this could be extended
Key: { PK: `STEP_DEF#${stepDefinitionId}`, SK: `METADATA` },
};
try {
const { Item } = await ddbDocClient.send(new GetCommand(getParams));
if (!Item) {
throw new PermanentStepError(`Step Definition not found for id: ${stepDefinitionId}`);
}
return StepDefinitionSchema.parse(Item);
}
catch (e) {
if (e instanceof PermanentStepError)
throw e;
log_error('Failed to load or parse Step Definition from DynamoDB', { error: e.message, params: getParams }, correlationId);
throw e;
}
}
/**
* Loads a specific or the latest published version of a prompt template from DynamoDB.
* @param promptTemplateId The ID of the prompt template to load.
* @param version The specific version number or 'LATEST_PUBLISHED'. Defaults to 'LATEST_PUBLISHED'.
* @param correlationId Optional ID for logging.
* @returns The parsed prompt template.
* @throws An error if the prompt template is not found or fails parsing.
*/
export async function loadPromptTemplate(promptTemplateId, version = 'LATEST_PUBLISHED', // Default to latest published
correlationId) {
log_info('Loading Prompt Template', { promptTemplateId, version, tableName: CONFIG_TABLE_NAME }, correlationId);
let versionToFetch = version;
if (version === 'LATEST_PUBLISHED') {
versionToFetch = await getPublishedVersion(promptTemplateId, 'PROMPT_TEMPLATE', correlationId);
}
const getParams = {
TableName: CONFIG_TABLE_NAME,
Key: { PK: `PROMPT_TEMPLATE#${promptTemplateId}`, SK: `VERSION#${versionToFetch}` },
};
try {
const { Item } = await ddbDocClient.send(new GetCommand(getParams));
if (!Item) {
throw new PermanentStepError(`Prompt Template not found for id: ${promptTemplateId}, version: ${versionToFetch}`);
}
// The storage item includes PK, SK, itemType which are not part of the API model.
// We strip them before parsing to match the PromptTemplateSchema.
const { ...apiItem } = Item;
return PromptTemplateSchema.parse(apiItem);
}
catch (e) {
if (e instanceof PermanentStepError)
throw e;
log_error('Failed to load or parse Prompt Template from DynamoDB', { error: e.message, params: getParams }, correlationId);
throw e;
}
}
//# sourceMappingURL=config-loader.js.map