@distrohelena/canton-typescript-sdk 0.1.40 → 0.1.42

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.
@@ -475,7 +475,7 @@ function provesActive(predicate) {
475
475
  return false;
476
476
  }
477
477
  else if (predicate.kind === "scalar") {
478
- return predicate.path.length === 1 && predicate.path[0] === "active" && predicate.operator === "equals" && predicate.value === true;
478
+ return provesActiveScalar(predicate);
479
479
  }
480
480
  else if (predicate.kind === "and") {
481
481
  return predicate.children.some(provesActive);
@@ -485,6 +485,20 @@ function provesActive(predicate) {
485
485
  }
486
486
  return false;
487
487
  }
488
+ /** A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the same fact as active: true. */
489
+ function provesActiveScalar(predicate) {
490
+ if (predicate.path.length !== 1) {
491
+ return false;
492
+ }
493
+ const [field] = predicate.path;
494
+ if (field === "active") {
495
+ return predicate.operator === "equals" && predicate.value === true;
496
+ }
497
+ else if (field === "archivedAt" || field === "archivedEventOffset") {
498
+ return predicate.operator === "is" && predicate.value === null;
499
+ }
500
+ return false;
501
+ }
488
502
  function validateOperatorValue(operator, value, field, kind, nullable) {
489
503
  if (operator === "is" || operator === "isNot") {
490
504
  if (value !== null) {
@@ -23,6 +23,8 @@ exports.GrpcPackageRelationError = GrpcPackageRelationError;
23
23
  class GrpcPackageRelationReader {
24
24
  packageService;
25
25
  packageLoader;
26
+ /** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
27
+ resolved = new Map();
26
28
  constructor(packageService, packageLoader = new daml_lf_package_loader_js_1.DamlLfPackageLoader()) {
27
29
  this.packageService = packageService;
28
30
  this.packageLoader = packageLoader;
@@ -53,6 +55,10 @@ class GrpcPackageRelationReader {
53
55
  return Object.freeze(packages.slice().sort((left, right) => left.id.localeCompare(right.id)));
54
56
  }
55
57
  async readPackageAsync(packageId) {
58
+ const cached = this.resolved.get(packageId);
59
+ if (cached !== undefined) {
60
+ return cached;
61
+ }
56
62
  let response;
57
63
  try {
58
64
  response = await this.packageService.getPackageAsync({ packageId });
@@ -88,12 +94,14 @@ class GrpcPackageRelationReader {
88
94
  .map((template) => templateMetadata(packageId, packageName, module.name, template))),
89
95
  ...interfaceMetadata(packageId, packageName, result.rawPackage),
90
96
  ];
91
- return Object.freeze({
97
+ const metadata = Object.freeze({
92
98
  id: packageId,
93
99
  name: packageName,
94
100
  version: requiredPackageText(pkg.packageVersion, "version"),
95
101
  templates: Object.freeze(templates),
96
102
  });
103
+ this.resolved.set(packageId, metadata);
104
+ return metadata;
97
105
  }
98
106
  catch (error) {
99
107
  throw isGrpcPackageRelationError(error) ? error : new GrpcPackageRelationError(packageId, errorMessage(error));
@@ -88,14 +88,24 @@ class DefaultGrpcQueryDataProvider {
88
88
  const history = await this.snapshots.readHistoryAsync(endInclusive);
89
89
  const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
90
90
  const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)(transactions);
91
+ const useCreationsOnlyMetadata = requiresPackageMetadata(closure)
92
+ && query.relation !== "packages" && query.relation !== "contractTypes" && query.relation !== "exerciseTypes"
93
+ && derivableFromCreationsOnly(closure);
91
94
  const packageMetadata = !requiresPackageMetadata(closure)
92
95
  ? []
93
96
  : query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes"
94
97
  ? await this.packages.readAllAsync()
95
- : await this.packages.readPackagesAsync((0, grpc_relation_mapper_js_1.referencedGrpcPackageIds)(fragment));
98
+ : useCreationsOnlyMetadata
99
+ ? (0, grpc_relation_mapper_js_1.contractTypeMetadataFromCreations)(fragment.creationIdentities)
100
+ : await this.packages.readPackagesAsync((0, grpc_relation_mapper_js_1.referencedGrpcPackageIds)(fragment));
101
+ // The closure guarantees this query never reads "exercises" here, but the replayed window can still
102
+ // contain unrelated exercises (createGrpcQueryDataset always resolves package metadata for every
103
+ // exercise in the fragment). contractTypeMetadataFromCreations only knows about templates, so drop
104
+ // the exercises this query doesn't need instead of paying for their canonical keys.
105
+ const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
96
106
  return packageMetadata.length === 0
97
107
  ? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
98
- : (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
108
+ : (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
99
109
  }
100
110
  const endInclusive = cached?.activeAtOffset ?? (await this.options.stateService.getLedgerEndAsync({})).offset;
101
111
  if (query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes") {
@@ -112,9 +122,16 @@ class DefaultGrpcQueryDataProvider {
112
122
  }
113
123
  const active = await this.snapshots.readActiveContractsAsync(endInclusive, partiesFor(query));
114
124
  const fragment = (0, grpc_relation_mapper_js_1.mapGrpcQueryRelationFragment)([], active.activeContracts);
115
- return requiresPackageMetadata(closure)
116
- ? (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(fragment, await this.packages.readPackagesAsync((0, grpc_relation_mapper_js_1.referencedGrpcPackageIds)(fragment)), endInclusive, this.options.endpointScope ?? "ledger")
117
- : fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
125
+ if (!requiresPackageMetadata(closure)) {
126
+ return fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
127
+ }
128
+ // Reaching here guarantees query.relation === "contracts" with needsHistory === false, which (per
129
+ // predicateRequiresHistory/includesRequireHistory above) is only possible when the closure never
130
+ // touches "exercises"/"exerciseTypes"/"packages"/"transactions"/"events" — those unconditionally force
131
+ // history. So requiresPackageMetadata(closure) here can only be "contractTypes", and every contract in
132
+ // this ACS-only fragment already carries its own packageName — no Package Service call needed.
133
+ const packageMetadata = (0, grpc_relation_mapper_js_1.contractTypeMetadataFromCreations)(fragment.creationIdentities);
134
+ return (0, grpc_relation_mapper_js_1.createGrpcQueryDataset)(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
118
135
  }
119
136
  }
120
137
  function requiresHistory(query) {
@@ -143,7 +160,15 @@ function predicateProvesActive(predicate) {
143
160
  return false;
144
161
  }
145
162
  else if (predicate.kind === "scalar") {
146
- return predicate.path.length === 1 && predicate.path[0] === "active" && predicate.operator === "equals" && predicate.value === true;
163
+ if (predicate.path.length !== 1) {
164
+ return false;
165
+ }
166
+ const [field] = predicate.path;
167
+ // A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the
168
+ // same fact as active: true (kept in sync with provesActive in canonical/query-normalizer.ts).
169
+ return field === "active"
170
+ ? predicate.operator === "equals" && predicate.value === true
171
+ : (field === "archivedAt" || field === "archivedEventOffset") && predicate.operator === "is" && predicate.value === null;
147
172
  }
148
173
  else if (predicate.kind === "and") {
149
174
  return predicate.children.some(predicateProvesActive);
@@ -156,6 +181,24 @@ function predicateProvesActive(predicate) {
156
181
  function requiresPackageMetadata(closure) {
157
182
  return closure.has("packages") || closure.has("contractTypes") || closure.has("exercises") || closure.has("exerciseTypes");
158
183
  }
184
+ /**
185
+ * True when contractType metadata can be derived from the fetched creation events alone (see
186
+ * contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
187
+ * own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
188
+ * "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
189
+ * "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
190
+ * the template side — so it is not a reliable source for the choice owner's package name when a choice was
191
+ * exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
192
+ * equivalent at all.
193
+ *
194
+ * The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
195
+ * it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
196
+ * this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
197
+ * every exercise present — this function only knows about templates, not choices.
198
+ */
199
+ function derivableFromCreationsOnly(closure) {
200
+ return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
201
+ }
159
202
  function predicateRequiresHistory(relation, predicate) {
160
203
  if (predicate === undefined || predicate.kind === "scalar") {
161
204
  return false;
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mapGrpcQueryRelationFragment = mapGrpcQueryRelationFragment;
4
4
  exports.createGrpcQueryDataset = createGrpcQueryDataset;
5
5
  exports.referencedGrpcPackageIds = referencedGrpcPackageIds;
6
+ exports.contractTypeMetadataFromCreations = contractTypeMetadataFromCreations;
6
7
  const validation_error_js_1 = require("../../core/errors/validation-error.js");
7
8
  const query_dataset_js_1 = require("../canonical/query-dataset.js");
8
9
  const public_identity_js_1 = require("../canonical/public-identity.js");
@@ -243,6 +244,46 @@ function referencedGrpcPackageIds(fragment) {
243
244
  ...fragment.typeIdentities.filter((identity) => identity.choice !== undefined).map((identity) => identity.packageId),
244
245
  ])].sort());
245
246
  }
247
+ /**
248
+ * Derives contractType metadata straight from already-fetched created-contract events instead of the
249
+ * Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
250
+ * {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
251
+ * so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
252
+ * can see the "packages" relation, so it is never observed.
253
+ */
254
+ function contractTypeMetadataFromCreations(creationIdentities) {
255
+ const packages = new Map();
256
+ for (const creation of creationIdentities) {
257
+ const packageId = creation.representativePackageId ?? creation.creationPackageId;
258
+ const { moduleName, entityName } = creation.templateId;
259
+ let pkg = packages.get(packageId);
260
+ if (pkg === undefined) {
261
+ pkg = { name: creation.packageName, templates: new Map() };
262
+ packages.set(packageId, pkg);
263
+ }
264
+ else if (pkg.name !== creation.packageName) {
265
+ throw new validation_error_js_1.ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
266
+ }
267
+ const identity = `${moduleName}${entityName}`;
268
+ if (!pkg.templates.has(identity)) {
269
+ const templateFqn = `${pkg.name}:${moduleName}:${entityName}`;
270
+ pkg.templates.set(identity, Object.freeze({
271
+ moduleName,
272
+ entityName,
273
+ payloadType: "template",
274
+ aliases: Object.freeze([templateFqn, `${moduleName}:${entityName}`, entityName]),
275
+ templateFqn,
276
+ choices: Object.freeze([]),
277
+ }));
278
+ }
279
+ }
280
+ return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
281
+ id,
282
+ name: pkg.name,
283
+ version: "unresolved",
284
+ templates: Object.freeze([...pkg.templates.values()]),
285
+ })));
286
+ }
246
287
  function compareTemplateMetadata(left, right) {
247
288
  return left.package.id.localeCompare(right.package.id) || left.template.moduleName.localeCompare(right.template.moduleName) || left.template.entityName.localeCompare(right.template.entityName);
248
289
  }
