@chcaa/strapi-utils v0.5.0
@chcaa/strapi-utils
A set of utility modules to make it easier to work with strapi.
Installation
Run npm install @chcaa/strapi-utils
Modules
@chcaa/strapi-utils contains the following modules:
lifecycle/CollectionChangedHook
Strapi's native collection lifecycle hooks are fine when you only care about changes to the collection itself, but if you also need to know when relations have changed, e.g. to update a slug field, it can become a complex task to handle when only using the native hooks.
The @chcaa/strapi-utils/lifecycle.CollectionChangedHook makes it easy to be notified whenever a collection or it's
relations has changed simply by registering the paths of interest.
Create an instance using let cch = new CollectionChangedHook().
This would typically be done in strapi's bootstrap() startup hook, but could be done anywhere.
The constructor can take an optional options object to configure the registered listeners (can be overwritten pr. listener). The following options can be set:
options:object
onSelf:objectemitBeforeCreate:boolean default=falseshould listeners observing their own collection receivebeforeCreateevents?emitAfterCreate:boolean default=trueshould listeners observing their own collection receiveafterCreateevents?emitBeforeUpdate:boolean default=falseshould listeners observing their own collection receivebeforeUpdateevents?emitAfterUpdate:boolean default=trueshould listeners observing their own collection receiveafterUpdateevents?emitBeforeDelete:boolean default=falseshould listeners observing their own collection receivebeforeDeleteevents?emitAfterDelete:boolean default=falseshould listeners observing their own collection receiveafterDeleteevents?
causedBy:objectincludeBeforeState:boolean default=falseinclude before state of changed entries on afterUpdate events. To e.g. create diffsincludeAfterState:boolean default=falseinclude after state of changed entries on afterUpdate events. To e.g. create diffs in combination withincludeBeforeStatecalculateAttributesChanged:boolean default=falsecalculate and include which attributes did change on afterUpdate events. For each entity changed an object with properties matching the entities will be included withtrue|falseif the attribute value was changed or not. If not set explicitly tofalseincludeBeforeStateandincludeAfterStatewill be included as wellincludeInferredAfterState:boolean default=falseinclude inferred after state of changed entries onbeforeUpdateevents on listeners observing the collection itself. The state is inferred using the current state and thenstrapiEvent.params.dataobject.calculateInferredAttributesChanged:boolean, default=falsecalculate and include inbeforeUpdateevents on listeners observing the collection itself which inferred attributes would change. The changes are based on comparing the current state with the inferred after state.NOTE
includeInferredAfterStateandcalculateInferredAttributesChangedcannot infer changes to component data due to insufficient info in the strapiEvent, for components use theafterStateandcalculateAttributesChangesin afterXXX events. HINTids,beforeState,afterStateandchangedAttributesarrays are all ordered in the same way so to find the matching object ofbeforeState,afterStateandchangedAttributescan be done as:for (let i = 0; i < ids.length; i ++) { let beforeEntity = causedBy.beforeState[i]; let afterEntity = causedBy.afterState[i]; let attributesChanged = causedBy.attributesChanged[i]; }
Register listeners for each collection to be notified about changes to using:
register(collectionUid, relationsPaths, listener, options)
collectionUid:stringthe uid of the collection to be notified about changes torelationPaths:string[]one or more relation paths to track for changes.listener:function(event:object)the function to call when changes occurevent.idsAffected:number[]the ids of the collection objects affected by the changeevent.causedBy:objectan object with information about the changesrelationPath:stringone of the registered paths which resulted in the change eventcollectionUid:stringthe uid of the collection which changedids:number[]: the ids of the collection which changedeventType:stringdescribes what happened to the affected path in form of one of"afterCreate"|"afterUpdate"|"afterDelete"and equivalent"beforeXXX"if enabled inoptionsstrapiEvent:objectthe strapi-event that triggered this event
options:object [optional]a set of options to configure the listener (overwrites the default options set in the constructor). See the constructor above for details
All relational paths must point to a collection but are allowed to have intermediate "components" in their path.
To get notifications about the collection itself register an empty string ("") along with the
other paths of interest.
In the example below we want to be notified about changes to movies when the movie itself ("") changes,
directors of the movie ("directors") changes or an actor in the cast ("cast.actor") changes.
const { CollectionChangedHook } = require('@chcaa/strapi-utils/lifecycle');
let cch = new CollectionChangedHook({ onSelf: { emitBeforeEvents: true }});
cch.register("api::movie.movie", ["", "directors", "cast.actor"], ({ idsAffected, causedBy }) => {
if (causedBy.eventType.startsWith('after')) { // we are also listening for beforeEvents on the path "", but for now we only react on afterXxx
for (let idChunk of _.chunk(idsAffected, 100)) { // use lodash to chunk the ids into sub-arrays of 100 ids each
let entries = await strapi.entityService.findMany('api::movie.movie', {
filters: {
$and: [{ id: { $in: idChunk } }]
},
populate: {
genres: true,
cast: { populate: { actor: true } }
}
});
// do something with the entries
}
}
});Warning! Prevent Infinite Loops 1. Do only modify data of paths not listened to or objects and relations attached to the owning collection identified by
idsAffected.Performance Note
Nested paths, especially ones including "components" can cause performance issues on larger datasets as they require a lot of joins for strapi to resolve the relations from the database.
To unregister use:
unregister(collectionUid, relationaths, listener)
collectionUid:stringthe uid of the collection to NOT be notified about changes to anymore. E.g. "api::movie.movie"relationPaths:string[]the relational paths to stop listening tolistener:functionthe listener to unregister (must be the same function which was passed toregister()
If the listener was registered for the relationPaths "a", "b" but only unregisters for "a" it will still be notified about changes to "b".