evrythng.js v5.0.0

The release of evrythng.js version 5.0.0 brings several minor breaking changes, mostly relating to semantics and paving a way for future versions to easily expand functionality.

This migration guide details changes that require existing code to be updated, and what to do in each case. It also includes a list of all the new features added in this version.


Breaking Changes

Jump To ↓

Now includes evrythng-extended.js
Browser global variables
Updated scope names
User scope now requires only API key
$init has been replaced
The authorization API option is now apiKey
Resource iterators are now async generators
Reactor API consolidated


Now Includes evrythng-extended.js

This version of evrythng.js brings the additional Trusted Application and Operator related functionality into the main library, removing the need for a separate library and extra dependency. Simply update your app to version [email protected] or above and remove evrythng-extended from the dependency list.

Browser Global Variables

Apps that include evrythng.js using a <script> tag and access the EVT global variable must now access the evrythng global variable instead.

// Before
EVT.setup({ geolocation: false });
EVT.api(options);

// After
evrythng.setup({ geolocation: false });
evrythng.api(options);

Updated Scope Names

The App and TrustedApp scopes have been given clearer names to bring them inline with the rest of the scope types.

// Before
const app = new EVT.App(API_KEY);
const trustedApp = new EVT.TrustedApp(API_KEY);

// After
const app = new evrythng.Application(API_KEY);
const trustedApp = new evrythng.TrustedApplication(API_KEY);

User Scope Now Requires Only the API Key

Apps that store Application User credentials to be re-created each time an app is used now do so using only the API key.

const id = localStorage.getItem('userId');
const apiKey = localStorage.getItem('userApiKey');

// Before
const user = new EVT.User({ id, apiKey });

// After
const user = new evrythng.User(apiKey);

$init Has Been Replaced

Apps that immediately required a scope's resource data were previously required to await the $init promise exposed as a scope property. This has been formalized into a similarly named init() method that provides the same functionality.

📘

Note

If a scope's data is not immediately required, this step can continue to be skipped.

// Before
const app = new EVT.App(API_KEY);
await app.$init;
console.log(app.customFields);

// After
const app = new evrythng.Application(API_KEY);
await app.init();
console.log(app.customFields);

The Authorization API Option Is Now apiKey

When using api(), specifying an API key was previously done using the authorization option. This is now called apiKey to align with other instances where keys are specified, instead of representing the header name directly.

// Before
EVT.api({
  url: '/thngs',
  method: 'post',
  authorization: apiKey,
  data,
});

// After
evrythng.api({
  url: '/thngs',
  method: 'post',
  apiKey,
  data,
});

Resource Iterators Are Now async Generators

When paging through over 100 resources (such as Thngs), iterator() must be used. Previously this was done with an iterator-like object and the EVT.Utils.forEachAsync() method. Now, async generators can be used instead with pages().

const params = { perPage: 100, project }; 

// Before
const iterator = operator.thng().iterator({ params });
EVT.Utils.forEachAsync(iterator, (page) => {
  console.log(page);
});

// After
const iterator = operator.thng().pages({ params });
let page = await iterator.next();
while(!page.done) {
  console.log(page.value);
  page = await iterator.next();
}

Reactor API Consolidated

The methods for working with Reactor scripts, schedules, and logs have been merged into methods instead of the idiosyncratic property. Methods that were previously on a child property are now included on the parent property. An application resource now has reactorScript(), reactorSchedule(), and reactorLog(). An example for Reactor schedules is shown below:

// Before
trustedApp.reactor.schedule().read();

// After
trustedApp.reactorSchedule().read();

New APIs

Besides the above breaking changes, this version provides some new features and APIs that were missing in previous versions.

Jump To ↓

Thng and Product Redirection
Shared Accounts and Account Accesses
Domains and Short Domains
Trusted Application Secret Key
Account and Application Redirector
Aliased Resources
Parameter Setters
New Resource Methods


Thng and Product Redirection

Create, read, update, or delete a Thng or product's redirection directly.

const thngId = '...';
const data = { defaultRedirectUrl: 'https://google.com?thng={shortId}' };

// Before
EVT.api({
  url: `/thngs/${thngId}/redirector`,
  method: 'PUT',
  authorization: operator.apiKey,
  data,
});

// After (can be thng or product)
await operator.thng(thngId).redirection().update(data);

Shared Accounts and Account Accesses

Read and update accounts that the chosen operator has access to and is sharing with others.

// Before
EVT.api({
  url: '/accounts',
  authorization: operator.apiKey,
});

// After
const accounts = await operator.sharedAccount().read();

Operators can also read the individual accesses granted to each shared account:

const accountId = '...';

// Before
EVT.api({
  url: `/accounts/${accountId}/accesses`,
  authorization: operator.apiKey,
});

