@distrohelena/canton-typescript-sdk 0.1.45 → 0.1.47

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.
Files changed (30) hide show
  1. package/dist/cjs/core/types/canton-logger.js +2 -0
  2. package/dist/cjs/index.js +10 -6
  3. package/dist/cjs/query/canton-manager.js +4 -1
  4. package/dist/cjs/query/errors/contract-cache-required-error.js +21 -0
  5. package/dist/cjs/query/errors/history-walk-required-error.js +20 -0
  6. package/dist/cjs/query/grpc/grpc-contract-cache.js +293 -9
  7. package/dist/cjs/query/grpc/grpc-query-client.js +113 -130
  8. package/dist/cjs/query/grpc/grpc-query-snapshot-reader.js +5 -3
  9. package/dist/cjs/query/pqs/pqs-query-client.js +3 -0
  10. package/dist/core/types/canton-logger.d.ts +4 -0
  11. package/dist/core/types/canton-logger.js +1 -0
  12. package/dist/index.d.ts +3 -0
  13. package/dist/index.js +2 -0
  14. package/dist/query/canton-manager-options.d.ts +26 -0
  15. package/dist/query/canton-manager.js +4 -1
  16. package/dist/query/errors/contract-cache-required-error.d.ts +11 -0
  17. package/dist/query/errors/contract-cache-required-error.js +17 -0
  18. package/dist/query/errors/history-walk-required-error.d.ts +10 -0
  19. package/dist/query/errors/history-walk-required-error.js +16 -0
  20. package/dist/query/grpc/grpc-contract-cache.d.ts +40 -3
  21. package/dist/query/grpc/grpc-contract-cache.js +293 -9
  22. package/dist/query/grpc/grpc-query-client.d.ts +11 -1
  23. package/dist/query/grpc/grpc-query-client.js +113 -130
  24. package/dist/query/grpc/grpc-query-snapshot-reader.d.ts +4 -0
  25. package/dist/query/grpc/grpc-query-snapshot-reader.js +5 -3
  26. package/dist/query/grpc/grpc-relation-mapper.d.ts +3 -1
  27. package/dist/query/pqs/pqs-query-client.d.ts +2 -1
  28. package/dist/query/pqs/pqs-query-client.js +3 -0
  29. package/dist/query/query-client.d.ts +20 -0
  30. package/package.json +1 -1
@@ -1,25 +1,42 @@
1
1
  import { isProxy } from "node:util/types";
2
2
  import { ValidationError } from "../../core/errors/validation-error.js";
3
+ import { TransactionShape } from "../../transports/grpc/generated/canton/com/daml/ledger/api/v2/transaction_filter.js";
3
4
  import { mapGrpcQueryContractsRequest } from "../../transports/grpc/mappers/contracts-mapper.js";
4
5
  import { QuerySource } from "../query-source.js";
5
6
  import { mapGrpcQueryRelationFragment } from "./grpc-relation-mapper.js";
6
7
  import { isCanonicalGrpcOffset } from "./grpc-query-snapshot-reader.js";
