Migration to QuickJS from SpiderMonkey
To support ES2023 JavaScript features and speed up index building, IBM Cloudant is replacing the SpiderMonkey JavaScript engine with QuickJS.
Some JavaScript features available in the SpiderMonkey engine are not available in QuickJS. When these are used, design documents must be updated to replace these features with QuickJS-compatible JavaScript. IBM Cloudant have analysed all customer JavaScript functions and have identified that less than 1% of customers will need to do this.
Do I need to take action?
IBM Cloudant have already migrated the majority of instances that are already using QuickJS-compatible JavaScript to a QuickJS environment. These customers have no further action needed.
To check whether your instance has been migrated, execute the following call:
$ curl -XGET https://$ACCOUNT.cloudant.com/
{
"couchdb":"Welcome",
"version":"3.5.0+cloudant",
"vendor":{"name":"IBM Cloudant","version":"8665","variant":"paas"},
"features":["quickjs","search","access-ready","iam","partitioned"],
"features_flags":["partitioned"]
}
Check the features
array:
- If
quickjs
is included in thefeatures
array, your instance is already using QuickJS and no action is needed. - If your instance does not have
quickjs
in thefeatures
array, you may need to make changes to your design documents to ensure they stay working as expected through the transition to QuickJS.
Actions to take if your instance is not already using QuickJS:
- Please read below for details on the JavaScript changes in QuickJS. Check your design documents for JavaScript using these features.
- Test your code in the QuickJS engine following the details here.
- If you are still unsure, open a support ticket and our team will be happy to advise on any changes you might need to make.
When will the migration be complete for all instances?
There is no completion date for the migration at the current time.
Ensure you are receiving IBM Cloudant notifications to receive updates to the timeline.
Will there be any downtime during migration?
There will be zero downtime during migration. After the migration, JavaScript code with incompatible JavaScript will see errors or unexpected changes to results.
Indexes will not need to be rebuilt during the migration.
What happens if I don’t update incompatible code before migration?
There are two classes of change:
- Changes that result in exceptions and errors being thrown. In these cases, documents will not be indexed, and requests that use JavaScript will receive error responses.
- Changes that alter results (eg,
parseInt
). In this case, documents will still be indexed, and requests will still return successfully. However, the indexes and responses may contain unexpected values.
Does this affect only indexing or also query execution?
It affects all uses of JavaScript in IBM Cloudant.
Are there any performance differences beyond index build speed?
Our testing does not indicate major changes for other operations.
QuickJS does not change the published limits for query execution.
What level of support does QuickJS have for ES2023 and ES2024?
test262.fyi, which runs daily tests of compatibility levels, reports 95% compatibility with both editions.
How can I test my design docs in QuickJS without impacting production?
Two ways to test your JavaScript are:
-
Upload a new design document containing your view, setting the
language
field tojavascript_quickjs
:{ "language": "javascript_quickjs", // other design document fields: views, indexes etc. ... "views": { // define your views... } }
This must be in a new design document; changing / setting this field in an existing design document will cause all indexes in the document to be rebuilt!
-
Test locally by running QuickJS in your local environment.
Read QuickJS for faster index builds) on the IBM Cloudant blog for details on how to do this.
Details of JavaScript changes in the QuickJS engine
The list below contains the behaviour differences and incompatible functionality between SpiderMonkey and QuickJS. Compare the information in this list with your design documents to understand if your code needs updating.
The changes are split into changes that will cause exceptions and those which instead cause changes in output.
Changes causing exceptions in existing code
-
Invalid expressions following
function(){...}
expressions used to be ignored, but when executed by QuickJS will throw a syntax error.Previously this would have compiled and discarded the unparsable code (
sfsdfsdfsdfsdf
):// Inside a view definition { "map": "function(doc) { emit(doc.foo, doc.bar); } sfsdfsdfsdfsdf" }
In QuickJS, this will instead cause JavaScript execution to fail.
-
for each (var x in ...)
expressions are removed. Usages should be changed as follows:When using
for each
with an array:js> arr = ['a','b','c'] ["a", "b", "c"] // Old code > for each(let e in arr) {print(e)} a b c // New code js> for(let i in arr) {print(arr[i])} a b c
When using
for each
with an object:js> obj ={'a':'x', 'b':'y'} ({a:"x", b:"y"}) // Old code js> for each(let e in obj) {print(e)} x y // New code js> for(let x in obj) {print(obj[x])} x y
-
Array.concat
is removed, useArray.prototype.concat
instead:// Old code Array.concat([0], [1,2,3]) // New code [0].concat([1,2,3])
-
The
toSource()
method is removed.toSource()
is a SpiderMonkey‑specific method that returns a source‑code string for an object. QuickJS does not expose this method, socontent[k].toSource()
throws an error:TypeError: content[k].toSource is not a function
.For the majority of use-cases, use
JSON.stringify
instead. -
String
substring(val, start, end)
is removed.Old code:
js> String.substring("abcd", 1, 2) "b"
Use
String.prototype.substring(start, end)
instead:js> "abcd".substring(1, 2) "b"
-
The
toISOString()
throws an error on invalidDate
objects.// Old behavior js> (new Date(undefined)).toISOString() "Invalid Date" // New behavior -- error js> (new Date(undefined)).toISOString() typein:1:23 RangeError: invalid date Stack: @typein:1:23
-
The
Date.prototype.toLocaleFormat()
function is deprecated.Previous result:
> d = new Date("Dec 1, 2015 3:22:46 PM") (new Date(1449001366000)) > d.toLocaleFormat("%Y-%m-%d") "2015-12-01"
New result:
> d = new Date("Dec 1, 2015 3:22:46 PM") (new Date(1449001366000)) > d.toLocaleFormat("%Y-%m-%d") typein:2:3 TypeError: d.toLocaleFormat is not a function
A better way to emit a timestamp is using
Date.prototype.toISOString()
:> to_iso(new Date("05 October 2011 14:48 UTC")) "2011-10-05T14:48:00.000Z" > to_iso(new Date("2011-10-05T14:48:00.000Z")) "2011-10-05T14:48:00.000Z" > to_iso(new Date("some_invalid_date")) RangeError: invalid date
Date validity can be checked before calling functions on the
Date
by usingisNaN(new Date("..."))
.To build a custom format use the
Date.prototype.getUTC...()
methods:> d = new Date("05 October 2011 14:48 UTC") > (d.getUTCMonth() + 1) + "/" + d.getUTCDate() + "/" + d.getUTCFullYear() "10/5/2011"
-
Constant values leak out of nested scopes
In SpiderMonkey
const
values leak from nested expression scopes. Referencing them in SpiderMonkey produces undefined, while in QuickJS this raises aReferenceError
.// Old behavior - note the existence of value leaks, not the content js> f = function(doc){if(doc.x === 'x') { const value='inside_if'}; print(value)}; js> f({'x':'y'}) undefined // New behavior - error js> f = function(doc){if(doc.x === 'x') {const value='inside_if';}; print(value)}; js> f({'x':'y'}) typein:1:23 TypeError: can't access property "x", doc is undefined
-
Callable regular expressions
SpiderMonkey allowed calling regular expression as a function. The call worked the same as calling the .exec() method.
// Old behavior js> /.*abc$/("abc") ["abc"] // New behavior - error, call exec() explicitly js> /.*abc$/("abc") typein:1:9 TypeError: /.*abc$/ is not a function Stack: @typein:1:9 js> /.*abc$/.exec("abc") ["abc"]
-
E4X (ECMAScript for XML) is deprecated
Previous result:
> var xml = <root><x></x></root> > xml.(x) <root> <x/> </root>
New result:
> var xml = <root><x></x></root> typein:1:11 SyntaxError: expected expression, got '<':
Changes causing unexpected results
-
Zero-prefixed input with
parseInt()
.The parseInt() function in SpiderMonkey treats a leading 0 as octal (base 8) prefix. It then parses the following input as an octal number. QuickJS assumes a base 10 as a default even when parsing numbers with leading zeros.
This can be a stumbling block especially when parsing months and days in a date string. One way to mitigate this discrepancy is to use an explicit base.
// Old behavior js> parseInt("08") 0 js> parseInt("09") 0 js> parseInt("010") 8 js> parseInt("08", 10) 8 // New behavior js> parseInt("08") 8 js> parseInt("09") 9 js> parseInt("010") 10 js> parseInt("08", 10) 8
-
RegExp.$1...$9
regular expressions feature is deprecated. The$1
...$9
properties will returnundefined
. For example:Previous result:
> v="abc" "abc" > v.match(/(b)/) ["b", "b"] > RegExp.$1 "b"
New result:
> v="abc" "abc" > v.match(/(b)/) ["b", "b"] > RegExp.$1 undefined
-
Date.prototype.toString()
result doesn't include the timezone name, just the offset. For example:Previous result:
> (new Date()).toString(); "Thu Sep 05 2024 17:04:03 GMT-0400 (EDT)"
New result:
> (new Date()).toString(); "Thu Sep 05 2024 17:03:23 GMT-0400"
-
Date.prototype.toLocaleString()
used to ignore locale strings, the new version will return the correct format:Previous result:
> (new Date("2019-01-15T19:32:52.915Z")).toLocaleString('en-US') "Tue Jan 15 14:32:52 2019"
New result:
> (new Date("2019-01-15T19:32:52.915Z")).toLocaleString('en-US') "01/15/2019, 02:32:52 PM"
-
Object key order changed:
Previous result:
> r={}; ["Xyz", "abc", 1].forEach(function(v) {r[v]=v;}); Object.keys(r) ["Xyz", "abc", "1"]
New result:
> r={}; ["Xyz", "abc", 1].forEach(function(v) {r[v]=v;}); Object.keys(r) ["1", "Xyz", "abc"]
-
String.prototype.match(undefined)
used to returnnull
, and now will return[""]
:Previous result:
> "abc".match(undefined) null
New result
> "abc".match(undefined) [""]