@distrohelena/canton-typescript-sdk 0.1.35 → 0.1.36

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.
package/README.md CHANGED
@@ -132,30 +132,63 @@ npm run example:topology:party-hosting
132
132
 
133
133
  ### Workflow examples
134
134
 
135
- The four workflow examples are standalone proofs, not a sequence: each uploads
136
- or verifies the fixture DAR, resolves a party, reads the participant status,
137
- and creates its own run-scoped data. First make a Canton participant available
138
- and verify the source with `npm run examples:check`; the default endpoints and
139
- authentication environment variables are documented below. Run any example
140
- independently:
135
+ The seven workflow examples are standalone proofs, not a sequence:
136
+ each uploads or verifies the fixture DAR, resolves a party, reads the
137
+ participant status, and creates its own run-scoped data. First make a Canton
138
+ participant available and verify the source with `npm run examples:check`; the
139
+ default endpoints and authentication environment variables are documented below.
140
+ The pruning preflight is a separate read-only operator check and does none of
141
+ those setup or mutation steps. Run any example independently:
141
142
 
142
143
  ```bash
143
144
  npm run example:workflow:atomic
144
145
  npm run example:workflow:retry
145
146
  npm run example:workflow:resume
146
147
  npm run example:workflow:stale-contract
148
+ npm run example:workflow:command-completion
149
+ npm run example:workflow:contract-lifecycle-audit
150
+ npm run example:workflow:update-lookup-reconciliation
151
+ npm run example:workflow:pruning-preflight
147
152
  ```
148
153
 
149
- They intentionally leave durable state behind. A missing `SDK_EXAMPLE_PARTY`
154
+ Those seven established stateful workflows intentionally leave durable state behind. A missing `SDK_EXAMPLE_PARTY`
150
155
  causes fallback party allocation, which creates durable topology state; every
