@distrohelena/canton-typescript-sdk 0.1.50 → 0.1.52

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.
@@ -30,6 +30,7 @@ class ProjectEmitter {
30
30
  /** Emits the complete in-memory DAML interface project from analyzed templates. */
31
31
  emitProject(analysis, moduleImportStyle = daml_module_import_style_js_1.DamlModuleImportStyles.esm) {
32
32
  this.namedTypeEmitter.prepareProjectOrThrow(analysis.templates, analysis.typeDefinitions, analysis.packageMetadata);
33
+ this.templateBindingEmitter.providePackageMetadata(analysis.packageMetadata);
33
34
  const templateBindingFiles = analysis.templates.map((template) => this.templateBindingEmitter.emitTemplateBindingFile(template));
34
35
  const namedTypeFiles = this.namedTypeEmitter.emitPreparedNamedTypeFiles(analysis.typeDefinitions, templateBindingFiles, moduleImportStyle);
35
36
  const templateFiles = analysis.templates.map((template) => this.templateBindingEmitter.emitTemplateFile(template, namedTypeFiles, moduleImportStyle));
@@ -22,7 +22,7 @@ class RegistryEmitter {
22
22
  "export class GeneratedRegistry {",
23
23
  " public static fromCreatedEvent(event: DamlCreatedEventSource): unknown {",
24
24
  " const normalized = DamlEventSourceNormalizer.normalizeCreated(event);",
25
- " switch (`${normalized.metadata.templateId.packageId}:${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
25
+ " switch (`${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
26
26
  ...createdCases,
27
27
  " default:",
28
28
  " throw new DamlMaterializationError(\"template ID\", \"no generated template binding matches the created event\");",
@@ -31,7 +31,7 @@ class RegistryEmitter {
31
31
  "",
32
32
  " public static fromExercisedEvent(event: DamlExercisedEventSource): unknown {",
33
33
  " const normalized = DamlEventSourceNormalizer.normalizeExercised(event);",
34
- " switch (`${normalized.metadata.templateId.packageId}:${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
34
+ " switch (`${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
35
35
  ...exercisedCases,
36
36
  " default:",
37
37
  " throw new DamlMaterializationError(\"template ID\", \"no generated template binding matches the exercised event\");",
@@ -43,7 +43,21 @@ class RegistryEmitter {
43
43
  });
44
44
  }
45
45
  emitCases(project, methodName) {
46
- return project.templateFiles.map((file) => ` case "${file.binding.templateIdLiteral}":\n return ${file.binding.className}.${methodName}(event);`);
46
+ // Dispatch is by module:entity — package ids are version-specific under smart contract upgrades,
47
+ // so any version of a template routes to its binding; the binding's own identity guard then
48
+ // verifies the package name when the event carries one.
49
+ const seen = new Map();
50
+ return project.templateFiles.map((file) => {
51
+ const parts = file.binding.templateIdLiteral.split(":");
52
+ const dispatchKey = `${parts[1]}:${parts[2]}`;
53
+ const existing = seen.get(dispatchKey);
54
+ if (existing !== undefined && existing !== file.binding.templateIdLiteral) {
55
+ throw new Error(`Generated registry cannot dispatch '${dispatchKey}': templates '${existing}' and `
56
+ + `'${file.binding.templateIdLiteral}' collide on module:entity across packages.`);
57
+ }
58
+ seen.set(dispatchKey, file.binding.templateIdLiteral);
59
+ return ` case "${dispatchKey}":\n return ${file.binding.className}.${methodName}(event);`;
60
+ });
47
61
  }
48
62
  }
49
63
  exports.RegistryEmitter = RegistryEmitter;
@@ -10,10 +10,15 @@ const relative_module_specifier_js_1 = require("./relative-module-specifier.js")
10
10
  /** Emits typed contract and exercise-event bindings for analyzed DAML templates. */
11
11
  class TemplateBindingEmitter {
12
12
  nameResolver;
13
+ packageMetadata;
13
14
  constructor(nameResolver = new type_script_name_resolver_js_1.TypeScriptNameResolver()) {
14
15
  this.nameResolver = nameResolver;
15
16
  void this.nameResolver;
16
17
  }
18
+ /** Supplies per-package metadata so emitted identity checks can match by package NAME across upgrades. */
19
+ providePackageMetadata(metadata) {
20
+ this.packageMetadata = metadata;
21
+ }
17
22
  /** Prepares stable collision-safe names before emitting a complete project. */
18
23
  prepareTemplatesOrThrow(templates) {
19
24
  this.nameResolver.prepareTemplatesOrThrow(templates);
@@ -49,7 +54,8 @@ class TemplateBindingEmitter {
49
54
  templateIdentityKey: this.nameResolver.getTemplateIdentityKey(template),
50
55
  namespaceAlias: this.nameResolver.getNamespaceAlias(template),
51
56
  className,
52
- templateIdLiteral: this.nameResolver.getTemplateIdLiteral(template),
57
+ templateIdLiteral: this.templateIdLiteral(template),
58
+ packageName: this.packageMetadata?.get(template.templateId.packageId)?.packageName,
53
59
  path: this.nameResolver.getTemplateFilePath(template),
54
60
  createFieldsTypeName: this.nameResolver.getCreateFieldsTypeName(template),
55
61
  createdEventTypeName: this.nameResolver.getCreatedEventTypeName(template),
@@ -133,6 +139,7 @@ class TemplateBindingEmitter {
133
139
  return [
134
140
  `export class ${binding.className} extends ${this.getSdkName("DamlTemplate", runtimeWrapperTypeNames)} implements ${binding.createFieldsTypeName} {`,
135
141
  ` public static readonly templateId = ${JSON.stringify(binding.templateIdLiteral)};`,
142
+ ...(binding.packageName === undefined ? [] : [` public static readonly packageName = ${JSON.stringify(binding.packageName)};`]),
136
143
  ` private static readonly descriptor: ${this.getSdkName("DamlTypeDescriptor", runtimeWrapperTypeNames)} = ${this.emitTemplateDescriptor(binding)};`,
137
144
  "",
138
145
  ...binding.createFields.map((field) => ` public readonly ${field.propertyName}: ${field.typeName};`),
@@ -144,7 +151,7 @@ class TemplateBindingEmitter {
144
151
  "",
145
152
  ` public static fromCreatedEvent(event: ${this.getSdkName("DamlCreatedEventSource", runtimeWrapperTypeNames)}): ${binding.className} {`,
146
153
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeCreated(event);`,
147
- ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId);`,
154
+ ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
148
155
  ` const fields = ${this.getSdkName("DamlValueMaterializer", runtimeWrapperTypeNames)}.materialize<${binding.createFieldsTypeName}>(${this.getSdkName("DamlValueConverter", runtimeWrapperTypeNames)}.decode(normalized.payload, ${binding.className}.descriptor, GeneratedDamlTypeDescriptorRegistry, "create arguments"));`,
149
156
  ` return new ${binding.className}(`,
150
157
  " normalized.contractId,",
@@ -154,7 +161,7 @@ class TemplateBindingEmitter {
154
161
  "",
155
162
  ` public static fromExercisedEvent(event: ${this.getSdkName("DamlExercisedEventSource", runtimeWrapperTypeNames)}): ${exercisedReturnType} {`,
156
163
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeExercised(event);`,
157
- ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId);`,
164
+ ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
158
165
  " switch (normalized.choice) {",
159
166
  ...binding.choices.map((choice) => ` case ${JSON.stringify(choice.name)}:\n return ${choice.exercisedEventTypeName}.fromNormalizedEvent(normalized);`),
160
167
  " default:",
@@ -162,11 +169,7 @@ class TemplateBindingEmitter {
162
169
  " }",
163
170
  " }",
164
171
  "",
165
- " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }): void {",
166
- ` if (identity.packageId !== ${JSON.stringify(this.packageId(binding))} || identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
167
- ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
168
- " }",
169
- " }",
172
+ ...this.emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames),
170
173
  "}",
171
174
  ].join("\n");
172
175
  }
@@ -193,12 +196,12 @@ class TemplateBindingEmitter {
193
196
  "",
194
197
  ` public static fromExercisedEvent(event: ${this.getSdkName("DamlExercisedEventSource", runtimeWrapperTypeNames)}): ${choice.exercisedEventTypeName} {`,
195
198
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeExercised(event);`,
196
- ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(normalized.metadata.templateId);`,
199
+ ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
197
200
  ` return ${choice.exercisedEventTypeName}.fromNormalizedEvent(normalized);`,
198
201
  " }",
199
202
  "",
200
203
  ` public static fromNormalizedEvent(event: ${this.getSdkName("DamlNormalizedExercisedEvent", runtimeWrapperTypeNames)}): ${choice.exercisedEventTypeName} {`,
201
- ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(event.metadata.templateId);`,
204
+ ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(event.metadata.templateId, event.metadata.packageName);`,
202
205
  ` if (event.choice !== ${JSON.stringify(choice.name)}) {`,
203
206
  ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("choice", \`Expected choice '${choice.name}' but received '\${event.choice}'\`);`,
204
207
  " }",
@@ -207,14 +210,28 @@ class TemplateBindingEmitter {
207
210
  ` return new ${choice.exercisedEventTypeName}(event.contractId, argument, result, event.consuming, event.metadata);`,
208
211
  " }",
209
212
  "",
210
- " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }): void {",
211
- ` if (identity.packageId !== ${JSON.stringify(this.packageId(binding))} || identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
212
- ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
213
- " }",
214
- " }",
213
+ ...this.emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames),
215
214
  "}",
216
215
  ].join("\n");
217
216
  }
217
+ /**
218
+ * Emits the identity guard for materialization. Module and entity must match exactly; the package is
219
+ * matched by NAME when the event provides one — never by exact package id, because smart contract
220
+ * upgrades give every version its own id while contracts from any version stay materializable.
221
+ */
222
+ emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames) {
223
+ const lines = [
224
+ " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }, packageName?: string): void {",
225
+ ` if (identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
226
+ ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
227
+ " }",
228
+ ];
229
+ if (binding.packageName !== undefined) {
230
+ lines.push(` if (packageName !== undefined && packageName !== ${JSON.stringify(binding.packageName)}) {`, ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected package '${binding.packageName}' but received '\${packageName}' for template '${binding.templateIdLiteral}'\`);`, " }");
231
+ }
232
+ lines.push(" }");
233
+ return lines;
234
+ }
218
235
  emitTemplateDescriptor(binding) {
219
236
  return `{ kind: "record", fields: [${binding.createFields.map((field) => `{ damlLabel: ${JSON.stringify(field.name)}, propertyName: ${JSON.stringify(field.propertyName)}, type: ${this.emitDescriptor(field.type)} }`).join(", ")}] }`;
220
237
  }
@@ -515,8 +532,16 @@ class TemplateBindingEmitter {
515
532
  }
516
533
  return (hash >>> 0).toString(36).padStart(6, "0").slice(-6);
517
534
  }
518
- packageId(binding) {
519
- return binding.templateIdLiteral.split(":")[0];
535
+ /**
536
+ * The emitted identity literal NEVER contains a package id when the package name is known: package ids
537
+ * are version-specific under smart contract upgrades, while packageName:module:entity is stable. The
538
+ * id-based form remains only for metadata-less invocations (tests driving the emitter directly).
539
+ */
540
+ templateIdLiteral(template) {
541
+ const packageName = this.packageMetadata?.get(template.templateId.packageId)?.packageName;
542
+ return packageName === undefined
543
+ ? this.nameResolver.getTemplateIdLiteral(template)
544
+ : `${packageName}:${template.templateId.moduleName}:${template.templateId.templateName}`;
520
545
  }
521
546
  moduleName(binding) {
522
547
  return binding.templateIdLiteral.split(":")[1];
@@ -21,6 +21,8 @@ class GeneratedTemplateBinding {
21
21
  namespaceAlias;
22
22
  className;
23
23
  templateIdLiteral;
24
+ /** The owning package's name; enables upgrade-aware (name-based) identity checks in emitted code. */
25
+ packageName;
24
26
  path;
25
27
  createFieldsTypeName;
26
28
  createdEventTypeName;
@@ -31,6 +33,7 @@ class GeneratedTemplateBinding {
31
33
  this.namespaceAlias = init.namespaceAlias ?? init.className;
32
34
  this.className = init.className;
33
35
  this.templateIdLiteral = init.templateIdLiteral;
36
+ this.packageName = init.packageName;
34
37
  this.path = init.path;
35
38
  this.createFieldsTypeName = init.createFieldsTypeName;
36
39
  this.createdEventTypeName = init.createdEventTypeName;
@@ -147,6 +147,7 @@ function identityFrom(value, path) {
147
147
  function freezeCreatedMetadata(event, templateId) {
148
148
  return Object.freeze(removeUndefined({
149
149
  templateId,
150
+ packageName: optionalPackageName(event, "created event source"),
150
151
  offset: optionalString(event, ["offset", "createdEventOffset", "created_event_offset"], "offset", "created event source"),
151
152
  nodeId: optionalNodeId(event, ["nodeId", "node_id"], "node ID", "created event source"),
152
153
  witnessParties: optionalStringArray(event, ["witnessParties", "witness_parties", "witnesses"], "witness parties", "created event source"),
@@ -160,6 +161,7 @@ function freezeExercisedMetadata(event, source, templateId) {
160
161
  const transaction = asObject(readProperty(event, ["transaction"]).value) ?? asObject(root?.transaction);
161
162
  return Object.freeze(removeUndefined({
162
163
  templateId,
164
+ packageName: optionalPackageName(event, "exercised event source"),
163
165
  offset: optionalString(event, ["offset"], "offset", "exercised event source")
164
166
  ?? optionalString(transaction ?? {}, ["offset"], "offset", "exercised event source"),
165
167
  nodeId: optionalNodeId(event, ["nodeId", "node_id"], "node ID", "exercised event source"),
@@ -527,6 +529,17 @@ function cloneAndFreeze(value, seen = new WeakMap()) {
527
529
  function removeUndefined(value) {
528
530
  return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined));
529
531
  }
532
+ /** Protobuf-generated events default packageName to ""; treat the default as absent, not invalid. */
533
+ function optionalPackageName(event, context) {
534
+ const { value } = readProperty(event, ["packageName", "package_name"]);
535
+ if (value === undefined || value === null || value === "") {
536
+ return undefined;
537
+ }
538
+ if (typeof value !== "string") {
539
+ throw sourceError(context, "package name must be a string");
540
+ }
541
+ return value;
542
+ }
530
543
  function sourceError(path, detail) {
531
544
  return new daml_materialization_error_js_1.DamlMaterializationError(path, detail);
532
545
  }
@@ -27,6 +27,7 @@ export class ProjectEmitter {
27
27
  /** Emits the complete in-memory DAML interface project from analyzed templates. */
28
28
  emitProject(analysis, moduleImportStyle = DamlModuleImportStyles.esm) {
29
29
  this.namedTypeEmitter.prepareProjectOrThrow(analysis.templates, analysis.typeDefinitions, analysis.packageMetadata);
30
+ this.templateBindingEmitter.providePackageMetadata(analysis.packageMetadata);
30
31
  const templateBindingFiles = analysis.templates.map((template) => this.templateBindingEmitter.emitTemplateBindingFile(template));
31
32
  const namedTypeFiles = this.namedTypeEmitter.emitPreparedNamedTypeFiles(analysis.typeDefinitions, templateBindingFiles, moduleImportStyle);
32
33
  const templateFiles = analysis.templates.map((template) => this.templateBindingEmitter.emitTemplateFile(template, namedTypeFiles, moduleImportStyle));
@@ -19,7 +19,7 @@ export class RegistryEmitter {
19
19
  "export class GeneratedRegistry {",
20
20
  " public static fromCreatedEvent(event: DamlCreatedEventSource): unknown {",
21
21
  " const normalized = DamlEventSourceNormalizer.normalizeCreated(event);",
22
- " switch (`${normalized.metadata.templateId.packageId}:${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
22
+ " switch (`${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
23
23
  ...createdCases,
24
24
  " default:",
25
25
  " throw new DamlMaterializationError(\"template ID\", \"no generated template binding matches the created event\");",
@@ -28,7 +28,7 @@ export class RegistryEmitter {
28
28
  "",
29
29
  " public static fromExercisedEvent(event: DamlExercisedEventSource): unknown {",
30
30
  " const normalized = DamlEventSourceNormalizer.normalizeExercised(event);",
31
- " switch (`${normalized.metadata.templateId.packageId}:${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
31
+ " switch (`${normalized.metadata.templateId.moduleName}:${normalized.metadata.templateId.entityName}`) {",
32
32
  ...exercisedCases,
33
33
  " default:",
34
34
  " throw new DamlMaterializationError(\"template ID\", \"no generated template binding matches the exercised event\");",
@@ -40,6 +40,20 @@ export class RegistryEmitter {
40
40
  });
41
41
  }
42
42
  emitCases(project, methodName) {
43
- return project.templateFiles.map((file) => ` case "${file.binding.templateIdLiteral}":\n return ${file.binding.className}.${methodName}(event);`);
43
+ // Dispatch is by module:entity — package ids are version-specific under smart contract upgrades,
44
+ // so any version of a template routes to its binding; the binding's own identity guard then
45
+ // verifies the package name when the event carries one.
46
+ const seen = new Map();
47
+ return project.templateFiles.map((file) => {
48
+ const parts = file.binding.templateIdLiteral.split(":");
49
+ const dispatchKey = `${parts[1]}:${parts[2]}`;
50
+ const existing = seen.get(dispatchKey);
51
+ if (existing !== undefined && existing !== file.binding.templateIdLiteral) {
52
+ throw new Error(`Generated registry cannot dispatch '${dispatchKey}': templates '${existing}' and `
53
+ + `'${file.binding.templateIdLiteral}' collide on module:entity across packages.`);
54
+ }
55
+ seen.set(dispatchKey, file.binding.templateIdLiteral);
56
+ return ` case "${dispatchKey}":\n return ${file.binding.className}.${methodName}(event);`;
57
+ });
44
58
  }
45
59
  }
@@ -6,7 +6,12 @@ import { type DamlModuleImportStyle } from "./daml-module-import-style.js";
6
6
  /** Emits typed contract and exercise-event bindings for analyzed DAML templates. */
7
7
  export declare class TemplateBindingEmitter {
8
8
  private readonly nameResolver;
9
+ private packageMetadata?;
9
10
  constructor(nameResolver?: TypeScriptNameResolver);
11
+ /** Supplies per-package metadata so emitted identity checks can match by package NAME across upgrades. */
12
+ providePackageMetadata(metadata: ReadonlyMap<string, {
13
+ readonly packageName: string;
14
+ }>): void;
10
15
  /** Prepares stable collision-safe names before emitting a complete project. */
11
16
  prepareTemplatesOrThrow(templates: readonly AnalyzedTemplate[]): void;
12
17
  /** Emits a generated TypeScript file for one analyzed DAML template. */
@@ -18,6 +23,12 @@ export declare class TemplateBindingEmitter {
18
23
  private emitImports;
19
24
  private emitTemplateClass;
20
25
  private emitChoiceEventClass;
26
+ /**
27
+ * Emits the identity guard for materialization. Module and entity must match exactly; the package is
28
+ * matched by NAME when the event provides one — never by exact package id, because smart contract
29
+ * upgrades give every version its own id while contracts from any version stay materializable.
30
+ */
31
+ private emitAssertTemplateIdentity;
21
32
  private emitTemplateDescriptor;
22
33
  private emitDescriptor;
23
34
  private getTypeName;
@@ -33,7 +44,12 @@ export declare class TemplateBindingEmitter {
33
44
  private getNamedReferenceKey;
34
45
  private toTypeName;
35
46
  private shortHash;
36
- private packageId;
47
+ /**
48
+ * The emitted identity literal NEVER contains a package id when the package name is known: package ids
49
+ * are version-specific under smart contract upgrades, while packageName:module:entity is stable. The
50
+ * id-based form remains only for metadata-less invocations (tests driving the emitter directly).
51
+ */
52
+ private templateIdLiteral;
37
53
  private moduleName;
38
54
  private entityName;
39
55
  private relativeFilePath;
@@ -7,10 +7,15 @@ import { RelativeModuleSpecifier } from "./relative-module-specifier.js";
7
7
  /** Emits typed contract and exercise-event bindings for analyzed DAML templates. */
8
8
  export class TemplateBindingEmitter {
9
9
  nameResolver;
10
+ packageMetadata;
10
11
  constructor(nameResolver = new TypeScriptNameResolver()) {
11
12
  this.nameResolver = nameResolver;
12
13
  void this.nameResolver;
13
14
  }
15
+ /** Supplies per-package metadata so emitted identity checks can match by package NAME across upgrades. */
16
+ providePackageMetadata(metadata) {
17
+ this.packageMetadata = metadata;
18
+ }
14
19
  /** Prepares stable collision-safe names before emitting a complete project. */
15
20
  prepareTemplatesOrThrow(templates) {
16
21
  this.nameResolver.prepareTemplatesOrThrow(templates);
@@ -46,7 +51,8 @@ export class TemplateBindingEmitter {
46
51
  templateIdentityKey: this.nameResolver.getTemplateIdentityKey(template),
47
52
  namespaceAlias: this.nameResolver.getNamespaceAlias(template),
48
53
  className,
49
- templateIdLiteral: this.nameResolver.getTemplateIdLiteral(template),
54
+ templateIdLiteral: this.templateIdLiteral(template),
55
+ packageName: this.packageMetadata?.get(template.templateId.packageId)?.packageName,
50
56
  path: this.nameResolver.getTemplateFilePath(template),
51
57
  createFieldsTypeName: this.nameResolver.getCreateFieldsTypeName(template),
52
58
  createdEventTypeName: this.nameResolver.getCreatedEventTypeName(template),
@@ -130,6 +136,7 @@ export class TemplateBindingEmitter {
130
136
  return [
131
137
  `export class ${binding.className} extends ${this.getSdkName("DamlTemplate", runtimeWrapperTypeNames)} implements ${binding.createFieldsTypeName} {`,
132
138
  ` public static readonly templateId = ${JSON.stringify(binding.templateIdLiteral)};`,
139
+ ...(binding.packageName === undefined ? [] : [` public static readonly packageName = ${JSON.stringify(binding.packageName)};`]),
133
140
  ` private static readonly descriptor: ${this.getSdkName("DamlTypeDescriptor", runtimeWrapperTypeNames)} = ${this.emitTemplateDescriptor(binding)};`,
134
141
  "",
135
142
  ...binding.createFields.map((field) => ` public readonly ${field.propertyName}: ${field.typeName};`),
@@ -141,7 +148,7 @@ export class TemplateBindingEmitter {
141
148
  "",
142
149
  ` public static fromCreatedEvent(event: ${this.getSdkName("DamlCreatedEventSource", runtimeWrapperTypeNames)}): ${binding.className} {`,
143
150
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeCreated(event);`,
144
- ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId);`,
151
+ ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
145
152
  ` const fields = ${this.getSdkName("DamlValueMaterializer", runtimeWrapperTypeNames)}.materialize<${binding.createFieldsTypeName}>(${this.getSdkName("DamlValueConverter", runtimeWrapperTypeNames)}.decode(normalized.payload, ${binding.className}.descriptor, GeneratedDamlTypeDescriptorRegistry, "create arguments"));`,
146
153
  ` return new ${binding.className}(`,
147
154
  " normalized.contractId,",
@@ -151,7 +158,7 @@ export class TemplateBindingEmitter {
151
158
  "",
152
159
  ` public static fromExercisedEvent(event: ${this.getSdkName("DamlExercisedEventSource", runtimeWrapperTypeNames)}): ${exercisedReturnType} {`,
153
160
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeExercised(event);`,
154
- ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId);`,
161
+ ` ${binding.className}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
155
162
  " switch (normalized.choice) {",
156
163
  ...binding.choices.map((choice) => ` case ${JSON.stringify(choice.name)}:\n return ${choice.exercisedEventTypeName}.fromNormalizedEvent(normalized);`),
157
164
  " default:",
@@ -159,11 +166,7 @@ export class TemplateBindingEmitter {
159
166
  " }",
160
167
  " }",
161
168
  "",
162
- " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }): void {",
163
- ` if (identity.packageId !== ${JSON.stringify(this.packageId(binding))} || identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
164
- ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
165
- " }",
166
- " }",
169
+ ...this.emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames),
167
170
  "}",
168
171
  ].join("\n");
169
172
  }
@@ -190,12 +193,12 @@ export class TemplateBindingEmitter {
190
193
  "",
191
194
  ` public static fromExercisedEvent(event: ${this.getSdkName("DamlExercisedEventSource", runtimeWrapperTypeNames)}): ${choice.exercisedEventTypeName} {`,
192
195
  ` const normalized = ${this.getSdkName("DamlEventSourceNormalizer", runtimeWrapperTypeNames)}.normalizeExercised(event);`,
193
- ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(normalized.metadata.templateId);`,
196
+ ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(normalized.metadata.templateId, normalized.metadata.packageName);`,
194
197
  ` return ${choice.exercisedEventTypeName}.fromNormalizedEvent(normalized);`,
195
198
  " }",
196
199
  "",
197
200
  ` public static fromNormalizedEvent(event: ${this.getSdkName("DamlNormalizedExercisedEvent", runtimeWrapperTypeNames)}): ${choice.exercisedEventTypeName} {`,
198
- ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(event.metadata.templateId);`,
201
+ ` ${choice.exercisedEventTypeName}.assertTemplateIdentity(event.metadata.templateId, event.metadata.packageName);`,
199
202
  ` if (event.choice !== ${JSON.stringify(choice.name)}) {`,
200
203
  ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("choice", \`Expected choice '${choice.name}' but received '\${event.choice}'\`);`,
201
204
  " }",
@@ -204,14 +207,28 @@ export class TemplateBindingEmitter {
204
207
  ` return new ${choice.exercisedEventTypeName}(event.contractId, argument, result, event.consuming, event.metadata);`,
205
208
  " }",
206
209
  "",
207
- " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }): void {",
208
- ` if (identity.packageId !== ${JSON.stringify(this.packageId(binding))} || identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
209
- ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
210
- " }",
211
- " }",
210
+ ...this.emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames),
212
211
  "}",
213
212
  ].join("\n");
214
213
  }
214
+ /**
215
+ * Emits the identity guard for materialization. Module and entity must match exactly; the package is
216
+ * matched by NAME when the event provides one — never by exact package id, because smart contract
217
+ * upgrades give every version its own id while contracts from any version stay materializable.
218
+ */
219
+ emitAssertTemplateIdentity(binding, runtimeWrapperTypeNames) {
220
+ const lines = [
221
+ " private static assertTemplateIdentity(identity: { readonly packageId: string; readonly moduleName: string; readonly entityName: string }, packageName?: string): void {",
222
+ ` if (identity.moduleName !== ${JSON.stringify(this.moduleName(binding))} || identity.entityName !== ${JSON.stringify(this.entityName(binding))}) {`,
223
+ ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected template '${binding.templateIdLiteral}' but received '\${identity.packageId}:\${identity.moduleName}:\${identity.entityName}'\`);`,
224
+ " }",
225
+ ];
226
+ if (binding.packageName !== undefined) {
227
+ lines.push(` if (packageName !== undefined && packageName !== ${JSON.stringify(binding.packageName)}) {`, ` throw new ${this.getSdkName("DamlMaterializationError", runtimeWrapperTypeNames)}("template ID", \`Expected package '${binding.packageName}' but received '\${packageName}' for template '${binding.templateIdLiteral}'\`);`, " }");
228
+ }
229
+ lines.push(" }");
230
+ return lines;
231
+ }
215
232
  emitTemplateDescriptor(binding) {
216
233
  return `{ kind: "record", fields: [${binding.createFields.map((field) => `{ damlLabel: ${JSON.stringify(field.name)}, propertyName: ${JSON.stringify(field.propertyName)}, type: ${this.emitDescriptor(field.type)} }`).join(", ")}] }`;
217
234
  }
@@ -512,8 +529,16 @@ export class TemplateBindingEmitter {
512
529
  }
513
530
  return (hash >>> 0).toString(36).padStart(6, "0").slice(-6);
514
531
  }
515
- packageId(binding) {
516
- return binding.templateIdLiteral.split(":")[0];
532
+ /**
533
+ * The emitted identity literal NEVER contains a package id when the package name is known: package ids
534
+ * are version-specific under smart contract upgrades, while packageName:module:entity is stable. The
535
+ * id-based form remains only for metadata-less invocations (tests driving the emitter directly).
536
+ */
537
+ templateIdLiteral(template) {
538
+ const packageName = this.packageMetadata?.get(template.templateId.packageId)?.packageName;
539
+ return packageName === undefined
540
+ ? this.nameResolver.getTemplateIdLiteral(template)
541
+ : `${packageName}:${template.templateId.moduleName}:${template.templateId.templateName}`;
517
542
  }
518
543
  moduleName(binding) {
519
544
  return binding.templateIdLiteral.split(":")[1];
@@ -19,6 +19,8 @@ export declare class GeneratedTemplateBinding {
19
19
  readonly namespaceAlias: string;
20
20
  readonly className: string;
21
21
  readonly templateIdLiteral: string;
22
+ /** The owning package's name; enables upgrade-aware (name-based) identity checks in emitted code. */
23
+ readonly packageName?: string;
22
24
  readonly path: string;
23
25
  readonly createFieldsTypeName: string;
24
26
  readonly createdEventTypeName: string;
@@ -29,6 +31,7 @@ export declare class GeneratedTemplateBinding {
29
31
  namespaceAlias?: string;
30
32
  className: string;
31
33
  templateIdLiteral: string;
34
+ packageName?: string;
32
35
  path: string;
33
36
  createFieldsTypeName: string;
34
37
  createdEventTypeName: string;
@@ -17,6 +17,8 @@ export class GeneratedTemplateBinding {
17
17
  namespaceAlias;
18
18
  className;
19
19
  templateIdLiteral;
20
+ /** The owning package's name; enables upgrade-aware (name-based) identity checks in emitted code. */
21
+ packageName;
20
22
  path;
21
23
  createFieldsTypeName;
22
24
  createdEventTypeName;
@@ -27,6 +29,7 @@ export class GeneratedTemplateBinding {
27
29
  this.namespaceAlias = init.namespaceAlias ?? init.className;
28
30
  this.className = init.className;
29
31
  this.templateIdLiteral = init.templateIdLiteral;
32
+ this.packageName = init.packageName;
30
33
  this.path = init.path;
31
34
  this.createFieldsTypeName = init.createFieldsTypeName;
32
35
  this.createdEventTypeName = init.createdEventTypeName;
@@ -8,6 +8,8 @@ export type DamlCreatedEventSource = CreatedEvent | GetContractResponse | Active
8
8
  export type DamlExercisedEventSource = ExercisedEvent | ExerciseResult | Event | DamlJsonEventRecord;
9
9
  export type DamlCreatedEventMetadata = {
10
10
  readonly templateId: DamlTypeIdentity;
11
+ /** The template's package NAME — the stable identity across smart-contract-upgrade versions. */
12
+ readonly packageName?: string;
11
13
  readonly offset?: string;
12
14
  readonly nodeId?: number;
13
15
  readonly witnessParties?: readonly string[];
@@ -18,6 +20,8 @@ export type DamlCreatedEventMetadata = {
18
20
  };
19
21
  export type DamlExercisedEventMetadata = {
20
22
  readonly templateId: DamlTypeIdentity;
23
+ /** The template's package NAME — the stable identity across smart-contract-upgrade versions. */
24
+ readonly packageName?: string;
21
25
  readonly offset?: string;
22
26
  readonly nodeId?: number;
23
27
  readonly actingParties?: readonly string[];
@@ -143,6 +143,7 @@ function identityFrom(value, path) {
143
143
  function freezeCreatedMetadata(event, templateId) {
144
144
  return Object.freeze(removeUndefined({
145
145
  templateId,
146
+ packageName: optionalPackageName(event, "created event source"),
146
147
  offset: optionalString(event, ["offset", "createdEventOffset", "created_event_offset"], "offset", "created event source"),
147
148
  nodeId: optionalNodeId(event, ["nodeId", "node_id"], "node ID", "created event source"),
148
149
  witnessParties: optionalStringArray(event, ["witnessParties", "witness_parties", "witnesses"], "witness parties", "created event source"),
@@ -156,6 +157,7 @@ function freezeExercisedMetadata(event, source, templateId) {
156
157
  const transaction = asObject(readProperty(event, ["transaction"]).value) ?? asObject(root?.transaction);
157
158
  return Object.freeze(removeUndefined({
158
159
  templateId,
160
+ packageName: optionalPackageName(event, "exercised event source"),
159
161
  offset: optionalString(event, ["offset"], "offset", "exercised event source")
160
162
  ?? optionalString(transaction ?? {}, ["offset"], "offset", "exercised event source"),
161
163
  nodeId: optionalNodeId(event, ["nodeId", "node_id"], "node ID", "exercised event source"),
@@ -523,6 +525,17 @@ function cloneAndFreeze(value, seen = new WeakMap()) {
523
525
  function removeUndefined(value) {
524
526
  return Object.fromEntries(Object.entries(value).filter(([, field]) => field !== undefined));
525
527
  }
528
+ /** Protobuf-generated events default packageName to ""; treat the default as absent, not invalid. */
529
+ function optionalPackageName(event, context) {
530
+ const { value } = readProperty(event, ["packageName", "package_name"]);
531
+ if (value === undefined || value === null || value === "") {
532
+ return undefined;
533
+ }
534
+ if (typeof value !== "string") {
535
+ throw sourceError(context, "package name must be a string");
536
+ }
537
+ return value;
538
+ }
526
539
  function sourceError(path, detail) {
527
540
  return new DamlMaterializationError(path, detail);
528
541
  }
@@ -91,6 +91,8 @@ cat > "$RUNTIME_DIR/canton.conf" <<EOF
91
91
  canton {
92
92
  parameters { manual-start = no }
93
93
  participants.participant358 {
94
+ # The pruning live spec needs a safe pruning offset promptly on a fresh participant.
95
+ parameters { journal-garbage-collection-delay = 0s }
94
96
  storage {
95
97
  type = postgres
96
98
  config {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.50",
3
+ "version": "0.1.52",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -87,6 +87,7 @@
87
87
  },
88
88
  "scripts": {
89
89
  "build": "node ./scripts/clean-dist.mjs && tsc -p tsconfig.json && tsc -p tsconfig.cjs.json && node ./scripts/write-cjs-package.mjs",
90
+ "prepublishOnly": "npm run build && npm run test:unit",
90
91
  "generate:daml-interface": "node ./dist/daml-interface/cli/daml-interface-cli-main.js",
91
92
  "generate:grpc": "node ./scripts/generate-grpc-bindings.mjs",
92
93
  "examples:check": "npm run build && tsc -p tsconfig.examples.json --noEmit",