@distrohelena/canton-typescript-sdk 0.1.7 → 0.1.9

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.
@@ -186,6 +186,8 @@ import { CommitmentChunkObserver } from "../../services/participant-inspection/c
186
186
  import { ContractObserver } from "../../services/contracts/contract-observer.interface.js";
187
187
  import { TransactionObserver } from "../../services/events/transaction-observer.interface.js";
188
188
  import { CommandSigners, ICommandSigner } from "../signing/command-signer.interface.js";
189
+ import { SignCommandResult } from "../signing/sign-command-result.js";
190
+ import { PreparedCommandSubmission } from "../types/prepared-command-submission.js";
189
191
  import { RequestOptions } from "../types/request-options.js";
190
192
  import { SubmitCommandRequest } from "../types/requests/submit-command-request.js";
191
193
  export interface ITransport {
@@ -381,4 +383,6 @@ export interface ITransport {
381
383
  * Supported on JSON and gRPC. External signing is gRPC-only.
382
384
  */
383
385
  submitCommandAsync(request: SubmitCommandRequest, signer?: ICommandSigner | CommandSigners, options?: RequestOptions): Promise<SubmitCommandResponse>;
386
+ prepareCommandAsync?(request: SubmitCommandRequest, options?: RequestOptions): Promise<PreparedCommandSubmission>;
387
+ executePreparedCommandAndWaitAsync?(prepared: PreparedCommandSubmission, signatures: Readonly<Record<string, SignCommandResult>>, options?: RequestOptions): Promise<SubmitCommandResponse>;
384
388
  }
@@ -0,0 +1,9 @@
1
+ import { SubmitCommandRequest } from "./requests/submit-command-request.js";
2
+ /** Opaque result of interactive command preparation, suitable for detached signing. */
3
+ export declare class PreparedCommandSubmission {
4
+ readonly request: SubmitCommandRequest;
5
+ readonly transaction: unknown;
6
+ readonly transactionHash: Uint8Array;
7
+ readonly hashingSchemeVersion: number;
8
+ constructor(request: SubmitCommandRequest, transaction: unknown, transactionHash: Uint8Array, hashingSchemeVersion: number);
9
+ }
@@ -0,0 +1,13 @@
1
+ /** Opaque result of interactive command preparation, suitable for detached signing. */
2
+ export class PreparedCommandSubmission {
3
+ request;
4
+ transaction;
5
+ transactionHash;
6
+ hashingSchemeVersion;
7
+ constructor(request, transaction, transactionHash, hashingSchemeVersion) {
8
+ this.request = request;
9
+ this.transaction = transaction;
10
+ this.transactionHash = transactionHash;
11
+ this.hashingSchemeVersion = hashingSchemeVersion;
12
+ }
13
+ }
package/dist/index.d.ts CHANGED
@@ -207,6 +207,7 @@ export { CreateExternalPartyRequest, } from "./core/types/requests/create-extern
207
207
  export type { ExternalPartySigner, ExternalPartySigningRequest, ExternalPartySigningResult, ExternalPartySigningPayloadKind, } from "./core/types/requests/create-external-party-request.js";
208
208
  export { AllocatePartyRequest } from "./core/types/requests/allocate-party-request.js";
209
209
  export { DisclosedContract } from "./core/types/disclosed-contract.js";
210
+ export { PreparedCommandSubmission } from "./core/types/prepared-command-submission.js";
210
211
  export { AddPartyAsyncArguments } from "./core/types/requests/add-party-async-request.js";
211
212
  export { AddPartyAsyncRequest } from "./core/types/requests/add-party-async-request.js";
212
213
  export { AssembleSignedTopologyTransactionsRequest } from "./core/types/requests/assemble-signed-topology-transactions-request.js";
package/dist/index.js CHANGED
@@ -194,6 +194,7 @@ export { AllocateExternalPartyRequest } from "./core/types/requests/allocate-ext
194
194
  export { CreateExternalPartyRequest, } from "./core/types/requests/create-external-party-request.js";
195
195
  export { AllocatePartyRequest } from "./core/types/requests/allocate-party-request.js";
196
196
  export { DisclosedContract } from "./core/types/disclosed-contract.js";
197
+ export { PreparedCommandSubmission } from "./core/types/prepared-command-submission.js";
197
198
  export { AddPartyAsyncArguments } from "./core/types/requests/add-party-async-request.js";
198
199
  export { AddPartyAsyncRequest } from "./core/types/requests/add-party-async-request.js";
199
200
  export { AssembleSignedTopologyTransactionsRequest } from "./core/types/requests/assemble-signed-topology-transactions-request.js";
@@ -44,7 +44,7 @@ export class GrpcContractQueryClient {
44
44
  args.where?.contractId?.in !== undefined ||
45
45
  args.where?.contractId?.is !== undefined ||
46
46
  args.where?.contractId?.isNot !== undefined ||
47
- args.where?.templateId !== undefined) {
47
+ (args.where?.templateId !== undefined && typeof args.where.templateId.equals !== "string")) {
48
48
  throw new QueryCapabilityError(QuerySource.grpc, "contracts.findMany");
49
49
  }
50
50
  const findArgs = args;
@@ -55,6 +55,11 @@ export class GrpcContractQueryClient {
55
55
  let rows = snapshot.filter((row) => args.where?.contractId?.equals === undefined
56
56
  ? true
57
57
  : row.contractId === args.where.contractId.equals);
58
+ if (args.where?.payload !== undefined)
59
+ rows = rows.filter((row) => matchesPayload(row.payload, args.where.payload));
60
+ const legacyTemplate = args.where?.templateId;
61
+ if (legacyTemplate?.equals !== undefined)
62
+ rows = rows.filter((row) => `${row.templateId.packageId}:${row.templateId.moduleName}:${row.templateId.entityName}` === legacyTemplate.equals);
58
63
  return rows;
59
64
  }
60
65
  unsupported(operation) {
@@ -99,7 +104,7 @@ export class GrpcContractQueryClient {
99
104
  function hasUnsupportedFilter(where) {
100
105
  if (where === undefined)
101
106
  return false;
102
- if ("and" in where || "or" in where || "not" in where || "payload" in where || "createdEventOffset" in where || "createdAt" in where || "archivedEventOffset" in where || "archivedAt" in where)
107
+ if ("and" in where || "or" in where || "not" in where || "createdEventOffset" in where || "createdAt" in where || "archivedEventOffset" in where || "archivedAt" in where)
103
108
  return true;
104
109
  for (const field of ["contractId", "templateId"]) {
105
110
  const filter = where[field];
@@ -115,7 +120,7 @@ function mapGrpcContract(value) {
115
120
  contractId: row.contractId ?? "",
116
121
  templateId: { packageId: template?.packageId ?? "", moduleName: template?.moduleName ?? "", entityName: template?.entityName ?? "" },
117
122
  packageId: null,
118
- payload: undefined,
123
+ payload: row.payload,
119
124
  witnesses: [],
120
125
  createdEventOffset: "",
121
126
  createdAt: null,
@@ -124,3 +129,24 @@ function mapGrpcContract(value) {
124
129
  active: true,
125
130
  };
126
131
  }
132
+ function matchesPayload(value, filter) {
133
+ const match = filter.match;
134
+ if (match === undefined)
135
+ return false;
136
+ const visit = (current, node) => Object.entries(node).every(([key, child]) => {
137
+ const next = current !== null && typeof current === "object" ? current[key] : undefined;
138
+ const predicate = child;
139
+ if (Object.keys(predicate).some((name) => ["equals", "lt", "lte", "gt", "gte", "like", "ilike"].includes(name)))
140
+ return compare(String(next ?? ""), predicate);
141
+ return visit(next, predicate);
142
+ });
143
+ return visit(value, match);
144
+ }
145
+ function compare(value, filter) { if (filter.equals !== undefined)
146
+ return value === filter.equals; if (filter.like !== undefined || filter.ilike !== undefined) {
147
+ const pattern = String(filter.like ?? filter.ilike).replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replaceAll("%", ".*").replaceAll("_", ".");
148
+ return new RegExp(`^${pattern}$`, filter.ilike === undefined ? "" : "i").test(value);
149
+ } if (filter.lt !== undefined)
150
+ return value < String(filter.lt); if (filter.lte !== undefined)
151
+ return value <= String(filter.lte); if (filter.gt !== undefined)
152
+ return value > String(filter.gt); return value >= String(filter.gte); }
@@ -106,9 +106,14 @@ type PayloadValueFilter = {
106
106
  readonly gte?: never;
107
107
  readonly like?: never;
108
108
  };
109
- export type ContractPayloadFilter = {
109
+ export type PayloadMatch = {
110
+ readonly [field: string]: PayloadMatch | PayloadValueFilter;
111
+ };
112
+ export type ContractPayloadFilter = ({
110
113
  readonly path: string;
111
- } & PayloadValueFilter;
114
+ } & PayloadValueFilter) | {
115
+ readonly match: PayloadMatch;
116
+ };
112
117
  type ContractWhereFields = {
113
118
  readonly contractId?: StringFilter;
114
119
  readonly templateId?: Partial<{
@@ -72,18 +72,30 @@ function compileWhere(where, addValue) {
72
72
  }
73
73
  if (key === "payload") {
74
74
  const payload = value;
75
- const path = payload.path;
76
- if (typeof path !== "string" || path.split(".").some((x) => x.length === 0))
77
- throw new Error("payload path must contain non-empty segments");
78
- const ops = ["equals", "lt", "lte", "gt", "gte", "like", "ilike"].filter((op) => payload[op] !== undefined);
79
- if (ops.length !== 1 || typeof payload[ops[0]] !== "string")
80
- throw new Error("payload requires exactly one string predicate");
81
- const op = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[ops[0]];
82
- parts.push(`contract_row.payload #>> ${addValue(path.split("."))}::text[] ${op} ${addValue(payload[ops[0]])}`);
75
+ const compilePayload = (path, filter) => { const ops = ["equals", "lt", "lte", "gt", "gte", "like", "ilike"].filter((op) => filter[op] !== undefined); if (ops.length === 1) {
76
+ const op = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[ops[0]];
77
+ parts.push(`contract_row.payload #>> ${addValue(path)}::text[] ${op} ${addValue(filter[ops[0]])}`);
78
+ return;
79
+ } for (const [name, child] of Object.entries(filter))
80
+ compilePayload([...path, name], child); };
81
+ if (payload.match !== undefined) {
82
+ compilePayload([], payload.match);
83
+ }
84
+ else {
85
+ const path = payload.path;
86
+ if (typeof path !== "string" || path.split(".").some((x) => x.length === 0))
87
+ throw new Error("payload path must contain non-empty segments");
88
+ compilePayload(path.split("."), payload);
89
+ }
83
90
  continue;
84
91
  }
85
92
  if (key === "templateId") {
86
93
  const fields = { packageId: "contract_row.creation_package_id", moduleName: "contract_tpe_row.module_name", entityName: "contract_tpe_row.entity_name" };
94
+ const legacy = value;
95
+ if (typeof legacy.equals === "string") {
96
+ parts.push(`(contract_row.creation_package_id || ':' || contract_tpe_row.module_name || ':' || contract_tpe_row.entity_name) = ${addValue(legacy.equals)}`);
97
+ continue;
98
+ }
87
99
  for (const [name, filter] of Object.entries(value))
88
100
  for (const [op, operand] of Object.entries(filter)) {
89
101
  const sql = { equals: "=", lt: "<", lte: "<=", gt: ">", gte: ">=", like: "like", ilike: "ilike" }[op];
@@ -3,9 +3,13 @@ import { RequestOptions } from "../../core/types/request-options.js";
3
3
  import { ITransport } from "../../core/transports/transport.interface.js";
4
4
  import { SubmitCommandRequest } from "../../core/types/requests/submit-command-request.js";
5
5
  import { SubmitCommandResponse } from "../../core/types/responses/submit-command-response.js";
6
+ import { PreparedCommandSubmission } from "../../core/types/prepared-command-submission.js";
7
+ import { SignCommandResult } from "../../core/signing/sign-command-result.js";
6
8
  export declare class CommandServiceClient {
7
9
  private readonly pipeline;
8
10
  constructor(transport: ITransport, signer?: ICommandSigner | CommandSigners);
9
11
  /** Submits a command and waits for the result. Supported on JSON and gRPC. */
10
12
  submitAndWaitAsync(request: SubmitCommandRequest, options?: RequestOptions): Promise<SubmitCommandResponse>;
13
+ prepareAsync(request: SubmitCommandRequest, options?: RequestOptions): Promise<PreparedCommandSubmission>;
14
+ executeAndWaitAsync(prepared: PreparedCommandSubmission, signatures: Readonly<Record<string, SignCommandResult>>, options?: RequestOptions): Promise<SubmitCommandResponse>;
11
15
  }
@@ -11,4 +11,6 @@ export class CommandServiceClient {
11
11
  submitAndWaitAsync(request, options) {
12
12
  return this.pipeline.submitAsync(request, options);
13
13
  }
14
+ prepareAsync(request, options) { return this.pipeline.prepareAsync(request, options); }
15
+ executeAndWaitAsync(prepared, signatures, options) { return this.pipeline.executeAsync(prepared, signatures, options); }
14
16
  }
@@ -3,6 +3,8 @@ import { ITransport } from "../../core/transports/transport.interface.js";
3
3
  import { RequestOptions } from "../../core/types/request-options.js";
4
4
  import { SubmitCommandRequest } from "../../core/types/requests/submit-command-request.js";
5
5
  import { SubmitCommandResponse } from "../../core/types/responses/submit-command-response.js";
6
+ import { PreparedCommandSubmission } from "../../core/types/prepared-command-submission.js";
7
+ import { SignCommandResult } from "../../core/signing/sign-command-result.js";
6
8
  export declare class CommandSubmissionPipeline {
7
9
  private readonly dependencies;
8
10
  constructor(dependencies: {
@@ -10,4 +12,6 @@ export declare class CommandSubmissionPipeline {
10
12
  signer?: ICommandSigner | CommandSigners;
11
13
  });
12
14
  submitAsync(request: SubmitCommandRequest, options?: RequestOptions): Promise<SubmitCommandResponse>;
15
+ prepareAsync(request: SubmitCommandRequest, options?: RequestOptions): Promise<PreparedCommandSubmission>;
16
+ executeAsync(prepared: PreparedCommandSubmission, signatures: Readonly<Record<string, SignCommandResult>>, options?: RequestOptions): Promise<SubmitCommandResponse>;
13
17
  }
@@ -12,4 +12,8 @@ export class CommandSubmissionPipeline {
12
12
  }
13
13
  return this.dependencies.transport.submitCommandAsync(request, this.dependencies.signer, options);
14
14
  }
15
+ prepareAsync(request, options) { if (!this.dependencies.transport.prepareCommandAsync)
16
+ throw new NotSupportedError("interactive command preparation is not supported by the selected transport"); return this.dependencies.transport.prepareCommandAsync(request, options); }
17
+ executeAsync(prepared, signatures, options) { if (!this.dependencies.transport.executePreparedCommandAndWaitAsync)
18
+ throw new NotSupportedError("interactive command execution is not supported by the selected transport"); return this.dependencies.transport.executePreparedCommandAndWaitAsync(prepared, signatures, options); }
15
19
  }
@@ -85,6 +85,8 @@ import { TopologyListVettedPackagesRequest } from "../../core/types/requests/top
85
85
  import { TrafficControlStateRequest } from "../../core/types/requests/traffic-control-state-request.js";
86
86
  import { UploadDarFileRequest } from "../../core/types/requests/upload-dar-file-request.js";
87
87
  import { CommandSigners, ICommandSigner } from "../../core/signing/command-signer.interface.js";
88
+ import { SignCommandResult } from "../../core/signing/sign-command-result.js";
89
+ import { PreparedCommandSubmission } from "../../core/types/prepared-command-submission.js";
88
90
  import { AllocatePartyResponse as SdkAllocatePartyResponse } from "../../core/types/responses/allocate-party-response.js";
89
91
  import { AllocateExternalPartyResponse } from "../../core/types/responses/allocate-external-party-response.js";
90
92
  import { AddPartyAsyncResponse } from "../../core/types/responses/add-party-async-response.js";
@@ -276,6 +278,8 @@ export declare class GrpcTransport implements ITransport {
276
278
  getUpdatesPageAsync(request: GetUpdatesPageRequest, options?: RequestOptions): Promise<GetUpdatesPageResponse>;
277
279
  getCompletionsAsync(request: GetCompletionsRequest, observer: CompletionObserver, options?: RequestOptions): Promise<void>;
278
280
  submitCommandAsync(request: SubmitCommandRequest, signer?: ICommandSigner | CommandSigners, options?: RequestOptions): Promise<SubmitCommandResponse>;
281
+ prepareCommandAsync(request: SubmitCommandRequest, options?: RequestOptions): Promise<PreparedCommandSubmission>;
282
+ executePreparedCommandAndWaitAsync(prepared: PreparedCommandSubmission, signatures: Readonly<Record<string, SignCommandResult>>, options?: RequestOptions): Promise<SubmitCommandResponse>;
279
283
  private throwIfDisposed;
280
284
  private throwPartyTopologyReadCompatibilityError;
281
285
  private isProtobufDeserializationFailure;
@@ -1,6 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import { ValidationError } from "../../core/errors/validation-error.js";
3
3
  import { SignCommandRequest } from "../../core/signing/sign-command-request.js";
4
+ import { PreparedCommandSubmission } from "../../core/types/prepared-command-submission.js";
4
5
  import { AllocatePartyResponse as SdkAllocatePartyResponse } from "../../core/types/responses/allocate-party-response.js";
5
6
  import { GetActiveContractsPageResponse } from "../../core/types/responses/get-active-contracts-page-response.js";
6
7
  import { GetLedgerApiVersionResponse as SdkGetLedgerApiVersionResponse } from "../../core/types/responses/get-ledger-api-version-response.js";
@@ -616,6 +617,22 @@ export class GrpcTransport {
616
617
  }), options);
617
618
  return mapGrpcInteractiveSubmitCommand(executed);
618
619
  }
620
+ async prepareCommandAsync(request, options) {
621
+ if (!this.operations.prepareSubmissionAsync)
622
+ throw new NotSupportedError("interactive gRPC command signing is not available on this transport");
623
+ const prepared = await this.operations.prepareSubmissionAsync(mapGrpcPrepareSubmissionRequest(request, randomUUID()), options);
624
+ if (!prepared.preparedTransaction || prepared.preparedTransactionHash.length === 0)
625
+ throw new ValidationError("interactive prepare submission returned an incomplete result");
626
+ return new PreparedCommandSubmission(request, prepared.preparedTransaction, prepared.preparedTransactionHash, prepared.hashingSchemeVersion);
627
+ }
628
+ async executePreparedCommandAndWaitAsync(prepared, signatures, options) {
629
+ if (!this.operations.executeSubmissionAndWaitAsync)
630
+ throw new NotSupportedError("interactive gRPC command signing is not available on this transport");
631
+ const signerResults = prepared.request.actAs.map((party) => { const result = signatures[party]; if (!result)
632
+ throw new ValidationError(`interactive command signing requires a signature for ${party}`); return { party, result }; });
633
+ const result = await this.operations.executeSubmissionAndWaitAsync(mapGrpcExecuteSubmissionAndWaitRequest({ request: prepared.request, preparedTransaction: prepared.transaction, hashingSchemeVersion: prepared.hashingSchemeVersion, submissionId: randomUUID(), signerResults }), options);
634
+ return mapGrpcInteractiveSubmitCommand(result);
635
+ }
619
636
  throwIfDisposed() {
620
637
  if (this.disposed) {
621
638
  throw new ObjectDisposedError("The client or transport has been disposed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",