// After
const accounts = await operator.sharedAccount(account.id)
  .access()
  .read();

Domains and Short Domains

Setting up redirections and GS1 Digital Links requires knowledge of which domains and short domains are available. This is now easier to do:

const accountId = '...';

// Before
const shortDomains = await EVT.api({
  url: `/accounts/${accountId}/shortDomains`,
  authorization: operator.apiKey,
});

// After
const shortDomains = await operator.sharedAccount(accountId)
  .shortDomain()
  .read();

Similarly for domains:

const accountId = '...';

// Before
const domains = await EVT.api({
  url: `/accounts/${accountId}/domains`,
  authorization: operator.apiKey,
});

// After
const domains = await operator.sharedAccount(accountId)
  .domain()
  .read();

Trusted Application Secret Key

An Operator can read an Application's secret API key (otherwise known as the Trusted Application API Key) to create a scope with that level of access.

const projectId = '...';
const applicationId = '...';

// Before
const { secretApiKey } = await EVT.api({
  url: `/projects/${projectId}/applications/${applicationId}/secretKey`,
  authorization: operator.apiKey,
});

// After
const { secretApiKey } = await operator.project(projectId)
  .application(application.id)
  .secretKey()
  .read();

Account and Application Redirector

Adding and updating Redirector rules for the account and individual Applications is now easier to do, for example in response to external integrations or as part of some project setup script.

const data = {
  rules: [{
    match: 'thng.name=test',
  }],
};

// Before
EVT.api({
  url: '/redirector',
  authorization: operator.apiKey,
  method: 'PUT',
  data,
});

// After
operator.redirector().update(data);

And similarly for an Application Redirector:

const projectId = '...';
const applicationId = '...';

// Before
EVT.api({
  url: `/projects/${projectId}/applications/${applicationId}/redirector`,
  authorization: operator.apiKey,
  method: 'PUT',
  data,
});

// After
await operator.project(projectId).application(application.id)
  .redirector()
  .update(data);

Aliased Resources

If a use case or integration is to be used in a space or by a team with special domain-specific language, a convenient new feature is alias(), which allows aliasing of an existing resource type under a new name. The existing resource is still available; the aliased one behaves as if it had the old name. For example, if an EVRYTHNG product is known as a SKU:

// Add 'sku' alias to any Operator scopes created
evrythng.alias({ product: 'sku' }, 'Operator');

// Read a list of 'sku's
const params = { perPage: 100, project };
const skuList = await operator.sku().read({ params });

Parameter Setters

Existing apps can use the params option when making an SDK-based API request to specify things like the number of items per page, which project to scope the request to, and so on. The syntax for this is cumbersome (especially so if only one parameter is to be used).

Some new chainable helper methods are available for most resources. As many as required can be used before finally calling create(), read(), update(), or delete().

const projectId = '...';

// Before
operator.thng().read({
  params: {
    filter: {
      tags: 'test',
    },
    project: projectId,
    perPage: 100,
  }
});

// After
operator.thng()
  .setFilter({ tags: 'test' })
  .setProject(projectId)
  .setPerPage(100)
  .read();

New Resource Methods

Some new convenience methods have been added to most resources to allow common operations to be performed.

Rescope a Resource

Change a resource's projects and users scopes without requiring a complex payload object. If users scopes aren't provided, they're preserved.

const projectId = '...';
const thngId = '...';

// Before
const payload = {
  scopes: {
    projects: [projectId]
  }
};
await operator.thng(thngId).update(payload);

// After
// Update just project scopes
await operator.thng(thngId).rescope([projectId]);

// Also scope to all users
await operator.thng(thngId).rescope([projectId], ['all']);

// Removed from all projects
await operator.thng(thngId).rescope([]);

Upsert a Resource

📘

Note

As of v5.3.0, upsert() also accepts a string to upsert by name.

If a resource is to exist uniquely according to some identifiers key (such as a serial number), the upsert() method on the resource can update it according to some payload, or else use that payload to create it. By default, it throws an error and does nothing if more than one match is found for the identifier value, but this can be overridden if required.

const payload = {
  name: 'Example Thng',
  identifiers: { serial: '2n8sfdn89f' },
};

// Before
const params = {
  filter: `identifiers.serial=${payload.identifiers.serial}`,
};
const found = await user.thng().read({ params });
if (!found) {
  return user.thng().create(payload);
}
return user.thng(found[0].id).update(payload);

// After
const updateKey = { serial: payload.identifiers.serial };
await user.thng().upsert(payload, updateKey);

Find a resource

The find() convenience method on a resource can locate one or more of that resource type by identifier.

const identifier = { serial: 'a87df678dfj' };

// Before
const params = {
  filter: `identifiers.serial=${identifier.serial}`, 
};
const found = await operator.thng().read({ params });

// After
const found = await operator.thng().find(identifier);