Add resolveCommitReferences method

This commit is contained in:
2026-06-08 13:09:38 +02:00
parent c7e1d69e2d
commit 69adee180a
5 changed files with 683 additions and 93 deletions
+72 -22
View File
@@ -17,6 +17,7 @@ import type {
XOInvitationOutput,
XOInvitationVariable,
XOInvitationVariableValue,
XOTemplate,
} from "@xo-cash/types";
import type { UnspentOutputData } from "@xo-cash/state";
import {
@@ -34,8 +35,14 @@ import type { BlockchainService } from "./electrum.js";
import { EventEmitter } from "../utils/event-emitter.js";
import { decodeExtendedJsonObject } from "../utils/ext-json.js";
import {
resolveCommitReferences,
type ResolvedInvitationData,
} from "../utils/resolve-invitation-data.js";
import { compileCashAssemblyString } from "@xo-cash/engine";
export type { ResolvedInvitationData } from "../utils/resolve-invitation-data.js";
export type InvitationEventMap = {
"invitation-updated": XOInvitation;
"invitation-status-changed": string;
@@ -103,11 +110,33 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
);
// Create the invitation
const invitationInstance = new Invitation(engineInvitation, dependencies);
const invitationInstance = new Invitation(
engineInvitation,
dependencies,
template,
);
return invitationInstance;
}
/**
* Flattened, template-enriched view of {@link Invitation.data}.
* Updated automatically whenever invitation data changes.
*/
public resolvedData: ResolvedInvitationData = {
invitationIdentifier: "",
templateIdentifier: "",
actionIdentifier: "",
variables: [],
inputs: [],
outputs: [],
};
/**
* The template used to enrich {@link resolvedData}.
*/
private template: XOTemplate;
/**
* The invitation data.
*/
@@ -145,14 +174,19 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
/**
* Create an invitation and start the SSE Session required for it.
*/
constructor(invitation: XOInvitation, dependencies: InvitationDependencies) {
constructor(
invitation: XOInvitation,
dependencies: InvitationDependencies,
template: XOTemplate,
) {
super();
this.data = invitation;
this.template = template;
this.engine = dependencies.engine;
this.syncServer = dependencies.syncServer;
this.storage = dependencies.storage;
this.electrum = dependencies.electrum;
this.updateInvitationData(invitation);
// Apply SSE updates serially so each engine update sees the latest history.
this.syncServer.on("message", (event) => {
@@ -167,6 +201,14 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
});
}
/**
* Updates raw invitation data and recomputes {@link resolvedData}.
*/
private updateInvitationData(invitation: XOInvitation): void {
this.data = invitation;
this.resolvedData = resolveCommitReferences(invitation, this.template);
}
private enqueueSyncUpdate(update: () => Promise<void>): Promise<void> {
const queuedUpdate = this.sseUpdateQueue.then(update);
this.sseUpdateQueue = queuedUpdate.catch(() => {});
@@ -197,19 +239,21 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
try {
// Prefer keeping the engine's local invitation state in sync.
this.data = stripLocalInvitationMetadata(
await this.engine.updateInvitation({
...this.data,
...invitation,
commits: combinedCommits,
}),
this.updateInvitationData(
stripLocalInvitationMetadata(
await this.engine.updateInvitation({
...this.data,
...invitation,
commits: combinedCommits,
}),
),
);
} catch (error) {
this.emit(
"error",
error instanceof Error ? error : new Error(String(error)),
);
this.data = { ...this.data, commits: combinedCommits };
this.updateInvitationData({ ...this.data, commits: combinedCommits });
}
await this.storage.set(this.data.invitationIdentifier, this.data);
@@ -243,19 +287,21 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
const newCommits = this.mergeCommits(this.data.commits, invitation.commits);
try {
this.data = stripLocalInvitationMetadata(
await this.engine.updateInvitation({
...this.data,
...invitation,
commits: newCommits,
}),
this.updateInvitationData(
stripLocalInvitationMetadata(
await this.engine.updateInvitation({
...this.data,
...invitation,
commits: newCommits,
}),
),
);
} catch (error) {
this.emit(
"error",
error instanceof Error ? error : new Error(String(error)),
);
this.data = { ...this.data, commits: newCommits };
this.updateInvitationData({ ...this.data, commits: newCommits });
}
await this.storage.set(this.data.invitationIdentifier, this.data);
@@ -488,7 +534,9 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
*/
async accept(acceptParams?: InvitationParameters): Promise<void> {
// Accept the invitation
this.data = await this.engine.acceptInvitation(this.data, acceptParams);
this.updateInvitationData(
await this.engine.acceptInvitation(this.data, acceptParams),
);
// Sync the invitation to the sync server
await this.publishInvitation(this.data);
@@ -529,7 +577,7 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
// Store the signed invitation in the storage
await this.storage.set(this.data.invitationIdentifier, signedInvitation);
this.data = signedInvitation;
this.updateInvitationData(signedInvitation);
// Update the status of the invitation
await this.updateStatus();
@@ -563,9 +611,11 @@ export class Invitation extends EventEmitter<InvitationEventMap> {
await this.ensureAccepted();
// Append the commit to the invitation
this.data = await this.engine.appendInvitation(
this.data.invitationIdentifier,
data,
this.updateInvitationData(
await this.engine.appendInvitation(
this.data.invitationIdentifier,
data,
),
);
// Sync the invitation to the sync server