diff --git a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/OpenBISExtension.java b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/OpenBISExtension.java
index 3c25a749ec1584519e906bd9566e038dd43fda91..ff3e9eef070a73bf763c5b22689a57a707f2c0f3 100644
--- a/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/OpenBISExtension.java
+++ b/api-openbis-typescript/source/java/ch/ethz/sis/openbis/generic/typescript/OpenBISExtension.java
@@ -27,6 +27,7 @@ import java.lang.reflect.Type;
 import java.lang.reflect.TypeVariable;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -37,9 +38,11 @@ import ch.systemsx.cisd.base.annotation.JsonObject;
 import cz.habarta.typescript.generator.DefaultTypeProcessor;
 import cz.habarta.typescript.generator.Extension;
 import cz.habarta.typescript.generator.Logger;
+import cz.habarta.typescript.generator.TsProperty;
 import cz.habarta.typescript.generator.TsType;
 import cz.habarta.typescript.generator.TypeProcessor;
 import cz.habarta.typescript.generator.TypeScriptGenerator;
+import cz.habarta.typescript.generator.compiler.EnumMemberModel;
 import cz.habarta.typescript.generator.compiler.ModelCompiler;
 import cz.habarta.typescript.generator.compiler.Symbol;
 import cz.habarta.typescript.generator.compiler.SymbolTable;
@@ -47,12 +50,14 @@ import cz.habarta.typescript.generator.compiler.TsModelTransformer;
 import cz.habarta.typescript.generator.emitter.EmitterExtensionFeatures;
 import cz.habarta.typescript.generator.emitter.TsBeanCategory;
 import cz.habarta.typescript.generator.emitter.TsBeanModel;
+import cz.habarta.typescript.generator.emitter.TsEnumModel;
 import cz.habarta.typescript.generator.emitter.TsHelper;
 import cz.habarta.typescript.generator.emitter.TsMethodModel;
 import cz.habarta.typescript.generator.emitter.TsModel;
 import cz.habarta.typescript.generator.emitter.TsModifierFlags;
 import cz.habarta.typescript.generator.emitter.TsParameterModel;
 import cz.habarta.typescript.generator.emitter.TsPropertyModel;
+import cz.habarta.typescript.generator.emitter.TsStringLiteral;
 
 /**
  * This extension for the typescript-generator ({@link cz.habarta.typescript.generator}) Gradle <a href="URL#https://github.com/vojtechhabarta/typescript-generator">plugin</a> adds method and constructor signatures to the generated typescript interfaces.
@@ -141,6 +146,57 @@ public class OpenBISExtension extends Extension
             List<TsPropertyModel> tsBundleProperties = new ArrayList<>();
             List<TsHelper> tsHelpers = new ArrayList<>();
 
+            /*
+
+            For class "X" like:
+
+                @JsonObject("a.b.c.X")
+                class X<T> {
+                    public X(String p){}
+
+                    public String m(int p){}
+                }
+
+            Generate:
+
+                interface XConstructor {
+                    new <T>(p:string): X<T>
+                }
+
+                interface X<T> {
+                    m(p:number): string;
+                }
+
+                type a_b_c_X<T> = X<T>
+
+                bundle {
+                    ...
+                    X:XConstructor,
+                    a_b_c_X:XConstructor
+                    ...
+                }
+
+                export const X:XConstructor
+                export const a_b_c_X:XConstructor
+
+            To be able to do things like:
+
+                var test:openbis.X<string> = new openbis.X<string>("abc")
+                var test:openbis.X<string> = new openbis.a_b_c_X<string>("abc")
+
+                var test:openbis.a_b_c_X<string> = new openbis.X<string>("abc")
+                var test:openbis.a_b_c_X<string> = new openbis.a_b_c_X<string>("abc")
+
+                var bundle:openbis.bundle = ...;
+                var test:openbis.X<string> = new bundle.X<string>("abc")
+                var test:openbis.X<string> = new bundle.a_b_c_X<string>("abc")
+
+                var bundle:openbis.bundle = ...;
+                var test:openbis.a_b_c_X<string> = new bundle.X<string>("abc")
+                var test:openbis.a_b_c_X<string> = new bundle.a_b_c_X<string>("abc")
+
+            */
+
             for (TsBeanModel bean : model.getBeans())
             {
                 List<TsType.GenericVariableType> tsBeanTypeParametersWithBounds =
@@ -281,16 +337,134 @@ public class OpenBISExtension extends Extension
                     }
                 }
 
+                tsBeanMethods.sort(Comparator.comparing(TsMethodModel::getName).thenComparing(m -> m.getParameters().size()));
+
                 tsBeans.add(new TsBeanModel(bean.getOrigin(), bean.getCategory(), bean.isClass(), bean.getName(), tsBeanTypeParametersWithBounds,
                         bean.getParent(), bean.getExtendsList(), bean.getImplementsList(), Collections.emptyList(), bean.getConstructor(),
                         tsBeanMethods, bean.getComments()));
             }
 
