A newer version of this documentation is available.

View Latest

Function: Shipping Notifier

    March 9, 2025
    + 12

    Goal: Send notifications when an order scheduled to arrive, when it is shipped, and when it is delivered.

    • This function shippingNotifier demonstrates a shipping workflow.

    • Requires Eventing Storage (or metadata collection), and "active", "archive", "notify" collections.

    • Requires four buckets metadata, active (the source), archive, and a notify.

      • The "notify"collection can be used to integrate with SDK or Kafka to send notifications.

    • Will operate on any doc with type === "ship".

    • Will update the source document with key information on each notify.

    • Delivered the shipping recored is archived.

    • On each notify data is read from the "active" bucket for type === "cust" and type === "order" as needed to build the notification.

    Other:

    • Note we could have used curl() to send the notify messages instead of using our staging bucket called "notify".

    • There are no try catch blocks and only limited error checking to highlight the functionality.

    • It is expected that the application that processes the "notify" bucket will purge the notification documents.

    • The notification history is stored in the shipping document and archived for all time.

    javascript
    // To run configure the settings for this Function, shippingNotifier, as follows: // // Version 7.1+ // "Function Scope" // *.* (or try bulk.data if non-privileged) // Version 7.0+ // "Listen to Location" // bulk.data.active // "Eventing Storage" // rr100.eventing.metadata // Binding(s) // 1. "binding type", "alias name...", "bucket.scope.collection", "Access" // "bucket alias", "act_col", "bulk.data.active", "read and write" // "bucket alias", "arc_col", "bulk.data.archive", "read and write" // "bucket alias", "snd_col", "bulk.data.notify", "read and write" // // Version 6.X // "Source Bucket" // source // "MetaData Bucket" // metadata // Binding(s) // 1. "binding type", "alias name...", "bucket", "Access" // "bucket alias", "act_col", "active", "read and write" // "bucket alias", "arc_col", "archive", "read and write" // "bucket alias", "snd_col", "notify", "read and write" function sendNotifySchedDelivCallback(context) { // This is a normal sceduled delivery notificaton // Look up the controlling ship: doc var shipkey = context.id; var shipdoc = act_col[shipkey]; if (shipdoc === null) { // stale timer return; } // Make sure we are active and still need to send if (shipdoc.type != "ship" || !shipdoc.active || shipdoc.notifys[context.idx].notifySent) return; // Look up the realted order: doc var orderkey = "order" + ":" + shipdoc.id; var orderdoc = act_col[orderkey]; // Look up the realted cusomer: doc var custkey = "cust" + ":" + orderdoc.cust_id; var custdoc = act_col[custkey]; var notifyId = "ntfy" + ":" + context.idx + ":" + shipkey; // log('shipdoc', shipdoc); // log('orderdoc', orderdoc); // log('custdoc', custdoc); // log("notifyId",notifyId); var senddoc = { "notifyReason": context.item.notifyReason, "first_name": custdoc.first_name, "last_name": custdoc.last_name, "email": custdoc.email, "phone": custdoc.phone, "items": orderdoc.items, "utcOffset": shipdoc.utcOffset }; // Add any special details if (context.item.notifyReason === "scheduled delivery") { senddoc["schedDelivTs"] = shipdoc.schedDelivTs; } else if (context.item.notifyReason === "delivered") { senddoc["deliveredTs"] = shipdoc.deliveredTs; } else if (context.item.notifyReason === "shipped") { senddoc["shippedTs"] = shipdoc.shippedTs; } // Write to send bucket -or- emit via cURL snd_col[notifyId] = senddoc; // Mark as sent shipdoc.notifys[context.idx].notifySent = true; // See if we are done and can archive this if (shipdoc.delivered && context.item.notifyReason === "delivered") { shipdoc.active = false; // Yes we can archive write to archive bucket arc_col[shipkey] = shipdoc; // and remove delete act_col[shipkey]; } else { // No just update in the source bucket act_col[shipkey] = shipdoc; } log("senddoc", senddoc); } function OnUpdate(doc, meta) { // Filter out non interesting items if (doc.type != "ship" || !meta.id.startsWith("ship:") || !doc.active) return; var nowMs = Date.now(); // this instant or now in ms. var nowSec = Math.trunc(nowMs / 1000); // this instant or now in sec. if (doc.shipped || doc.delivered) { // these are events they do not need to be scheduled via a Timer if (doc.shipped) { if (doc.shippedTs === null) { doc.shippedTs = nowSec; } var item = { "notifyReason": 'shipped', "notifyTs": nowSec, "notifySent": false }; } if (doc.delivered) { if (doc.deliveredTs === null) { doc.deliveredTs = nowSec; } var item = { "notifyReason": 'delivered', "notifyTs": nowSec, "notifySent": false }; } // Add to the notification array or history doc.notifys.push(item); // Write the source doc since we will sending an immediate notification act_col[meta.id] = doc; var context = { "item": item, "idx": doc.notifys.length - 1, "id": meta.id }; // There no need for a timer we can do this now since it is an event sendNotifySchedDelivCallback(context); return; } // Look for any needed notifications in the future for (var idx = 0; idx < doc.notifys.length; idx++) { var item = doc.notifys[idx]; if (!item.notifySent) { // JavaScript works in ms. BUT the doc's fields are in sec. - so convert and make a Date() var fireAt = new Date(item.notifyTs * 1000); // Make unique ref for this notification can overwrite/adjust or cancel var notifyId = "ntfy" + ":" + idx + ":" + meta.id; // Pass minimal data in our context, the callback will look everything else up. var context = { "item": item, "idx": idx, "id": meta.id }; // We will always 'overwrite' this timer(s) notification by the Timer's // reference_id (6.6.0+ required for this) on every mutation // log("create/overwrite notification "+ notifyId, item); createTimer(sendNotifySchedDelivCallback, fireAt, notifyId, context); } } }