npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@beacon/utils

v3.0.8

Published

![](https://github.com/beaconcrm/utils/workflows/Build/badge.svg)

Readme

Utils

Generic utility functions for Beacon. Can be used browser and client side.

Local dev

Want to try a new util or a new version of a util in another repo that depends on @beacon/utils?

# in this repo:
yarn && yarn build
cd .build && yarn link

# in the target repo, where you want to consume @beacon/utils:
yarn link @beacon/utils

And to go back to the version from npm:

# in this repo:
yarn unlink

# in the target:
yarn unlink @beacon/utils

Installation

yarn add @beacon/utils

Usage

Each util should be imported specifically, like so:

Node.js

const firstName = require("@beacon/utils/firstName");
console.log(firstName("Frodo Baggins"));
// Frodo

Browser

import firstName from "@beacon/utils/firstName";
console.log(firstName("Frodo Baggins"));
// Frodo

Local development

If you're working on the utils locally, you can use the link command to link the package to your local environment.

# utils
yarn build
cd .build && yarn link

# target repo
yarn link @beacon/utils

Docs

Each exciting method documnented in full below!

Want to see the code? Or the unit tests? You'll find them in the /lib and /test directories.

auStateCodes

An array of all Australian states.

[{ code: 'ACT', name: 'Australian Capital Territory' }, ...]

billingFeatures

A list of all of the billing feature flags we support, and the plans/elements they correspond to.

billingPlans

An object containing starter, standard, and premium keys. Each object provides a core definition for each plan. Used by the other billing utils.

canRunEntityExport

Returns true if a user can run an entity export, false otherwise. User must have at least read access to all fields.

canRunEntityExport({
  entityType,
  entityExportTemplate, // optional. If provided, only needs to be able to read the fields in the template.
  allEntityTypes,
  permissions,
  isAdmin, // boolean
}); // true

cleanFilterConditions

Given an array of entity filter conditions, remove the conditions where ignore: true is set on them, and also omit the id (legacy) and ignore keys from each condition object.

Works at all levels of nesting. Added because it's possible for a filter condition to be "blank" when being configured in the UI, but we want to ignore blank conditions when running queries and saving filters to things like charts, workflows, etc.

const filterConditions = [{
  field: 'name.first',
  operator: 'contains',
  value: '',
  ignore: true,
}, {
  field: 'date_of_birth',
  operator: '==',
  value: 'this_year',
  ignore: false,
}];

const result = cleanFilterConditions(filterConditions);

// [{
//  field: 'date_of_birth',
//  operator: '==',
//  value: 'this_year',
//}]

closePopup

Close a popup window/tab that was previously opened. Browser-only.

closePopup(popup);

countDecimalPlaces

Count the number of decimal places in a number.

countDecimalPlaces(5.233); // 3

countryCodeThreeLetter

A mapping of all two letter country codes to their corresponding three letter iso codes

countryCodes

An array of all of the countries in the world. Useful for lists in forms.

console.log(countryCodes); // [{ code: 'GB', name: 'United Kingdom' }, ...]

Currently, the French locale is also supported:

console.log(countryCodes); // [{ code: 'GB', name: 'Royaume-Uni' }, ...]

countryDialCodes

A list of all of the country dialling codes in the world.

const countryDialCodes = require("@beacon/utils/countryDialCodes");
console.log(countryDialCodes); // [{ country_code: 'GB', dial_code: '+44' }, ...]

countryEmojiFlags

An object mapping of every country two letter iso code, to their corresponding flag in emoji format.

currencyCodes

A list of all of the currency codes in the world. Used under the hood in getCurrencySymbol;, currencySymbol;, and currencyFormat;

console.log(currencyCodes); // [{ isoCode: 'GBP', unicode: 'a3', locale: 'en', name: 'United Kingdom Pound' }]

decodeCharRefs

Decode hex unicode character references to their core... thing. Honestly I have no idea how this works but it's used internally in getCurrencySymbol.

detectDateFormat

Detect a date format from a date string. Defaults to detection in UK formats, but a second optional isUSDate property can be passed.

Uses moment under the hood.

detectDateFormat("04/05/2018"); // DD/MM/YYYY
detectDateFormat("01-03-20 13:30", "DD-MM-YY HH:mm");
detectDateFormat("04/05/2018", true); // MM/DD/YYYY

The format returned can be used in moment to parse a date string according to that format:

const format = detectDateFormat("04/05/2018"); // DD/MM/YYYY;
const m = moment("04/05/2018", format);

Optionally, a third preferredDateFormat parameter can be passed. If passed, this format will be checked first, and if it matches, then no other formats will be checked. (Useful if you're detecting across very large CSV files like the import validator does!)

doesEntityMatchSmartFieldRule

Util for rules-based smart fields. Return true if an entity matches a given smart field "rule", false otherwise.

rule objects must follow the standard smart field rule structure.

// Usage
doesEntityMatchSmartFieldRule({
  rule,
  entity,
  references,
  allEntityTypes,
});

// Example:
doesEntityMatchSmartFieldRule({
  rule: {
    conditions: [
      {
        field_id: 62,
        operator: "contains",
        value: "Donation",
      },
    ],
  },
  entity,
  references,
  allEntityTypes,
}); // true

dynamicDateValues

An array of dynamic date values (e.g. this_year) that can be used in entity filter conditions.

elements

An object containing element definitions for all elements.

expandZapierPayloadReferences

Function expects an entity and associated references array alongside all entity types for the account. Function will find each reference type field on the given entity and expand it from a pointer like [ 123 ] to a corresponding set of objects like [{ id: 123, name: 'Person Name', email: '[email protected]' }].

If the reference field can point to more than one entity types, function will handle all in the metadata.entity_types array and work with them.

If the reference field pointer is one-to-many (like [123, 456, 789]), function will handle all corresponding items in the references array to pull data from.

If there is no item in the references array corresponding to current reference field entity type, function will set a null value template expansion for utility like [{ id: null, name: null, email: null }].

The null value template is provided for use in the Zapier UI, where a payload may be used either as a source of data OR as a configuration template for future 'zap' executions (e.g. user can view recent invocations and use items in the payload to configure actions, paths etc.).

The template expansion will comprise the folowing properties:

  • always id, entity_type_id, entity_type_key and [<entity_type_key>]
  • under [<entity_type_key>], in case of a person entity type we provide name (full name) and email (primary); in all other cases [<primary_field>]
const entity = {
  id: 101,
  customer: [100],
  fundraiser: [],
};

const references = [
  {
    entity: {
      id: 100,
      name: {
        full: "Haydn Appleby",
        last: "Appleby",
        first: "Haydn",
        middle: null,
        prefix: null,
      },
      emails: [
        {
          email: "[email protected]",
          is_primary: true,
        },
      ],
    },
  },
];

const result = expandZapierPayloadReferences({
  entity,
  references,
  allEntityTypes,
});

/**
{
  id: 101,
  customer: [{
    id: 100,
    entity_type_id: 987,
    entity_type_key: 'person',
    person: {
      name: 'Haydn Appleby',
      email: '[email protected]',
    }
  }],
  fundraiser: [{
    id: null,
    entity_type_id: 987,
    entity_type_key: 'person',
    person: {
      name: null,
      email: null,
    }
  },{
    id: 101,
    entity_type_id: 654,
    entity_type_key: 'organization',
    organization: {
      name: 'Beacon CRM',
    }
  }]
}
*/

findIndexes

Finds all indexes for a given pattern in a string, and return them as an array.

findIndexes("my name is chris", " "); // [2, 7, 10]
findIndexes("my name is chris, my name is big dave", "my"); // [0, 18]

firstName

Return the first word within an string that isn't a "name prefix" (e.g. Mr).

firstName("Chris Houghton"); // Chris
firstName("Mr Chris Houghton"); // Chris
firstName("Mr. Chris Houghton"); // Chris
firstName("Prof Dr Chris Houghton"); // Chris

getContactPointIdByValue

Returns the contactPointId for a specific contact type (emails, phone numbers, or address) on a person entity.

const contactPointId = getContactPointIdByValue({
  personEntity,
  contactPointFieldType: 'emails',
  value: '[email protected]',
});
// => '019b2d14-9700-75e1-8a0a-89883f5abac6'

getContactPointsForNewEntity

Returns an object with contact points of the new object.

Useful when you need to get contactPointIds for a person entity that has just been upserted. Eg draftEntity is the mapped entity before upserting, publishedEntity is the returned entity which has been populated with contactPointIds.

draftEntity is optional; if you omit it, the contact point fields of a published entity will be returned.

// Simplified example usage within apps
const draftEntity = {
  name: { first: 'John', last: 'Tester' },
  emails: [{ email: '[email protected]' }],
  // and other fields
}

const { ids: personIds } = await api.bulkUpsertEntities({
  type: 'person',
  primary_field_key: ['emails', 'name'],
  entities: [draftEntity],
  update_policy: 'merge_and_skip',
});

const { entity: publishedEntity } = await api.readEntity({
  type: 'person',
  id: personIds[0]
});

const contactPoints = getContactPointsForNewEntity({
  draftEntity,
  publishedEntity
});

// Returns:
// {
//   emails: [{
//     email: '[email protected]',
//     contact_point_id: '019b2d14-9700-75e1-8a0a-89883f5abac6'
//   }],
//   phone_numbers: [],
//   address: [],
// }

formatEntityForEmailTemplate

Given an entity object, and it's references, format it into a format that can be added directly into an email template.

Information like accountUsers and allEntityTypes are passed as they are relevant to the data that gets added.

The locale affects date formats (powered by moment.js).

formatEntityForEmailTemplate({
  entity,
  references = [],
  accountUsers,
  allEntityTypes,
  locale = 'en_GB',
});

formatPhoneNumber

formatUKPostcode

Format a UK postcode into the standard format. It automatically does the following:

  • trims whitespace
  • uppercases
  • adds a space in the middle
formatUKPostcode("sw112ae"); // SW11 2AE
formatUKPostcode("SW112AE"); // SW11 2AE
formatUKPostcode("sw11    2Ae    "); // SW11 2AE

getBillingPlans

Returns an object containing starter, standard, and premium keys. Each object provides a core definition for each plan. Used by the other billing utils.

Requires a version number to return the billing plans for a specific pricing model version.

getBillingPlans(2);

getBillingPrices

For a given plan, number of contacts, and billing frequency, return the prices as shown on the pricing page. This automatically applies an annual discount to all prices.

Optional parameters:

  • discountPercent - discount everything by this percentage (in addition to any annual discount)
  • annualDiscountPercent - defaults to 10
  • freeCustomFields - override the number of allowed free custom fields on the plan
  • billing - override the standard prices (only if plan matches billing.plan). Overrides annual discount regardless of plan.
getBillingPrices({
  plan: "starter",
  contacts: 2000,
  frequency: "annual",
  // annualDiscountPercent: 20, // optional. default 10
  // discountPercent: 15, // optional. in addition to annual discount
});
// => {
//   base: 91,
//   elements: [
//     { key: 'fundraising', price: 24.5 },
//     { key: 'finance', price: 12.5 },
//     { key: 'ticketing', price: 12.5 },
//     { key: 'email_integrations', price: 3.75 },
//     { key: 'memberships', price: 17 },
//     { key: 'volunteering', price: 10 },
//     { key: 'case_management', price: 10 }
//   ],
//   limit_increases: [
//     {
//       key: 'custom_fields',
//       number_free: 25,
//       unit_cost: 25,
//       step: 25,
//       maximum: 100
//     },
//     {
//       key: 'users',
//       number_free: 3,
//       unit_cost: 20,
//       step: 1,
//       maximum: 10
//     }
//   ]
// }

getBillingEstimate

Calculate how much Beacon should cost a customer, based on their contacts, plan, elements, limit increases, billing frequency, and custom discount percent.

Optional parameters:

  • annualDiscountPercent - override the default 10 annual discount percent. Will be overridden if billing is passed.
  • discountPercent - discount everything by this percentage (in addition to any annual discount)
  • billing - override the standard prices (if plan matches billing.plan, and billing.version matches version). Annual discount overrides regardless of plan.
getBillingEstimate({
  contacts: 50000,
  plan: "premium",
  version: 2,
  elements: ["fundraising", "finance", "workflows", "case_management"],
  limitIncreases: [
    {
      key: "custom_fields",
      quantity: 2,
    },
  ],
  frequency: "monthly",
  // annualDiscountPercent: 20, // optional
  // discountPercent: 15, // optional
  // billing: {...} // optional
});
// => {
//   base: 495,
//   elements: [
//     { key: 'fundraising', price: 136 },
//     { key: 'finance', price: 68 },
//     { key: 'workflows', price: 136 },
//     { key: 'case_management', price: 40 }
//   ],
//   limit_increases: [ { key: 'custom_fields', quantity: 2, price: 100 } ],
//   total: 975
// }

getBillingFeatures

Get a list of all the billing feature flags, and the plans/elements they correspond to. Takes a version argument to return the billing features for a specific pricing model version.

const features = getBillingFeatures(2);
// => [
//   { key: 'developer_api', title: 'Developer API', elements: ['core'], plans: ['standard', 'premium', 'ultimate'] }
//   etc...
// ]

hasCustomBillingPrices

Given a billing object, return true if billing prices have been customised (compared to the standard billing plans), false otherwise.

const isCustom = hasCustomBillingPrices(billing);
// => true

getCountryCode

For a given string containing either an ISO country code, an official country name, or an unofficial country name, return the two-letter country code for it.

Case and whitespace insensitive. Really useful for saying "given a string, get the country for it". (Like in CSV imports)

getCountryCode("GB"); // GB
getCountryCode("  gb"); // GB
getCountryCode("United Kingdom"); // GB
getCountryCode("    united STates "); // US
getCountryCode("United States of America"); // `US` (unofficial name)
getCountryCode("Hong Kong"); // `HK` (unofficial name)

getCountryNameFromCode

Given a two letter ISO country code (e.g. GB, US), return the name of that country.

getCountryNameFromCode("GB"); // United Kingdom
getCountryNameFromCode("US"); // United States
getCountryNameFromCode("CHRIS"); // undefined

getCurrencySymbol

Given a 3 letter ISO currency code (e.g. GBP, USD), return a symbol that can be used for it. If you're using this util in the frontend, be sure to pass the encode: false option.

I don't think we have a single place using the encoded version, but it's often a requirement for server side usage.

getCurrencySymbol("GBP"); // &#xa3;
getCurrencySymbol("GBP", { encode: false }); // £
getCurrencySymbol("USD", { encode: false }); // $

currencyFormat

Given a number value and a 3 letter ISO currency code (e.g. GBP, USD), default of 'GBP', return formatted number with the appropiate currency symbol at the correct position (start or end).

It uses the locales so it will also use the correct spacing for that country (see below Poland Zloty uses spaces (' ') instead of commas (',').

currencyFormat({ value: 100000 }); // £100,000
currencyFormat({ value: 100000, nDecimalDigits: 2 }); // £100,000.00
currencyFormat({ value: 100000, currency: "PLN" }); // 100 000 zł

currencySymbol

Given a 3 letter ISO currency code (e.g. GBP, USD), return a symbol that can be used for it (this function never returns an encoded version). This function is different to get currency symbol as it uses toLocaleString and gets the currency symbol based on a locale. It seems to be more accurate for certain currencies

e.g. Poland Zolty
currencySymbol = zł (this is correct)
getCurrencySymbol = z (this is wrong)
currencySymbol("GBP"); // £
currencySymbol("USD"); // $
currencySymbol("PLN"); // zł

enforcePrecision

ported from moutjs

Enforce a specific amount of decimal digits and also fix floating point rounding issues.

Example:

enforcePrecision(0.615, 2); // 0.62
enforcePrecision(0.625, 2); // 0.63
//floating point rounding "error" (rounds to odd number)
+(0.615).toFixed(2);        // 0.61
+(0.625).toFixed(2);        // 0.63

getDefaultStaticData

Get default static data. Used by Beacon apps.

getDefaultStaticData({
  idealStaticData,
  entityType,
});

getDependentSmartFields

For a given smart field, return an array of all of the fields that depend on it. This method is recursive, so it will also return fields that depend on fields that depend on this field.

getDependentSmartFields({
  field, // smart field
  entityType,
});
// => [{ ... }]

getDirectories

Get a list of directories at a given path. Node.js only.

getDirectories(`${__dirname}/sampledirectory`); // ['anothersub', 'sub']

getDomainFromEmail

Given an email address, get the domain from it.

getDomainFromEmail("[email protected]"); // beaconcrm.org

getDomainFromUrl

Given a URL or website address, return the top level domain for it.

getDomainFromUrl("https://www.facebook.com/test"); // facebook.com
getDomainFromUrl("www.beaconcrm.org"); // beaconcrm.org
getDomainFromUrl("beaconcrm.org"); // beaconcrm.org

getElementName

Return a human friendly name for an element, given an element "key" (e.g. fundraising).

getElementName("roles_and_permissions");
// => "Roles & permissions"

getElementFeatures

Return an array of element feature flags for a given element. Optionally, a plan can be passed to only return the features available as standard on a given plan.

getElementFeatures({
  element: "roles_and_permissions",
});
// => ['feature_permissions', 'entity_type_permissions', 'field_permissions']

getElementFeatures({
  element: "roles_and_permissions",
  plan: "standard",
});
// => ['feature_permissions', 'entity_type_permissions']

getLimitIncreaseName

For a given limit increase key (e.g. "custom_fields"), return a human friendly name for that limit increase ("Custom fields").

getLimitIncreaseName("custom_fields");
// => "Custom fields"

getEntityFieldPermission

For a given fieldId, return the permission that the user has for that field. Possible return values are:

  • write - user can edit this field
  • read - user can read this field (also applies to core platform fields, e.g. created_at)
  • forbidden - user cannot read or edit this field
getEntityFieldPermission({
  fieldId, // integer
  entityTypeId, // integer
  isAdmin, // boolean
  permissions,
});

getEntityTitle

Return the "title" for a given entity, in a basic format. Work in progress.

getEntityTitle({
  entity, // entity to get the title for
  references, // array of references. Defaults to []
  allEntityTypes, // list of all entity types
  fallback, // fallback if no title is available. Defaults to "Unknown"
});

getEntityTypeFieldIds

Return an array of field ids for a given entity type. Optionally, a second options object can be passed, to exclude specific field ids or keys.

getEntityTypeFieldIds(entityType); // [1, 2, 3, ...]
getEntityTypeFieldIds(entityType, {
  exclude: [1, 2],
}); // [3, ...]
getEntityTypeFieldIds(entityType, {
  excludeByParam: {
    is_read_only: true, // Removes all read only fields
  }
})

getEntityTypeInboundReferenceFields

Return an array of fields that point at a given entity type.

getEntityTypeInboundReferenceFields({
  entityTypeId, // the entity type being referenced
  allEntityTypes,
  fromEntityTypeId, // (optional) only return fields _from_ a particular type
  includeRollupFields = false, // include first/last reference rollup fields? (don't by default)
});

getFeatureElements

For a given billing feature (e.g. xero_app), return the element keys (e.g. ['finance']) that the feature belongs to. All features are owned by one or more elements.

For core features, core is returned.

getFeatureElement("xero_app");
// => ['finance']

getFeatureElement("custom_entity_types");
// => ['core']

getFeatureLimit

Return the limit that should be applied for a given feature (e.g. workflows).

getFeatureLimit("workflows", billing);
getFeatureLimit("users", billing); // 10

getFieldIdsInFilterConditions

Returns an array of all field ids found in an array of filter conditions. Useful for knowing field dependencies.

getFieldIdsInFilterConditions({
  entityTypeId, // id of entity type being filtered
  filterConditions, // array of the filter conditions
  allEntityTypes, // all entity types in the db
});

getEntityTypeIdsInFilterConditions

Returns an array of all entity type ids found in an array of filter conditions. Useful for knowing entity type dependencies.

getEntityTypeIdsInFilterConditions({
  entityTypeId, // id of entity type being filtered
  filterConditions, // array of the filter conditions
  allEntityTypes, // all entity types in the db
});

getFieldsInSmartFieldRules

Returns an array of all of the fields used in the rules of a given smart field.

IMPORTANT: this only returns the fields on the entity type that the smart field belongs to - not the ones on the records pointed at.

getFieldsInSmartFieldRules({
  field, // smart field
  entityType,
});
// => [{ ... }]

getFieldsInAllSmartFieldRules

Returns an array of all of the fields used in any of rules-based smart fields for a given entity type.

IMPORTANT: this only returns the fields on the entity type that the smart field belongs to - not the ones on the records pointed at.

getFieldsInAllSmartFieldRules({
  entityType,
});
// => [{ ... }]

getFieldsInSmartFieldTemplate

Returns an array of all of the fields used in the template of a template-based smart field.

getFieldsInSmartFieldTemplate({
  field, // smart field
  entityType,
});
// => [{ ... }]

getFieldsInAllSmartFieldTemplates

Returns an array of all of the fields used in the templates of all template-based smart fields for a given entity type.

getFieldsInAllSmartFieldTemplates({
  entityType,
});
// => [{ ... }]

getRemoteFieldsInSmartFieldTemplate

Returns an array of all of the remote fields used in a smart field template, for a given outbound referenceField.

You'll want to run this if a reference field is returned in getFieldsInSmartFieldTemplate - to see if we need to go deeper at all.

getRemoteFieldsInSmartFieldTemplate({
  field, // smart field
  referenceField, // that exists in the smart field
  allEntityTypes,
});
// => [{ ... }]

getFilesInDirectory

Node.js only. Get a list of all of the files in a given directory. Optionally, an second options parameter can be passed, with the following parameters:

  • recursive: (boolean) set to true if you'd like all files in sub directories too
getFilesInDirectory(`${__dirname}/src`); // ['img.png', ...']
getFilesInDirectory(`${__dirname}/src`, {
  recursive: true,
});

getFriendlyIndex

Given an index (starting at zero), return a human readable string to describe the index. Powered by Mout's nth function.

getFriendlyIndex(0); // 1st
getFriendlyIndex(1); // 2nd
getFriendlyIndex(2); // 3rd
// etc

getFullName

Given a name object, return a "full name" by concatenating the different parts of the name together.

getFullName({ prefix: "Mr", first: "John", middle: "Harry", last: "Smith" }); // Mr John Harry Smith
getFullName({ first: "John", last: "Smith" }); // John Smith
getFullName({ first: "John" }); // John
getFullName({}); // ''

getInboundEntityTypes

Get a list of the inbound entity types pointing at a given entityTypeId. That is - the entity types that can point at a given entity type via one or more "Point to another record" fields.

getInboundEntityTypes({
  entityTypeId, // the entity type being referenced
  allEntityTypes,
  includeRollupFields = false,  // include first/last reference rollup fields? (don't by default)
});

getMimeTypeCategory

For a given mime type, return a "normalised" category for it. The following categories are currently supported:

  • document
  • powerpoint
  • spreadsheet
  • pdf
  • image
  • video
getMimeTypeCategory("application/pdf"); // pdf
getMimeTypeCategory("image/png"); // image
getMimeTypeCategory(
  "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
); // spreadsheet

getMonthlySubscriptionAmount

For a given billing object, return the monthly amount rate that they are paying based on the number of licences they have across all account user types.

Returns the monthly cost, tax exclusive. The annual discount is applied to this cost if they are paying annually. Pass false as the second argument to disable this.

Returns 0 if billing is not active yet.

getMonthlySubscriptionAmount({
  "id": 23,
  "name": "Professional"
  "stripe_technology_subscription_id": "sub_123"
  // ...
}) // 50

getPortalFormUrl

Get the URL of a form, given a portalForm, account, and enviroment.

getPortalFormUrl({
  portalForm,
  account,
  enviroment: "production",
});
// => https://saveewoks.beaconforms.com/form/abc123

getPostcodeArea

Returns the UK postcode "area" from a postcode string. Only supports UK postcodes.

getPostcodeArea(`SY3 8AY`); // SY
getPostcodeArea(`e148dw`); // E
getPostcodeArea("Chris"); // null

getPrimaryField

For a given entity type (standard format with fields populated within it), return the primary field object for it.

getPrimaryField(personEntityType); // { id: 123, key: 'name', type: 'person_name', ... }

getQuotePairs

For a string, return an array of objects reflecting all of the double quote pairs in the string.

If there an odd number of quotes, the final closing will be null.

getQuotePairs('Hello "my" name is Chris');
// => [{ opening: 6, closing: 9 }]

getReferencedEntityTypes

Return an array of the entity types that a particular entity type references. For example, if you were looking at payments, you would get an array containing entity types like people, campaigns, funds, etc.

getReferencedEntityTypes({
  entityTypeId, // entity type doing the referencing
  allEntityTypes, // list of all entity types
  includeRollupFields, // should first/last rollup fields be included? (default `false`)
}); // [{ id: 123, key: 'person', ... }]

getRelatedFieldOptions

Return an array of options that reflect fields on a particular entity type, along with fields on record types that are pointed at. Each option includes:

  • id: the field ID of the option
  • via_entity_field_id: the reference field ID that we need to go "via" to get to id. This is only set for "remote" options.
  • label: nice human-friendly label
  • key: a unique string key for the option. This is just a JSON.stringify with sorted keys of id and via_entity_field_id. Nothing clever going on here.
getRelatedFieldOptions({
  entityTypeId,
  allEntityTypes,
  allowedFieldTypes: ["string", "select"], // defaults to all field types
  includeRollupFields: false, // defaults to true
  includeSmartFields: true, // defaults to true
  includeReferenceRollupFields: false, // defaults to whatever includeRollupFields is set to
  includeReferenceSmartFields: true, // defaults to whatever includeSmartFields is set to
});

getRelatedFieldValue

Given an entity and references, along with fieldId and viaFieldId, return the value of the fieldId for this entity.

This method always returns an array of field values (unless one of the field IDs is invalid). When viaFieldId is not specified, the array is always of length 1 (value is pulled from entity). When viaFieldId is specified, we're pulling from the values of each of the references.

A very useful counterpart to getRelatedFieldOptions.

// on the current entity
getRelatedFieldValue({
  entity,
  references,
  fieldId: 69,
  viaFieldId: null,
  allEntityTypes,
}); // => [{ amount: 10, currency: 'GBP' }]

// on a referenced record (returns an array of values)
getRelatedFieldValue({
  entity,
  references,
  fieldId: 69,
  viaFieldId: 52,
  allEntityTypes,
}); // => [['Donor'], ['Fundraiser']]

getSmartFieldTemplateParameters

For a given smart field template, return the parameters included in it.

getSmartFieldTemplateParameters("{{{amount}}} from {{{customer}}}");
// => ['amount', 'customer']

getStartOfUKTaxYear

For a given date, return the date at the start of the tax year in the UK.

getStartOfUKTaxYear(new Date(2016, 5, 1)); // new Date(2016, 3, 6)
getStartOfUKTaxYear(new Date(2013, 2, 1)); // new Date(2012, 3, 6)
getStartOfUKTaxYear(new Date(2012, 5, 2)); // new Date(2012, 3, 6)

getSubscriptionAmount

For a given billing object, return the amount that the customer is paying us on each invoice, tax exclusive.

Accounts for custom frequencies, and applies an annual discount if the technology_plan_frequency is 12.

Returns 0 if billing is not active yet.

getSubscriptionAmount({
  "id": 23,
  "name": "Professional"
  "stripe_technology_subscription_id": "sub_123"
  // ...
}) // 540

getSQSGroupId(identifier, numberOfGroups)

Given an integer identifier, return a string value that can be used as an SQS group ID to limit the number of concurrent messages being processed in a FIFO queue.

For example, we might only want to create max 3 indexes in the database at once. In this case, you can pass 3 as the second parameter, and the account ID as the first.

This function will return a string integer 1-3, depending on the number of the identifier.

You can also pass strings as the identifier! Only the numberic characters in the identifier are used to generate the group ID though.

// integer id usage
getSQSGroupId(20600, 5); // '1'
getSQSGroupId(20601, 5); // '2'
getSQSGroupId(20602, 5); // '3'
getSQSGroupId(20603, 5); // '4'
getSQSGroupId(20604, 5); // '5'
getSQSGroupId(20605, 5); // '1'
getSQSGroupId(20606, 5); // '2'

// string usage:
getSQSGroupId("acct_1JuxkJE1b0hni1ao", 2); // 2
getSQSGroupId("OR0000ES93S5XN", 7); // 5
getSQSGroupId("95520cb7-9b45-4fdf-8ed4-b9ecd2cd2f21", 3); // 2

getTaxExempt

Return the Stripe tax exempt status for a given country code.

  • none (taxable) for UK
  • reverse (reverse charge) for EU countries
  • exempt for everyone else (US, channel islands etc)
getTaxExempt('GB'); // none
getTaxExempt('DE'); // reverse
getTaxExempt('US'); // exempt

getUKTaxYear

For a given date, return the UK tax year that that date falls into, as a 4 digit integer. This returns the LAST year (e.g. 2018 from 2017/2018).

getUKTaxYear(new Date(2017, 2, 1)); // 2017
getUKTaxYear(new Date(2017, 3, 5)); // 2017
getUKTaxYear(new Date(2017, 11, 2)); // 2018

getPaymentProviderFromExternalId

For a given Stripe/GoCardless external ID stored on an entity, return the payment provider that corresponds to it.

Both subscription and payment IDs are supported.

getPaymentProviderFromExternalId("sub_123"); // stripe
getPaymentProviderFromExternalId("ch_123"); // stripe
getPaymentProviderFromExternalId("SB123"); // gocardless
getPaymentProviderFromExternalId("PM123"); // gocardless
getPaymentProviderFromExternalId(null); // undefined
getPaymentProviderFromExternalId(123); // undefined

hasDeletedFieldInFilterConditions

Given an array of filter conditions, return true if any of the fields (or entity types) used within the filter conditions at ANY DEPTH are deleted.

hasDeletedFieldInFilterConditions({
  filterConditions, // [{ field: 'name.first', operator: '==', ... }]
  entityTypeId, // ID of entity type being filtered
  allEntityTypes,
  includeAggregateConditions, // default `true`. Traverse through nested aggregate conditions?
});

hasDynamicDateFieldInFilterConditions

Given an array of filter conditions, return true if any of the fields are date fields using a "dynamic value" (e.g. this_month, today) in them.

hasDynamicDateFieldInFilterConditions({
  filterConditions, // [{ field: .... }]
  entityTypeId, // id of the entity type we're filtering
  allEntityTypes,
});

hasDynamicDateFieldInFilterConditionsChangedSinceYesterday

Given an array of filter conditions, return true if any of the fields are date fields using a "dynamic value" (e.g. this_month, today) in them, and the date bounds that they represent have changed since yesterday.

For example, if you were using this_week in a filter condition, this would only return true on Mondays.

hasDynamicDateFieldInFilterConditionsChangedSinceYesterday({
  filterConditions, // [{ field: .... }]
  entityTypeId, // id of the entity type we're filtering
  allEntityTypes,
});

hasDynamicDateValueChangedSinceYesterday

Given a dynamic date value (e.g. this_year, today), return true if the date bounds that it represents have changed since yesterday, false otherwise.

hasDynamicDateValueChangedSinceYesterday("today"); // true
hasDynamicDateValueChangedSinceYesterday("this_week"); // false (`true` on mondays)

hasEntityFieldPermission

Counterpart to getEntityFieldPermission. Returns true if a user has a required permission on a particular field, false otherwise.

hasEntityFieldPermission({
  fieldId, // field id to check
  entityTypeId, // entity type it belongs to
  isAdmin, // boolean
  permissions, // full user permissions object
  requiredPermission, // 'read' or 'write'
}); // `true`

hasFieldOfTypeInFilterConditions

Given an array of filter conditions, return true if any of the conditions are based on fields of a particular type, false otherwise.

Works at any level of nesting in reference and aggregate filters.

hasFieldOfTypeInFilterConditions({
  type, // e.g. 'date'
  filterConditions, // [{ field: .... }]
  entityTypeId, // id of the entity type we're filtering
  allEntityTypes,
  isAllowedCondition, // optional function
});

Optionally, an isAllowedCondition function can be passed, which is invoked with each condition. You can use this to evaluate whether a condition should be allowed, based on condition.operator, condition.value, etc.

hasSettingPermission

Return true if a user has permission to read or write on a particular setting (e.g. portal_forms), false otherwise.

hasSettingPermission({
  type, // e.g. 'apps'
  requiredPermission, // 'read' or 'write'
  isAdmin, // boolean
  permissions,
}); // true

hasStrongEntityTypePermission

Returns true if the user has a given permission on all fields in a given entity type, false otherwise.

If isAdmin is true, then this will always return true.

hasStrongEntityTypePermission({
  entityType,
  isAdmin, // true or false
  permissions, // permissions object
  requiredPermission, // 'read' or 'write'
}); // true

hasWeakEntityTypePermission

Returns true if the user has a given permission on any fields in a given entity type, false otherwise.

If isAdmin is true, then this will always return true.

hasWeakEntityTypePermission({
  entityType,
  isAdmin, // true or false
  permissions, // permissions object
  requiredPermission, // 'read' or 'write'
}); // true

isArrayFieldType

Return true if fieldType is an "array" field type (has values stored as an array), false otherwise.

isArrayFieldType("email"); // true
isArrayFieldType("select"); // true
isArrayFieldType("string"); // false
isArrayFieldType("number"); // false

isBeaconEmail

Returns true if an email address string is a Beacon email address, false otherwise. Checks for:

  • @beaconcrm.org
  • @beaconcrm.co.uk
  • @beaconproducts.co.uk
isBeaconEmail("[email protected]"); // true
isBeaconEmail("[email protected]"); // false

isBillingConfigured

Given a billing argument, return true if it reflects a configured billing setup, false otherwise.

isBillingConfigured({
  id: 23,
  plan: "Professional",
  // ...
}); // true

isBillingConfigured({
  id: null,
  plan: null,
  // ...
}); // false

isBillingActive

Given a billing argument, return true if it is "active" (there is a subscription), false otherwise.

isBillingConfigured({
  "id": 23,
  "name": "Professional"
  "stripe_technology_subscription_id": "sub_123"
  // ...
}) // true

isBillingConfigured({
  "key": "billing_not_configured"
  // ...
}) // false

isDisposableEmail

Returns true if an email address is disposable, false otherwise. Uses disposable-email-domains.

isDisposableEmail("[email protected]"); // `true`
isDisposableEmail("[email protected]  "); // true
isDisposableEmail("[email protected]"); // false

dynamicDateValues

An array of all of the possible dynamic date values, for example:

  • this_year
  • next_week
  • today

Used internally by isDynamicDateValue.

isDynamicDateValue

Returns true if the value is a dynamic date value (e.g. this_year), false otherwise.

isDynamicDateValue("this_year"); // true
isDynamicDateValue("2021-04-02"); // false
isDynamicDateValue("chris"); // false

dynamicDateOperators

An array of all of the possible dynamic date operators, for example:

  • date_after_relative
  • date_exact_relative
  • date_before_relative

Used internally by isDynamicDateOperator.

isDynamicDateOperator

Returns true if the operator is a dynamic date value (e.g. date_exact_relative), false otherwise.

isDynamicDateValue("date_exact_relative"); // true
isDynamicDateValue("date_on"); // false

isEntityFieldValueMatch

A synchronous JavaScript-only function to check to see if an entity field value matches a given condition.

This implements a sub-set of our standard filtering system (which is implemented at the database level), and is designed for immediate, more primitive value checking. Quite useful when you have all of the data you need in variables. (e.g. when checking an entity + its references)

Currently used in:

  • Card visibility
  • Smart field rules

If you need more rules that aren't yet implemented, please add them!

// Syntax:
isEntityFieldValueMatch({
  fieldValue, // the saved entity field value
  fieldType, // field type
  conditionOperator, // condition operator
  conditionValue, // condition value
});

// Example 1: "is"
isEntityFieldValueMatch({
  fieldValue: ["Donor"],
  fieldType: "select", // type of field
  conditionOperator: "contains", // condition operator
  conditionValue: "Donor", // condition value
}); // true

// Example 2: "is not checked"
isEntityFieldValueMatch({
  fieldValue: true,
  fieldType: "boolean", // type of field
  conditionOperator: "==",
  conditionValue: false,
}); // false

// Example 3: "starts with"
isEntityFieldValueMatch({
  fieldValue: "ch_123",
  fieldType: "string", // type of field
  conditionOperator: "starts_with",
  conditionValue: "ch_",
}); // true

isEntityTypeDeleted

Returns true if a entityTypeId does not exist within allEntityTypes, false otherwise.

If null or undefined is passed, also returns false. (Handy for drop-down fields when the value hasn't been selected yet)

isEntityTypeDeleted(12, allEntityTypes); // true
isEntityTypeDeleted(23, allEntityTypes); // false
isEntityTypeDeleted(null, allEntityTypes); // false
isEntityTypeDeleted(undefined, allEntityTypes); // false

isEntityTypeEnabled

Returns true if an entity type is enabled based on elements configuration, false otherwise.

isEntityTypeEnabled({
  id: 1
  enabledElements: [], // e.g. ['fundraising', 'workflows']
  allEntityTypes,
})
// => true

A key parameter can also be passed instead of the id.

isEntityTypeEnabled({
  key: "ticket",
  enabledElements: ["fundraising"],
  allEntityTypes,
});
// => false

isFieldEnabled

Returns true if a field is enabled based on elements configuration, false otherwise.

isFieldEnabled({
  id: 11,
  enabledElements: ["fundraising"],
  allEntityTypes,
});
// => true

isFeatureEnabled

Returns true if a feature (e.g. roles_and_permissions) is enabled on an account based on their billing settings, false otherwise. This method handles the basic feature flags, and add-ons as well.

isFeatureEnabled("workflows", billing); // true
isFeatureEnabled("roles_and_permissions", billing); // false
isFeatureEnabled("memberships", billing); // true
isFeatureEnabled("ticketing", billing); // true
isFeatureEnabled("zapier", billing); // true
isFeatureEnabled("api", billing); // true

isFieldDeleted

Returns true if a fieldId does not exist within allEntityTypes, false otherwise.

If null or undefined is passed, also returns false. (Handy for drop-down fields when the value hasn't been selected yet)

isFieldDeleted(123, allEntityTypes); // true
isFieldDeleted(456, allEntityTypes); // false
isFieldDeleted(null, allEntityTypes); // false
isFieldDeleted(undefined, allEntityTypes); // false

isLocationEqual

Returns true if two "location" objects are the same. Doesn't require strict equality, but does a lot of handy checks under the hood:

  • If address line 1 and postcode match, then will return true
  • (UK only) If only postcodes match, will return true
  • (International) If only postcodes match, will return false
  • One of the postcodes is blank, will return false
  • Auto-formats UK postcodes when comparing
const first = {
  address_line_one: "Crowmeole Farm",
  address_line_two: null,
  address_line_three: null,
  city: "Shrewsbury",
  country: "United Kingdom",
  country_code: "GB",
  is_primary: true,
  latitude: 52.700747,
  longitude: -2.785249,
  notes: null,
  postal_code: "SY3 8AY",
  region: null,
};

const second = {
  address_line_one: "crowmeole farm   ", // whitespace and case ignored
  address_line_two: null,
  address_line_three: null,
  city: "Shrewsbury",
  country: "United Kingdom",
  country_code: "GB",
  is_primary: true,
  latitude: 52.700747,
  longitude: -2.785249,
  notes: null,
  postal_code: "sy38ay   ",
  region: null,
};

isLocationEqual(first, second); // true

isNamePrefix

Returns true if a string is a "name prefix", false otherwise.

isNamePrefix("Mr"); // true
isNamePrefix("Mr."); // true
isNamePrefix("mr"); // true
isNamePrefix("  mr.   "); // true
isNamePrefix("chris"); // false

isNumeric

Returns true if a value is numeric, false otherwise.

isNumeric(1); // true
isNumeric("1"); // true
isNumeric("-51"); // true
isNumeric("1.23"); // true
isNumeric("chris"); // false

isPersonalEmail

Returns true if a string is a personal email address, false otherwise.

Auto-checks about 3.5k personal email domains (gmail.com, algeria.com, etc).

isPersonalEmail("[email protected]"); // true
isPersonalEmail("[email protected]"); // false

isRestrictedActivityEmailField

Activities can set restricted fields.

Check to see if a field is restricted or not.

isRestrictedActivityEmailField({
  field: {
    key: "content",
  },
  actorType: "user",
  actorId: 2,
  createdByUserId: 1,
  entityTypeKey: "activity",
  isPrivate: true,
});
// => true
isRestrictedActivityEmailField({
  field: {
    key: "content",
  },
  actorType: "user",
  actorId: 2,
  createdByUserId: 1,
  entityTypeKey: "person",
  isPrivate: true,
});
// => false

isSmartFieldCircularDependency

Return true if a smart field depends on itself via a circular dependency, false otherwise. (We should not allow users to create fields that get into this situation)

isSmartFieldCircularDependency({
  field, // smart field
  entityType,
});
// => false

isSnakeCase

Returns true if a string is snake cased, false otherwise.

isSnakeCase("chris_test"); // true
isSnakeCase("greatstuff"); // true
isSnakeCase("FrodoBaggins"); // false
isSnakeCase("Frodo Baggins"); // false

isValidCountryCode

Returns true if a string is a valid ISO 2 letter country code, false otherwise.

isValidCountryCode("GB"); // true
isValidCountryCode("CA"); // true
isValidCountryCode("FRODO"); // false

isValidCurrencyCode

Returns true if a string is a valid ISO 3 letter currency code, false otherwise.

isValidCurrencyCode("GBP"); // true
isValidCurrencyCode("Luke Skywalker"); // false
isValidCurrencyCode(null); // false

isValidLocale

Returns true if a string is a valid locale, false otherwise. Works for both long format locales (e.g. en_GB) and short formats (en).

isValidLocale("en_GB"); // true
isValidLocale("en"); // true
isValidLocale("orcish_MO"); // false

isValidPhoneNumber

Returns true the phone number is valid, false otherwise.

isValidPhoneNumber("07234567324", "GB"); // true

isValidUKPostcode

Returns true if a string is a valid UK postcode, false otherwise.

isValidUKPostcode("SW11 2AE"); // true
isValidUKPostcode("sw112ae"); // true
isValidUKPostcode("chris"); // false

isWrappedInQuotes

Returns true is a string is wrapped in double or single quotes, false otherwise.

isWrappedInQuotes('"Boris"'); // true
isWrappedInQuotes("Is the best prime minister ever"); // false

jsonStringifySorted

Runs JSON.stringify on an array/object, and sorts the keys within it. For all the times you need to have sorted JSON strings.

jsonStringifySorted({
  last_name: "Houghton",
  first_name: "Chris",
  age: 30,
}); // {"age":30,"first_name":"Chris","last_name":"Houghton"}

lastName

Get the last name for a given string. Treats the first non-prefix word as the first name, and all other names after it as the last name.

lastName("Frodo Baggins"); // Baggins
lastName("Mr Frodo Baggins"); // Baggins
lastName("Mr. Frodo David Baggins"); // David Baggins

locales

An array of all supported locales, including their code and name. Used in isValidLocale.

console.log(locales); // [{ code: 'af', name: 'Afrikaans' }, ...]

mapFormSections

For default behaviour, no need to pass a predicate:

Given a beacon portal form 'sections' array and a callback, applies callback to any generic fields type field under a section, whether deeply nested or not. The rest of the data is returned unmodified.

mapFormSections({
  sections: [
    {
      id: 2,
      prop: true,
      fields: [{}, {}, {}],
    },
  ],
  callback: (fields) => map(fields, (field) => ({ ...field, new_prop: true })),
});
// [ { id: 2, prop: true, fields: [ { new_prop: true }, { new_prop: true }, { new_prop: true } ] } ]

For non-default behaviour, pass a special predicate for callback application:

mapFormSections({
  sections: [
    {
      id: 2,
      prop: true,
      something_else: [{}, {}, {}],
    },
  ],
  callback: (data) => map(data, (x) => ({ ...x, new_prop: true })),
  predicate: ([key]) => key === "something_else",
});
// [ { id: 2, prop: true, something_else: [ { new_prop: true }, { new_prop: true }, { new_prop: true } ] } ]

mapRelativeDate

Given a string (e.g. tomorrow) return a date string in the format YYYY-MM-DD that corresponds to that date. By default, this date is relative to the current time, but a second date parameter can be passed to set a custom time.

Also supports an n_days format, where n is the number of days after today.

All supported relative dates are provided in the example below:

mapRelativeDate("today"); // 2020-10-25
mapRelativeDate("tomorrow"); // 2020-10-26
mapRelativeDate("monday"); // 2020-10-26
mapRelativeDate("one_week"); // 2020-11-01
mapRelativeDate("one_month"); // 2020-11-25
mapRelativeDate("6_days"); // 2020-10-31

matchesSchema

Validates a value against a given JSON schema. If invalid, an error will be thrown. If valid, then nothing will happen.

Powered by revalidator. (Go there for more docs)

const value = {
  first: "Chris",
  last: null,
};

matchesSchema(value, {
  type: "object",
  properties: {
    first: {
      type: "string",
      required: true,
    },
    last: {
      type: "string",
      required: true,
    },
  },
});

// throws ValidationError(`last is required`);

Optionally, a third prefix argument can be provided, which prefixes the error message.

A fourth options argument can be provided, which directly corresponds to the options parameter in revalidator. It defaults to:

{
  validateFormats: true,
  validateFormatsStrict: false,
  additionalProperties: false,
}

namePrefix

For a given string, returns the string for the prefix(es) at the start of it.

namePrefix("Mr Frodo Baggins"); // Mr
namePrefix("Mr Prof Frodo Baggins"); // Mr Prof
namePrefix("Frodo Baggins"); // undefined

namePrefixes

An array of 250+ name "prefixes", including Mr, Mrs, Seaman, and Rabbi.

console.log(namePrefixes); // ['Mr', 'Mrs', ...]

openPopup

Client side only. Opens a popup window. Should be executed synchronously immediately after clicking on a button to prevent popup blockers.

Useful for OAuth flows, among other things.

openPopup(url, title, width, height);

parallelLimit

Runs an array of promise-returning functions in batches (default batch size 10), waiting for each batch to finish before starting the next. Results are returned in the same order as the input functions.

const parallelLimit = require('@beacon/utils/parallelLimit');

// change this to an array of promises
const tasks = [async () => 'a', async () => 'b', async () => 'c'];
const results = await parallelLimit(tasks, 2); // ['a', 'b', 'c']

personNameToString

For a Beacon "person name" object (format of the person name field), return a string that can be rendered into emails, or otherwise.

const name = {
  prefix: null,
  first: "Frodo",
  middle: "David",
  last: "Baggins",
};
personNameToString(name); // Frodo David Baggins

Optionally, a second includePrefix argument can be provided, indicating whether to prepend the string with the prefix.

platformEntityFields

An array of the "platform" entity fields, including id, avatar, created_by_id, etc.

console.log(platformEntityFields); // ['id', 'created_at', ....]

sanitizeEntityPayload

For a given payload that will be sent to the create/update entity API endpoint, santise the data, ensuring that:

  • Only one value is provided if allow_multiple is false (applies to select, email, location, phone, user, and reference field types)
  • Only valid values in select fields
  • Remove invalid reference IDs from reference fields

safeParseJson

Safely parses a JSON string, returning the parsed object or a default value if parsing fails.

safeParseJSON('{"name": "Fazza"}'); // {name: "Fazza"}
safeParseJSON('invalid json'); // undefined
safeParseJSON('invalid json', {}); // {}
safeParseJSON('invalid json', null); // null

sentenceCase

Convert a string into "Sentence case" (first letter of each sentence capitalized, rest lowercase).

sentenceCase('lorem Ipsum doLOr. sit amet dolor.');
// output: Lorem ipsum dolor. Sit amet dolor.

snakecase

Given a string it will convert it to snakecase

snakecase('my wildString with áccénts')
// my_wild_string_with_accents

spliceString

Inserts a string into another string at a certain index. Returns a new string value, does not mutate the original one.

spliceString("Chris Houghton", 6, "John "); // Chris John Houghton

startsWithVowel

Returns true if a string starts with a vowel, false otherwise.

startsWithVowel("chris"); // false
startsWithVowel("alice"); // true

stateCodes

An array of all US states.

[{ code: 'AL', name: 'Alabama' }, ...]

timeout

Promise wrapper around the native setTimeout.

await timeout(1000);

titleCase

Convert a string into "Title Case" (each word capitalised);

titleCase('this string is ODD');
// output: This String Is Odd

getGraphQLEntityField

Given a particular field type, return a value that will allow the selection of all of the data in that field.

This is the JSON expression that will get passed to jsonToGraphQLQuery that's used to generate the actual graphql query.

This works for primitive field types (e.g. short text just returns a boolean of true) and complex fields.

getGraphQLEntityField(fieldType);

hasNotificationPermission

Given an 'account notification setting' validates whether a user has the relevant permissions to receive a notification.

hasNotificationPermission({
  accountNotificationSetting,
  accountUser,
  permissions,
  entityType,
});

isFieldValueBlank

Determines if the value of a given field is blank. Checks things like whether each key in a person field is blank.

isFieldValueBlank({
  value,
  field,
});

getMappingFieldDepth

Mapping fields are used to map fields to columns when running imports. Mappings which are at least two steps removed will have a depth of 1 upwards. The mapping depth is used to determine offsets when evaluting a key.

getMappingFieldDepth("123.456.789");

findRanges

Find all of the "ranges" where a particular regex pattern matches.

findRanges("my name is chris and his name is david", /(name)/g);
// => [{ string: 'name', start: 3, end: 7, length: 4 }, ...]);

replaceRange

Replace a range (between two indexes) of text within a string with a new bit of text.

Note: the second index is exclusive, i.e. before that index.

replaceRange(
  "My name is Chris",
  3, // start (inclusive)
  7, // end (exclusive)
  "first name"
);
// => My first name is Chris

requireIndex

Copy of https://github.com/stephenhandley/requireindex/blob/master/index.js with 'ts' added to the list of exts.

requireIndex(`${__dirname}/../../controllers`);

redactData

For a given object or array, redact the data found at the specific keys with a replacement (default is [REDACTED]).

const data = {
  id: 1,
  name: "Chris Houghton",
  email: {
    provider: "gmail",
    address: "[email protected]",
  },
};

redactData(data, {
  keys: ["name", "email.address"],
});
// =>
// {
//   id: 1,
//   name: '[REDACTED]',
//   email: {
//     provider: 'gmail',
//     address: '[REDACTED]',
//   },
// };

substituteSmartParameters

Given formula (starting with =), return a string that's ready to be passed through our formula parser. Substitute parameters, and auto-fix/quote them as needed.

For templates (not starting with =), this util just substitutes in parameters.

substituteSmartParameters("=IF({{{Widget count}}} > 4, TRUE, FALSE)", {
  "Widget count": 5,
});
// => =IF(5 > 4, TRUE, FALSE)

substituteSmartParameters('=IF({{{Type}}} = "Donor", TRUE, FALSE)', {
  Type: "Donor",
});
// => =IF("Donor" = "Donor", TRUE, FALSE)

substituteSmartParameters("{{{Title}}} is good", {
  Title: "Fundraising",
});
// => Fundraising is good

getFieldFromMappingKey

Get the field from a mapping key.

getFieldFromMappingKey("123.456.789", entityTypes);

findMatViewCondition

Get the first (if any) 'in_materialized_view' condition block from a set of filter conditions. Can handle any degree of nested-ness...

NOTE! - at this point, does not yet take account of related X-filters or aggregate conditions...

findMatViewCondition({
  filter_conditions: [
    {
      field: "id",
      operator: "s987df",
      value: 395,
    },
    {
      field: "id",
      operator: "rahh",
      value: 765,
    },
    {
      field: "id",
      operator: "in_materialized_view",
      value: 146,
    },
  ],
  filter_strictness: "all",
});

cleanFileName

Clean file names to ensure they can be read from S3.

cleanFileName("thefilename$$"); // returns thefilename

hasAuditLogInFilterConditions

Returns if the filter conditions contain an audit_log filter

hasAuditLogInFilterConditions({ filterConditions }); // returns true/false

validateSchema

Validates a value against a given Zod schema. If invalid, an error will be throw. If valid, the validated object will be returned with the correct type.

This is useful for validating input that may be unknown, e.g. from a request, invocation, a file, etc.

Powered by zod. (Go there for more docs)

// imagine we've loaded some data from a CSV file
const value = parsedCsvFile[0];

// we can validate against the schema to ensure correctness
const result = validateSchema(value, z.object({
  first: z.string(),
  last: z.string(),
}));

// result is of type { first: string; last: string; }

hasEntityAccessRuleChanged

Initialise on a current state of access rules for a given role: util returns a callback function cb to be passed as a callback to an iterator on a new state of the same rules; cb returns boolean whether the matching rule state has changed...

Where rule contains via_field_ids or filter_conditions util will ignore changes of item order in change determination - this only is feasible in case of filter_conditions due to access rules forbidding deeply nested conditions.

Note that if core rule data is invalid (e.g. core properties have changed) a validation error is thrown, in all other cases we return boolean.

Note that because we are testing the new state against the old, if items are being removed from the old state and therefore are not contained in the new, util will be none the wiser - ensuring that deleted items are removed is a related but separate activity.

Note that currently util provides its own types and schema definitions - these duplicate definitions buyild in the entities service but maybe we can find a way to centralise these definitions at some time (in @beacon/types etc.).

const state: AccessRule[] = [
  {
    id: 10,
    account_id: 11,
    entity_type_id: 12,
    role_id: 13,
    type: 'all',
    conditions: null,
  },
];

const rule: AccessRule = {
  id: 10,
  account_id: 11,
  entity_type_id: 12,
  role_id: 13,
  type: 'forbidden',
  conditions: null,
};

hasRuleChanged(state)(rule); // true

[rule].filter(hasRuleChanged(state)) // [rule]

namecase

Given a name string, convert it into a propercased format, allowing for some of the exceptions that exist in the world.

A direct port of the PHP library https://github.com/tamtamchik/namecase

Usage

nameCase('chris houghton')
// => Chris Houghton

Examples

keith = Keith
yuri's = Yuri's
leigh-williams = Leigh-Williams
mccarthy = McCarthy
machin = Machin
machlin = Machlin
machar = Machar
mackle = Mackle
macklin = Macklin
mackie = Mackie
macquarie = Macquarie
machado = Machado
macevicius = Macevicius
maciulis = Maciulis
macias = Macias
macmurdo = MacMurdo
o'callaghan = O'Callaghan
st. john = St. John
von streit = von Streit
van dyke = van Dyke
van = Van
ap llwyd dafydd = ap Llwyd Dafydd
al fahd = al Fahd
al = Al
el grecco = el Grecco
ben gurion = ben Gurion
ben = Ben
da vinci = da Vinci
di caprio = di Caprio
du pont = du Pont
de legate = de Legate
del crond = del Crond
der sind = der Sind
van der post = van der Post
van den thillart = van den Thillart
della vinci = della Vinci
von trapp = von Trapp
la poisson = la Poisson
le figaro = le Figaro
mack knife = Mack Knife
dougal macdonald = Dougal MacDonald
ruiz y picasso = Ruiz y Picasso
dato e iradier = Dato e Iradier
mas i gavarró = Mas i Gavarró
henry viii = Henry VIII
louis iii = Louis III
louis xiv = Louis XIV
charles ii = Charles II
fred xlix = Fred XLIX
yusof bin ishak = Yusof bin Ishak

timeLogger

Structured logging utility for measuring how long a block of code takes.

Example:

import { setContext, timeLogger } from '@beacon/utils/timeLogger';

setContext({
  serviceName: 'hello-world-service',
  accountIds: [123, 345],
});

const logger = createTimeLogger({ type: 'my-func', accountId: 123 });

const span = logger.startSpan('foo', { bar: 999 });
// do some work
await new Promise((resolve) => setTimeout(resolve, 100));
span.end();

// do more work
await new Promise((resolve) => setTimeout(resolve, 100));

logger.end();
{"service":"hello-world-service","account_id":123,"type":"my-func","metric_event":"foo","duration_ms":101,"bar":999}
{"service":"hello-world-service","account_id":123,"type":"my-func","metric_event":"my-func","duration_ms":202}

logging

A logger is available in the @beacon/utils/logging module. It will set the expected log level based on the deployment stage (development, staging, production - via NODE_ENV), or the LOGGING_LEVEL environment variable if set.

import logger from '@beacon/utils/logging';

logger.info('hello world');

_types

This is a private package to enforce type strictness internally. It should not be used outside of code in beacon/utils

Deployment

Merge into the master branch with a new package.json version and Github actions will deploy to NPM.

License

Apache2