@c7-digital/ledger 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,19 @@
10
10
  */
11
11
  import { operations } from "./generated/api.js";
12
12
  import { ValidationMode } from "./validation.js";
13
+ import { type JsCantonError } from "./types.js";
14
+ /**
15
+ * Error thrown when the Canton ledger API returns a non-OK HTTP response.
16
+ * Captures the HTTP status, status text, and — when the response body
17
+ * is a JsCantonError — the structured error details from Canton.
18
+ */
19
+ export declare class LedgerApiError extends Error {
20
+ readonly status: number;
21
+ readonly statusText: string;
22
+ readonly cantonError?: JsCantonError;
23
+ readonly responseBody?: unknown;
24
+ constructor(status: number, statusText: string, body?: unknown);
25
+ }
13
26
  type SubmitAndWaitOperation = operations["postV2CommandsSubmit-and-wait"];
14
27
  type SubmitAndWaitRequest = SubmitAndWaitOperation["requestBody"]["content"]["application/json"];
15
28
  type SubmitAndWaitResponse = SubmitAndWaitOperation["responses"]["200"]["content"]["application/json"];
@@ -43,13 +56,21 @@ export declare class TypedHttpClient {
43
56
  private validator?;
44
57
  constructor(config: TypedHttpClientConfig);
45
58
  private request;
59
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
46
60
  submitAndWait(commands: SubmitAndWaitRequest): Promise<SubmitAndWaitResponse>;
61
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
47
62
  submitAndWaitForTransaction(commands: SubmitAndWaitForTransactionRequest): Promise<SubmitAndWaitForTransactionResponse>;
63
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
48
64
  getParties(): Promise<GetPartiesResponse>;
65
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
49
66
  getUserInfo(userId: string): Promise<GetUserResponse>;
67
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
50
68
  getUserRights(userId: string): Promise<GetUserRightsResponse>;
69
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
51
70
  queryActiveContracts(queryRequest: QueryActiveContractsRequest): Promise<QueryActiveContractsResponse>;
71
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
52
72
  allocateParty(request: AllocatePartyRequest): Promise<AllocatePartyResponse>;
73
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
53
74
  getLedgerEnd(): Promise<GetLedgerEndResponse>;
54
75
  }
55
76
  export {};
package/lib/src/client.js CHANGED
@@ -1,5 +1,28 @@
1
1
  import { SchemaValidator } from "./validation.js";
2
+ import { isCantonError } from "./types.js";
2
3
  import fetch from "cross-fetch";
