@distrohelena/canton-typescript-sdk 0.1.51 → 0.1.53

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
  }
@@ -98,6 +98,19 @@ es256_runtime_dir() {
98
98
  printf '%s\n' "${START_LOCAL_ES256_RUNTIME_DIR:-$REPO_ROOT/.generated/localnet-es256}"
99
99
  }
100
100
 
101
+ resolve_no_auth_enabled() {
102
+ local value="${LOCALNET_NO_AUTH:-0}"
103
+ if [[ "$value" != "0" && "$value" != "1" ]]; then
104
+ echo "LOCALNET_NO_AUTH must be 0 or 1." >&2
105
+ return 1
106
+ fi
107
+ printf '%s\n' "$value"
108
+ }
109
+
110
+ no_auth_runtime_dir() {
111
+ printf '%s\n' "${START_LOCAL_NO_AUTH_RUNTIME_DIR:-$REPO_ROOT/.generated/localnet-no-auth}"
112
+ }
113
+
101
114
  resolve_tls_enabled() {
102
115
  local value="${LOCALNET_TLS:-0}"
103
116
  if [[ "$value" != "0" && "$value" != "1" ]]; then
@@ -447,6 +460,66 @@ EOF
447
460
  export LOCALNET_ES256_TOKEN_PATH="$token_path"
448
461
  }
449
462
 
