Changeset View
Changeset View
Standalone View
Standalone View
lib/app/base-chips/field-types/cached-value.js
"use strict"; | module.exports = App => ({ | ||||
const { getDateTime } = require("../../../utils/get-datetime.js"); | |||||
module.exports = app => ({ | |||||
name: "cached-value", | name: "cached-value", | ||||
get_description: function() { | get_description() { | ||||
return "Caches custom values. Takes care of cache invalidation."; | return "Caches custom values. Takes care of cache invalidation."; | ||||
}, | }, | ||||
get_default_value: async () => null, | get_default_value: async () => null, | ||||
value_path_after_field_name: ".value", | value_path_after_field_name: ".value", | ||||
is_proper_value: function(context, params, new_value) { | is_proper_value(context, params, new_value) { | ||||
if (!context.is_super) { | if (!context.is_super) { | ||||
return Promise.reject("This is a read-only field"); | return Promise.reject("This is a read-only field"); | ||||
} | } | ||||
return this._call_base_method( | return this._call_base_method( | ||||
"is_proper_value", | "is_proper_value", | ||||
context, | context, | ||||
params, | params, | ||||
new_value | new_value | ||||
); | ); | ||||
}, | }, | ||||
filter_to_query: async function(context, params, field_filter) { | async filter_to_query(context, params, field_filter) { | ||||
return this._call_base_method( | return this._call_base_method( | ||||
"filter_to_query", | "filter_to_query", | ||||
context, | context, | ||||
params, | params, | ||||
field_filter | field_filter | ||||
); | ); | ||||
}, | }, | ||||
init: function(collection, field_name, params) { | init(collection, field_name, params) { | ||||
const { refresh_on, get_value, base_field_type } = params; | const { refresh_on, get_value } = params; | ||||
this._check_for_possible_recursive_edits( | this._check_for_possible_recursive_edits( | ||||
app, | App, | ||||
refresh_on, | refresh_on, | ||||
collection.name, | collection.name, | ||||
field_name | field_name | ||||
); | ); | ||||
const create_action = refresh_on.find(({ event_matcher }) => | const create_action = refresh_on.find(({ event_matcher }) => | ||||
event_matcher.containsAction("create") | event_matcher.containsAction("create") | ||||
); | ); | ||||
if (create_action) { | if (create_action) { | ||||
app.addHook( | App.addHook({ when: "after", action: "start" }, () => | ||||
{ when: "after", action: "start" }, | this._refresh_outdated_cache_values( | ||||
async () => | |||||
await this._refresh_outdated_cache_values( | |||||
create_action, | create_action, | ||||
collection, | collection, | ||||
field_name, | field_name, | ||||
params | params | ||||
) | ) | ||||
); | ); | ||||
} | } | ||||
for (let { event_matcher, resource_id_getter } of refresh_on) { | for (let { event_matcher, resource_id_getter } of refresh_on) { | ||||
app.addHook(event_matcher, async (emitted_event, resource) => { | App.addHook(event_matcher, async (emitted_event, resource) => { | ||||
const cache_resource_id = await resource_id_getter( | const cache_resource_id = await resource_id_getter( | ||||
emitted_event, | emitted_event, | ||||
resource | resource | ||||
); | ); | ||||
await app.run_action( | await App.run_action( | ||||
new app.Sealious.SuperContext( | new App.Sealious.SuperContext( | ||||
emitted_event.metadata.context | emitted_event.metadata.context | ||||
), | ), | ||||
["collections", collection.name, cache_resource_id], | ["collections", collection.name, cache_resource_id], | ||||
"edit", | "edit", | ||||
{ | { | ||||
[field_name]: await get_value( | [field_name]: await get_value( | ||||
emitted_event.metadata.context, | emitted_event.metadata.context, | ||||
cache_resource_id | cache_resource_id | ||||
), | ), | ||||
} | } | ||||
); | ); | ||||
}); | }); | ||||
} | } | ||||
}, | }, | ||||
_check_for_possible_recursive_edits: function( | _check_for_possible_recursive_edits( | ||||
app, | app, | ||||
refresh_on, | refresh_on, | ||||
collection_name, | collection_name, | ||||
field_name | field_name | ||||
) { | ) { | ||||
const { Collection, Resource } = app.Sealious.EventMatchers; | const { Collection, Resource } = app.Sealious.EventMatchers; | ||||
const doesAnyMatches = refresh_on.some(({ event_matcher }) => { | const doesAnyMatches = refresh_on.some(({ event_matcher }) => { | ||||
if ( | if ( | ||||
event_matcher instanceof Collection || | event_matcher instanceof Collection || | ||||
event_matcher instanceof Resource | event_matcher instanceof Resource | ||||
) { | ) { | ||||
return event_matcher.collection_name === collection_name; | return event_matcher.collection_name === collection_name; | ||||
} | } | ||||
event_matcher.subject_path.test(`collections.${collection_name}`); | return event_matcher.subject_path.test( | ||||
`collections.${collection_name}` | |||||
); | |||||
}); | }); | ||||
if (doesAnyMatches) { | if (doesAnyMatches) { | ||||
throw new Error( | throw new Error( | ||||
`In the ${collection_name} collection definition you've tried to create the ${field_name} cached-value field that refers to the collection itself. Consider using 'derived-value' field type to avoid problems with endless recurrence.` | `In the ${collection_name} collection definition you've tried to create the ${field_name} cached-value field that refers to the collection itself. Consider using 'derived-value' field type to avoid problems with endless recurrence.` | ||||
); | ); | ||||
} | } | ||||
}, | }, | ||||
_refresh_outdated_cache_values: async function( | async _refresh_outdated_cache_values( | ||||
create_action, | create_action, | ||||
collection, | collection, | ||||
field_name, | field_name, | ||||
params | params | ||||
) { | ) { | ||||
const referenced_collection_name = | const referenced_collection_name = | ||||
create_action.event_matcher.collection_name; | create_action.event_matcher.collection_name; | ||||
const last_modified_resource = (await app.run_action( | const last_modified_resource = (await App.run_action( | ||||
new app.Sealious.SuperContext(), | new App.Sealious.SuperContext(), | ||||
["collections", referenced_collection_name], | ["collections", referenced_collection_name], | ||||
"show", | "show", | ||||
{ | { | ||||
sort: { "last_modified_context._metadata.timestamp": "desc" }, | sort: { "last_modified_context._metadata.timestamp": "desc" }, | ||||
pagination: { items: 1 }, | pagination: { items: 1 }, | ||||
} | } | ||||
)).items[0]; | )).items[0]; | ||||
if (!last_modified_resource) { | if (!last_modified_resource) { | ||||
return; | return; | ||||
} | } | ||||
const last_modified_timestamp = | const last_modified_timestamp = | ||||
last_modified_resource._metadata.last_modified_context.timestamp; | last_modified_resource._metadata.last_modified_context.timestamp; | ||||
const outdated_resources = await app.Datastore.aggregate( | const outdated_resources = await App.Datastore.aggregate( | ||||
collection.name, | collection.name, | ||||
[ | [ | ||||
{ | { | ||||
$match: { | $match: { | ||||
$or: [ | $or: [ | ||||
{ | { | ||||
[`${field_name}.timestamp`]: { | [`${field_name}.timestamp`]: { | ||||
$lt: last_modified_timestamp, | $lt: last_modified_timestamp, | ||||
}, | }, | ||||
}, | }, | ||||
{ [field_name]: { $exists: false } }, | { [field_name]: { $exists: false } }, | ||||
], | ], | ||||
}, | }, | ||||
}, | }, | ||||
] | ] | ||||
); | ); | ||||
if (!outdated_resources) { | if (!outdated_resources) { | ||||
return; | return; | ||||
} | } | ||||
const context = new app.Sealious.SuperContext(); | const context = new App.Sealious.SuperContext(); | ||||
for (let resource of outdated_resources) { | for (let resource of outdated_resources) { | ||||
const cache_value = await this.encode( | const cache_value = await this.encode( | ||||
context, | context, | ||||
params, | params, | ||||
await params.get_value(context, resource.sealious_id) | await params.get_value(context, resource.sealious_id) | ||||
); | ); | ||||
await app.Datastore.update( | await App.Datastore.update( | ||||
collection.name, | collection.name, | ||||
{ sealious_id: resource.sealious_id }, | { sealious_id: resource.sealious_id }, | ||||
{ $set: { [field_name]: cache_value } } | { $set: { [field_name]: cache_value } } | ||||
); | ); | ||||
} | } | ||||
}, | }, | ||||
encode: async function(context, params, value) { | async encode(context, params, value) { | ||||
return { | return { | ||||
timestamp: context.timestamp, | timestamp: context.timestamp, | ||||
value: await this._call_base_method( | value: await this._call_base_method( | ||||
"encode", | "encode", | ||||
context, | context, | ||||
params, | params, | ||||
value | value | ||||
), | ), | ||||
}; | }; | ||||
}, | }, | ||||
decode: function(context, params, value_in_db) { | decode(context, params, value_in_db) { | ||||
return this._call_base_method( | return this._call_base_method( | ||||
"decode", | "decode", | ||||
context, | context, | ||||
params, | params, | ||||
value_in_db ? value_in_db.value : null | value_in_db ? value_in_db.value : null | ||||
); | ); | ||||
}, | }, | ||||
format: function(context, params, decoded_value, format) { | format(context, params, decoded_value, format) { | ||||
const base_field_type = app.FieldType(params.base_field_type.name); | const base_field_type = App.FieldType(params.base_field_type.name); | ||||
return base_field_type.format( | return base_field_type.format( | ||||
context, | context, | ||||
params.base_field_type.params || {}, | params.base_field_type.params || {}, | ||||
decoded_value, | decoded_value, | ||||
format | format | ||||
); | ); | ||||
}, | }, | ||||
_call_base_method(method, context, params, arg) { | _call_base_method(method, context, params, arg) { | ||||
const base_field_type = app.FieldType(params.base_field_type.name); | const base_field_type = App.FieldType(params.base_field_type.name); | ||||
return base_field_type[method]( | return base_field_type[method]( | ||||
context, | context, | ||||
params.base_field_type.params || {}, | params.base_field_type.params || {}, | ||||
arg | arg | ||||
); | ); | ||||
}, | }, | ||||
}); | }); |