@distrohelena/canton-typescript-sdk 0.1.43 → 0.1.45

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.
@@ -242,40 +242,92 @@ export function referencedGrpcPackageIds(fragment) {
242
242
  * Derives contractType metadata straight from already-fetched created-contract events instead of the
243
243
  * Package Service. Only valid where the caller has confirmed the query's relation closure is a subset of
244
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.
245
+ * so no archive decode is needed. "version" is a per-package placeholder: this path is unreachable from
246
+ * any query that can see the "packages" relation, so it is never observed.
247
247
  */
248
248
  export function contractTypeMetadataFromCreations(creationIdentities) {
249
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
- }));
250
+ addDerivedCreationEntries(packages, creationIdentities);
251
+ return finalizeDerivedPackages(packages);
252
+ }
253
+ /**
254
+ * Extends the creations-only derivation to exercises: for a direct exercise the choice owner IS the
255
+ * exercised template, so the event's own packageName names the owner's package and choice/consuming come
256
+ * straight off the event. Returns undefined when the window contains an interface-exercised choice — its
257
+ * owner is the interface, whose package name the event does not carry, so only the decoded archive can
258
+ * produce its canonical choiceFqn. Unobserved choices are simply absent, which is complete for query plans
259
+ * that reach exerciseTypes only through observed exercises (never for exerciseTypes catalog queries).
260
+ */
261
+ export function packageMetadataFromEvents(fragment) {
262
+ if (fragment.typeIdentities.some((identity) => identity.choice !== undefined && identity.packageName === undefined)) {
263
+ return undefined;
264
+ }
265
+ const packages = new Map();
266
+ addDerivedCreationEntries(packages, fragment.creationIdentities);
267
+ for (const identity of fragment.typeIdentities) {
268
+ if (identity.packageName === undefined) {
269
+ throw new ValidationError("gRPC query contract type identity is missing its package name");
270
+ }
271
+ const template = derivedTemplateFor(derivedPackageFor(packages, identity.packageId, identity.packageName), identity.templateId.moduleName, identity.templateId.entityName);
272
+ if (identity.choice !== undefined) {
273
+ const consuming = template.choices.get(identity.choice);
274
+ if (consuming !== undefined && consuming !== identity.consuming) {
275
+ throw new ValidationError(`gRPC query choice ${identity.choice} reports conflicting consuming flags`);
276
+ }
277
+ template.choices.set(identity.choice, identity.consuming === true);
272
278
  }
273
279
  }
280
+ return finalizeDerivedPackages(packages);
281
+ }
282
+ function addDerivedCreationEntries(packages, creationIdentities) {
283
+ for (const creation of creationIdentities) {
284
+ derivedTemplateFor(derivedPackageFor(packages, creation.representativePackageId ?? creation.creationPackageId, creation.packageName), creation.templateId.moduleName, creation.templateId.entityName);
285
+ }
286
+ }
287
+ function derivedPackageFor(packages, packageId, packageName) {
288
+ const existing = packages.get(packageId);
289
+ if (existing === undefined) {
290
+ const created = { name: packageName, templates: new Map() };
291
+ packages.set(packageId, created);
292
+ return created;
293
+ }
294
+ else if (existing.name !== packageName) {
295
+ throw new ValidationError(`gRPC query package ${packageId} reports conflicting package names`);
296
+ }
297
+ return existing;
298
+ }
299
+ function derivedTemplateFor(pkg, moduleName, entityName) {
300
+ const identity = `${moduleName} ${entityName}`;
301
+ let template = pkg.templates.get(identity);
302
+ if (template === undefined) {
303
+ template = { moduleName, entityName, choices: new Map() };
304
+ pkg.templates.set(identity, template);
305
+ }
306
+ return template;
307
+ }
308
+ function finalizeDerivedPackages(packages) {
274
309
  return Object.freeze([...packages.entries()].map(([id, pkg]) => Object.freeze({
275
310
  id,
276
311
  name: pkg.name,
277
- version: "unresolved",
278
- templates: Object.freeze([...pkg.templates.values()]),
312
+ // Unique per package: two versions of the same package name can both appear in one window, and
313
+ // duplicate name+version pairs are rejected during dataset normalization.
314
+ version: `unresolved-${id}`,
315
+ templates: Object.freeze([...pkg.templates.values()].map((template) => {
316
+ const templateFqn = `${pkg.name}:${template.moduleName}:${template.entityName}`;
317
+ return Object.freeze({
318
+ moduleName: template.moduleName,
319
+ entityName: template.entityName,
320
+ payloadType: "template",
321
+ aliases: Object.freeze([templateFqn, `${template.moduleName}:${template.entityName}`, template.entityName]),
322
+ templateFqn,
323
+ choices: Object.freeze([...template.choices.entries()].map(([choice, consuming]) => Object.freeze({
324
+ choice,
325
+ consuming,
326
+ aliases: Object.freeze([`${templateFqn}:${choice}`, `${template.moduleName}:${template.entityName}:${choice}`, `${template.entityName}:${choice}`, choice]),
327
+ choiceFqn: `${templateFqn}:${choice}`,
328
+ })).sort((left, right) => left.choice.localeCompare(right.choice))),
329
+ });
330
+ })),
279
331
  })));
280
332
  }