463
+ prepare_no_auth_runtime_files() {
464
+ if [[ "$(resolve_no_auth_enabled)" != "1" ]]; then
465
+ return 0
466
+ fi
467
+
468
+ local runtime_dir
469
+ runtime_dir="$(no_auth_runtime_dir)"
470
+ local compose_file="$runtime_dir/compose-no-auth.yaml"
471
+ local canton_config_file="$runtime_dir/canton-no-auth.conf"
472
+ mkdir -p "$runtime_dir"
473
+
474
+ cat > "$canton_config_file" <<EOF
475
+ include file("/app/base-app.conf")
476
+
477
+ $(if [[ "$(resolve_tls_enabled)" == "1" ]]; then printf 'include file("/app/localnet-tls.conf")\n'; fi)
478
+
479
+ EOF
480
+ local participant profile
481
+ for participant in app-provider app-user sv; do
482
+ case "$participant" in
483
+ app-provider) profile="$APP_PROVIDER_PROFILE" ;;
484
+ app-user) profile="$APP_USER_PROFILE" ;;
485
+ sv) profile="$SV_PROFILE" ;;
486
+ esac
487
+ if [[ "$profile" != "off" ]]; then
488
+ cat >> "$canton_config_file" <<EOF
489
+ canton.participants.${participant}.ledger-api.auth-services = []
490
+ EOF
491
+ fi
492
+ done
493
+
494
+ cat > "$compose_file" <<EOF
495
+ services:
496
+ canton:
497
+ volumes:
498
+ - "${LOCALNET_DIR}/conf/canton/app.conf:/app/base-app.conf:ro"
499
+ - "$canton_config_file:/app/app.conf:ro"
500
+ pqs-app-provider:
501
+ environment:
502
+ SCRIBE_SOURCE_LEDGER_AUTH: NoAuth
503
+ pqs-app-user:
504
+ environment:
505
+ SCRIBE_SOURCE_LEDGER_AUTH: NoAuth
506
+ pqs-sv:
507
+ environment:
508
+ SCRIBE_SOURCE_LEDGER_AUTH: NoAuth
509
+ EOF
510
+ export LOCALNET_NO_AUTH_COMPOSE_FILE="$compose_file"
511
+ }
512
+
513
+ append_no_auth_args() {
514
+ local -n compose_args_ref="$1"
515
+ if [[ "$(resolve_no_auth_enabled)" == "1" ]]; then
516
+ local compose_file="$(no_auth_runtime_dir)/compose-no-auth.yaml"
517
+ if [[ -f "$compose_file" ]]; then
518
+ compose_args_ref+=( -f "$compose_file" )
519
+ fi
520
+ fi
521
+ }
522
+
450
523
  append_es256_args() {
451
524
  local -n compose_args_ref="$1"
452
525
  if [[ "$(resolve_es256_enabled)" == "1" ]]; then
@@ -485,6 +558,23 @@ docker_compose() {
485
558
  "${DOCKER_COMPOSE_CMD[@]}" "$@"
486
559
  }
487
560
 
561
+ resolve_extra_participant_auth() {
562
+ local index="$1"
563
+ local var="EXTRA_PARTICIPANT_${index}_AUTH"
564
+ local value="${!var:-}"
565
+ if [[ -z "$value" ]]; then
566
+ if [[ "$(resolve_no_auth_enabled)" == "1" ]]; then value="none"; else value="jwt"; fi
567
+ fi
568
+ case "$value" in
569
+ jwt|none) ;;
570
+ *)
571
+ echo "EXTRA_PARTICIPANT_${index}_AUTH must be 'jwt' or 'none'." >&2
572
+ return 1
573
+ ;;
574
+ esac
575
+ printf '%s\n' "$value"
576
+ }
577
+
488
578
  resolve_extra_participants() {
489
579
  local value="${EXTRA_PARTICIPANTS:-0}"
490
580
  if [[ ! "$value" =~ ^[0-9]+$ ]]; then
@@ -684,6 +774,16 @@ EOF
684
774
  done
685
775
  fi
686
776
 
777
+ local no_auth_index
778
+ for ((no_auth_index = 1; no_auth_index <= count; no_auth_index++)); do
779
+ if [[ "$(resolve_extra_participant_auth "$no_auth_index")" == "none" ]]; then
780
+ cat >> "$canton_config_file" <<EOF
781
+ canton.participants.extra-${no_auth_index}.ledger-api.auth-services = []
782
+
783
+ EOF
784
+ fi
785
+ done
786
+
687
787
  if [[ "$(resolve_tls_enabled)" == "1" ]]; then
688
788
  local index
689
789
  for ((index = 1; index <= count; index++)); do
@@ -842,7 +942,7 @@ EOF
842
942
  environment:
843
943
  SCRIBE_SOURCE_LEDGER_HOST: canton
844
944
  SCRIBE_SOURCE_LEDGER_PORT: \${EXTRA_PARTICIPANT_${index}_LEDGER_API_PORT}
845
- SCRIBE_SOURCE_LEDGER_AUTH: OAuth
945
+ SCRIBE_SOURCE_LEDGER_AUTH: $(if [[ "$(resolve_extra_participant_auth "$index")" == "none" ]]; then printf 'NoAuth'; else printf 'OAuth'; fi)
846
946
  SCRIBE_TARGET_POSTGRES_HOST: postgres-pqs-extra-${index}
847
947
  SCRIBE_TARGET_POSTGRES_PORT: 5432
848
948
  SCRIBE_TARGET_POSTGRES_DATABASE: \${EXTRA_PQS_${index}_POSTGRES_DB}
@@ -1218,8 +1318,9 @@ start_ledger_stack() {
1218
1318
  load_localnet_common_env "$localnet_dir/env/common.env"
1219
1319
  prepare_tls_runtime_files
1220
1320
  prepare_es256_runtime_files
1321
+ prepare_no_auth_runtime_files
1221
1322
  local extra_participants
1222
- extra_participants="$(resolve_extra_participants)"
1323
+ extra_participants="$(resolve_extra_participants)"
1223
1324
  if (( extra_participants > 0 )) && [[ "$auth_mode" != "shared-secret" ]]; then
1224
1325
  echo "EXTRA_PARTICIPANTS with extra PQS currently supports AUTH_MODE=shared-secret only." >&2
1225
1326
  return 1
@@ -1252,6 +1353,7 @@ start_ledger_stack() {
1252
1353
  append_extra_participant_args "$extra_participants" "$auth_mode" compose_args
1253
1354
  append_tls_args compose_args
1254
1355
  append_es256_args compose_args
1356
+ append_no_auth_args compose_args
1255
1357
  docker_compose "${compose_args[@]}" down -v --remove-orphans
1256
1358
  mapfile -t startup_services < <(prerequisite_services "$auth_mode")
1257
1359
  mapfile -t followup_services < <(dependent_services)
@@ -1289,17 +1391,22 @@ load_repo_root_env
1289
1391
  QUICKSTART_DIR="$(resolve_quickstart_dir)"
1290
1392
  cd "$QUICKSTART_DIR"
1291
1393
 
1394
+ if [[ "$(resolve_no_auth_enabled)" == "1" && "$(resolve_es256_enabled)" == "1" ]]; then
1395
+ echo "LOCALNET_NO_AUTH=1 conflicts with LOCALNET_ES256_JWT=1: pick one auth mode." >&2
1396
+ exit 1
1397
+ fi
1398
+
1292
1399
  if [[ ! -f .env.local ]]; then
1293
1400
  echo ".env.local not found. Bootstrapping Quickstart with 'make setup'."
1294
1401
  make setup
1295
1402
  fi
1296
1403
 
1297
- if [[ "$(resolve_es256_enabled)" != "1" && "$(resolve_tls_enabled)" != "1" ]] && make_target_exists start-local-ledger; then
1404
+ if [[ "$(resolve_es256_enabled)" != "1" && "$(resolve_tls_enabled)" != "1" && "$(resolve_no_auth_enabled)" != "1" ]] && make_target_exists start-local-ledger; then
1298
1405
  make start-local-ledger
1299
1406
  else
1300
1407
  auth_mode="$(read_env_value AUTH_MODE || true)"
1301
1408
  auth_mode="${auth_mode:-shared-secret}"
1302
- if [[ "$(resolve_es256_enabled)" == "1" || "$(resolve_tls_enabled)" == "1" ]]; then
1409
+ if [[ "$(resolve_es256_enabled)" == "1" || "$(resolve_tls_enabled)" == "1" || "$(resolve_no_auth_enabled)" == "1" ]]; then
1303
1410
  echo "Generated localnet transport overlay enabled. Starting ledger-only compose stack directly."
1304
1411
  else
1305
1412
  echo "start-local-ledger target not found. Starting ledger-only compose stack directly."
@@ -92,6 +92,19 @@ es256_runtime_dir() {
92
92
  printf '%s\n' "${START_LOCAL_ES256_RUNTIME_DIR:-$REPO_ROOT/.generated/localnet-es256}"
93
93
  }
94
94
 
95
+ resolve_no_auth_enabled() {
96
+ local value="${LOCALNET_NO_AUTH:-0}"
97
+ if [[ "$value" != "0" && "$value" != "1" ]]; then
98
+ echo "LOCALNET_NO_AUTH must be 0 or 1." >&2
99
+ return 1
100
+ fi
101
+ printf '%s\n' "$value"
102
+ }
103
+
104
+ no_auth_runtime_dir() {
105
+ printf '%s\n' "${START_LOCAL_NO_AUTH_RUNTIME_DIR:-$REPO_ROOT/.generated/localnet-no-auth}"
106
+ }
107
+
95
108
  resolve_tls_enabled() {
96
109
  local value="${LOCALNET_TLS:-0}"
97
110
  if [[ "$value" != "0" && "$value" != "1" ]]; then
@@ -125,6 +138,16 @@ append_tls_args() {
125
138
  fi
126
139
  }
127
140
 
141
+ append_no_auth_args() {
142
+ local -n compose_args_ref="$1"
143
+ if [[ "$(resolve_no_auth_enabled)" == "1" ]]; then
144
+ local compose_file="$(no_auth_runtime_dir)/compose-no-auth.yaml"
145
+ if [[ -f "$compose_file" ]]; then
146
+ compose_args_ref+=( -f "$compose_file" )
147
+ fi
148
+ fi
149
+ }
150
+
128
151
  resolve_docker_compose_cmd() {
129
152
  if (( ${#DOCKER_COMPOSE_CMD[@]} > 0 )); then
130
153
  return 0
@@ -203,6 +226,7 @@ stop_ledger_stack() {
203
226
  append_existing_extra_participant_args compose_args
204
227
  append_tls_args compose_args
205
228
  append_es256_args compose_args
229
+ append_no_auth_args compose_args
206
230
  docker_compose "${compose_args[@]}" down -v --remove-orphans
207
231
  }
208
232
 
@@ -215,7 +239,7 @@ if [[ ! -f .env.local ]]; then
215
239
  exit 0
216
240
  fi
217
241
 
218
- if [[ "$(resolve_es256_enabled)" != "1" && "$(resolve_tls_enabled)" != "1" ]] && make_target_exists stop-local-ledger; then
242
+ if [[ "$(resolve_es256_enabled)" != "1" && "$(resolve_tls_enabled)" != "1" && "$(resolve_no_auth_enabled)" != "1" ]] && make_target_exists stop-local-ledger; then
219
243
  make stop-local-ledger
220
244
  else
221
245
  auth_mode="$(read_env_value AUTH_MODE || true)"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@distrohelena/canton-typescript-sdk",
3
- "version": "0.1.51",
3
+ "version": "0.1.53",
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",