Enketo-core dependency on enketo-express to be eliminated

Hi,
I am using enketo-core in my own application. In my opinion, the dependency of enketo-core on enketo-express is a huge drawback for this great tool. I have used many npm libraries in my development life, I have used them knowing nothing about the source code. The only thing I cared about was how to invoke the functions and what parameters to supply for them. And I think this is the nature of all 3rd party libraries despite their language. Whether it's npm packages or pip packages in python or NuGet packages in C#. But with enketo-core, it's totally different. When it comes to translation, file management, and even some GUI stuff (like alert and confirm dialogs) it is dependent on enketo-express. The problem with this is that when somebody like me is using the library somewhere else where enketo-express is absent he faces problems. As an example, I had no idea how to supply the URLs for the file attachments in the form. I ended up exploring the source code of enketo-core for two whole days to find out that it takes them from a function named getFileUrl() inside enketo-express. It would have been better if I could supply them directly to enketo-core. Something like this:

const enketoForm = new Form("form.or", {
    modelStr: "...",
    instanceStr: "...",
    external: [
        {id: "itemset.csv", xml: "some xml"}
    ],
    // supplying attachment fils' urls directly
    fileUrls: {
        "someid":"someurl.com/someid.jpg"
    }
}, options);

Or any other way, that I don't have to write a module and give it an alias like enketo/file-manager which is known enketo-core. Realy don't know if this is possible because guess it requires too much work but I guess it is necessary.

Thank you

There is no dependency from enketo-core to enketo-express but I agree that the separation of concerns is unexpected. I think it's helpful to think of enketo-core as a partial frontend form engine that can be used in different contexts including in a native app wrapper. That's why it doesn't include things like file system management.

What kind of system are you building? When we decided to use Enketo as part of Central, we wanted to use enketo-core and embed it but we backed out of that and use server-to-server communication with Express. That is awkward in many ways but it gives access to many useful features that are in Enketo Express.

As we've been maintaining Enketo, we've explored shifting the boundaries of the different packages but so far the costs have outweighed the benefits.

CC @eyelidlessness and @martijnr in case anything I said needs correcting!

Just to add a little nuance: enketo-core doesn’t specify enketo-express as a direct dependency, but some of its functionality is limited or stubbed with the expectation that a dependent application will either supplement or replace that functionality, and that functionality is implicitly tightly coupled to enketo-express as a default (or reference) implementation. As @hussainhussaini says, file-manager is one such case.

This functions as a dependency injection, but the mechanism for it is indeed difficult to track down because it depends on module aliases which are resolved at build time (and which are declared in package.json which is fairly unconventional). More importantly because build time in this case applies to the consuming application, not to the published version of enketo-core. I’ve similarly sunk quite a bit of time into this until I understood how it worked and committed that understanding to memory.

I don’t have full context for how each module alias DI case came to be. I definitely conceptually understand why file-manager is an injected dependency—it’s a common area of responsibility for that sort of thing. But I agree that the mechanism for it is not optimal, it’s on my list of things I’d like to rethink eventually, and I broadly agree that a more direct DI mechanism would be an improvement. This would improve code comprehension, but it would also importantly remove the need for consuming applications to perform a very specific build step (or use other tooling complexities like import maps, or their various configuration equivalents).

Without getting too into design weeds, I think an appropriate solution would probably involve reviewing the existing file-manager interface (revising/simplifying as appropriate), and additively allow an implementation satisfying that interface to be supplied to the Form constructor. We could probably do the same for the other module aliases, but I’m more cautious about introducing that much more API surface area without reviewing whether it would be appropriate.

In the meantime, to hopefully unblock @hussainhussaini or anyone else having difficulty with this: if you have a build process already, and your build tool supports module aliasing, you’ll want to include aliases for those enketo/* modules specified in package.json in your build process. I can’t say without looking deeper whether the enketo-express implementations are a reasonable default, but I hope they’ll be close.

1 Like