281
333
  function compareTemplateMetadata(left, right) {
@@ -298,7 +350,6 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
298
350
  }
299
351
  const packageValues = Array.from(packages);
300
352
  const packageIds = new Set();
301
- const packageNameVersions = new Set();
302
353
  const normalized = [];
303
354
  for (const pkg of packageValues) {
304
355
  if (pkg === null || typeof pkg !== "object") {
@@ -313,11 +364,10 @@ function normalizeGrpcPackageMetadataUnsafe(packages) {
313
364
  throw new ValidationError(`gRPC query has duplicate package metadata ${packageId}`);
314
365
  }
315
366
  packageIds.add(packageId);
316
- const nameVersion = `${packageName}\u0000${packageVersion}`;
317
- if (packageNameVersions.has(nameVersion)) {
318
- throw new ValidationError(`gRPC query has duplicate package metadata ${packageName}@${packageVersion}`);
319
- }
320
- packageNameVersions.add(nameVersion);
367
+ // Same name+version under two different ids is NOT rejected: package ids are content-addressed, so a
368
+ // recompiled or re-uploaded DAR legitimately reappears with a new id and an unchanged manifest. The
369
+ // name-derived canonical rows go through deduplicateCanonicalRows, which throws on any genuine
370
+ // metadata conflict for one canonical key — the only conflict that would actually matter.
321
371
  const templatesValue = value.templates;
322
372
  if (!Array.isArray(templatesValue)) {
323
373
  throw new ValidationError(`gRPC query package ${packageId} templates are invalid`);
@@ -563,12 +613,19 @@ function identitiesFor(event) {
563
613
  }
564
614
  function typeIdentityRows(event, registry) {
565
615
  const template = requiredTemplate(event.templateId, "event template");
566
- const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId };
616
+ const contract = { pk: registry.get(contractIdentity(template)), templateId: copyTemplate(template), packageId: template.packageId, packageName: event.packageName };
567
617
  if (!isExercised(event)) {
568
618
  return [contract];
569
619
  }
570
620
  const owner = exerciseOwner(event);
571
- return [contract, { pk: registry.get(exerciseIdentity(owner, event.choice, event.consuming)), templateId: copyTemplate(owner), packageId: owner.packageId, choice: event.choice, consuming: event.consuming }];
621
+ return [contract, {
622
+ pk: registry.get(exerciseIdentity(owner, event.choice, event.consuming)),
623
+ templateId: copyTemplate(owner),
624
+ packageId: owner.packageId,
625
+ ...(event.interfaceId === undefined ? { packageName: event.packageName } : {}),
626
+ choice: event.choice,
627
+ consuming: event.consuming,
628
+ }];
572
629
  }
573
630
  function activeContractEntries(responses) {
574
631
  const entries = responses.map((response) => {
@@ -1,10 +1,17 @@
1
1
  import { QueryContractsResponse } from "../../../core/types/responses/query-contracts-response.js";
2
2
  import { GetActiveContractsPageRequest } from "../generated/canton/com/daml/ledger/api/v2/state_service.js";
3
+ /** A fully qualified template reference; packageId is a concrete package id or a "#package-name" reference. */
4
+ export interface GrpcQueryTemplateRef {
5
+ readonly packageId: string;
6
+ readonly moduleName: string;
7
+ readonly entityName: string;
8
+ }
3
9
  export declare function mapGrpcQueryContractsRequest(request: {
4
10
  party?: string;
5
11
  parties?: readonly string[];
6
12
  allParties?: boolean;
7
13
  templateId?: string;
14
+ templateRefs?: readonly GrpcQueryTemplateRef[];
8
15
  interfaceId?: string;
9
16
  includeInterfaceView?: boolean;
10
17
  includeCreatedEventBlob?: boolean;
@@ -52,6 +52,17 @@ function createFilters(request) {
52
52
  },
53
53
  });
54
54
  }
55
+ for (const ref of request.templateRefs ?? []) {
56
+ cumulative.push({
57
+ identifierFilter: {
58
+ oneofKind: "templateFilter",
59
+ templateFilter: {
60
+ templateId: { packageId: ref.packageId, moduleName: ref.moduleName, entityName: ref.entityName },
61
+ includeCreatedEventBlob,
62
+ },
63
+ },
64
+ });
65
+ }
55
66
  if (request.interfaceId) {
56
67
  cumulative.push({
57
68
  identifierFilter: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.43",
3
+ "version": "0.1.45",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",