public static void GenerateJsFileV2(string source, Endpoint endpoint, JsFile jsFile, Dictionary <string, ChangeDescriptor> fullChangeSet = null, bool rulesChanged = false) { var generateMetric = new Performance.ExecutionBase("GenerateJsFileV2"); var noChanges = false; try { // TODO: Figure out out casing on this property string jsNamespace = null;//endpoint.JsNamespace; if (string.IsNullOrWhiteSpace(jsNamespace)) { jsNamespace = endpoint.MetadataConnection.InitialCatalog; } var jsSafeNamespace = MakeNameJsSafe(jsNamespace); var routineContainerTemplate = WorkSpawner.TEMPLATE_RoutineContainer; var routineTemplate = WorkSpawner.TEMPLATE_Routine; var typescriptDefinitionsContainer = WorkSpawner.TEMPLATE_TypescriptDefinitions; endpoint.ApplyRules(jsFile); var includedRoutines = (from row in endpoint.CachedRoutines where !row.IsDeleted && (row.RuleInstructions[jsFile]?.Included ?? false == true) orderby row.FullName select row).ToList(); List <KeyValuePair <string, ChangeDescriptor> > changesInFile = null; if (fullChangeSet != null) { changesInFile = fullChangeSet.Where(change => includedRoutines.Count(inc => inc.FullName.Equals(change.Key, StringComparison.OrdinalIgnoreCase)) > 0).ToList(); // TODO: Consider if this is a good idea? // If we can reasonably say that there are no changes to routines that this JsFile cares about, why regenerate this file and why give it a new Version if (changesInFile.Count == 0) { noChanges = true; return; } } var jsSchemaLookupForJsFunctions = new Dictionary <string, List <string> /*Routine defs*/>(); var tsSchemaLookup = new Dictionary <string, List <string> /*Routine defs*/>(); var typeScriptParameterAndResultTypesSB = new StringBuilder(); var serverMethodPlugins = PluginLoader.Instance.PluginAssemblies .SelectMany(pa => pa.Plugins) .Where(p => p.Type == PluginType.ServerMethod && endpoint.Application.IsPluginIncluded(p.Guid.ToString())); var uniqueSchemas = new List <string>(); var mainLoopMetric = generateMetric.BeginChildStage("Main loop"); includedRoutines.ForEach(r => { try { if (r.TypescriptMethodStub == null) { r.PrecalculateJsGenerationValues(endpoint); } var jsSchemaName = JsFileGenerator.MakeNameJsSafe(r.Schema); var jsFunctionName = JsFileGenerator.MakeNameJsSafe(r.Routine); if (!jsSchemaLookupForJsFunctions.ContainsKey(jsSchemaName)) { jsSchemaLookupForJsFunctions.Add(jsSchemaName, new List <string>()); } if (!tsSchemaLookup.ContainsKey(jsSchemaName)) { tsSchemaLookup.Add(jsSchemaName, new List <string>()); } if (!uniqueSchemas.Contains(r.Schema)) { uniqueSchemas.Add(r.Schema); } var schemaIx = uniqueSchemas.IndexOf(r.Schema); // .js { var jsFunctionDefLine = routineTemplate.Replace("<<FUNC_NAME>>", jsFunctionName).Replace("<<SCHEMA_IX>>", schemaIx.ToString()).Replace("<<ROUTINE>>", r.Routine); if (r.Type.Equals(string.Intern("PROCEDURE"), StringComparison.OrdinalIgnoreCase)) { jsFunctionDefLine = jsFunctionDefLine.Replace("<<CLASS>>", "S"); } else { jsFunctionDefLine = jsFunctionDefLine.Replace("<<CLASS>>", "U"); } jsSchemaLookupForJsFunctions[jsSchemaName].Add(jsFunctionDefLine); } // .tsd { typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptParameterTypeDefinition); typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptOutputParameterTypeDefinition); typeScriptParameterAndResultTypesSB.AppendLine(r.TypescriptResultSetDefinitions); tsSchemaLookup[jsSchemaName].Add(r.TypescriptMethodStub); } } catch (Exception ex) { SessionLog.Exception(ex); // TODO: quit whole process } }); mainLoopMetric.End(); var finalSBMetric = generateMetric.BeginChildStage("Final SB"); var schemaAndRoutineDefs = string.Join("\r\n", jsSchemaLookupForJsFunctions.Select(s => "\tx." + s.Key + " = {\r\n\t\t" + string.Join(",\r\n\t\t", s.Value.ToArray()) + "\r\n\t}\r\n").ToArray()); var tsSchemaAndRoutineDefs = string.Join("\r\n", tsSchemaLookup.Select(s => "\t\tclass " + s.Key + " {\r\n" + string.Join(";\r\n", s.Value.ToArray()) + "\r\n\t\t}\r\n").ToArray()); var finalSB = new StringBuilder(routineContainerTemplate); jsFile.IncrementVersion(); // record changes against new version if (changesInFile != null && changesInFile.Count > 0) { JsFileChangesTracker.Instance.AddUpdate(endpoint, jsFile, changesInFile.Select(kv => kv.Value).ToList()); } if (rulesChanged) { JsFileChangesTracker.Instance.AddUpdate(endpoint, jsFile, new List <ChangeDescriptor> { ChangeDescriptor.Create("System", "One or more rules changed.") }); } finalSB.Replace("<<DATE>>", DateTime.Now.ToString("dd MMM yyyy, HH:mm")) .Replace("<<FILE_VERSION>>", jsFile.Version.ToString()) .Replace("<<SERVER_NAME>>", Environment.MachineName) .Replace("<<ENDPOINT>>", endpoint.Pedigree) .Replace("<<UNIQUE_SCHEMAS>>", string.Join(',', uniqueSchemas.Select(k => $"'{k}'"))) .Replace("<<Catalog>>", jsSafeNamespace) .Replace("<<ROUTINES>>", schemaAndRoutineDefs) ; var finalTypeScriptSB = new StringBuilder(); finalTypeScriptSB = finalTypeScriptSB.Append(typescriptDefinitionsContainer); // Custom/User types if (endpoint.CustomTypeLookupWithTypeScriptDef.Count > 0) { var customTSD = from kv in endpoint.CustomTypeLookupWithTypeScriptDef select $"\t\ttype {kv.Key} = {kv.Value};"; typeScriptParameterAndResultTypesSB.Insert(0, string.Join("\r\n", customTSD)); } var resultAndParameterTypes = typeScriptParameterAndResultTypesSB.ToString(); finalTypeScriptSB.Replace("<<DATE>>", DateTime.Now.ToString("dd MMM yyyy, HH:mm")) .Replace("<<FILE_VERSION>>", jsFile.Version.ToString()) .Replace("<<SERVER_NAME>>", Environment.MachineName) .Replace("<<Catalog>>", jsSafeNamespace) .Replace("<<ResultAndParameterTypes>>", resultAndParameterTypes) .Replace("<<MethodsStubs>>", tsSchemaAndRoutineDefs) ; finalSBMetric.End(); var toStringMetric = generateMetric.BeginChildStage("ToString"); var typescriptDefinitionsOutput = finalTypeScriptSB.ToString(); var finalOutput = finalSB.ToString(); toStringMetric.End(); var filePath = endpoint.OutputFilePath(jsFile); var minfiedFilePath = endpoint.MinifiedOutputFilePath(jsFile); var tsTypingsFilePath = endpoint.OutputTypeScriptTypingsFilePath(jsFile); var minifyMetric = generateMetric.BeginChildStage("Minify"); var minifiedSource = Uglify.Js(finalOutput /*, { }*/).Code; minifyMetric.End(); if (!Directory.Exists(endpoint.OutputDir)) { Directory.CreateDirectory(endpoint.OutputDir); } var fileOutputMetric = generateMetric.BeginChildStage("Write"); var jsFinalBytes = System.Text.Encoding.UTF8.GetBytes(finalOutput); var jsFinalMinifiedBytes = System.Text.Encoding.UTF8.GetBytes(minifiedSource); jsFile.ETag = Controllers.PublicController.ComputeETag(jsFinalBytes); jsFile.ETagMinified = Controllers.PublicController.ComputeETag(jsFinalMinifiedBytes); File.WriteAllText(filePath, finalOutput); File.WriteAllText(minfiedFilePath, minifiedSource); File.WriteAllText(tsTypingsFilePath, typescriptDefinitionsOutput); fileOutputMetric.End(); } finally { generateMetric.End(); SessionLog.InfoToFileOnly($"{endpoint.Pedigree.PadRight(25, ' ')} - {generateMetric.DurationInMS.ToString().PadLeft(4)} ms {jsFile.Filename.PadRight(20)} (source={source};rulesChanged={rulesChanged};changes={!noChanges}); {generateMetric.ChildDurationsSingleLine()}"); } }