Language Constructs
Language constructs are the fundamental units of a language.
This page describes which JavaScript constructs Eventing Functions do and do not support.
Couchbase functions inherit support for most ECMAScript constructs by using Google v8 as the execution container. Certain capabilities have been removed and are not supported in order to handle the automatic sharding and scaling of functions. |
Supported Language Features
Eventing Functions support the following features:
Basic Keyspace Accessors
Buckets that are bound to an Eventing Function appear as a global JavaScript map. Map operations like GET, SET, and DELETE are exposed to the GET, SET, and DELETE Data Service provider operations.
If the bucket binding has a wildcard *
for its scope or collection, you cannot use a Basic Keyspace Accessor to access the Data Service. Instead, you must use an Advanced Keyspace Accessor.
Operation | Description |
---|---|
GET |
Fetches the object from the KV bucket that the variable is bound to. Returns the parsed JSON value as a JavaScript object. Fetching a non-existent object from a bucket returns an undefined value. This operation throws an exception if the underlying bucket GET operation fails with an unexpected error. |
SET |
Sets the provided JavaScript value into the KV bucket that the variable is bound to. Replaces any existing value with the specified key. This operation throws an exception if the underlying bucket SET operation fails with an unexpected error. |
DELETE |
Deletes the provided key from the KV bucket that the variable is bound to. Returns a no-op if the object does not exist. This operation throws an exception if the underlying bucket DELETE operation fails with an unexpected error. |
function OnUpdate(doc, meta) {
// Assuming 'dest' is a bucket alias or binding to a keyspace
var val = dest[meta.id]; // this is a bucket GET operation
dest[meta.id] = {"status":3}; // this is a bucket SET operation
delete dest[meta.id]; // this is a bucket DEL operation
}
Advanced Keyspace Accessors
Advanced Keyspace Accessors expose a larger set of options and operators than Basic Keyspace Accessors. They have non-trivial argument sets and return values.
See Advanced Keyspace Accessors for more details.
Logging
The log()
function allows Eventing Functions to log user-defined messages.
log()
statements are logged in each Eventing Function’s log file.
log()
does not throw exceptions.
function OnUpdate(doc, meta) {
log("Now processing: " + meta.id);
}
The Eventing Service also creates a system log file named eventing.log
.
This file exists in all Eventing Functions and captures management and lifecycle information.
The end-user cannot write to this file.
See Logging Functions for more information.
SQL++ Statements
You can use top-level SQL++ keywords like SELECT, UPDATE, INSERT, and DELETE as inline words in Eventing Functions. These operations are accessible through the returned iterable handle.
SQL++ Query results, through the SELECT operation, are streamed in batches to the iterable handle as the iteration progresses through the result set.
To avoid recursion, an Eventing Function can listen for mutations in a bucket. SQL++ DML statements canot manipulate documents in that same bucket. To work around this, you can use the exposed data service KV map in your Eventing Function. |
The following Function has a feed boundary of Everything, which means the same SQL++ statement is executed 7,303 times.
To execute only one query, you can configure your feed boundary to be From now and to mutate only one document in the keyspace beer-sample
._default
._default
.
You can also use an optimal index, which makes your query performance 24 times faster.
function OnUpdate(doc, meta) {
var strong = 70;
var results =
SELECT * /* SQL++ queries are embedded directly */
FROM `beer-sample`._default._default /* Token escaping is standard SQL++ style */
WHERE abv > $strong; // Local variable reference using $ syntax
for (var beer of results) { // Stream results using 'for' iterator
log(beer);
break;
}
results.close(); // End the query and free resources held
}
The embedded SQL++ call starts the query and returns a JavaScript iterable object that represents the result set of the query. You can iterate the returned handle using standard JavaScript mechanisms like for…of
loops.
The iterator is an input iterator, meaning the elements are read-only.
The variables created inside the iterator are local to it.
You cannot use the keyword this
in the body of the iterator.
You must close each result set with the close()
method, which stops the underlying SQL++ query and releases associated resources.
In some cases like nested SQL++ lookups, failing to explicitly call close()
can use too many SQL++ resources and lead to poor performance.
Valid and Invalid Statements
SQL++ is not syntactically part of the JavaScript language.
Eventing transpiles the Eventing Function code to identify SQL++ statements and convert them to a standard JavaScript function call.
This call then returns an iterable object with a close()
method.
To use a JavaScript variable in a query statement, you must use $<variable>
.
This parameter is substituted in the query by the corresponding JavaScript variable’s runtime value.
You cannot use the meta.id
expression in the query statement.
Instead, you must use var id = meta.id
.
The following is a valid statement:
var id = meta.id;
DELETE FROM mybucket.myscope.transactions WHERE username = $id;
The following is an invalid statement:
DELETE FROM mybucket.myscope.transactions WHERE username = $meta.id;
Escaped Identifiers
When you use a SQL++ query inside an Eventing Function, you must also use an escaped identifier for keyspaces with special characters. To escape an identifier, enclose it in back ticks (``).
If the bucket name is beer-sample
and the scope and collection are both _default
, you only need to escape the bucket in the SQL++ query:
SELECT * FROM `beer-sample`._default._default WHERE type ...
If the bucket name is beersample
, you do not need to escape the keyspace of the SQL++ query:
SELECT * FROM beersample._default._default WHERE type ...
End of Line Comments
In multiline SQL++ statements, you cannot use single line // end of line comments
before the semicolon at the end of the statement.
This causes syntax errors in the transformation and compilation of the SQL++ statement.
To include comments in multiline statements, use /* this format */
instead.
Unsupported Language Features
The following features are not supported by Eventing Functions:
Global State
Eventing Functions do not support global variables. This restriction makes sure that the logic of Eventing Functions remains agnostic of rebalance operations.
Instead of using global variables, you must save and retrieve all states from persistence providers like the Data Service. You can use bindings to make all global states contained in Data Service buckets available to Eventing Functions.
var count = 0; // Not allowed - global variable.
function OnUpdate(doc, meta) {
count++;
}
You can use Constant alias bindings in your Function’s settings to access global constants within a Function’s JavaScript.
For example, a Constant alias of debug
with a value of true
or false
behaves in the same way as the statement const debug = true
.
Asynchrony
Eventing Functions do not support asynchronous flows.
Asynchrony creates a node-specific, long-running state that prevents persistence providers from capturing the entire state. This limits Eventing Functions to execute short-running, straight-line code without sleep and wakeups.
You can use Timers to add limited asynchrony back into your Function. Timers are designed specifically to prevent a state from being node-specific.
function OnUpdate(doc, meta) {
setTimeout(function(){}, 300); // Not allowed - asynchronous flow.
}
Browser and Other Extensions
Eventing Functions do not support browser extensions, like window methods and DOM events.
You can use Timers instead of setTimeout
and curl calls instead of XMLHttpRequests
.
function OnUpdate(doc, meta) {
var rpc = window.XMLHttpRequest(); // Not allowed - browser extension.
}
Built-in Functions
Eventing Functions support the following built-in functions:
N1QL()
You cannot use the N1QL()
function call directly because it bypasses the semantic and syntactic checks of the transpiler.
The N1QL() function has replaced the deprecated N1qlQuery() .
|
The N1QL()
function contains the following parameters:
Parameter | Description |
---|---|
|
The identified SQL++ statement. This is passed to SQL++ through SDK to run as a prepared statement. All of the JavaScript variables referenced in the statement using the |
|
Can be a JavaScript array or a JavaScript map object.
You cannot mix positional and named parameters. Example of an iterator using a positional
Example of an iterator using a named
|
|
A JSON object that has various query runtime options as keys. The following settings are available:
|
|
Returns a JavaScript iterable object that represents the result set of the query.
You can iterate the returned handle using standard JavaScript mechanisms like You can use the |
Exceptions thrown |
The The returned iterable handle throws an exception if the underlying SQL++ query fails after it has started. The |
ANALYTICS()
(Introduced in Couchbase Server 7.6)
The ANALYTICS()
function provides integration with SQL++ Analytics directly from the Eventing Service.
Integrating Eventing with Analytics:
-
Allows Eventing to benefit from the high availability and load balancing of Analytics, where requests can take turns being submitted across nodes
-
Simplifies Eventing code logic and improves code readability
-
Eliminates security and network latency issues with the
curl()
function
function OnUpdate(doc, meta) {
// Ignore information we don't care about
if (doc.type !== 'airline') return;
// Get the total routes per IATA
var route_cnt = 0;
// Uses a true variable as a SQL++ parameter
var airline = doc.iata;
var results = ANALYTICS(
"SELECT COUNT(*) AS cnt
FROM `travel-sample`.`inventory`.`route`
WHERE type = \"route\"
AND airline = $1", [doc.iata]
);
// Stream results using the 'for' iterator
for (var item of results) {
route_cnt = item.cnt;
}
// End the query and free the resources held
results.close();
// Log the KEY, AIRLINE and ROUTE_CNT
log("key: " + meta.id + ", airline: " + doc.iata + ", route_cnt: " + route_cnt);
}
For more information about SQL++ Analytics, see What’s SQL++ for Analytics?.
crc64()
The crc64()
function:
-
Calculates the CRC64 hash of an object using the ISO polynomial
-
Suppresses double mutations
The crc64()
function takes the object to checksum as its only parameter.
The parameter can be any JavaScript object that can be encoded to JSON.
The function returns the hash as a string. The hash is sensitive to the order of the parameters in case of map objects.
If multiple Eventing Functions share the same crc64
checksum documents as the Sync Gateway, real mutations can be suppressed and missed.
To prevent this from happening, you can make the checksum documents unique to each Eventing Function.
function OnUpdate(doc, meta) {
var crc_str = crc64(doc);
/// Code goes here
}
You can also use the crc64
function to suppress a double mutation.
A double mutation can happen when the Sync Gateway and the Eventing Function leverage the same bucket.
The Sync Gateway updates the metadata of the document inside the bucket and generates an event for the Eventing Function to process. The Eventing Function cannot differentiate between events from the Sync Gateway and events from SDKs, SQL++, and other sources.
function OnUpdate(doc, meta) {
// Ignore documents created by Sync Gateway
if(meta.id.startsWith("_sync") == true) return;
// Ignore documents whose body has not changed since we last saw it
var prev_crc = checksum_bucket[meta.id];
var curr_crc = crc64(doc);
if (prev_crc === curr_crc) return;
checksum_bucket[meta.id] = curr_crc;
// Business logic goes in here
}
base64()
(Introduced in Couchbase Server 7.6.2)
The base64()
functions let you pack large-dimensional arrays of floats as base64 encoded strings when you use the Eventing Service to generate vector embeddings.
This encoding process stores and transmits arrays as text, ensuring data integrity and compatibility with text-based systems.
The following base64()
functions are available:
-
base64Encode()
, which takes a JSON argument and returns a base64 string.function OnUpdate(doc, meta) { var base_str = base64Encode(doc); /// Code goes here }
-
base64Decode()
, which takes a base64 encoded string and returns a value string.function OnUpdate(doc, meta) { var base_str = base64Decode(doc); /// Code goes here }
-
base64Float32ArrayEncode()
, which takes a float32 number array and returns a base64 string. -
base64Float32ArrayDecode()
, which takes a base64 encoded string and returns a float32 number array. -
base64Float64ArrayEncode()
, which takes a float64 number array and returns a base64 string. -
base64Float64ArrayDecode()
, which takes a base64 encoded string and returns a float64 number array.
createTimer()
and cancelTimer()
Timers are asynchronous compute. They allow Eventing Functions to execute in reference to wall-clock events.
To create a Timer, call the createTimer()
function using createTimer(callback, date, reference, context)
.
This function executes at or close to a specified date.
The reference is an identifier for the Timer that is scoped to an Eventing Function and callback. The context must be serializable data that is available to the callback when the Timer is fired.
To cancel a Timer, you can do one of the following:
-
Call the
createTimer()
function again using a reference from the existing Timer you want to cancel. -
Call the
cancelTimer()
function usingcancelTimer(callback, reference)
.
For more information about Timers, see Timers.
curl()
The curl()
function lets you interact with external entities through a REST endpoint from Eventing Functions, using either HTTP or HTTPS.
For more information about the curl()
function, see cURL.
Handler Signatures
The Eventing Service calls the following JavaScript functions on events like mutations and fired Timers:
OnUpdate Handler
The OnUpdate
handler is called when you create or modify a document using an operation like insert or update.
The entry point OnUpdate(doc, meta)
listens to mutations in the associated source bucket.
The OnUpdate
handler has the following limitations:
-
If a document is modified several times in a short period of time, the handler calls might be combined into a single event due to deduplication.
-
You cannot distinguish between a Create and an Update operation.
function OnUpdate(doc, meta) {
if (doc.type === 'order' && doc.value > 5000) {
// ‘phoneverify’ is a bucket alias or binding to a keyspace
phoneverify[meta.id] = doc.customer;
}
}
OnDelete Handler
The OnDelete
handler is called when a document is deleted or removed due to expiration.
The entry point OnDelete(meta, options)
listens to mutations like deletions and expirations in the associated source bucket.
To make sure that a document has been deleted or has expired, you can inspect the optional argument options
.
The options
argument is a JavaScript map object that contains the boolean property expired
.
You cannot get the value of a deleted or expired document.
function OnDelete(meta,options) {
if (options.expired) {
log("Document expired", meta.id);
} else {
log("Document deleted", meta.id);
}
var addr = meta.id;
var res = SELECT id from mybucket.myscope.orders WHERE shipaddr = $addr;
for (var id of res) {
log("Address invalidated for pending order: " + id);
}
}
In versions of Couchbase Server before version 6.6.0, the optional argument options
is not present and the entry point for the handler is OnDelete(meta)
.
The entry point is still supported, but using it means you’re unable to differentiate deletion from expiration.
function OnDelete(meta) {
log("Document deleted or expired", meta.id);
}
Timer Callback Handler
Timer callbacks are user-defined JavaScript functions passed as the callback argument in the built-in createTimer(callback, date, reference, context)
function call.
The Timer callback handler is an entry point for the event when a timer, created by the specific Eventing Function, matures and fires.
// Timer Callback Handler (user-defined entry point)
function DocTimerCallback(context) {
log("Timer fired running callback 'DocTimerCallback' with context: " + context);
}
// Insert/Update Handler or entry point
function OnUpdate(doc, meta) {
// filter out docs of no interest
if (meta.id != 'make_timer:1') return;
// Create a Date value 60 seconds from now
var oneMinuteFromNow = new Date(); // Get current time & add 60 sec. to it
oneMinuteFromNow.setSeconds(oneMinuteFromNow.getSeconds() + 60);
// Create a doc to hold context to pass state to the callback function
var context = { docId: meta.id, random_text: "arbitrary text" };
// Create a timer that will fire an event in the future
log("createTimer with callback 'DocTimerCallback'");
createTimer(DocTimerCallback, oneMinuteFromNow, meta.id, context);
}
For more information about Timers, see Timers.
Reserved Words
You cannot use reserved words as variable names, function names, or JavaScript code properties in Eventing Functions. If you use a reserved word, the Eventing Function returns a deployment error.
The following reserved words are used by the transpiler to integrate SQL++ with Eventing:
SQL++ Reserved Words | |||||
---|---|---|---|---|---|
ALTER |
BUILD |
CREATE |
DELETE |
DROP |
EXECUTE |
EXPLAIN |
GRANT |
INFER |
INSERT |
MERGE |
PREPARE |
RENAME |
REVOKE |
SELECT |
UPDATE |
UPSERT |