151
- workflow also creates durable contracts. Set `SDK_EXAMPLE_PARTY` to an existing
152
- party to rerun all four against that party and avoid fallback allocation. The
153
- fixture DAR remains installed after a run.
154
-
155
- Each program prints its actor plus the full participant version returned by the
156
+ one also creates durable contracts. Set `SDK_EXAMPLE_PARTY` to an existing
157
+ party to rerun the established workflows against that party and avoid fallback
158
+ allocation. The fixture DAR remains installed after a run.
159
+
160
+ The completion-correlation workflow (`npm run example:workflow:command-completion`)
161
+ is a standalone successful proof that creates durable Message state. It uses the
162
+ same normal `SDK_EXAMPLE_*` configuration: `SDK_EXAMPLE_LEDGER_ENDPOINT`,
163
+ `SDK_EXAMPLE_LEDGER_ADMIN_ENDPOINT`, `SDK_EXAMPLE_PARTICIPANT_ADMIN_ENDPOINT`,
164
+ `SDK_EXAMPLE_BEARER_TOKEN`, `SDK_EXAMPLE_LEDGER_BEARER_TOKEN`,
165
+ `SDK_EXAMPLE_LEDGER_ADMIN_BEARER_TOKEN`,
166
+ `SDK_EXAMPLE_PARTICIPANT_ADMIN_BEARER_TOKEN`,
167
+ `SDK_EXAMPLE_TLS_ROOT_CERTIFICATE`, `SDK_EXAMPLE_PARTY`,
168
+ `SDK_EXAMPLE_PARTY_PREFIX`, and `SDK_EXAMPLE_TIMEOUT_MS`.
169
+ `SDK_EXAMPLE_USER_ID` is mandatory: absent or blank input is rejected, while
170
+ every nonblank value is preserved untrimmed and exactly submitted and exactly
171
+ matched in the completion. With bearer authentication, the configured declared
172
+ user must equal the token's Ledger API user/subject; the example does not inspect
173
+ the token. It keeps the ledger end as its saved exclusive offset and begins the
174
+ first stream read before submission.
175
+ No public wait-for-command-completion helper or API is introduced; the
176
+ correlation helper remains example-only.
177
+ The completion-correlation example asserts successful correlation only; it does
178
+ not assert rejected-command correlation. On both participant observations, the
179
+ stream-first rejected-command probes observed no exact completion before their
180
+ bounded stream transport errors.
181
+
182
+ The contract-lifecycle audit workflow (`npm run example:workflow:contract-lifecycle-audit`) is a standalone gRPC-only proof. It uses the normal `SDK_EXAMPLE_*` endpoint, authentication, party, and timeout configuration: `SDK_EXAMPLE_LEDGER_ENDPOINT`, `SDK_EXAMPLE_LEDGER_ADMIN_ENDPOINT`, `SDK_EXAMPLE_PARTICIPANT_ADMIN_ENDPOINT`, `SDK_EXAMPLE_BEARER_TOKEN`, `SDK_EXAMPLE_LEDGER_BEARER_TOKEN`, `SDK_EXAMPLE_LEDGER_ADMIN_BEARER_TOKEN`, `SDK_EXAMPLE_PARTICIPANT_ADMIN_BEARER_TOKEN`, `SDK_EXAMPLE_PARTY`, `SDK_EXAMPLE_PARTY_PREFIX`, and `SDK_EXAMPLE_TIMEOUT_MS`. An explicit `SDK_EXAMPLE_PARTY` is reused; otherwise fallback allocation creates durable topology. The fixture upload leaves a durable DAR, and the workflow leaves durable contracts. It uses the alpha ContractService to prove the original active Message before replacement and the replacement active Message afterward, then uses EventQuery for the original contract's create/archive history. It makes no post-archive ContractService claim for the original.
183
+
184
+ The update-lookup reconciliation workflow (`npm run example:workflow:update-lookup-reconciliation`) is a standalone gRPC-only proof that observes one exact self-party Message transaction from `UpdateService.GetUpdates`, then immediately reconciles it through `getUpdateById` and `getUpdateByOffset`. It uses the normal `SDK_EXAMPLE_*` endpoint, authentication, party, and timeout configuration, including `SDK_EXAMPLE_PARTY` and `SDK_EXAMPLE_TIMEOUT_MS`; first run `npm run examples:check` and make an authenticated participant available. An explicit party is reused, while fallback allocation creates durable topology; the fixture upload leaves a durable DAR and the workflow leaves durable contracts. The same unchanged implementation is tested against authenticated Participant 3.5.7 and the isolated Participant 3.5.8 sidecar, in default-party and explicit-party modes.
185
+
186
+ The pruning-preflight workflow (`npm run example:workflow:pruning-preflight`) is a standalone gRPC-only, read-only operator check. It requires `SDK_EXAMPLE_OFFSET` to be a canonical positive decimal integer and uses the normal endpoint, authentication, and timeout variables: `SDK_EXAMPLE_LEDGER_ENDPOINT`, `SDK_EXAMPLE_LEDGER_ADMIN_ENDPOINT`, `SDK_EXAMPLE_PARTICIPANT_ADMIN_ENDPOINT`, `SDK_EXAMPLE_BEARER_TOKEN`, `SDK_EXAMPLE_LEDGER_BEARER_TOKEN`, `SDK_EXAMPLE_LEDGER_ADMIN_BEARER_TOKEN`, `SDK_EXAMPLE_PARTICIPANT_ADMIN_BEARER_TOKEN`, and `SDK_EXAMPLE_TIMEOUT_MS`. The participant-admin credential is required for the schedule and safe-pruning context reads. Its safe-pruning context request sends the saved ledger end and a validated current timestamp encoded in `beforeOrAt`; it leaves commitment-state absent. It does not mutate the participant or create durable state: no party, DAR, command, update query, schedule change, or pruning request is made. The later participant watermark classifies the supplied offset as `alreadyPruned`, `beyondLedgerEnd`, or `notObservedPruned`; all-divulged watermarks and schedule/safe-pruning context are reported separately and do not alter that result. `notObservedPruned` is not proven queryable, because pruning can race after the later observation and other query preconditions can still fail. The unchanged implementation is supported on Participant 3.5.7 and the isolated Participant 3.5.8 sidecar.
187
+
188
+ Each of the seven established stateful workflows prints its actor plus the full participant version returned by the
156
189
  authenticated status API, its parsed release core, and its selected path:
