Sign up to receive SuiteScript examples and advice directly in your email inbox.

SuiteScript Design - The Singleton Record

Created: November 10, 2020

For today's SuiteScript Design Pattern, I'll present how I create "Singletons" - though not the programmatic kind. Instead, we're going to see how to force a Custom Record Type to be a Singleton. In other words, we're only going to allow a single instance of a record type to exist.

I find integrations to be a great use case for this type of record - though it certainly has applications well beyond just integrations. Nearly any integration is going to require authentication, and most modern authentication relies on tokens which may or may not expire over time.

A Custom Record is a great place to store your token information for a specific integration, but each integration typically only needs one token, so we should only need a single instance of the record. In fact, if we allow multiple records of this Custom Record to exist, it might take a lot more work to figure out which one is current and correct, so let's ensure that only one instance can ever exist.

  1. We need to allow the first instance to be created, but thereafter we need to prevent any additional record creations or copies.
  2. We need to prevent deletion of that first instance.

We can do all of this with a single User Event. You can find the full source code for this pattern here.

beforeLoad can be used to detect some new creations and copies:

exports.beforeLoad = context => {
  // Ensure that only one instance of the record exsts at a time
  let isCreationEvent = ([
    context.UserEventType.CREATE,
    context.UserEventType.COPY
  ].includes(context.type))
  
  if (isCreationEvent && configRecordExists(context.newRecord.type)) {
    redirectToExisting(context.newRecord.type)
  }
}

and beforeSubmit can be used to prevent deletions and creations beyond the first:

exports.beforeSubmit = context => {
  let isCreate = (context.type === context.UserEventType.CREATE)
  let isDelete = (context.type === context.UserEventType.DELETE)
  let isFirstRecord = (context.newRecord.id === 1)

  // Prevent deletion of the first instance of this record
  if (isDelete && isFirstRecord) {
    throw err.create(Errors.ConfigRecordNoDelete)
  }

  // Ensure that only one instance of the record exists at a time
  if (isCreate && configRecordExists(context.newRecord.type)) {
    throw err.create(Errors.ConfigRecordExists)
  }
}

Because we've made the record a singleton, we've guaranteed that the only instance of it will have an internal ID of 1. This assumption presents us with opportunities for many shortcuts like our isFirstRecord variable not having to do any sort of search.

The variable and function names should give a clear picture of the logic. I'll leave it as an exercise for you to dig into the specifics of the smaller functions in the full source code.

HTH

-EG