8
+ const DELTA_DEFAULT_MAX_OFFSET_GAP = 1_000_000;
9
+ const DELTA_MAX_PAGES = 10_000;
7
10
  export class GrpcContractCache {
8
11
  stateService;
9
12
  store;
10
13
  ttlMs;
11
14
  endpointScope;
12
15
  now;
16
+ maxPageSize;
17
+ updateService;
18
+ delta;
13
19
  inflight = new Map();
14
- constructor(stateService, store, ttlMs, endpointScope, now = Date.now) {
20
+ constructor(stateService, store, ttlMs, endpointScope, now = Date.now, maxPageSize, updateService, delta = {}) {
15
21
  this.stateService = stateService;
16
22
  this.store = store;
17
23
  this.ttlMs = ttlMs;
18
24
  this.endpointScope = endpointScope;
19
25
  this.now = now;
26
+ this.maxPageSize = maxPageSize;
27
+ this.updateService = updateService;
28
+ this.delta = delta;
20
29
  if (!Number.isFinite(ttlMs) || ttlMs <= 0) {
21
30
  throw new ValidationError("Contract cache ttlMs must be a positive finite number.");
22
31
  }
32
+ else if (maxPageSize !== undefined && (!Number.isFinite(maxPageSize) || !Number.isInteger(maxPageSize) || maxPageSize <= 0)) {
33
+ throw new ValidationError("Contract cache maxPageSize must be a positive integer.");
34
+ }
35
+ for (const [name, value] of Object.entries({ maxOffsetGap: delta.maxOffsetGap, maxUpdates: delta.maxUpdates })) {
36
+ if (value !== undefined && (!Number.isFinite(value) || !Number.isInteger(value) || value < 0)) {
37
+ throw new ValidationError(`Contract cache delta ${name} must be a non-negative integer.`);
38
+ }
39
+ }
23
40
  }
24
41
  async cacheContracts(args) {
25
42
  const parties = normalizeParties(args);
@@ -45,7 +62,7 @@ export class GrpcContractCache {
45
62
  if (snapshot === undefined) {
46
63
  return undefined;
47
64
  }
48
- return Object.freeze({ activeAtOffset: snapshot.activeAtOffset, contracts: snapshot.contracts });
65
+ return Object.freeze({ activeAtOffset: snapshot.activeAtOffset, contracts: snapshot.contracts, creationMetadata: snapshot.creationMetadata });
49
66
  }
50
67
  async invalidateContractsCache(args) {
51
68
  const parties = normalizeParties(args);
@@ -58,7 +75,186 @@ export class GrpcContractCache {
58
75
  }
59
76
  await this.store.deleteAsync(key);
60
77
  }
78
+ /** Measures the cached snapshot against the current ledger end, without changing anything. */
79
+ async inspectContractsCacheAsync(args) {
80
+ const parties = normalizeParties(args);
81
+ const base = asCompatibleSnapshot(await this.store.getAsync(cacheKey(this.endpointScope, parties)), this.endpointScope, parties, currentEpochMs(this.now), true);
82
+ if (base === undefined || this.stateService.getLedgerEndAsync === undefined) {
83
+ return undefined;
84
+ }
85
+ const ledgerEnd = parseCacheOffset((await this.stateService.getLedgerEndAsync({})).offset);
86
+ const activeAt = parseCacheOffset(base.activeAtOffset);
87
+ if (ledgerEnd === undefined || activeAt === undefined || ledgerEnd < activeAt) {
88
+ return undefined;
89
+ }
90
+ return Object.freeze({
91
+ activeAtOffset: base.activeAtOffset,
92
+ ledgerEndOffset: ledgerEnd.toString(),
93
+ offsetGap: (ledgerEnd - activeAt).toString(),
94
+ contractCount: base.contracts.length,
95
+ expiresAt: new Date(base.expiresAtEpochMs),
96
+ });
97
+ }
61
98
  async populateAsync(parties, key) {
99
+ // An expired snapshot is still a valid delta base: expiry gates queries, not refreshes.
100
+ const base = asCompatibleSnapshot(await this.store.getAsync(key), this.endpointScope, parties, currentEpochMs(this.now), true);
101
+ if (base !== undefined && this.updateService !== undefined && this.delta.enabled === true) {
102
+ const delta = await this.tryDeltaRefreshAsync(parties, key, base);
103
+ if (delta !== undefined) {
104
+ return delta;
105
+ }
106
+ }
107
+ return this.fullRefreshAsync(parties, key, base);
108
+ }
109
+ /**
110
+ * Patches the base snapshot forward from the update stream instead of re-downloading the ACS. Returns
111
+ * undefined — meaning "do the full download instead" — whenever correctness cannot be proven cheaply:
112
+ * pruning past the base offset, an offset gap or update count beyond the configured budgets, any
113
+ * reassignment or topology change in the window (multi-synchronizer moves cannot be patched safely), an
114
+ * exercised event where ACS_DELTA promises none, or an archive/create inconsistent with the base rows.
115
+ */
116
+ async tryDeltaRefreshAsync(parties, key, base) {
117
+ if (this.stateService.getLedgerEndAsync === undefined || this.stateService.getLatestPrunedOffsetsAsync === undefined || this.updateService === undefined) {
118
+ return undefined;
119
+ }
120
+ try {
121
+ const baseOffset = parseCacheOffset(base.activeAtOffset);
122
+ const pruned = parseCacheOffset((await this.stateService.getLatestPrunedOffsetsAsync({})).participantPrunedUpToInclusive);
123
+ const ledgerEnd = parseCacheOffset((await this.stateService.getLedgerEndAsync({})).offset);
124
+ if (baseOffset === undefined || pruned === undefined || pruned > baseOffset || ledgerEnd === undefined || ledgerEnd < baseOffset) {
125
+ return undefined;
126
+ }
127
+ const offsetGap = ledgerEnd - baseOffset;
128
+ if (offsetGap === 0n) {
129
+ return this.writeSnapshotAsync(parties, key, base.activeAtOffset, base.contracts, base.creationMetadata, { refresh: "noop", offsetGap: "0" });
130
+ }
131
+ const maxOffsetGap = BigInt(this.delta.maxOffsetGap ?? DELTA_DEFAULT_MAX_OFFSET_GAP);
132
+ if (offsetGap > maxOffsetGap) {
133
+ return undefined;
134
+ }
135
+ const window = await this.readDeltaWindowAsync(parties, base, ledgerEnd.toString());
136
+ if (window === undefined) {
137
+ return undefined;
138
+ }
139
+ const baseIds = new Set(base.contracts.map((row) => row.contractId));
140
+ const adds = new Map();
141
+ const removes = new Set();
142
+ for (const entry of window.entries) {
143
+ if (entry.kind === "created") {
144
+ if (baseIds.has(entry.event.contractId) || adds.has(entry.event.contractId)) {
145
+ return undefined;
146
+ }
147
+ adds.set(entry.event.contractId, { event: entry.event, synchronizerId: entry.synchronizerId });
148
+ }
149
+ else if (adds.has(entry.contractId)) {
150
+ adds.delete(entry.contractId);
151
+ }
152
+ else if (baseIds.has(entry.contractId)) {
153
+ removes.add(entry.contractId);
154
+ }
155
+ else {
156
+ return undefined;
157
+ }
158
+ }
159
+ const survivors = [...adds.values()].map(({ event, synchronizerId }) => ({
160
+ contractEntry: {
161
+ oneofKind: "activeContract",
162
+ activeContract: { createdEvent: event, synchronizerId, reassignmentCounter: "0" },
163
+ },
164
+ }));
165
+ const fragment = mapGrpcQueryRelationFragment([], survivors);
166
+ const contracts = [...base.contracts.filter((row) => !removes.has(row.contractId)), ...fragment.contracts]
167
+ .sort((left, right) => left.contractId.localeCompare(right.contractId));
168
+ const addedMetadata = fragment.creationIdentities.map((identity) => ({
169
+ contractId: identity.contractId,
170
+ packageName: identity.packageName,
171
+ representativePackageId: identity.representativePackageId,
172
+ }));
173
+ const creationMetadata = [...base.creationMetadata.filter((entry) => !removes.has(entry.contractId)), ...addedMetadata]
174
+ .sort((left, right) => left.contractId.localeCompare(right.contractId));
175
+ return await this.writeSnapshotAsync(parties, key, ledgerEnd.toString(), contracts, creationMetadata, {
176
+ refresh: "delta",
177
+ offsetGap: offsetGap.toString(),
178
+ deltaUpdateCount: window.entries.length,
179
+ });
180
+ }
181
+ catch {
182
+ // Any validation failure in the window falls back to the full download, which re-validates from scratch.
183
+ return undefined;
184
+ }
185
+ }
186
+ async readDeltaWindowAsync(parties, base, endInclusive) {
187
+ const eventFormat = mapGrpcQueryContractsRequest(parties === undefined ? { allParties: true } : { parties }).eventFormat;
188
+ const maxUpdates = this.delta.maxUpdates ?? Math.max(1_000, 2 * base.contracts.length);
189
+ const entries = [];
190
+ const seenPageTokens = new Set();
191
+ const end = parseCacheOffset(endInclusive);
192
+ let expectedLowestExclusive = parseCacheOffset(base.activeAtOffset);
193
+ let pageToken;
194
+ let updatesSeen = 0;
195
+ for (let pagesRead = 0; pagesRead < DELTA_MAX_PAGES; pagesRead += 1) {
196
+ const request = {
197
+ beginOffsetExclusive: base.activeAtOffset,
198
+ endOffsetInclusive: endInclusive,
199
+ updateFormat: { includeTransactions: { eventFormat, transactionShape: TransactionShape.ACS_DELTA } },
200
+ descendingOrder: false,
201
+ pageToken: pageToken === undefined ? undefined : Uint8Array.from(pageToken),
202
+ };
203
+ const response = await this.updateService.getUpdatesPageAsync(request);
204
+ const lowest = parseCacheOffset(response.lowestPageOffsetExclusive);
205
+ const highest = parseCacheOffset(response.highestPageOffsetInclusive);
206
+ if (lowest === undefined || highest === undefined || lowest !== expectedLowestExclusive || highest < lowest || highest > end) {
207
+ return undefined;
208
+ }
209
+ updatesSeen += response.updates.length;
210
+ if (updatesSeen > maxUpdates) {
211
+ return undefined;
212
+ }
213
+ for (const update of response.updates) {
214
+ const collected = collectDeltaEntries(update, entries);
215
+ if (!collected) {
216
+ return undefined;
217
+ }
218
+ }
219
+ const nextPageToken = response.nextPageToken;
220
+ if (nextPageToken === undefined || nextPageToken.length === 0) {
221
+ return highest === end ? { entries } : undefined;
222
+ }
223
+ else if (highest >= end || highest <= lowest) {
224
+ return undefined;
225
+ }
226
+ const tokenKey = Array.from(nextPageToken).join(",");
227
+ if (seenPageTokens.has(tokenKey)) {
228
+ return undefined;
229
+ }
230
+ seenPageTokens.add(tokenKey);
231
+ expectedLowestExclusive = highest;
232
+ pageToken = Uint8Array.from(nextPageToken);
233
+ }
234
+ return undefined;
235
+ }
236
+ async writeSnapshotAsync(parties, key, activeAtOffset, contracts, creationMetadata, outcome) {
237
+ const expiresAtEpochMs = effectiveExpiryEpochMs(this.now, this.ttlMs);
238
+ const snapshot = {
239
+ version: 2,
240
+ endpointScope: this.endpointScope,
241
+ parties,
242
+ activeAtOffset,
243
+ expiresAtEpochMs,
244
+ contracts: copyRows(contracts),
245
+ creationMetadata: creationMetadata.map((entry) => ({ ...entry })),
246
+ };
247
+ await this.store.setAsync(key, snapshot, this.ttlMs);
248
+ return {
249
+ source: QuerySource.grpc,
250
+ cached: true,
251
+ activeAtOffset,
252
+ contractCount: contracts.length,
253
+ expiresAt: new Date(expiresAtEpochMs),
254
+ ...outcome,
255
+ };
256
+ }
257
+ async fullRefreshAsync(parties, key, base) {
62
258
  const activeContracts = [];
63
259
  const seenPageTokens = new Set();
64
260
  // Every continuation request must be identical to the first page's request apart from the page
@@ -66,7 +262,9 @@ export class GrpcContractCache {
66
262
  // the response's now-explicit activeAtOffset into page 2+ makes it reject the token
67
263
  // (INVALID_ACS_PAGE_TOKEN) — the token was prepared for a request with the field absent. The token
68
264
  // itself pins the snapshot offset; the echoed offset is only tracked to validate it stays constant.
69
- const baseRequest = mapGrpcQueryContractsRequest(parties === undefined ? { allParties: true } : { parties });
265
+ const baseRequest = mapGrpcQueryContractsRequest(parties === undefined
266
+ ? { allParties: true, maxPageSize: this.maxPageSize }
267
+ : { parties, maxPageSize: this.maxPageSize });
70
268
  let activeAtOffset;
71
269
  let pageToken;
72
270
  do {
@@ -90,22 +288,35 @@ export class GrpcContractCache {
90
288
  seenPageTokens.add(tokenKey);
91
289
  }
92
290
  } while (pageToken !== undefined && pageToken.length > 0);
93
- const contracts = mapGrpcQueryRelationFragment([], activeContracts).contracts;
291
+ const fragment = mapGrpcQueryRelationFragment([], activeContracts);
292
+ const contracts = fragment.contracts;
293
+ const creationMetadata = fragment.creationIdentities.map((identity) => ({
294
+ contractId: identity.contractId,
295
+ packageName: identity.packageName,
296
+ representativePackageId: identity.representativePackageId,
297
+ }));
94
298
  const expiresAtEpochMs = effectiveExpiryEpochMs(this.now, this.ttlMs);
299
+ const baseOffset = base === undefined ? undefined : parseCacheOffset(base.activeAtOffset);
300
+ const newOffset = parseCacheOffset(activeAtOffset);
95
301
  const result = {
96
302
  source: QuerySource.grpc,
97
303
  cached: true,
98
304
  activeAtOffset: activeAtOffset,
99
305
  contractCount: contracts.length,
100
306
  expiresAt: new Date(expiresAtEpochMs),
307
+ refresh: "full",
308
+ ...(baseOffset !== undefined && newOffset !== undefined && newOffset >= baseOffset
309
+ ? { offsetGap: (newOffset - baseOffset).toString() }
310
+ : {}),
101
311
  };
102
312
  const snapshot = {
103
- version: 1,
313
+ version: 2,
104
314
  endpointScope: this.endpointScope,
105
315
  parties,
106
316
  activeAtOffset: activeAtOffset,
107
317
  expiresAtEpochMs,
108
318
  contracts: copyRows(contracts),
319
+ creationMetadata,
109
320
  };
110
321
  await this.store.setAsync(key, snapshot, this.ttlMs);
111
322
  return result;
@@ -150,7 +361,43 @@ export function normalizeParties(args) {
150
361
  function cacheKey(endpointScope, parties) {
151
362
  return `grpc-contract-cache:v1:${JSON.stringify([endpointScope, parties])}`;
152
363
  }
153
- function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
364
+ /** Collects created/archived events from one ACS_DELTA update; false means the window cannot be patched. */
365
+ function collectDeltaEntries(update, entries) {
366
+ if (update === null || typeof update !== "object") {
367
+ return false;
368
+ }
369
+ const oneof = update.update;
370
+ if (oneof === undefined || oneof.oneofKind === "offsetCheckpoint") {
371
+ return oneof !== undefined;
372
+ }
373
+ else if (oneof.oneofKind !== "transaction") {
374
+ // Reassignments and topology changes move contracts between synchronizers/visibility in ways a
375
+ // row-level patch cannot represent safely.
376
+ return false;
377
+ }
378
+ const transaction = oneof.transaction;
379
+ const synchronizerId = transaction?.synchronizerId;
380
+ if (typeof synchronizerId !== "string" || synchronizerId.length === 0 || !Array.isArray(transaction?.events)) {
381
+ return false;
382
+ }
383
+ for (const wrapped of transaction.events) {
384
+ const event = wrapped?.event;
385
+ if (event?.oneofKind === "created" && event.created !== undefined) {
386
+ entries.push({ kind: "created", event: event.created, synchronizerId });
387
+ }
388
+ else if (event?.oneofKind === "archived" && typeof event.archived?.contractId === "string" && event.archived.contractId.length > 0) {
389
+ entries.push({ kind: "archived", contractId: event.archived.contractId });
390
+ }
391
+ else {
392
+ return false;
393
+ }
394
+ }
395
+ return true;
396
+ }
397
+ function parseCacheOffset(value) {
398
+ return isCanonicalGrpcOffset(value) ? BigInt(value) : undefined;
399
+ }
400
+ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs, ignoreExpiry = false) {
154
401
  try {
155
402
  if (value === null || typeof value !== "object") {
156
403
  return undefined;
@@ -163,7 +410,8 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
163
410
  const activeAtOffset = candidate.activeAtOffset;
164
411
  const expiresAtEpochMs = candidate.expiresAtEpochMs;
165
412
  const contracts = materializeContractRows(candidate.contracts);
166
- if (version !== 1
413
+ const creationMetadata = materializeCreationMetadata(candidate.creationMetadata, contracts);
414
+ if (version !== 2
167
415
  || storedEndpointScope !== endpointScope
168
416
  || (rawParties !== undefined && storedParties === undefined)
169
417
  || !sameParties(storedParties, parties)
@@ -174,8 +422,9 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
174
422
  || typeof expiresAtEpochMs !== "number"
175
423
  || !Number.isFinite(expiresAtEpochMs)
176
424
  || !Number.isFinite(new Date(expiresAtEpochMs).getTime())
177
- || expiresAtEpochMs <= nowEpochMs
178
- || contracts === undefined) {
425
+ || (!ignoreExpiry && expiresAtEpochMs <= nowEpochMs)
426
+ || contracts === undefined
427
+ || creationMetadata === undefined) {
179
428
  return undefined;
180
429
  }
181
430
  return {
@@ -185,6 +434,7 @@ function asCompatibleSnapshot(value, endpointScope, parties, nowEpochMs) {
185
434
  activeAtOffset,
186
435
  expiresAtEpochMs,
187
436
  contracts,
437
+ creationMetadata,
188
438
  };
189
439
  }
190
440
  catch {
@@ -248,6 +498,40 @@ function materializePageToken(value) {
248
498
  throw new Error("Active-contracts response nextPageToken is invalid.");
249
499
  }
250
500
  }
501
+ function materializeCreationMetadata(value, contracts) {
502
+ if (contracts === undefined) {
503
+ return undefined;
504
+ }
505
+ const entries = materializeIndexedValues(value, materializeCreationMetadataEntry);
506
+ if (entries === undefined || entries.some((entry) => entry === undefined)) {
507
+ return undefined;
508
+ }
509
+ const materialized = entries;
510
+ // Coherence: exactly one metadata entry per contract row, so the contractType join can never dangle.
511
+ const metadataContractIds = new Set(materialized.map((entry) => entry.contractId));
512
+ if (metadataContractIds.size !== materialized.length
513
+ || materialized.length !== contracts.length
514
+ || !contracts.every((row) => metadataContractIds.has(row.contractId))) {
515
+ return undefined;
516
+ }
517
+ return materialized;
518
+ }
519
+ function materializeCreationMetadataEntry(value) {
520
+ if (value === null || typeof value !== "object") {
521
+ return undefined;
522
+ }
523
+ const candidate = value;
524
+ const contractId = candidate.contractId;
525
+ const packageName = candidate.packageName;
526
+ const representativePackageId = candidate.representativePackageId;
527
+ if (typeof contractId !== "string"
528
+ || typeof packageName !== "string"
529
+ || packageName.length === 0
530
+ || (typeof representativePackageId !== "string" && representativePackageId !== null)) {
531
+ return undefined;
532
+ }
533
+ return { contractId, packageName, representativePackageId };
534
+ }
251
535
  function materializeContractRows(value) {
252
536
  const rows = materializeIndexedValues(value, materializeContractRow);
253
537
  if (rows === undefined) {
@@ -1,9 +1,10 @@
1
1
  import type { PackageServiceClient } from "../../services/package/package-service-client.js";
2
2
  import type { StateServiceClient } from "../../services/state/state-service-client.js";
3
3
  import type { UpdateServiceClient } from "../../services/update/update-service-client.js";
4
+ import type { CantonLogger } from "../../core/types/canton-logger.js";
4
5
  import { type QueryDataset } from "../canonical/query-dataset.js";
5
6
  import type { NormalizedAggregateQuery, NormalizedCountQuery, NormalizedFindManyQuery, NormalizedFindUniqueQuery, NormalizedGroupByQuery } from "../canonical/query-ast.js";
6
- import type { ContractCacheArgs, ContractCacheResult, QueryClient } from "../query-client.js";
7
+ import type { ContractCacheArgs, ContractCacheInspection, ContractCacheResult, QueryClient } from "../query-client.js";
7
8
  import { QuerySource } from "../query-source.js";
8
9
  import { GrpcContractCache } from "./grpc-contract-cache.js";
9
10
  type NormalizedQuery = NormalizedFindManyQuery | NormalizedFindUniqueQuery | NormalizedCountQuery | NormalizedAggregateQuery | NormalizedGroupByQuery;
@@ -23,6 +24,14 @@ export interface GrpcQueryClientOptions {
23
24
  * the retained window lives for this client's lifetime and its RAM cost is the full replayed history.
24
25
  */
25
26
  readonly incrementalHistory?: boolean;
27
+ /**
28
+ * Opt-in: permit queries that replay ledger history (transactions/events/exercises, and contracts
29
+ * queries reaching archived state). Off by default — such queries throw HistoryWalkRequiredError so the
30
+ * replay cost is never paid implicitly.
31
+ */
32
+ readonly walkHistory?: boolean;
33
+ /** Receives SDK diagnostics (e.g. the once-per-relation full-replay warning); defaults to console. */
34
+ readonly logger?: CantonLogger;
26
35
  }
27
36
  export declare class GrpcQueryClient implements QueryClient {
28
37
  private readonly options;
@@ -41,6 +50,7 @@ export declare class GrpcQueryClient implements QueryClient {
41
50
  $queryRaw<TRow>(_sql: string, _values?: readonly unknown[]): Promise<readonly TRow[]>;
42
51
  cacheContracts(args?: ContractCacheArgs): Promise<ContractCacheResult>;
43
52
  invalidateContractsCache(args?: ContractCacheArgs): Promise<void>;
53
+ inspectContractsCache(args?: ContractCacheArgs): Promise<ContractCacheInspection | undefined>;
44
54
  private delegate;
45
55
  private collectionDelegate;
46
56
  private execute;