157
190
  `Participant version:`, `Release core:`, and `Compatibility path:`. The current
158
- implementation uses one common-code path for release cores 3.5.7 and 3.5.8. It
191
+ stateful-workflow implementation uses one common-code path for release cores 3.5.7 and 3.5.8. It
159
192
  adds a version-specific behavioral difference only after live evidence proves
160
193
  one; it does not infer compatibility from a container tag or endpoint.
161
194
 
@@ -179,15 +212,15 @@ request and is not a retry proof. The resume example treats the saved offset as
179
212
  exclusive, so its resumed stream must observe only updates after the saved
180
213
  ledger end.
181
214
 
182
- The workflow source and unit contracts were developed and live-tested against
183
- authenticated Participant 3.5.7 and the isolated Participant 3.5.8. Both
184
- final-tree matrices selected the same unchanged implementation and common
215
+ The seven established stateful workflow sources and unit contracts were developed
216
+ and live-tested against authenticated Participant 3.5.7 and the isolated
217
+ Participant 3.5.8. Both final-tree matrices selected the same unchanged implementation and common
185
218
  compatibility path. The normalized outcome comparison is identical: atomic
186
219
  reports `invalidChoice` before its replacement proof, retry reports
187
220
  `duplicateCommand` with one active contract, resume reports `idle-timeout` and
188
221
  a post-offset update, and stale-contract reports `staleContract`.
189
222
 
190
- The best multi-version path reads the authenticated full version, parses its
223
+ For those seven established stateful workflows, the best multi-version path reads the authenticated full version, parses its
191
224
  release core, and uses data-only structured compatibility for observed
192
225
  outcomes. A behavioral difference is introduced only after live proof; it is
193
226
  never inferred from a container tag, endpoint, or prose error message. For the
@@ -555,7 +588,11 @@ const contracts = await client.stateService.getActiveContractsPageAsync(
555
588
  );
