Cached value field
This field is useful when a certain property of the item is expensive
to calculate but changes rarely, or when it depends on fields from
other resources, but needs to be stored in the database and not
calculated on-the-fly so it can be efficiently filtered by.
The new value can be calculated either on some collection event, or on
a cron-like clock basis.
Constructor
The basic synnopsis is:
new FieldTypes.CachedValue(child_field: Field, cache_settings: CachedValueSettings);
the field created by above constructor would have the structure of given
child_field, but will not be writable by the user directly, instead it's value
will be determined by cache_settings. cache_settings is an object with the
following keys:
- refresh_on: an array of RefreshCondition objects (described below)
- get_value: a function that calculates a new value for this field, based on the received event (see RefreshCondition). The function takes context (Context) and item (CollectionItem) as arguments and has to return data that is storaable within child_field.
- initial_value: the value this field should have right when it's created for a new resource.
- derive_from: optional. A list of strings. If you put any field names in this array, any change to any of those given fields will trigger get_value reevaluation.
RefreshCondition has the following structure:
- event: instance of Sealious.EventDescription. Example: new Sealious.EventDescription("users", "after:create") will make the refresh condition react every time a new user is created.
- resource_id_getter: a function that is called when the event is triggered. The function has to return the ID (as string or a Promise<string>) of the resource whose cached_value needs to be recalculated. The function is given context, item (the item that triggered the event) and item_id (that points to the item which is this new value will be added to)
Example
This app will keep count of how many people like a given person. The
popularity field will be recalculated every time a who-likes-who entry
pointing at the given person is created. This way you can efficiently sort and
filter people by their popularity.
To fully realize the functionality one would have to also add a listener for
deleting who-likes-who, but the below code is only for demonstrative purposes.
const app = new (class extends App { config = { /*...*/ }; manifest = { /*...*/ }; collections = { ...App.BaseCollections, people: new (class extends Collection { fields = { name: new FieldTypes.Text(), popularity: new FieldTypes.CachedValue(new FieldTypes.Int({ min: 0 }), { refresh_on: [ new CollectionRefreshCondition( "who-likes-who", "after:create", ([, item]) => [item.get("likes_this_person") as string] ), ], get_value: async function (context, item) { const is_liked_by = (await context.app.collections["who-likes-who"] .suList() .filter({ likes_this_person: item.id, }) .fetch()) as ItemListResult<any>; return is_liked_by.items.length; }, initial_value: 0, }), }; })(), "who-likes-who": new (class extends Collection { fields = { this_person: new FieldTypes.SingleReference("people"), likes_this_person: new FieldTypes.SingleReference("people"), }; })(), }; })();
Instead of CollectionRefreshCondition, you can use
ClockEventDescription to make the value refresh on some interval.
class TheApp extends App { collections = { ...App.BaseCollections, tick_tock: new (class TickTock extends Collection { fields = { seconds: new CachedValue(new Int(), { initial_value: 0, refresh_on: [ new ClockEventDescription( "* * * * * *", async ([context]) => { const { items } = await context.app.collections.tick_tock .list(context) .fetch(); return items.map((i) => i.id); }, (app: App) => new SuperContext(app, Date.now()) ), ], get_value: async (context, item) => { return count.next().value; }, }), }; })(), }; }
Example with derived_from
By using derived_from, you can combine the benefits of CachedValue
and DerivedValue. The below example creates an is_hot field that
returns true either if given product is cheap (price of that product
is below 100), or if the product has any comments:
class TheApp extends App { collections = { ...App.BaseCollections, comments: new (class extends Collection { fields = { text: new FieldTypes.Text(), product: new FieldTypes.SingleReference("products"), }; })(), products: new (class extends Collection { fields = { name: new FieldTypes.Text(), price: new FieldTypes.Float(), // a product is considered hot if it's cheap or has at least one comment is_hot: new FieldTypes.CachedValue(new FieldTypes.Boolean(), { refresh_on: [ new CollectionRefreshCondition( "comments", ["after:create", "after:edit", "after:remove"], async ([, item]) => [item.get("product") as string] ), ], get_value: async (context, item) => { const price = await item.getDecoded("price", context); const comments_count = ( await context.app.collections["comments"] .suList() .filter({ product: item.id, }) .fetch() ).items.length; return (price as number) < 100 || comments_count > 0; }, initial_value: false, derive_from: ["price"], }), }; })(), }; }
File Metadata
- Mime Type
- text/plain
- Expires
- Mon, Dec 23, 04:53 (19 h, 3 m)
- Storage Engine
- blob
- Storage Format
- Raw Data
- Storage Handle
- 556818
- Default Alt Text
- cached-value.remarkup (6 KB)