Add resolveCommitReferences method
This commit is contained in:
295
src/utils/resolve-invitation-data.ts
Normal file
295
src/utils/resolve-invitation-data.ts
Normal file
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* Transforms a raw XO invitation into a flattened, template-enriched structure
|
||||
* suitable for UI display without manually resolving template references.
|
||||
*
|
||||
* The original invitation format is unchanged in storage and transport; this
|
||||
* function produces a read model that merges commit data with template metadata
|
||||
* (names, descriptions, icons, roles, etc.).
|
||||
*/
|
||||
|
||||
import type {
|
||||
XOInvitation,
|
||||
XOInvitationInput,
|
||||
XOInvitationOutput,
|
||||
XOInvitationVariable,
|
||||
XOInvitationVariableValue,
|
||||
XOTemplate,
|
||||
XOTemplateInput,
|
||||
XOTemplateOutput,
|
||||
XOTemplateVariable,
|
||||
} from "@xo-cash/types";
|
||||
|
||||
/**
|
||||
* View metadata copied from a template definition onto a resolved invitation item.
|
||||
*/
|
||||
interface TemplateViewMetadata {
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Role-specific view metadata from a template output definition.
|
||||
*/
|
||||
export interface ResolvedInvitationOutputRoleMetadata {
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable from invitation commits enriched with its template definition.
|
||||
*/
|
||||
export interface ResolvedInvitationVariable {
|
||||
entityIdentifier: string;
|
||||
variableIdentifier: string;
|
||||
roleIdentifier?: string;
|
||||
value: XOInvitationVariableValue;
|
||||
name?: string;
|
||||
description?: string;
|
||||
type?: string;
|
||||
hint?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A transaction input from invitation commits enriched with its template definition.
|
||||
*/
|
||||
export type ResolvedInvitationInput = XOInvitationInput & {
|
||||
entityIdentifier: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
unlockingScript?: string;
|
||||
omitChangeAmounts?: XOTemplateInput["omitChangeAmounts"];
|
||||
};
|
||||
|
||||
/**
|
||||
* A transaction output from invitation commits enriched with its template definition.
|
||||
*/
|
||||
export type ResolvedInvitationOutput = XOInvitationOutput & {
|
||||
entityIdentifier: string;
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
roles?: Record<string, ResolvedInvitationOutputRoleMetadata>;
|
||||
lockingScript?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Flattened, template-enriched invitation data for UI consumption.
|
||||
*/
|
||||
export interface ResolvedInvitationData {
|
||||
invitationIdentifier: string;
|
||||
templateIdentifier: string;
|
||||
actionIdentifier: string;
|
||||
variables: ResolvedInvitationVariable[];
|
||||
inputs: ResolvedInvitationInput[];
|
||||
outputs: ResolvedInvitationOutput[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks human-readable view fields from a template definition.
|
||||
*/
|
||||
function pickTemplateViewMetadata(
|
||||
definition: TemplateViewMetadata | undefined,
|
||||
): TemplateViewMetadata {
|
||||
if (!definition) return {};
|
||||
|
||||
return {
|
||||
...(definition.name !== undefined && { name: definition.name }),
|
||||
...(definition.description !== undefined && {
|
||||
description: definition.description,
|
||||
}),
|
||||
...(definition.icon !== undefined && { icon: definition.icon }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks variable metadata from a template variable definition.
|
||||
*/
|
||||
function pickTemplateVariableMetadata(
|
||||
definition: XOTemplateVariable | undefined,
|
||||
): Pick<ResolvedInvitationVariable, "name" | "description" | "type" | "hint"> {
|
||||
if (!definition) return {};
|
||||
|
||||
return {
|
||||
...pickTemplateViewMetadata(definition),
|
||||
...(definition.type !== undefined && { type: definition.type }),
|
||||
...(definition.hint !== undefined && { hint: definition.hint }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks input metadata from a template input definition.
|
||||
*/
|
||||
function pickTemplateInputMetadata(
|
||||
definition: XOTemplateInput | undefined,
|
||||
): Pick<
|
||||
ResolvedInvitationInput,
|
||||
"name" | "description" | "icon" | "unlockingScript" | "omitChangeAmounts"
|
||||
> {
|
||||
if (!definition) return {};
|
||||
|
||||
return {
|
||||
...pickTemplateViewMetadata(definition),
|
||||
...(definition.unlockingScript !== undefined && {
|
||||
unlockingScript: definition.unlockingScript,
|
||||
}),
|
||||
...(definition.omitChangeAmounts !== undefined && {
|
||||
omitChangeAmounts: definition.omitChangeAmounts,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Template display metadata layered onto a committed output.
|
||||
*/
|
||||
interface TemplateOutputMetadata {
|
||||
name?: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
roles?: Record<string, ResolvedInvitationOutputRoleMetadata>;
|
||||
lockingScript?: string;
|
||||
valueSatoshis?: bigint | string;
|
||||
token?: XOTemplateOutput["token"];
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks output metadata from a template output definition.
|
||||
*
|
||||
* Committed output values (e.g. lockingBytecode) take precedence over template
|
||||
* defaults; display-oriented fields like name, description, and template
|
||||
* valueSatoshis expressions are layered on for UI rendering.
|
||||
*/
|
||||
function pickTemplateOutputMetadata(
|
||||
definition: XOTemplateOutput | undefined,
|
||||
): TemplateOutputMetadata {
|
||||
if (!definition) return {};
|
||||
|
||||
const roles = definition.roles
|
||||
? Object.fromEntries(
|
||||
Object.entries(definition.roles).map(([roleId, roleDefinition]) => [
|
||||
roleId,
|
||||
pickTemplateViewMetadata(roleDefinition),
|
||||
]),
|
||||
)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
...pickTemplateViewMetadata(definition),
|
||||
...(roles !== undefined && Object.keys(roles).length > 0 && { roles }),
|
||||
...(definition.lockingScript !== undefined && {
|
||||
lockingScript: definition.lockingScript,
|
||||
}),
|
||||
...(definition.valueSatoshis !== undefined && {
|
||||
valueSatoshis: definition.valueSatoshis,
|
||||
}),
|
||||
...(definition.token !== undefined && { token: definition.token }),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches a committed variable with its template definition.
|
||||
*/
|
||||
function resolveVariable(
|
||||
variable: XOInvitationVariable,
|
||||
entityIdentifier: string,
|
||||
template: XOTemplate,
|
||||
): ResolvedInvitationVariable {
|
||||
const definition = template.variables?.[variable.variableIdentifier];
|
||||
|
||||
return {
|
||||
entityIdentifier,
|
||||
variableIdentifier: variable.variableIdentifier,
|
||||
...(variable.roleIdentifier !== undefined && {
|
||||
roleIdentifier: variable.roleIdentifier,
|
||||
}),
|
||||
value: variable.value,
|
||||
...pickTemplateVariableMetadata(definition),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches a committed input with its template definition when an identifier is present.
|
||||
*/
|
||||
function resolveInput(
|
||||
input: XOInvitationInput,
|
||||
entityIdentifier: string,
|
||||
template: XOTemplate,
|
||||
): ResolvedInvitationInput {
|
||||
const definition = input.inputIdentifier
|
||||
? template.inputs?.[input.inputIdentifier]
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
entityIdentifier,
|
||||
...input,
|
||||
...pickTemplateInputMetadata(definition),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enriches a committed output with its template definition when an identifier is present.
|
||||
*/
|
||||
function resolveOutput(
|
||||
output: XOInvitationOutput,
|
||||
entityIdentifier: string,
|
||||
template: XOTemplate,
|
||||
): ResolvedInvitationOutput {
|
||||
const definition = output.outputIdentifier
|
||||
? template.outputs?.[output.outputIdentifier]
|
||||
: undefined;
|
||||
const templateMetadata = pickTemplateOutputMetadata(definition);
|
||||
|
||||
return {
|
||||
entityIdentifier,
|
||||
...output,
|
||||
...templateMetadata,
|
||||
} as ResolvedInvitationOutput;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns flattened, template-enriched invitation data for UI display.
|
||||
*
|
||||
* Commits are walked in order; variables, inputs, and outputs are collected
|
||||
* into top-level arrays with `entityIdentifier` and template metadata attached.
|
||||
* Items without a template identifier (e.g. ad-hoc change outputs) keep only
|
||||
* their committed fields.
|
||||
*
|
||||
* @param invitation - The raw invitation in standard XO format.
|
||||
* @param template - The template referenced by the invitation.
|
||||
* @returns Resolved invitation data ready for display.
|
||||
*/
|
||||
export function resolveCommitReferences(
|
||||
invitation: XOInvitation,
|
||||
template: XOTemplate,
|
||||
): ResolvedInvitationData {
|
||||
const variables: ResolvedInvitationVariable[] = [];
|
||||
const inputs: ResolvedInvitationInput[] = [];
|
||||
const outputs: ResolvedInvitationOutput[] = [];
|
||||
|
||||
for (const commit of invitation.commits ?? []) {
|
||||
for (const variable of commit.data?.variables ?? []) {
|
||||
variables.push(
|
||||
resolveVariable(variable, commit.entityIdentifier, template),
|
||||
);
|
||||
}
|
||||
|
||||
for (const input of commit.data?.inputs ?? []) {
|
||||
inputs.push(resolveInput(input, commit.entityIdentifier, template));
|
||||
}
|
||||
|
||||
for (const output of commit.data?.outputs ?? []) {
|
||||
outputs.push(resolveOutput(output, commit.entityIdentifier, template));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
invitationIdentifier: invitation.invitationIdentifier,
|
||||
templateIdentifier: invitation.templateIdentifier,
|
||||
actionIdentifier: invitation.actionIdentifier,
|
||||
variables,
|
||||
inputs,
|
||||
outputs,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user