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);
Updated almost 2 years ago