Avoiding MLE/JavaScript pitfalls: the requested module does not provide an export named ‘default’

Oracle Database 23ai saw the introduction of JavaScript as an additional language for writing server-side code. As with PL/SQL, it is possible to separate JavaScript code into separate units. This concept is similar to PL/SQL where you group code logic into packages. In JavaScript, these code units are called modules. Just as with PL/SQL, you can expose functionality in module A that can be consumed by other modules.

This is typically done in more complex database applications, but it is more commonly done when importing a JavaScript module from a community repository (like NPM) or one of the many content delivery networks.

Note: When reading the documentation, you might occasionally encounter the term MLE module. MLE, in this context, refers to Multilingual Engine, which has been the underlying technology powering JavaScript in Oracle Database since release 21c. For the purpose of this article, the terms MLE module and JavaScript module can be used interchangeably.

The JavaScript engine in Oracle Database defaults to ECMAScript module syntax. That means you’ll have to use the import and export keywords respectively. Beginning with Oracle Database 23ai, CommonJS’s require is deprecated and kept for backward compatibility only.

To be fair, the issue discussed in this article isn’t MLE/Oracle specific, it’s purely JavaScript related. You’d get the same error in node, bun, deno, and your favourite web-browser.

Error

When importing functionality exported by MLE modules into your module, you need to be aware of a pitfall. Here is a code snippet that – at first glance – looks correct:

SQL> create or replace mle module mod_a language javascript as 
2 export function hello() {
3 return 'hello from mod_a';
4 }
5* /

MLE module MOD_A compiled

SQL> create or replace mle module mod_b language javascript version '1.0.0' as
2
3 import a from 'a';
4
5 export function logSomething() {
6 console.log(a.hello());
7 }
8* /

MLE module MOD_B compiled

SQL> create or replace mle env mod_a_b imports ('a' module mod_a);

MLE env MOD_A_B created.

SQL> create or replace procedure p
2 as mle module mod_b
3 env mod_a_b
4 signature 'logSomething';
5* /

Procedure P compiled

Right, so everything compiled fine in Oracle Database Free 23.5/aarch64 running in a container, but … there’s a hidden problem in the code. It materialises as soon as you try to invoke the call specification – p – in SQL:

SQL> exec  p
BEGIN p; END;
*
ERROR at line 1:
ORA-04160: SyntaxError: The requested module 'a' does not provide an export named 'default'
ORA-06512: at "EMILY.P", line 1
ORA-06512: at line 1

This is absolutely correct, and it’s caused by the import statement in module B. Looking closer at the import statement in module B, the author probably intended to import module A in the kind of namespace [c.f. previously mentioned MDN article] referred to as ‘a‘ in the code. Instead, he effectively imported the defaultExport from the module. Since there isn’t one defined in module A, the error is thrown.

Nearby: you should consider adding unit tests when coding. Unit tests are a great way to make you aware of runtime errors before deployment, and you should have a good handle on potential code regressions. They don’t protect you from everything, but they are a great tool to be aware of and use.

Solution

In cases where a default export makes sense, you can define it in module A as shown here:

create or replace mle module mod_a language javascript as
export default function hello() {
return 'hello from mod_a';
}
/

You also need to update module B, or else you get a TypeError: (intermediate value).hello is not a function

create or replace mle module mod_b language javascript version '1.0.0' as

import hello from 'a';

export function logSomething() {
console.log(hello());
}
/

Only then can you call p without errors:

SQL> set serveroutput on
SQL> exec p
hello from mod_a

PL/SQL procedure successfully completed.

It is more likely though that the error in module B was caused by a misunderstanding of the import syntax. This is what the author actually intended to write:

create or replace mle module mod_a language javascript as
export function hello() {
return 'hello from mod_a';
}
/

create or replace mle module mod_b language javascript version '1.0.0' as

import * as a from 'a';

export function logSomething() {
console.log(a.hello());
}
/

This way, anything module A exports is available to module B via the pseudo-namespace, a. Which, admittedly, has not been given a very creative name, but naming things in IT is hard. The code executes fine now:

SQL> exec p
hello from mod_a

PL/SQL procedure successfully completed.

Summary

When importing JavaScript modules in your own code, pay attention to how to do so. Some modules, particularly those provided via community repositories, might require you to import the default export. The popular validator module is a good example. Others offer named exports, which you find documented alongside the code. What’s a named export I head you ask? That a way to import a specific thing from a JavaScript module, like so:

create or replace mle module mod_b language javascript version '1.0.0' as

import {hello} from 'a';

export function logSomething() {
console.log(hello());
}
/

Feel free to reference Mozilla Developer Network for the nitty-gritty details about the import statement.