556
589
  ```
557
590
 
558
- `stateService.getActiveContractsPageAsync(...)` accepts the generated Ledger API request on gRPC, including its `eventFormat`, `activeAtOffset`, `maxPageSize`, and `pageToken` fields. Build party, template, and interface filters in `eventFormat`. JSON does not implement this paginated RPC; use `getActiveContractsAsync(...)` for JSON streaming reads.
591
+ `stateService.getActiveContractsPageAsync(...)` is gRPC-only and accepts the generated Ledger API request, including its `eventFormat`, `activeAtOffset`, `maxPageSize`, and `pageToken` fields. Build party, template, and interface filters in `eventFormat`.
592
+
593
+ `stateService.getActiveContractsPagesAsync(...)` is the gRPC-only lazy, raw, bounded traversal API. It starts from the same generated request and yields raw `GetActiveContractsPageResponse` values one page at a time. The caller selects the shared `OperationDeadline`, maximum pages, and maximum contracts with `ActiveContractsTraversalOptions`; there is no collect-all wrapper. Transport errors from dispatched RPCs propagate unchanged. Traversal safety, invariant, and bound failures use `ActiveContractsTraversalError` codes, such as an inconsistent offset, repeated page token, or exceeded bound.
594
+
595
+ JSON does not implement either paginated gRPC API. Its existing `stateService.getActiveContractsAsync(...)` behavior remains the distinct JSON streaming read.
559
596
 
560
597
  For interface views, do not use `contractService.getContractAsync(...)`. That contract lookup surface cannot return interface views; use `stateService` or `updateService` instead.
561
598
 
@@ -703,11 +740,12 @@ unsupported query features reject with `QueryCapabilityError`.
703
740
  - `commandService.submitAndWaitAsync(...)`: `json`, `grpc`
704
741
  - `commandSubmissionService.submitAsync(...)`: reserved, currently unsupported
705
742
  - `stateService.getActiveContractsPageAsync(...)`: `grpc` only
706
- - `stateService.getActiveContractsAsync(...)`: `json` only
743
+ - `stateService.getActiveContractsPagesAsync(...)`: `grpc` only, lazy raw bounded traversal
744
+ - `stateService.getActiveContractsAsync(...)`: `json` only, existing distinct streaming read
707
745
  - `updateService.getUpdatesAsync(...)`: `grpc` only
708
- - `commandCompletionService`: placeholder, no methods yet
709
- - `eventQueryService`: placeholder, no methods yet
710
- - `contractService`: placeholder, no methods yet
746
+ - `commandCompletionService.getCompletionsAsync(...)`: `grpc` only, existing streaming API
747
+ - `eventQueryService.getEventsByContractIdAsync(...)`: `grpc` only; JSON rejects this request
748
+ - `contractService.getContractAsync(...)`: `grpc` only; JSON rejects this request
711
749
 
712
750
  - Ledger Admin endpoint:
713
751
  - `partyManagementService.allocatePartyAsync(...)`: `json`, `grpc`
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.StateServiceClient = void 0;
4
+ const active_contracts_traversal_error_js_1 = require("../../core/errors/active-contracts-traversal-error.js");
5
+ const validation_error_js_1 = require("../../core/errors/validation-error.js");
4
6
  class StateServiceClient {
5
7
  transport;
6
8
  constructor(transport) {
@@ -11,6 +13,10 @@ class StateServiceClient {
11
13
  getActiveContractsPageAsync(request, options) {
12
14
  return this.transport.getActiveContractsPageAsync(request, options);
13
15
  }
16
+ /** Reads active-contract pages lazily. Supported on gRPC; JSON rejects it. */
17
+ getActiveContractsPagesAsync(request, options) {
18
+ return this.getActiveContractsPagesLazy(request, options);
19
+ }
14
20
  /** Reads active contracts as a stream. JSON-backed; gRPC currently rejects it. */
15
21
  getActiveContractsAsync(request, observer, options) {
16
22
  return this.transport.getActiveContractsAsync(request, observer, options);
@@ -27,5 +33,55 @@ class StateServiceClient {
27
33
  getLatestPrunedOffsetsAsync(request, options) {
28
34
  return this.transport.getLatestPrunedOffsetsAsync(request, options);
29
35
  }
36
+ async *getActiveContractsPagesLazy(request, options) {
37
+ if (request.pageToken && request.pageToken.length > 0) {
38
+ throw new validation_error_js_1.ValidationError("active contracts traversal must not start with a page token");
39
+ }
40
+ let pageRequest = request;
41
+ let pagesRead = 0;
42
+ let contractsRead = 0;
43
+ let activeAtOffset;
44
+ const observedPageTokens = new Set();
45
+ while (true) {
46
+ if (pagesRead >= options.maxPages) {
47
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("max-pages-exceeded", "active contracts traversal exceeded maxPages");
48
+ }
49
+ const response = await this.transport.getActiveContractsPageAsync(pageRequest, options.deadline.createRequestOptions());
50
+ pagesRead += 1;
51
+ if (typeof response.activeAtOffset !== "string" ||
52
+ response.activeAtOffset.trim().length === 0) {
53
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("missing-active-at-offset", "active contracts response is missing activeAtOffset");
54
+ }
55
+ else if (activeAtOffset === undefined) {
56
+ if (request.activeAtOffset !== undefined &&
57
+ request.activeAtOffset !== response.activeAtOffset) {
58
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("active-at-offset-mismatch", "active contracts response activeAtOffset does not match the request");
59
+ }
60
+ activeAtOffset = response.activeAtOffset;
61
+ }
62
+ else if (activeAtOffset !== response.activeAtOffset) {
63
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("active-at-offset-mismatch", "active contracts response activeAtOffset changed during traversal");
64
+ }
65
+ contractsRead += response.activeContracts.length;
66
+ if (contractsRead > options.maxContracts) {
67
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("max-contracts-exceeded", "active contracts traversal exceeded maxContracts");
68
+ }
69
+ yield response;
70
+ if (!response.nextPageToken || response.nextPageToken.length === 0) {
71
+ return;
72
+ }
73
+ const pageTokenKey = Array.from(response.nextPageToken).join(",");
74
+ if (observedPageTokens.has(pageTokenKey)) {
75
+ throw new active_contracts_traversal_error_js_1.ActiveContractsTraversalError("repeated-page-token", "active contracts traversal received a repeated page token");
76
+ }
77
+ observedPageTokens.add(pageTokenKey);
78
+ pageRequest = {
79
+ activeAtOffset,
80
+ eventFormat: request.eventFormat,
81
+ maxPageSize: request.maxPageSize,
82
+ pageToken: response.nextPageToken,
83
+ };
84
+ }
85
+ }
30
86
  }
31
87
  exports.StateServiceClient = StateServiceClient;
@@ -1,4 +1,5 @@
1
1
  import { ITransport } from "../../core/transports/transport.interface.js";
2
+ import { ActiveContractsTraversalOptions } from "../../core/types/active-contracts-traversal-options.js";
2
3
  import { RequestOptions } from "../../core/types/request-options.js";
3
4
  import { GetActiveContractsRequest } from "../../core/types/requests/get-active-contracts-request.js";
4
5
  import type { GetActiveContractsPageRequest, GetActiveContractsPageResponse, GetConnectedSynchronizersRequest, GetConnectedSynchronizersResponse, GetLedgerEndRequest, GetLedgerEndResponse, GetLatestPrunedOffsetsRequest, GetLatestPrunedOffsetsResponse } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/state_service.js";
@@ -8,6 +9,8 @@ export declare class StateServiceClient {
8
9
  constructor(transport: ITransport);
9
10
  /** Reads a page of active contracts. Supported on gRPC; JSON rejects it. */
10
11
  getActiveContractsPageAsync(request: GetActiveContractsPageRequest, options?: RequestOptions): Promise<GetActiveContractsPageResponse>;
12
+ /** Reads active-contract pages lazily. Supported on gRPC; JSON rejects it. */
13
+ getActiveContractsPagesAsync(request: GetActiveContractsPageRequest, options: ActiveContractsTraversalOptions): AsyncIterable<GetActiveContractsPageResponse>;
11
14
  /** Reads active contracts as a stream. JSON-backed; gRPC currently rejects it. */
12
15
  getActiveContractsAsync(request: GetActiveContractsRequest, observer: ContractObserver, options?: RequestOptions): Promise<void>;
13
16
  /** Reads connected synchronizers. Supported on gRPC; JSON rejects it. */
@@ -16,4 +19,5 @@ export declare class StateServiceClient {
16
19
  getLedgerEndAsync(request: GetLedgerEndRequest, options?: RequestOptions): Promise<GetLedgerEndResponse>;
17
20
  /** Reads the latest participant pruning offsets. Supported on gRPC; JSON rejects it. */
18
21
  getLatestPrunedOffsetsAsync(request: GetLatestPrunedOffsetsRequest, options?: RequestOptions): Promise<GetLatestPrunedOffsetsResponse>;
22
+ private getActiveContractsPagesLazy;
19
23
  }
@@ -1,3 +1,5 @@
1
+ import { ActiveContractsTraversalError } from "../../core/errors/active-contracts-traversal-error.js";
2
+ import { ValidationError } from "../../core/errors/validation-error.js";
1
3
  export class StateServiceClient {
2
4
  transport;
3
5
  constructor(transport) {
@@ -8,6 +10,10 @@ export class StateServiceClient {
8
10
  getActiveContractsPageAsync(request, options) {
9
11
  return this.transport.getActiveContractsPageAsync(request, options);
10
12
  }
13
+ /** Reads active-contract pages lazily. Supported on gRPC; JSON rejects it. */
14
+ getActiveContractsPagesAsync(request, options) {
15
+ return this.getActiveContractsPagesLazy(request, options);
16
+ }
11
17
  /** Reads active contracts as a stream. JSON-backed; gRPC currently rejects it. */
12
18
  getActiveContractsAsync(request, observer, options) {
13
19
  return this.transport.getActiveContractsAsync(request, observer, options);
@@ -24,4 +30,54 @@ export class StateServiceClient {
24
30
  getLatestPrunedOffsetsAsync(request, options) {
25
31
  return this.transport.getLatestPrunedOffsetsAsync(request, options);
26
32
  }
33
+ async *getActiveContractsPagesLazy(request, options) {
34
+ if (request.pageToken && request.pageToken.length > 0) {
35
+ throw new ValidationError("active contracts traversal must not start with a page token");
36
+ }
37
+ let pageRequest = request;
38
+ let pagesRead = 0;
39
+ let contractsRead = 0;
40
+ let activeAtOffset;
41
+ const observedPageTokens = new Set();
42
+ while (true) {
43
+ if (pagesRead >= options.maxPages) {
44
+ throw new ActiveContractsTraversalError("max-pages-exceeded", "active contracts traversal exceeded maxPages");
45
+ }
46
+ const response = await this.transport.getActiveContractsPageAsync(pageRequest, options.deadline.createRequestOptions());
47
+ pagesRead += 1;
48
+ if (typeof response.activeAtOffset !== "string" ||
49
+ response.activeAtOffset.trim().length === 0) {
50
+ throw new ActiveContractsTraversalError("missing-active-at-offset", "active contracts response is missing activeAtOffset");
51
+ }
52
+ else if (activeAtOffset === undefined) {
53
+ if (request.activeAtOffset !== undefined &&
54
+ request.activeAtOffset !== response.activeAtOffset) {
55
+ throw new ActiveContractsTraversalError("active-at-offset-mismatch", "active contracts response activeAtOffset does not match the request");
56
+ }
57
+ activeAtOffset = response.activeAtOffset;
58
+ }
59
+ else if (activeAtOffset !== response.activeAtOffset) {
60
+ throw new ActiveContractsTraversalError("active-at-offset-mismatch", "active contracts response activeAtOffset changed during traversal");
61
+ }
62
+ contractsRead += response.activeContracts.length;
63
+ if (contractsRead > options.maxContracts) {
64
+ throw new ActiveContractsTraversalError("max-contracts-exceeded", "active contracts traversal exceeded maxContracts");
65
+ }
66
+ yield response;
67
+ if (!response.nextPageToken || response.nextPageToken.length === 0) {
68
+ return;
69
+ }
70
+ const pageTokenKey = Array.from(response.nextPageToken).join(",");
71
+ if (observedPageTokens.has(pageTokenKey)) {
72
+ throw new ActiveContractsTraversalError("repeated-page-token", "active contracts traversal received a repeated page token");
73
+ }
74
+ observedPageTokens.add(pageTokenKey);
75
+ pageRequest = {
76
+ activeAtOffset,
77
+ eventFormat: request.eventFormat,
78
+ maxPageSize: request.maxPageSize,
79
+ pageToken: response.nextPageToken,
80
+ };
81
+ }
82
+ }
27
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -98,6 +98,10 @@
98
98
  "example:workflow:retry": "npm run build && node --loader ts-node/esm examples/91-idempotent-command-retry.ts",
99
99
  "example:workflow:resume": "npm run build && node --loader ts-node/esm examples/92-resume-update-stream.ts",
100
100
  "example:workflow:stale-contract": "npm run build && node --loader ts-node/esm examples/93-archive-and-stale-contract.ts",
101
+ "example:workflow:command-completion": "npm run build && node --loader ts-node/esm examples/94-command-completion-correlation.ts",
102
+ "example:workflow:contract-lifecycle-audit": "npm run build && node --loader ts-node/esm examples/95-contract-lifecycle-audit.ts",
103
+ "example:workflow:update-lookup-reconciliation": "npm run build && node --loader ts-node/esm examples/96-update-lookup-reconciliation.ts",
104
+ "example:workflow:pruning-preflight": "npm run build && node --loader ts-node/esm examples/97-pruning-preflight.ts",
101
105
  "lint": "eslint . --max-warnings=0",
102
106
  "lint:fix": "eslint . --fix --max-warnings=0",
103
107
  "start:local-ledger": "bash node/start-local.sh",