4
+ /**
5
+ * Error thrown when the Canton ledger API returns a non-OK HTTP response.
6
+ * Captures the HTTP status, status text, and — when the response body
7
+ * is a JsCantonError — the structured error details from Canton.
8
+ */
9
+ export class LedgerApiError extends Error {
10
+ constructor(status, statusText, body) {
11
+ const cantonErr = isCantonError(body) ? body : undefined;
12
+ const detail = cantonErr
13
+ ? `${cantonErr.code}: ${cantonErr.cause}`
14
+ : (typeof body === "string" ? body : undefined);
15
+ const message = detail
16
+ ? `HTTP ${status}: ${statusText} — ${detail}`
17
+ : `HTTP ${status}: ${statusText}`;
18
+ super(message);
19
+ this.name = "LedgerApiError";
20
+ this.status = status;
21
+ this.statusText = statusText;
22
+ this.cantonError = cantonErr;
23
+ this.responseBody = body;
24
+ }
25
+ }
3
26
  export class TypedHttpClient {
4
27
  constructor(config) {
5
28
  this.token = config.token;
@@ -22,7 +45,19 @@ export class TypedHttpClient {
22
45
  }
23
46
  const response = await fetch(url, requestInit);
24
47
  if (!response.ok) {
25
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
48
+ let body;
49
+ try {
50
+ body = await response.json();
51
+ }
52
+ catch {
53
+ try {
54
+ body = await response.text();
55
+ }
56
+ catch {
57
+ // Response body unreadable — leave undefined
58
+ }
59
+ }
60
+ throw new LedgerApiError(response.status, response.statusText, body);
26
61
  }
27
62
  const parsed = await response.json();
28
63
  if (this.validator && responseSchemaName) {
@@ -35,27 +70,35 @@ export class TypedHttpClient {
35
70
  }
36
71
  return parsed;
37
72
  }
73
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
38
74
  async submitAndWait(commands) {
39
75
  return this.request("/v2/commands/submit-and-wait", "POST", commands, "#/components/schemas/SubmitAndWaitResponse");
40
76
  }
77
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
41
78
  async submitAndWaitForTransaction(commands) {
42
79
  return this.request("/v2/commands/submit-and-wait-for-transaction", "POST", commands, "#/components/schemas/JsSubmitAndWaitForTransactionResponse");
43
80
  }
81
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
44
82
  async getParties() {
45
83
  return this.request("/v2/parties", "GET", undefined, "#/components/schemas/GetPartiesResponse");
46
84
  }
85
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
47
86
  async getUserInfo(userId) {
48
87
  return this.request(`/v2/users/${userId}`, "GET", undefined, "#/components/schemas/GetUserResponse");
49
88
  }
89
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
50
90
  async getUserRights(userId) {
51
91
  return this.request(`/v2/users/${userId}/rights`, "GET", undefined, "#/components/schemas/ListUserRightsResponse");
52
92
  }
93
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
53
94
  async queryActiveContracts(queryRequest) {
54
95
  return this.request("/v2/state/active-contracts", "POST", queryRequest, "#/components/schemas/JsGetActiveContractsResponse", true);
55
96
  }
97
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
56
98
  async allocateParty(request) {
57
99
  return this.request("/v2/parties", "POST", request, "#/components/schemas/AllocatePartyResponse");
58
100
  }
101
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
59
102
  async getLedgerEnd() {
60
103
  return this.request("/v2/state/ledger-end", "GET", undefined, "#/components/schemas/GetLedgerEndResponse");
61
104
  }
@@ -1,5 +1,5 @@
1
1
  // Auto-generated file - do not edit manually
2
- // Generated from /Users/stefanpla/Development/c7/c7_ledger/c7_ledger/ledger/specs/asyncapi_3.4.9.yaml
2
+ // Generated from /Users/stefanpla/Development/c7/c7_ledger/ledger/specs/asyncapi_3.4.9.yaml
3
3
  export const ASYNCAPI_SCHEMA = `asyncapi: 2.6.0
4
4
  info:
5
5
  title: JSON Ledger API WebSocket endpoints
@@ -1,5 +1,5 @@
1
1
  // Auto-generated file - do not edit manually
2
- // Generated from /Users/stefanpla/Development/c7/c7_ledger/c7_ledger/ledger/specs/openapi_3.4.9.yaml
2
+ // Generated from /Users/stefanpla/Development/c7/c7_ledger/ledger/specs/openapi_3.4.9.yaml
3
3
  export const OPENAPI_SCHEMA = `openapi: 3.0.3
4
4
  info:
5
5
  title: JSON Ledger API HTTP endpoints
@@ -1,5 +1,5 @@
1
1
  export { Ledger, type LedgerOptions, createCmd, createAndExerciseCmd, exerciseCmd } from "./ledger.js";
2
- export { TypedHttpClient, type TypedHttpClientConfig } from "./client.js";
2
+ export { TypedHttpClient, type TypedHttpClientConfig, LedgerApiError } from "./client.js";
3
3
  export { WebSocketClient, type StreamConfig, type CompletionStreamRequest, type CompletionStreamMessage, type ActiveContractsStreamRequest, type ActiveContractsStreamMessage, type UpdatesStreamRequest, type UpdatesStreamMessage, } from "./websocket.js";
4
4
  export { type ValidationMode } from "./validation.js";
5
5
  export { type Logger, ConsoleLogger, NoOpLogger, logger, setLogger } from "./logger.js";
package/lib/src/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // Main export for the new ledger package
2
2
  export { Ledger, createCmd, createAndExerciseCmd, exerciseCmd } from "./ledger.js";
3
- export { TypedHttpClient } from "./client.js";
3
+ export { TypedHttpClient, LedgerApiError } from "./client.js";
4
4
  export { WebSocketClient, } from "./websocket.js";
5
5
  export { ConsoleLogger, NoOpLogger, logger, setLogger } from "./logger.js";
6
6
  export * from "./types.js";
@@ -17,7 +17,7 @@
17
17
  * you need access to Canton-specific endpoints or full control over API calls.
18
18
  */
19
19
  import { ContractId, Party, Choice, InterfaceCompanion, Template } from "@daml/types";
20
- import { AllocatePartyRequest, AllocatePartyResponse, Command, CreateCommand, CreateAndExerciseCommand, CreateEvent, ExerciseCommand, Event, Interface, LedgerOffset, Stream, InterfaceStream, InterfaceMapping, InterfaceMultiStream, PartyDetails, User, MultiStream, TemplateMapping, VersionedRegistry } from "./types.js";
20
+ import { AllocatePartyRequest, AllocatePartyResponse, AnyCommand, CreateCommand, CreateAndExerciseCommand, CreateEvent, ExerciseCommand, Event, Interface, LedgerOffset, Stream, InterfaceStream, InterfaceMapping, InterfaceMultiStream, PartyDetails, User, MultiStream, TemplateMapping, VersionedRegistry } from "./types.js";
21
21
  import { ValidationMode } from "./validation.js";
22
22
  import { PackageIdString } from "./valueTypes.js";
23
23
  export declare function createCmd<T extends object, K = unknown>(template: Template<T, K, string>, payload: T): CreateCommand<T, K>;
@@ -85,7 +85,9 @@ export declare class Ledger {
85
85
  private resolveOffset;
86
86
  private getLedgerEnd;
87
87
  getTokenUserId(): string;
88
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
88
89
  getTokenUserInfo(): Promise<User | null>;
90
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
89
91
  getTokenActAsParties(): Promise<Party[]>;
90
92
  /**
91
93
  * Query for the Active Contract Set of a specific template.
@@ -96,6 +98,7 @@ export declare class Ledger {
96
98
  * @param verbose
97
99
  * @param readAsParties
98
100
  * @returns
101
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
99
102
  */
100
103
  query<T extends object, K = unknown>(template: Template<T, K, PackageIdString>, atOffset?: LedgerOffset, includeCreatedEventBlob?: boolean, verbose?: boolean, readAsParties?: Party[]): Promise<CreateEvent<T, K>[]>;
101
104
  /**
@@ -106,6 +109,7 @@ export declare class Ledger {
106
109
  * @param verbose
107
110
  * @param readAsParties
108
111
  * @returns
112
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
109
113
  */
110
114
  queryInterface<I extends object, K = unknown>(interface_: InterfaceCompanion<I, K, PackageIdString>, atOffset?: LedgerOffset, includeCreatedEventBlob?: boolean, verbose?: boolean, readAsParties?: Party[]): Promise<Interface<I>[]>;
111
115
  /**
@@ -114,6 +118,7 @@ export declare class Ledger {
114
118
  * @param payload
115
119
  * @param actAs
116
120
  * @returns
121
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
117
122
  */
118
123
  create<T extends object, K = unknown, TTemplateId extends string = string>(template: Template<T, K, TTemplateId>, payload: T, actAs?: Party[]): Promise<CreateEvent<T, K>>;
119
124
  /**
@@ -123,6 +128,7 @@ export declare class Ledger {
123
128
  * @param argument
124
129
  * @param actAs
125
130
  * @returns
131
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
126
132
  */
127
133
  exercise<T extends object, C, R, K = unknown>(choice: Choice<T, C, R, K>, contractId: ContractId<T>, argument: C, actAs?: Party[]): Promise<Event<object, unknown>[]>;
128
134
  /**
@@ -133,8 +139,9 @@ export declare class Ledger {
133
139
  * @param commands
134
140
  * @param actAs Defaults to the actAs parties of the user in the token.
135
141
  * @returns Stream of events resulting from the submitted commands.
142
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
136
143
  */
137
- submit(commands: Command<any, any>[], actAs?: Party[]): Promise<Event<object, unknown>[]>;
144
+ submit(commands: AnyCommand[], actAs?: Party[]): Promise<Event<object, unknown>[]>;
138
145
  private initClient;
139
146
  /**
140
147
  * Stream functionality using WebSockets
@@ -192,7 +199,10 @@ export declare class Ledger {
192
199
  */
193
200
  createMultiStream<TM extends TemplateMapping>(tm: TM, offset?: LedgerOffset, skipAcs?: boolean, includeCreatedEventBlob?: boolean, readAsParties?: Party[]): Promise<MultiStream<TM>>;
194
201
  createMultiInterfaceStream<IM extends InterfaceMapping>(im: IM, offset?: LedgerOffset, skipAcs?: boolean, includeCreatedEventBlob?: boolean, readAsParties?: Party[]): Promise<InterfaceMultiStream<IM>>;
202
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
195
203
  getUserInfo(userId: string): Promise<User | null>;
204
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
196
205
  getParties(): Promise<PartyDetails[]>;
206
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
197
207
  allocateParty(request: AllocatePartyRequest): Promise<AllocatePartyResponse>;
198
208
  }
package/lib/src/ledger.js CHANGED
@@ -549,6 +549,7 @@ export class Ledger {
549
549
  getTokenUserId() {
550
550
  return this.tokenUserId;
551
551
  }
552
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
552
553
  async getTokenUserInfo() {
553
554
  if (this.tokenUserInfo) {
554
555
  return this.tokenUserInfo;
@@ -558,6 +559,7 @@ export class Ledger {
558
559
  return this.tokenUserInfo;
559
560
  }
560
561
  }
562
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
561
563
  async getTokenActAsParties() {
562
564
  const userInfo = await this.getTokenUserInfo();
563
565
  return (userInfo?.rights.filter(right => right.type === "canActAs").map(right => right.party) || []);
@@ -571,6 +573,7 @@ export class Ledger {
571
573
  * @param verbose
572
574
  * @param readAsParties
573
575
  * @returns
576
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
574
577
  */
575
578
  async query(template, atOffset = "end", includeCreatedEventBlob = false, verbose = false, readAsParties) {
576
579
  const activeAtOffset = await this.resolveOffset(atOffset);
@@ -620,6 +623,7 @@ export class Ledger {
620
623
  * @param verbose
621
624
  * @param readAsParties
622
625
  * @returns
626
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
623
627
  */
624
628
  async queryInterface(interface_, atOffset = "end", includeCreatedEventBlob = false, verbose = false, readAsParties) {
625
629
  if (this.options.versionedRegistry === undefined) {
@@ -676,6 +680,7 @@ export class Ledger {
676
680
  * @param payload
677
681
  * @param actAs
678
682
  * @returns
683
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
679
684
  */
680
685
  async create(template, payload, actAs) {
681
686
  const createCommand = {
@@ -729,6 +734,7 @@ export class Ledger {
729
734
  * @param argument
730
735
  * @param actAs
731
736
  * @returns
737
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
732
738
  */
733
739
  async exercise(choice, contractId, argument, actAs) {
734
740
  // Extract actAs from meta or use a default
@@ -777,6 +783,7 @@ export class Ledger {
777
783
  * @param commands
778
784
  * @param actAs Defaults to the actAs parties of the user in the token.
779
785
  * @returns Stream of events resulting from the submitted commands.
786
+ * @throws {LedgerApiError} on non-OK HTTP response from the ledger
780
787
  */
781
788
  async submit(commands, actAs) {
782
789
  const jsCommands = commands.map((command) => convertCommand(command));
@@ -906,7 +913,7 @@ export class Ledger {
906
913
  const stream = new InterfaceStreamImpl(filters, parties_, this.initClient(), activeAtOffset, this.options.versionedRegistry, skipAcs, includeCreatedEventBlob, this.options.autoReconnect ?? true);
907
914
  return new InterfaceMultiStreamImpl(stream);
908
915
  }
909
- // User information
916
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
910
917
  async getUserInfo(userId) {
911
918
  const response = await this.client.getUserInfo(userId);
912
919
  if (!response.user) {
@@ -922,7 +929,7 @@ export class Ledger {
922
929
  };
923
930
  }
924
931
  }
925
- // Party management
932
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
926
933
  async getParties() {
927
934
  const response = await this.client.getParties();
928
935
  return (response.partyDetails || []).map((party) => ({
@@ -931,6 +938,7 @@ export class Ledger {
931
938
  isLocal: party.isLocal || false,
932
939
  }));
933
940
  }
941
+ /** @throws {LedgerApiError} on non-OK HTTP response from the ledger */
934
942
  async allocateParty(request) {
935
943
  const allocateRequest = {
936
944
  partyIdHint: request.partyIdHint
@@ -1,6 +1,8 @@
1
1
  import { ContractId, Party, Choice, Template, InterfaceCompanion } from "@daml/types";
2
2
  import { PartyIdString, UserIdString, PackageIdString } from "./valueTypes.js";
3
- import { JsCantonError } from "./websocket.js";
3
+ import type { components } from "./generated/async-api.js";
4
+ export type JsCantonError = components["schemas"]["JsCantonError"];
5
+ export declare function isCantonError(response: unknown): response is JsCantonError;
4
6
  export type CreateEvent<T extends object, K = unknown> = {
5
7
  type: "create";
6
8
  templateId: PackageIdString;
@@ -226,6 +228,13 @@ export type ExerciseCommand<T extends object, C, R, K = unknown> = {
226
228
  argument: C;
227
229
  };
228
230
  export type Command<T extends object, C, R = unknown, K = unknown> = CreateCommand<T, K> | CreateAndExerciseCommand<T, C, R, K> | ExerciseCommand<T, C, R, K>;
231
+ /**
232
+ * A command with erased type parameters, suitable for heterogeneous arrays
233
+ * passed to `Ledger.submit()`. Each variant is independently widened to `any`,
234
+ * so a `CreateCommand<A, KA>` and an `ExerciseCommand<B, CB, RB, KB>` can
235
+ * coexist in the same `AnyCommand[]` without a cast.
236
+ */
237
+ export type AnyCommand = CreateCommand<any, any> | CreateAndExerciseCommand<any, any, any, any> | ExerciseCommand<any, any, any, any>;
229
238
  export interface AllocatePartyRequest {
230
239
  partyIdHint?: PartyIdString;
231
240
  displayName?: string;
package/lib/src/types.js CHANGED
@@ -1 +1,8 @@
1
- export {};
1
+ // Type guard to check if a response is a JsCantonError
2
+ export function isCantonError(response) {
3
+ return (typeof response === "object" &&
4
+ response !== null &&
5
+ "code" in response &&
6
+ "cause" in response &&
7
+ "context" in response);
8
+ }
@@ -7,6 +7,8 @@
7
7
  */
8
8
  import type { channels, components } from "./generated/async-api.js";
9
9
  import { ValidationMode } from "./validation.js";
10
+ import { isCantonError, type JsCantonError } from "./types.js";
11
+ export { JsCantonError, isCantonError };
10
12
  export interface StreamConfig {
11
13
  token: string;
12
14
  wsBaseUrl: string;
@@ -22,7 +24,6 @@ export type ActiveContractsStreamRequest = ActiveContractsChannel["publish"]["me
22
24
  export type ActiveContractsStreamMessage = ActiveContractsChannel["subscribe"]["message"];
23
25
  export type UpdatesStreamRequest = UpdatesChannel["publish"]["message"];
24
26
  export type UpdatesStreamMessage = UpdatesChannel["subscribe"]["message"];
25
- export type JsCantonError = components["schemas"]["JsCantonError"];
26
27
  export type Response<T> = {
27
28
  status: "success";
28
29
  data: T;
@@ -36,7 +37,6 @@ export type UpdatesResponse = Response<components["schemas"]["JsGetUpdatesRespon
36
37
  export interface StopClient {
37
38
  (): void;
38
39
  }
39
- export declare function isCantonError(response: unknown): response is JsCantonError;
40
40
  export declare function parseStreamResponse<T>(response: T | JsCantonError): Response<T>;
41
41
  export declare function isTransaction(response: unknown): response is {
42
42
  Transaction: components["schemas"]["Transaction"];
@@ -75,4 +75,3 @@ export declare class WebSocketClient {
75
75
  */
76
76
  streamUpdates(request: UpdatesStreamRequest, onMessage: (message: UpdatesResponse) => void, onError?: (error: Error) => void, onClose?: (code: number, reason: string) => void): StopClient;
77
77
  }
78
- export {};
@@ -1,7 +1,9 @@
1
1
  import { SchemaValidator } from "./validation.js";
2
2
  import { logger } from "./logger.js";
3
3
  import { logTokenExpiration } from "./token.js";
4
+ import { isCantonError } from "./types.js";
4
5
  import WebSocket from "isomorphic-ws";
6
+ export { isCantonError };
5
7
  // Constant mapping from endpoints to their JSON Schema validation paths
6
8
  const SCHEMA_MAP = {
7
9
  "/v2/commands/completions": "#/components/schemas/Either_JsCantonError_CompletionStreamResponse",
@@ -10,14 +12,6 @@ const SCHEMA_MAP = {
10
12
  "/v2/updates/flats": "#/components/schemas/Either_JsCantonError_JsGetUpdatesResponse",
11
13
  "/v2/updates/trees": "#/components/schemas/Either_JsCantonError_JsGetUpdateTreesResponse",
12
14
  };
13
- // Type guard to check if a response is a JsCantonError
14
- export function isCantonError(response) {
15
- return (typeof response === "object" &&
16
- response !== null &&
17
- "code" in response &&
18
- "cause" in response &&
19
- "context" in response);
20
- }
21
15
  export function parseStreamResponse(response) {
22
16
  if (isCantonError(response)) {
23
17
  return { status: "error", error: response };