UNPKG

@daml/ledger

Version:

Client side API implementation for a Daml based ledger. This library implements the JSON based API for a Daml ledger documented in https://docs.daml.com/json-api/index.html.

557 lines • 26.9 kB
"use strict"; // Copyright (c) 2024 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved. // SPDX-License-Identifier: Apache-2.0 var __assign = (this && this.__assign) || function () { __assign = Object.assign || function(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype); return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); var types_1 = require("@daml/types"); var index_1 = __importDefault(require("./index")); var index_2 = require("./index"); var jtv = __importStar(require("@mojotech/json-type-validation")); var jest_mock_console_1 = __importDefault(require("jest-mock-console")); var mockLive = jest.fn(); var mockChange = jest.fn(); var mockConstructor = jest.fn(); var mockSend = jest.fn(); var mockClose = jest.fn(); var mockFunctions = [ mockLive, mockChange, mockConstructor, mockSend, mockClose, ]; var fooKey = "fooKey"; var mockInstance = undefined; jest.mock("isomorphic-ws", function () { return /** @class */ (function () { function class_1() { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i] = arguments[_i]; } mockConstructor.apply(void 0, args); // eslint-disable-next-line @typescript-eslint/no-this-alias mockInstance = this; // eslint-disable-next-line @typescript-eslint/no-var-requires var EventEmitter = require("events").EventEmitter; this.eventEmitter = new EventEmitter(); // eslint-disable-next-line @typescript-eslint/no-var-requires var WsState = require("./index").WsState; this.readyState = WsState.Connecting; } class_1.prototype.addEventListener = function (event, handler) { this.eventEmitter.on(event, handler); }; class_1.prototype.send = function (message) { mockSend(JSON.parse(message)); }; class_1.prototype.close = function () { // eslint-disable-next-line @typescript-eslint/no-var-requires var WsState = require("./index").WsState; this.readyState = WsState.Closed; mockClose(); }; class_1.prototype.serverOpen = function () { this.eventEmitter.emit("open"); // eslint-disable-next-line @typescript-eslint/no-var-requires var WsState = require("./index").WsState; this.readyState = WsState.Open; }; class_1.prototype.serverSend = function (message) { this.eventEmitter.emit("message", { data: JSON.stringify(message) }); }; class_1.prototype.serverClose = function (event) { this.eventEmitter.emit("close", event); // eslint-disable-next-line @typescript-eslint/no-var-requires var WsState = require("./index").WsState; this.readyState = WsState.Closing; }; return class_1; }()); }); var Foo = { sdkVersion: "2.10.2", templateId: "foo-id", keyDecoder: jtv.string(), keyEncode: function (s) { return s; }, decoder: jtv.object({ key: jtv.string() }), encode: function (o) { return o; }, // eslint-disable-next-line @typescript-eslint/ban-types Archive: {}, }; var fooCreateEvent = function (coid, key) { return { templateId: "foo-id", contractId: coid.toString(), signatories: [], observers: [], agreementText: "fooAgreement", key: key || fooKey, payload: { key: fooKey }, }; }; var fooEvent = function (coid) { return { created: fooCreateEvent(coid), matchedQueries: [0] }; }; var fooArchiveEvent = function (coid) { return { archived: { templateId: "foo-id", contractId: coid.toString(), }, }; }; var mockOptions = { token: "dummyToken", httpBaseUrl: "http://localhost:5000/", wsBaseUrl: "ws://localhost:4000/", }; beforeEach(function () { mockFunctions.forEach(function (f) { return f.mockClear(); }); }); describe("internals", function () { test("assert throws as expected", function () { (0, index_2.assert)(true, "not thrown"); expect(function () { return (0, index_2.assert)(false, "throws"); }).toThrow(); }); }); describe("streamSubmit", function () { test("receive unknown message", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); expect(mockConstructor).toHaveBeenCalledTimes(1); expect(mockConstructor).toHaveBeenLastCalledWith("ws://localhost:4000/v1/stream/query", ["jwt.token.dummyToken", "daml.ws.auth"]); stream.on("change", mockChange); mockInstance.serverOpen(); expect(mockSend).toHaveBeenCalledTimes(1); expect(mockSend).toHaveBeenLastCalledWith([ { templateIds: [Foo.templateId] }, ]); var restoreConsole = (0, jest_mock_console_1.default)(); mockInstance.serverSend("mickey mouse"); expect(console.error).toHaveBeenCalledWith("Ledger.streamQuery unknown message", "mickey mouse"); restoreConsole(); }); test("receive warnings", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQueries(Foo, []); stream.on("change", mockChange); var restoreConsole = (0, jest_mock_console_1.default)(); mockInstance.serverOpen(); mockInstance.serverSend({ warnings: ["oh oh"] }); expect(console.warn).toHaveBeenCalledWith("Ledger.streamQueries warnings", { warnings: ["oh oh"], }); restoreConsole(); }); test("receive errors", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKey(Foo, fooKey); stream.on("change", mockChange); var restoreConsole = (0, jest_mock_console_1.default)(); mockInstance.serverOpen(); mockInstance.serverSend({ errors: ["not good!"] }); expect(console.error).toHaveBeenCalledWith("Ledger.streamFetchByKey errors", { errors: ["not good!"] }); restoreConsole(); }); test("receive null offset", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("live", mockLive); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [], offset: null }); expect(mockLive).toHaveBeenCalledTimes(1); expect(mockLive).toHaveBeenLastCalledWith([]); expect(mockChange).not.toHaveBeenCalled(); }); test("reconnect on server close with appropriate offsets, when ws multiplexing is enabled", function () { return __awaiter(void 0, void 0, void 0, function () { var reconnectThreshold, ledger, stream; return __generator(this, function (_a) { switch (_a.label) { case 0: reconnectThreshold = 200; ledger = new index_1.default(__assign(__assign({}, mockOptions), { reconnectThreshold: reconnectThreshold, multiplexQueryStreams: true })); stream = ledger.streamQuery(Foo, { key: "1" }); stream.on("live", mockLive); stream.on("close", mockClose); mockInstance.serverOpen(); mockInstance.serverSend({ events: [], offset: "4" }); return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, reconnectThreshold * 2); })]; case 1: _a.sent(); mockConstructor.mockClear(); mockInstance.serverClose({ code: 1, reason: "test close" }); expect(mockConstructor).toHaveBeenCalled(); mockInstance.serverOpen(); //first query with no offsets. expect(mockSend).toHaveBeenNthCalledWith(1, [ { query: { key: "1" }, templateIds: ["foo-id"] }, ]); //subsequent one on reconnection with offsets received expect(mockSend).toHaveBeenNthCalledWith(2, [ { offset: "4", query: { key: "1" }, templateIds: ["foo-id"] }, ]); mockSend.mockClear(); mockConstructor.mockClear(); // check that the client doesn't try to reconnect again. it should only reconnect if it // received an event confirming the stream is live again, i.e. {events: [], offset: '4'} mockInstance.serverClose({ code: 1, reason: "test close" }); expect(mockConstructor).not.toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); test("reconnect on server close with appropriate offsets, when ws multiplexing is disabled", function () { return __awaiter(void 0, void 0, void 0, function () { var reconnectThreshold, ledger, stream; return __generator(this, function (_a) { switch (_a.label) { case 0: reconnectThreshold = 200; ledger = new index_1.default(__assign(__assign({}, mockOptions), { reconnectThreshold: reconnectThreshold, multiplexQueryStreams: false })); stream = ledger.streamQuery(Foo); stream.on("live", mockLive); stream.on("close", mockClose); mockInstance.serverOpen(); mockInstance.serverSend({ events: [], offset: "3" }); return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, reconnectThreshold * 2); })]; case 1: _a.sent(); mockConstructor.mockClear(); mockInstance.serverClose({ code: 1, reason: "test close" }); expect(mockConstructor).toHaveBeenCalled(); mockInstance.serverOpen(); expect(mockSend).toHaveBeenNthCalledWith(1, [{ templateIds: ["foo-id"] }]); //initial query expect(mockSend).toHaveBeenNthCalledWith(2, { offset: "3" }); // offsets sent when reconnecting. expect(mockSend).toHaveBeenNthCalledWith(3, [{ templateIds: ["foo-id"] }]); //reconnect query request. mockSend.mockClear(); mockConstructor.mockClear(); // check that the client doesn't try to reconnect again. it should only reconnect if it // received an event confirming the stream is live again, i.e. {events: [], offset: '4'} mockInstance.serverClose({ code: 1, reason: "test close" }); expect(mockConstructor).not.toHaveBeenCalled(); return [2 /*return*/]; } }); }); }); test("do not reconnect on client close", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); expect(mockConstructor).toHaveBeenCalled(); mockConstructor.mockClear(); stream.close(); expect(mockConstructor).not.toHaveBeenCalled(); }); test("receive empty events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [] }); expect(mockChange).toHaveBeenCalledTimes(0); }); test("stop listening to a stream", function () { var ledger = new index_1.default(mockOptions); (0, types_1.registerTemplate)(Foo); var stream = ledger.streamQuery(Foo); var count1 = jest.fn(); var count2 = jest.fn(); stream.on("change", count1); stream.on("change", count2); mockInstance.serverOpen(); mockInstance.serverSend({ events: [1, 2, 3].map(fooEvent) }); expect(count1).toHaveBeenCalledTimes(1); expect(count2).toHaveBeenCalledTimes(1); stream.off("change", count1); mockInstance.serverSend({ events: [1, 2, 3].map(fooEvent) }); expect(count1).toHaveBeenCalledTimes(1); expect(count2).toHaveBeenCalledTimes(2); }); }); describe("streamQuery", function () { test("receive live event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("live", mockLive); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1)], offset: "3" }); expect(mockLive).toHaveBeenCalledTimes(1); expect(mockLive).toHaveBeenLastCalledWith([fooCreateEvent(1)]); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenLastCalledWith([fooCreateEvent(1)]); }); test("receive one event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenLastCalledWith([fooCreateEvent(1)]); }); test("receive several events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [1, 2, 3].map(fooEvent) }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([1, 2, 3].map(function (cid) { return fooCreateEvent(cid); })); }); test("drop matching created and archived events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQuery(Foo); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1), fooEvent(2)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([ fooCreateEvent(1), fooCreateEvent(2), ]); mockChange.mockClear(); mockInstance.serverSend({ events: [fooArchiveEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([fooCreateEvent(2)]); }); }); describe("streamQueries", function () { test("receive live event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQueries(Foo, []); stream.on("live", mockLive); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1)], offset: "3" }); expect(mockLive).toHaveBeenCalledTimes(1); expect(mockLive).toHaveBeenLastCalledWith([fooCreateEvent(1)]); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenLastCalledWith([fooCreateEvent(1)]); }); test("receive one event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQueries(Foo, []); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenLastCalledWith([fooCreateEvent(1)]); }); test("receive several events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQueries(Foo, []); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [1, 2, 3].map(fooEvent) }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([1, 2, 3].map(function (cid) { return fooCreateEvent(cid); })); }); test("drop matching created and archived events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamQueries(Foo, []); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverOpen(); mockInstance.serverSend({ events: [fooEvent(1), fooEvent(2)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([ fooCreateEvent(1), fooCreateEvent(2), ]); mockChange.mockClear(); mockInstance.serverSend({ events: [fooArchiveEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([fooCreateEvent(2)]); }); }); describe("streamFetchByKey", function () { test("receive no event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKey(Foo, "badKey"); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [] }); expect(mockChange).toHaveBeenCalledTimes(0); }); test("receive one event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKey(Foo, fooKey); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith(fooCreateEvent(1)); }); test("receive several events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKey(Foo, fooKey); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1), fooEvent(2), fooEvent(3)], }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith(fooCreateEvent(3)); }); test("drop matching created and archived events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKey(Foo, fooKey); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith(fooCreateEvent(1)); mockChange.mockClear(); mockInstance.serverSend({ events: [fooArchiveEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith(null); }); }); describe("streamFetchByKeys", function () { test("receive one event", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKeys(Foo, [fooKey]); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([fooCreateEvent(1)]); }); test("receive several events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKeys(Foo, [fooKey]); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1), fooEvent(2), fooEvent(3)], }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([fooCreateEvent(3)]); }); test("drop matching created and archived events", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKeys(Foo, [fooKey]); stream.on("change", function (state) { return mockChange(state); }); mockInstance.serverSend({ events: [fooEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([fooCreateEvent(1)]); mockChange.mockClear(); mockInstance.serverSend({ events: [fooArchiveEvent(1)] }); expect(mockChange).toHaveBeenCalledTimes(1); expect(mockChange).toHaveBeenCalledWith([null]); }); test("watch multiple keys", function () { var create = function (cid, key) { return ({ created: fooCreateEvent(cid, key), matchedQueries: [0], }); }; var archive = fooArchiveEvent; var send = function (events) { return mockInstance.serverSend({ events: events }); }; var expectCids = function (expected) { return expect(mockChange).toHaveBeenCalledWith(expected.map(function (cid, idx) { return cid ? fooCreateEvent(cid, "key".concat(idx + 1)) : null; })); }; var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKeys(Foo, ["key1", "key2"]); stream.on("change", function (state) { return mockChange(state); }); send([create(1, "key1")]); expectCids([1, null]); send([create(2, "key2")]); expectCids([1, 2]); send([archive(1), create(3, "key1")]); expectCids([3, 2]); send([archive(2), archive(3), create(4, "key2")]); expectCids([null, 4]); }); test("watch zero keys", function () { var ledger = new index_1.default(mockOptions); var stream = ledger.streamFetchByKeys(Foo, []); stream.close(); var change = jest.fn(); stream.on("change", function (state) { return change(state); }); expect(change).toHaveBeenCalledTimes(1); expect(change).toHaveBeenCalledWith([]); mockInstance.serverSend({ events: [1, 2, 3].map(fooEvent) }); expect(change).toHaveBeenCalledWith([]); expect(change).toHaveBeenCalledTimes(1); }); }); //# sourceMappingURL=index.test.js.map