@@ -622,6 +663,7 @@ function creationDescriptor(event, offset) {
622
663
  templateId: copyTemplate(template),
623
664
  creationPackageId: template.packageId,
624
665
  representativePackageId: nullableString(event.representativePackageId),
666
+ packageName: event.packageName,
625
667
  payload: (0, grpc_query_value_mapper_js_1.mapGrpcQueryValue)({ sum: { oneofKind: "record", record: event.createArguments } }),
626
668
  witnesses: immutableStrings(event.witnessParties, "created event witnesses", true),
627
669
  createdAt,
@@ -466,7 +466,7 @@ function provesActive(predicate) {
466
466
  return false;
467
467
  }
468
468
  else if (predicate.kind === "scalar") {
469
- return predicate.path.length === 1 && predicate.path[0] === "active" && predicate.operator === "equals" && predicate.value === true;
469
+ return provesActiveScalar(predicate);
470
470
  }
471
471
  else if (predicate.kind === "and") {
472
472
  return predicate.children.some(provesActive);
@@ -476,6 +476,20 @@ function provesActive(predicate) {
476
476
  }
477
477
  return false;
478
478
  }
479
+ /** A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the same fact as active: true. */
480
+ function provesActiveScalar(predicate) {
481
+ if (predicate.path.length !== 1) {
482
+ return false;
483
+ }
484
+ const [field] = predicate.path;
485
+ if (field === "active") {
486
+ return predicate.operator === "equals" && predicate.value === true;
487
+ }
488
+ else if (field === "archivedAt" || field === "archivedEventOffset") {
489
+ return predicate.operator === "is" && predicate.value === null;
490
+ }
491
+ return false;
492
+ }
479
493
  function validateOperatorValue(operator, value, field, kind, nullable) {
480
494
  if (operator === "is" || operator === "isNot") {
481
495
  if (value !== null) {
@@ -36,6 +36,8 @@ export declare class GrpcPackageRelationError extends CantonError {
36
36
  export declare class GrpcPackageRelationReader {
37
37
  private readonly packageService;
38
38
  private readonly packageLoader;
39
+ /** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
40
+ private readonly resolved;
39
41
  constructor(packageService: GrpcPackageService, packageLoader?: DamlLfPackageLoader);
40
42
  readAllAsync(): Promise<readonly GrpcPackageMetadata[]>;
41
43
  readPackagesAsync(packageIds: readonly string[]): Promise<readonly GrpcPackageMetadata[]>;
@@ -19,6 +19,8 @@ export class GrpcPackageRelationError extends CantonError {
19
19
  export class GrpcPackageRelationReader {
20
20
  packageService;
21
21
  packageLoader;
22
+ /** Package IDs are content-addressed SHA-256 digests (verified below), so a resolved entry is cached forever. */
23
+ resolved = new Map();
22
24
  constructor(packageService, packageLoader = new DamlLfPackageLoader()) {
23
25
  this.packageService = packageService;
24
26
  this.packageLoader = packageLoader;
@@ -49,6 +51,10 @@ export class GrpcPackageRelationReader {
49
51
  return Object.freeze(packages.slice().sort((left, right) => left.id.localeCompare(right.id)));
50
52
  }
51
53
  async readPackageAsync(packageId) {
54
+ const cached = this.resolved.get(packageId);
55
+ if (cached !== undefined) {
56
+ return cached;
57
+ }
52
58
  let response;
53
59
  try {
54
60
  response = await this.packageService.getPackageAsync({ packageId });
@@ -84,12 +90,14 @@ export class GrpcPackageRelationReader {
84
90
  .map((template) => templateMetadata(packageId, packageName, module.name, template))),
85
91
  ...interfaceMetadata(packageId, packageName, result.rawPackage),
86
92
  ];
87
- return Object.freeze({
93
+ const metadata = Object.freeze({
88
94
  id: packageId,
89
95
  name: packageName,
90
96
  version: requiredPackageText(pkg.packageVersion, "version"),
91
97
  templates: Object.freeze(templates),
92
98
  });
99
+ this.resolved.set(packageId, metadata);
100
+ return metadata;
93
101
  }
94
102
  catch (error) {
95
103
  throw isGrpcPackageRelationError(error) ? error : new GrpcPackageRelationError(packageId, errorMessage(error));
@@ -5,7 +5,7 @@ import { createQueryDataset } from "../canonical/query-dataset.js";
5
5
  import { normalizeAggregate, normalizeCount, normalizeFindMany, normalizeFindUnique, normalizeGroupBy } from "../canonical/query-normalizer.js";
6
6
  import { queryRelationEdges, queryRelations } from "../canonical/query-schema.js";
7
7
  import { QuerySource } from "../query-source.js";
8
- import { createGrpcQueryDataset, mapGrpcQueryRelationFragment, referencedGrpcPackageIds } from "./grpc-relation-mapper.js";
8
+ import { contractTypeMetadataFromCreations, createGrpcQueryDataset, mapGrpcQueryRelationFragment, referencedGrpcPackageIds } from "./grpc-relation-mapper.js";
9
9
  import { GrpcPackageRelationReader } from "./grpc-package-relation-reader.js";
10
10
  import { GrpcQuerySnapshotReader } from "./grpc-query-snapshot-reader.js";
11
11
  export class GrpcQueryClient {
@@ -84,14 +84,24 @@ class DefaultGrpcQueryDataProvider {
84
84
  const history = await this.snapshots.readHistoryAsync(endInclusive);
85
85
  const transactions = history.updates.flatMap((response) => response.update.oneofKind === "transaction" ? [response.update.transaction] : []);
86
86
  const fragment = mapGrpcQueryRelationFragment(transactions);
87
+ const useCreationsOnlyMetadata = requiresPackageMetadata(closure)
88
+ && query.relation !== "packages" && query.relation !== "contractTypes" && query.relation !== "exerciseTypes"
89
+ && derivableFromCreationsOnly(closure);
87
90
  const packageMetadata = !requiresPackageMetadata(closure)
88
91
  ? []
89
92
  : query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes"
90
93
  ? await this.packages.readAllAsync()
91
- : await this.packages.readPackagesAsync(referencedGrpcPackageIds(fragment));
94
+ : useCreationsOnlyMetadata
95
+ ? contractTypeMetadataFromCreations(fragment.creationIdentities)
96
+ : await this.packages.readPackagesAsync(referencedGrpcPackageIds(fragment));
97
+ // The closure guarantees this query never reads "exercises" here, but the replayed window can still
98
+ // contain unrelated exercises (createGrpcQueryDataset always resolves package metadata for every
99
+ // exercise in the fragment). contractTypeMetadataFromCreations only knows about templates, so drop
100
+ // the exercises this query doesn't need instead of paying for their canonical keys.
101
+ const datasetFragment = useCreationsOnlyMetadata ? { ...fragment, exercises: [] } : fragment;
92
102
  return packageMetadata.length === 0
93
103
  ? fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger")
94
- : createGrpcQueryDataset(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
104
+ : createGrpcQueryDataset(datasetFragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
95
105
  }
96
106
  const endInclusive = cached?.activeAtOffset ?? (await this.options.stateService.getLedgerEndAsync({})).offset;
97
107
  if (query.relation === "packages" || query.relation === "contractTypes" || query.relation === "exerciseTypes") {
@@ -108,9 +118,16 @@ class DefaultGrpcQueryDataProvider {
108
118
  }
109
119
  const active = await this.snapshots.readActiveContractsAsync(endInclusive, partiesFor(query));
110
120
  const fragment = mapGrpcQueryRelationFragment([], active.activeContracts);
111
- return requiresPackageMetadata(closure)
112
- ? createGrpcQueryDataset(fragment, await this.packages.readPackagesAsync(referencedGrpcPackageIds(fragment)), endInclusive, this.options.endpointScope ?? "ledger")
113
- : fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
121
+ if (!requiresPackageMetadata(closure)) {
122
+ return fragmentDataset(fragment, endInclusive, this.options.endpointScope ?? "ledger", false);
123
+ }
124
+ // Reaching here guarantees query.relation === "contracts" with needsHistory === false, which (per
125
+ // predicateRequiresHistory/includesRequireHistory above) is only possible when the closure never
126
+ // touches "exercises"/"exerciseTypes"/"packages"/"transactions"/"events" — those unconditionally force
127
+ // history. So requiresPackageMetadata(closure) here can only be "contractTypes", and every contract in
128
+ // this ACS-only fragment already carries its own packageName — no Package Service call needed.
129
+ const packageMetadata = contractTypeMetadataFromCreations(fragment.creationIdentities);
130
+ return createGrpcQueryDataset(fragment, packageMetadata, endInclusive, this.options.endpointScope ?? "ledger");
114
131
  }
115
132
  }
116
133
  function requiresHistory(query) {
@@ -139,7 +156,15 @@ function predicateProvesActive(predicate) {
139
156
  return false;
140
157
  }
141
158
  else if (predicate.kind === "scalar") {
142
- return predicate.path.length === 1 && predicate.path[0] === "active" && predicate.operator === "equals" && predicate.value === true;
159
+ if (predicate.path.length !== 1) {
160
+ return false;
161
+ }
162
+ const [field] = predicate.path;
163
+ // A contract is active iff archivedAt/archivedEventOffset are null, so either form proves the
164
+ // same fact as active: true (kept in sync with provesActive in canonical/query-normalizer.ts).
165
+ return field === "active"
166
+ ? predicate.operator === "equals" && predicate.value === true
167
+ : (field === "archivedAt" || field === "archivedEventOffset") && predicate.operator === "is" && predicate.value === null;
143
168
  }
144
169
  else if (predicate.kind === "and") {
145
170
  return predicate.children.some(predicateProvesActive);
@@ -152,6 +177,24 @@ function predicateProvesActive(predicate) {
152
177
  function requiresPackageMetadata(closure) {
153
178
  return closure.has("packages") || closure.has("contractTypes") || closure.has("exercises") || closure.has("exerciseTypes");
154
179
  }
180
+ /**
181
+ * True when contractType metadata can be derived from the fetched creation events alone (see
182
+ * contractTypeMetadataFromCreations), instead of decoding LF packages. Every CreatedEvent already carries its
183
+ * own packageName, so this always holds whenever the query's closure needs only "contractTypes" (plus
184
+ * "contracts"/"transactions"/"events", which carry no package linkage of their own). It does NOT extend to
185
+ * "exercises"/"exerciseTypes": ExercisedEvent.packageName documents "the package name of the contract" — i.e.
186
+ * the template side — so it is not a reliable source for the choice owner's package name when a choice was
187
+ * exercised via an interface. "packages" is excluded because its own rows (e.g. version) have no event
188
+ * equivalent at all.
189
+ *
190
+ * The caller is responsible for dropping any exercises the fetched fragment happens to contain before handing
191
+ * it to createGrpcQueryDataset: a full-history fragment reflects the *entire* replayed window, not just what
192
+ * this query's closure touches, and createGrpcQueryDataset unconditionally resolves package metadata for
193
+ * every exercise present — this function only knows about templates, not choices.
194
+ */
195
+ function derivableFromCreationsOnly(closure) {
196
+ return closure.has("contractTypes") && !closure.has("exercises") && !closure.has("exerciseTypes") && !closure.has("packages");
197
+ }
155
198
  function predicateRequiresHistory(relation, predicate) {
156
199
  if (predicate === undefined || predicate.kind === "scalar") {
157
200
  return false;
@@ -28,6 +28,7 @@ export interface GrpcQueryCreationIdentity {
28
28
  }>;
29
29
  readonly creationPackageId: string;
30
30
  readonly representativePackageId: string | null;
31
+ readonly packageName: string;
31
32
  readonly payload: unknown;
32
33
  readonly witnesses: readonly string[];
33
34
  readonly createdAt: Date;
@@ -65,3 +66,11 @@ export declare function mapGrpcQueryRelationFragment(source: readonly Transactio
65
66
  export declare function createGrpcQueryDataset(fragment: GrpcQueryRelationFragment, packages: readonly GrpcPackageMetadata[], endInclusive: string, instanceId: string): QueryDataset;
66
67
  /** Package payloads required for a contract/history relation plan, excluding creation-only provenance. */
67
68
  export declare function referencedGrpcPackageIds(fragment: GrpcQueryRelationFragment): readonly string[];
69
+ /**
70
+ * Derives contractType metadata straight from already-fetched created-contract events instead of the
71
+ * Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
72
+ * {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
73
+ * so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
74
+ * can see the "packages" relation, so it is never observed.
75
+ */
76
+ export declare function contractTypeMetadataFromCreations(creationIdentities: readonly GrpcQueryCreationIdentity[]): readonly GrpcPackageMetadata[];
@@ -238,6 +238,46 @@ export function referencedGrpcPackageIds(fragment) {
238
238
  ...fragment.typeIdentities.filter((identity) => identity.choice !== undefined).map((identity) => identity.packageId),
239
239
  ])].sort());
240
240
  }
241
+ /**
242
+ * Derives contractType metadata straight from already-fetched created-contract events instead of the
243
+ * Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
244
+ * {contracts, contractTypes} — every contract's own creation event already carries packageName directly,
245
+ * so no archive decode is needed. "version" is a placeholder: this path is unreachable from any query that
246
+ * can see the "packages" relation, so it is never observed.
247
+ */
248
+ export function contractTypeMetadataFromCreations(creationIdentities) {
249
+ const packages = new Map();
250
+ for (const creation of creationIdentities) {
251
+ const packageId = creation.representativePackageId ?? creation.creationPackageId;
252
+ const { moduleName, entityName } = creation.templateId;
253
+ let pkg = packages.get(packageId);
254
+ if (pkg === undefined) {
255
+ pkg = { name: creation.packageName, templates: new Map() };
256
+ packages.set(packageId, pkg);
257
+ }
258
+ else if (pkg.name !== creation.packageName) {
259
+ throw new ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
260
+ }
261
+ const identity = `${moduleName}${entityName}`;
262
+ if (!pkg.templates.has(identity)) {
263
+ const templateFqn = `${pkg.name}:${moduleName}:${entityName}`;
264
+ pkg.templates.set(identity, Object.freeze({
265
+ moduleName,
266
+ entityName,
267
+ payloadType: "template",
268
+ aliases: Object.freeze([templateFqn, `${moduleName}:${entityName}`, entityName]),
269
+ templateFqn,
270
+ choices: Object.freeze([]),
271
+ }));
272
+ }
273
+ }
274
+ return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
275
+ id,
276
+ name: pkg.name,
277
+ version: "unresolved",
278
+ templates: Object.freeze([...pkg.templates.values()]),
279
+ })));
280
+ }
241
281
  function compareTemplateMetadata(left, right) {
242
282
  return left.package.id.localeCompare(right.package.id) || left.template.moduleName.localeCompare(right.template.moduleName) || left.template.entityName.localeCompare(right.template.entityName);
243
283
  }
@@ -617,6 +657,7 @@ function creationDescriptor(event, offset) {
617
657
  templateId: copyTemplate(template),
618
658
  creationPackageId: template.packageId,
619
659
  representativePackageId: nullableString(event.representativePackageId),
660
+ packageName: event.packageName,
620
661
  payload: mapGrpcQueryValue({ sum: { oneofKind: "record", record: event.createArguments } }),
621
662
  witnesses: immutableStrings(event.witnessParties, "created event witnesses", true),
622
663
  createdAt,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.40",
3
+ "version": "0.1.42",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",