+            /*
+
+             For enum "X" like:
+
+                @JsonObject("a.b.c.X")
+                enum X {
+                    VALUE1,
+                    VALUE2
+                }
+
+             Generate:
+
+                 const X = {
+                     VALUE1 : "VALUE1",
+                     VALUE2 : "VALUE2"
+                 } as const
+
+                 const a_b_c_X = {
+                     VALUE1 : "VALUE1",
+                     VALUE2 : "VALUE2"
+                 } as const
+
+                 type X = typeof X[keyof typeof X];
+                 type a_b_c_X = typeof a_b_c_X[keyof typeof a_b_c_X];
+
+                 interface XObject {
+                     VALUE1:X,
+                     VALUE2:X
+                 }
+
+                 bundle {
+                    ...
+                    X:XObject,
+                    a_b_c_X:XObject
+                    ...
+                 }
+
+             To be able to do things like:
+
+                 var test:openbis.X = openbis.X.VALUE1
+                 var test:openbis.X = openbis.a_b_c_X.VALUE1
+                 var test:openbis.X = "VALUE1"
+
+                 var test:openbis.a_b_c_X = openbis.X.VALUE1
+                 var test:openbis.a_b_c_X = openbis.a_b_c_X.VALUE1
+                 var test:openbis.a_b_c_X = "VALUE1"
+
+                 var bundle:openbis.bundle = ...;
+                 var test:openbis.X = bundle.X.VALUE1
+                 var test:openbis.X = bundle.a_b_c_X.VALUE1
+
+                 var bundle:openbis.bundle = ...;
+                 var test:openbis.a_b_c_X = bundle.X.VALUE1
+                 var test:openbis.a_b_c_X = bundle.a_b_c_X.VALUE1
+
+             But not things like:
+
+                 var test:openbis.X = "ILLEGAL_VALUE"
+                 var test:openbis.a_b_c_X = "ILLEGAL_VALUE"
+
+            */
+
+            for (TsEnumModel tsEnum : model.getOriginalStringEnums())
+            {
+                String tsEnumObjectBeanName = tsEnum.getName().getSimpleName() + "Object";
+                List<TsPropertyModel> tsEnumObjectBeanProperties = new ArrayList<>();
+                StringBuilder tsEnumConstProperties = new StringBuilder();
+
+                for (EnumMemberModel tsMember : tsEnum.getMembers())
+                {
+                    tsEnumObjectBeanProperties.add(
+                            new TsPropertyModel(tsMember.getPropertyName(), new TsType.GenericReferenceType(tsEnum.getName()),
+                                    Collections.emptyList(), TsModifierFlags.None, true,
+                                    new TsStringLiteral(tsMember.getPropertyName()), Collections.emptyList()));
+
+                    tsEnumConstProperties.append(tsMember.getPropertyName()).append(" : \"").append(tsMember.getPropertyName()).append("\",\n");
+                }
+
+                tsBeans.add(new TsBeanModel(tsEnum.getOrigin(), tsEnum.getCategory(), false, new Symbol(tsEnumObjectBeanName),
+                        Collections.emptyList(), null, Collections.emptyList(), Collections.emptyList(), tsEnumObjectBeanProperties, null, null,
+                        Collections.emptyList()));
+
+                tsBundleProperties.add(new TsPropertyModel(tsEnum.getName().getSimpleName(),
+                        new TsType.ReferenceType(new Symbol(tsEnumObjectBeanName)), null, true, null));
+
+                tsHelpers.add(new TsHelper(
+                        Collections.singletonList("const " + tsEnum.getName().getSimpleName() + " = {\n" + tsEnumConstProperties + "} as const")));
+                tsHelpers.add(new TsHelper(Collections.singletonList(
+                        "type " + tsEnum.getName().getSimpleName() + " = typeof " + tsEnum.getName().getSimpleName() + "[keyof typeof "
+                                + tsEnum.getName().getSimpleName() + "]")));
+
+                JsonObject tsEnumJsonObject = tsEnum.getOrigin().getAnnotation(JsonObject.class);
+
+                if (tsEnumJsonObject != null)
+                {
+                    String tsEnumJsonName = tsEnumJsonObject.value().replaceAll("\\.", "_");
+
+                    if (!tsEnumJsonName.equals(tsEnum.getName().getSimpleName()))
+                    {
+                        tsBundleProperties.add(new TsPropertyModel(tsEnumJsonName,
+                                new TsType.ReferenceType(new Symbol(tsEnumObjectBeanName)), null, true, null));
+
+                        tsHelpers.add(
+                                new TsHelper(Collections.singletonList("const " + tsEnumJsonName + " = {\n" + tsEnumConstProperties + "} as const")));
+                        tsHelpers.add(new TsHelper(Collections.singletonList(
+                                "type " + tsEnumJsonName + " = typeof " + tsEnumJsonName + "[keyof typeof "
+                                        + tsEnumJsonName + "]")));
+                    }
+                }
+            }
+
+            tsBundleProperties.sort(Comparator.comparing(TsProperty::getName));
+
             tsBeans.add(
                     new TsBeanModel(null, TsBeanCategory.Data, false, new Symbol("bundle"), null, null, null, null, tsBundleProperties, null, null,
                             null));
 
-            return new TsModel(tsBeans, model.getEnums(), model.getOriginalStringEnums(), model.getTypeAliases(), tsHelpers);
+            tsBeans.sort(Comparator.comparing(b -> b.getName().getSimpleName()));
+            tsHelpers.sort(Comparator.comparing(h -> h.getLines().toString()));
+
+            return new TsModel(tsBeans, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), tsHelpers);
         }));
     }