@fairmint/canton-node-sdk
Version:
Canton Node SDK
195 lines • 8.44 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SubscribeToUpdates = exports.TransactionShape = void 0;
const zod_1 = require("zod");
const WebSocketClient_1 = require("../../../../../core/ws/WebSocketClient");
const event_format_builder_1 = require("../utils/event-format-builder");
const path = '/v2/updates';
/**
* The `TransactionShape` enum defines the event shape for `Transaction`s and can have two different flavors AcsDelta
* and LedgerEffects.
*
* ```protobuf
* enum TransactionShape {
* TRANSACTION_SHAPE_ACS_DELTA = 1;
* TRANSACTION_SHAPE_LEDGER_EFFECTS = 2;
* }
* ```
*
* - AcsDelta
*
* The transaction shape that is sufficient to maintain an accurate ACS view. This translates to create and archive
* events. The field witness_parties in events are populated as stakeholders, transaction filter will apply
* accordingly.
* - LedgerEffects
*
* The transaction shape that allows maintaining an ACS and also conveys detailed information about all exercises. This
* translates to create, consuming exercise and non-consuming exercise. The field witness_parties in events are
* populated as cumulative informees, transaction filter will apply accordingly.
*/
var TransactionShape;
(function (TransactionShape) {
TransactionShape["TRANSACTION_SHAPE_ACS_DELTA"] = "TRANSACTION_SHAPE_ACS_DELTA";
TransactionShape["TRANSACTION_SHAPE_LEDGER_EFFECTS"] = "TRANSACTION_SHAPE_LEDGER_EFFECTS";
})(TransactionShape || (exports.TransactionShape = TransactionShape = {}));
const SubscribeToUpdatesParamsSchema = zod_1.z.object({
/** Optional list of parties to scope the filter. */
parties: zod_1.z.array(zod_1.z.string()).optional(),
/** Optional template filters applied server-side. */
templateIds: zod_1.z.array(zod_1.z.string()).optional(),
/** Include created event blob in TemplateFilter results (default false). */
includeCreatedEventBlob: zod_1.z.boolean().optional(),
/** Beginning of the requested ledger section. Defaults to ledger end if not provided. */
beginExclusive: zod_1.z.number().optional(),
/**
* End of the requested ledger section (optional).
*
* When specified: Creates a bounded subscription that closes after reaching this offset. When omitted: Creates a
* persistent streaming connection that remains open indefinitely, delivering real-time updates as they occur on the
* ledger. The connection will remain active until manually closed or an error occurs.
*
* Use bounded subscriptions (with endInclusive) when catching up on historical data. Use unbounded subscriptions
* (without endInclusive) for real-time streaming.
*/
endInclusive: zod_1.z.number().optional(),
/** Include reassignments in the stream (default true). */
includeReassignments: zod_1.z.boolean().optional(),
/** Include topology events in the stream (default false). */
includeTopologyEvents: zod_1.z.boolean().optional(),
/** Transaction shape (default TRANSACTION_SHAPE_LEDGER_EFFECTS). */
transactionShape: zod_1.z.nativeEnum(TransactionShape).optional(),
});
/**
* Subscribes to ledger updates via WebSocket connection.
*
* Supports two modes of operation:
*
* 1. **Bounded subscription**: Specify both `beginExclusive` and `endInclusive` to fetch a specific range of historical
* transactions. The connection closes after reaching `endInclusive`.
* 2. **Persistent streaming**: Specify only `beginExclusive` (or omit both) to create a long-lived WebSocket connection
* that streams real-time updates indefinitely. The connection remains open until manually closed via the returned
* subscription object or an error occurs.
*
* @example
* ```typescript
* // Bounded subscription (historical data)
* await client.subscribeToUpdates({
* beginExclusive: 1000,
* endInclusive: 2000,
* parties: ['party1'],
* onMessage: (msg) => console.log(msg)
* });
*
* // Persistent streaming (real-time updates)
* await client.subscribeToUpdates({
* beginExclusive: 2000,
* // endInclusive omitted - connection stays open
* parties: ['party1'],
* onMessage: (msg) => console.log(msg)
* });
* ```;
*/
class SubscribeToUpdates {
constructor(client) {
this.client = client;
}
async connect(params) {
const validated = SubscribeToUpdatesParamsSchema.parse(params);
// Determine beginExclusive (default to ledger end if not specified)
let { beginExclusive } = validated;
if (beginExclusive === undefined) {
const ledgerEnd = await this.client.getLedgerEnd({});
beginExclusive = ledgerEnd.offset;
}
// Build party list - default to client's party list if not provided
const partyList = validated.parties && validated.parties.length > 0 ? validated.parties : this.client.buildPartyList();
// Build event format
const eventFormat = (0, event_format_builder_1.buildEventFormat)({
parties: partyList,
...(validated.templateIds !== undefined && { templateIds: validated.templateIds }),
...(validated.includeCreatedEventBlob !== undefined && {
includeCreatedEventBlob: validated.includeCreatedEventBlob,
}),
});
// Build update format with optional features
const updateFormat = {
includeTransactions: {
eventFormat,
transactionShape: validated.transactionShape ?? TransactionShape.TRANSACTION_SHAPE_LEDGER_EFFECTS,
},
};
// Add reassignments if enabled (default true)
if (validated.includeReassignments !== false) {
updateFormat.includeReassignments = {
filtersByParty: eventFormat.filtersByParty,
verbose: false,
};
}
// Add topology events if enabled (default false)
if (validated.includeTopologyEvents === true) {
updateFormat.includeTopologyEvents = {
includeParticipantAuthorizationEvents: {
parties: partyList,
},
};
}
// Build request message
const requestMessage = {
beginExclusive,
endInclusive: validated.endInclusive,
verbose: false,
updateFormat,
};
const wsClient = new WebSocketClient_1.WebSocketClient(this.client);
return new Promise((resolve, reject) => {
let settled = false;
void wsClient
.connect(path, requestMessage, {
onMessage: (raw) => {
try {
// Skip Zod validation for response types - just use the raw parsed JSON
// Zod validation is only needed for input types, not outputs
const parsed = raw;
// Call user's onMessage callback if provided
if (typeof params.onMessage === 'function') {
params.onMessage(parsed);
}
// Check if it's an error
if (typeof parsed === 'object' && ('errors' in parsed || 'error' in parsed)) {
if (!settled) {
settled = true;
reject(parsed);
}
}
}
catch (e) {
if (!settled) {
settled = true;
reject(e);
}
}
},
onError: (err) => {
if (!settled) {
settled = true;
reject(err);
}
},
onClose: () => {
if (!settled) {
settled = true;
resolve();
}
},
})
.catch((err) => {
if (!settled) {
settled = true;
reject(err);
}
});
});
}
}
exports.SubscribeToUpdates = SubscribeToUpdates;
//# sourceMappingURL=subscribe-to-updates.js.map