private string BuildMethodTypescriptStub(string jsFunctionName, string tsParameterTypeDefName,
                                                 string typeScriptOutputParameterTypeName,
                                                 List <string> resultTypes,
                                                 ref ConcurrentDictionary <string, string> customTypeLookupWithTypeScriptDef)
        {
            var tsArguments = "";

            if (!string.IsNullOrEmpty(tsParameterTypeDefName))
            {
                tsArguments = string.Format("_.{0}", tsParameterTypeDefName);
            }
            else
            {
                tsArguments = "{ }";
            }

            if (this.Type.Equals("FUNCTION", StringComparison.OrdinalIgnoreCase))
            {
                if (this.Parameters != null)
                {
                    var resultParm = this.Parameters.FirstOrDefault(p => p.IsResult);

                    if (resultParm == null)
                    {
                        return(null);
                    }

                    var tsMethodStub = string.Format("static {0}(parameters?: {1}): IUDFExecGeneric<{2}, {1}>", jsFunctionName, tsArguments,
                                                     RoutineParameterV2.GetTypescriptTypeFromSql(resultParm.SqlDataType, resultParm.CustomType,
                                                                                                 ref customTypeLookupWithTypeScriptDef));

                    //!tsSchemaLookup[jsSchemaName].Add(tsMethodStub);

                    return(tsMethodStub);
                }
                else
                {
                    return(null);
                    //!SessionLog.Warning($"Cannot generate UDF method stub because the parameters collection is empty. Does the object still exist? Endpoint = {endpoint.Name} ({endpoint.Id}), Routine={r.FullName}");
                }
            }
            else
            {
                var outputParmType = "void";
                // check if there are output parameters present
                if (!string.IsNullOrWhiteSpace(typeScriptOutputParameterTypeName))
                {
                    outputParmType = "_." + typeScriptOutputParameterTypeName;
                }

                if (resultTypes.Count > 0)
                {
                    var cnt = resultTypes.Count;

                    // cap ISprocExecGeneric to max available. Intellisense will not be able to show the additional results sets although they will be present
                    if (cnt > Constants.MAX_NUMBER_OF_RESULTS)
                    {
                        //!this.Log.Warning("Result set metadata is capped at {0} result sets. Intellisense will not show more than that although they will be accessible at run-time.", Constants.MAX_NUMBER_OF_RESULTS);
                        cnt = Constants.MAX_NUMBER_OF_RESULTS;
                    }

                    var tsMethodStub = string.Format("static {0}(parameters?: {1}): ISprocExecGeneric{3}<{4}, {2}, {1}>", jsFunctionName, tsArguments, string.Join(",", resultTypes.Take(Constants.MAX_NUMBER_OF_RESULTS).Select(rt => "_." + rt)), cnt, outputParmType);

                    //!tsSchemaLookup[jsSchemaName].Add(tsMethodStub);

                    return(tsMethodStub);
                }
                else
                { // NO RESULTSET
                    var tsMethodStub = string.Format("static {0}(parameters?: {1}): ISprocExecGeneric0<{2}, {1}>", jsFunctionName, tsArguments, outputParmType);

                    //!tsSchemaLookup[jsSchemaName].Add(tsMethodStub);
                    return(tsMethodStub);
                }
            }
        }
        private (List <string>, string) BuildResultSetTypescriptDefs(string jsSafeNamespace, string jsSchemaName, string jsFunctionName, ref ConcurrentDictionary <string, string> customTypeLookupWithTypeScriptDef)
        {
            var resultTypes = new List <string>();
            var typeScriptParameterAndResultTypesSB = new StringBuilder();

            int resultIx = 0;

            foreach (var kv in this.ResultSetMetadata)
            {
                var columnCounter = new SortedList <string, int>();

                // e.g. Icev0_Accpac_ItemMakeMappingGetListResult0
                var tsResultTypeDefName = string.Format("{0}_{1}_{2}Result{3}", jsSafeNamespace, jsSchemaName, jsFunctionName, resultIx++);

                var tsColumnDefs = new List <string>();

                int nonNameColIx = 0;

                //kv.Value = Table[0...N]
                foreach (var col in kv.Value)
                {
                    var colName    = col.ColumnName.ToString();
                    var dbDataType = col.DbDataType.ToString();

                    if (string.IsNullOrWhiteSpace(colName))
                    {
                        colName = string.Format("Unknown{0:000}", ++nonNameColIx);
                    }

                    var originalColName = colName;

                    // handle duplicate column names
                    if (columnCounter.ContainsKey(colName))
                    {
                        //!this.Log.Warning("Duplicate column name encountered for column '{0}' on routine {1}", colName, r.FullName);
                        var n = columnCounter[colName];
                        colName += ++n; // increase by one to give a new unique name
                        columnCounter[originalColName] = n;
                    }
                    else
                    {
                        columnCounter.Add(colName, 1);
                    }

                    colName = JsFileGenerator.QuoteColumnNameIfNecessary(colName);

                    // a bit backwards but gets the job done
                    var typeScriptDataType = RoutineParameterV2.GetTypescriptTypeFromSql(dbDataType, null, ref customTypeLookupWithTypeScriptDef);

                    // Col: TypeScript DataType
                    var ln = string.Format("{0}: {1}", colName, typeScriptDataType);
                    tsColumnDefs.Add(ln);
                }

                // TODO: What to return and what to persist?

                var typeScriptResultDef = string.Format("class {0} {{ {1} }}", tsResultTypeDefName, string.Join("; ", tsColumnDefs));

                resultTypes.Add(tsResultTypeDefName);
                typeScriptParameterAndResultTypesSB.AppendLine(typeScriptResultDef);
            } // foreach Result Set

            return(resultTypes, typeScriptParameterAndResultTypesSB.ToString());
        }
        // compute & persist some of the TypeScript definitions for reuse during .js generation
        public void PrecalculateJsGenerationValues(Endpoint endpoint)
        {
            try
            {
                if (this.IsDeleted)
                {
                    this.TypescriptOutputParameterTypeDefinition = this.TypescriptParameterTypeDefinition = this.TypescriptMethodStub = this.TypescriptResultSetDefinitions = null;
                    return;
                }

                // TODO: Figure out what to do with JsNamespace CASING. Sometimes case changes in connection string and that breaks all existing code
                string jsNamespace = null;//endpoint.JsNamespace;
                if (string.IsNullOrWhiteSpace(jsNamespace))
                {
                    jsNamespace = endpoint.MetadataConnection.InitialCatalog;
                }

                if (string.IsNullOrWhiteSpace(jsNamespace))
                {
                    jsNamespace = "NotSet";
                }

                var jsSafeNamespace = JsFileGenerator.MakeNameJsSafe(jsNamespace);

                var jsSchemaName   = JsFileGenerator.MakeNameJsSafe(this.Schema);
                var jsFunctionName = JsFileGenerator.MakeNameJsSafe(this.Routine);

                var customTypeLookupWithTypeScriptDef = endpoint.CustomTypeLookupWithTypeScriptDef;

                var paramTypeScriptDefList = new List <string>();

                this.Parameters.Where(p => !string.IsNullOrEmpty(p.Name)).ToList().ForEach(p =>
                {
                    var tsType = RoutineParameterV2.GetTypescriptTypeFromSql(p.SqlDataType, p.CustomType, ref customTypeLookupWithTypeScriptDef);
                    var name   = p.Name.TrimStart('@');

                    if (JsFileGenerator.StartsWithNum(name))
                    {
                        name = "$" + name;
                    }

                    var tsDefinition = $"{name}{(p.HasDefault ? "?" : "")}: {tsType}";

                    paramTypeScriptDefList.Add(tsDefinition);
                });

                var tsParameterTypeDefName = $"{ jsSafeNamespace }_{ jsSchemaName }_{ jsFunctionName }Parameters";

                // TypeScript input parameter definitions
                this.TypescriptParameterTypeDefinition = $"type {tsParameterTypeDefName} = {{ { string.Join(", ", paramTypeScriptDefList) } }}";

                // TypeScript output parameter definitions
                var tsOutputParamDefs = (from p in this.Parameters
                                         where !string.IsNullOrEmpty(p.Name) &&
                                         !p.IsResult &&
                                         p.IsOutput
                                         select string.Format("{0}?: {1}", JsFileGenerator.StartsWithNum(p.Name.TrimStart('@')) ? (/*"_" + */ p.Name.TrimStart('@')) : p.Name.TrimStart('@')
                                                              , RoutineParameterV2.GetTypescriptTypeFromSql(p.SqlDataType, p.CustomType, ref customTypeLookupWithTypeScriptDef))).ToList();

                string typeScriptOutputParameterTypeName = null;

                if (tsOutputParamDefs.Count > 0)
                {
                    typeScriptOutputParameterTypeName = string.Format("{0}_{1}_{2}OutputParms", jsSafeNamespace, jsSchemaName, jsFunctionName);

                    // TypeScript OUPUT parameter type definition
                    this.TypescriptOutputParameterTypeDefinition = string.Format("type {0} = {{ {1} }}", typeScriptOutputParameterTypeName, string.Join(", ", tsOutputParamDefs));
                }

                var resultTypes = new List <string>();

                if (this.ResultSetMetadata != null)
                {
                    (resultTypes, this.TypescriptResultSetDefinitions) = BuildResultSetTypescriptDefs(jsSafeNamespace, jsSchemaName, jsFunctionName, ref customTypeLookupWithTypeScriptDef);
                }

                this.TypescriptMethodStub = BuildMethodTypescriptStub(jsFunctionName, tsParameterTypeDefName, typeScriptOutputParameterTypeName, resultTypes, ref customTypeLookupWithTypeScriptDef);
            }
            catch (Exception ex)
            {
                ExceptionLogger.LogExceptionThrottled(ex, "PrecalculateJsGenerationValues", 2, $"{endpoint.Pedigree} - {this.Schema}.{this.Routine}");
            }
        }