BaristaLabs / BaristaCore

BaristaCore is a framework for providing a serverless platform using ChakraCore and .Net Core
MIT License
28 stars 3 forks source link

JsFunction set as property on JsObject is undefined when introspecting, but callable #59

Open JesperTreetop opened 6 years ago

JesperTreetop commented 6 years ago

I'm trying to get started with BaristaCore and it is a bumpy ride due to the lack of documentation. I could file a few issues, but I'm starting with this.

I created a new .NET Core 2.0 command line project, added the reference to the BaristaCore package (version 1.0.2-beta03) and used the following:

using System;
using BaristaLabs.BaristaCore;
using BaristaLabs.BaristaCore.Extensions;
using BaristaLabs.BaristaCore.ModuleLoaders;
using Microsoft.Extensions.DependencyInjection;

namespace TestBaristaRepro
{
    class Program
    {
        static void Main(string[] args)
        {
            var serviceCollection = new ServiceCollection();
            serviceCollection.AddBaristaCore();
            var moduleLoader = new InMemoryModuleLoader();
            moduleLoader.RegisterModule(new Test1Module());
            moduleLoader.RegisterModule(new Test2Module());
            serviceCollection.AddSingleton<IBaristaModuleLoader>(moduleLoader);

            var provider = serviceCollection.BuildServiceProvider();
            var baristaRuntimeFactory = provider.GetRequiredService<IBaristaRuntimeFactory>();

            using (var rt = baristaRuntimeFactory.CreateRuntime())
            using (var ctx = rt.CreateContext()) {
                var script = @"var objs = [{ name: 'm1', obj: m1 }, { name: 'm2', obj: m2 }];

var summary = """";
summary += ""result of manually running m1.test(1, 2): "" + m1.test(1, 2) + ""\n"";
//summary += ""result of manually running m2.test(1, 2): "" + m2.test(1, 2) + ""\n"";
for (let obj of objs) {
    var x = obj.obj;
    var name = obj.name;
    summary += ""#"" + name + ""#\n"" + ""= (toString: "" + (x === null ? ""NULL"" : (x === undefined ? ""UNDEFINED"" : ""'"" + x.toString() + ""'"")) + "" (JSON: "" + JSON.stringify(x) + "")\n"";
    for (var key in x) {
        summary += ""  "" + key + "": "" + global[key] + ""\n"";
    }
}
return summary;";

                using (ctx.Scope()) {
                    var moduleResult = ctx.EvaluateModule("import m1 from 'first-module'; import m2 from 'second-module'; export default function() {" + script + "}");
                    if (moduleResult is JsFunction fun) {
                        var resultOfCalling = fun.Call();
                        if (resultOfCalling is JsString str) {
                            var res = str.ToString();
                            Console.WriteLine("result is: ");
                            Console.WriteLine(res);
                        }
                    }
                }
            }

        }

        [BaristaModule("first-module", "Module one")]
        public class Test1Module : IBaristaModule
        {
            public JsValue ExportDefault(BaristaContext context, BaristaModuleRecord referencingModule) {
                var obj = context.CreateObject();
                var fnAdd = context.CreateFunction(new Func<JsObject, int, int, int>((thisObj, a, b) =>
                {
                    return a + b;
                }));
                obj.SetProperty("test", fnAdd);
                return obj;
            }
        }

        [BaristaModule("second-module", "Module two")]
        public class Test2Module : IBaristaModule
        {
            public JsValue ExportDefault(BaristaContext context, BaristaModuleRecord referencingModule) {
                return context.CreateExternalObject(new Test2());
            }
        }

        [BaristaObject("test2")]
        public class Test2 {
            [BaristaProperty("test", Configurable = false, Enumerable = true)]
            public int Test(int a, int b) {
                return a + b;
            }
        }
    }
}

This uses two different modules to make two different functions available. The code outputs the following:

result is:
result of manually running m1.test(1, 2): 3
#m1#
= (toString: '[object Object]' (JSON: {})
  test: undefined
#m2#
= (toString: '[object Object]' (JSON: {})

In other words, on the first module, test is callable, and it is visible as a property (so enumerable), but its value is undefined instead of a function. On the second module, test is neither callable nor visible.

So the question being: is there a way for the real value to be visible in case of the first module, and what do I need to do to make the second module work?

(Additionally, outside the scope of this issue, some background: I am trying to use BaristaCore to provide Javascript execution to make a small piece of business logic an interchangeable script. It would have a number of either modules or objects available that would return domain-specific information or allow it to perform the purpose of the script.

I try to follow along as well as I can in how to put this together, but the documentation is slim and differs from the code (in the documentation, the IBaristaModule interface is asynchronous and in the code it's not, and I don't even know which behavior is the outdated one and will be going away). I have been working with Jurassic previously and am weighing BaristaCore and using the C# wrappers for ChakraCore directly.

I don't quite understand how you're meant to just evaluate code without wrapping it inside a module which exports a default function that you then call. That you had to use Scope() took a while to figure out as well.)

JesperTreetop commented 6 years ago

I am sorry, I am a complete idiot - I forgot to change one variable of my test script - global[key] instead of x[key] will of course not return anything. (Welcome to JavaScript! 😅)

So the issue as defined is invalid, but I wouldn't mind your thoughts on the other things mentioned, like the second module.

Oceanswave commented 6 years ago

Sorry for the lack of response -- on a bit of a hiatus from this project until .net core 2.1 and ChakraCore 1.9.x is released in order to get some of the improvements in runtime performance out of both those platforms.

I think the difference in the 2nd module is that you're returning a JsObject that has a function property named 'test' that the code isn't calling.