Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "hawk.api",
"version": "1.1.29",
"version": "1.1.30",
"main": "index.ts",
"license": "UNLICENSED",
"scripts": {
Expand All @@ -21,6 +21,8 @@
"devDependencies": {
"@shelf/jest-mongodb": "^1.2.2",
"@types/jest": "^26.0.8",
"@types/lodash.clonedeep": "^4.5.9",
"@types/lodash.mergewith": "^4.6.9",
"eslint": "^6.7.2",
"eslint-config-codex": "1.2.4",
"eslint-plugin-import": "^2.19.1",
Expand All @@ -37,7 +39,8 @@
"@graphql-tools/schema": "^8.5.1",
"@graphql-tools/utils": "^8.9.0",
"@hawk.so/nodejs": "^3.1.1",
"@hawk.so/types": "^0.1.31",
"@hawk.so/types": "^0.1.33",
"@n1ru4l/json-patch-plus": "^0.2.0",
"@types/amqp-connection-manager": "^2.0.4",
"@types/bson": "^4.0.5",
"@types/debug": "^4.1.5",
Expand Down Expand Up @@ -70,6 +73,8 @@
"graphql-upload": "^13",
"jsonwebtoken": "^8.5.1",
"lodash": "^4.17.15",
"lodash.clonedeep": "^4.5.0",
"lodash.mergewith": "^4.6.2",
"migrate-mongo": "^7.0.1",
"mime-types": "^2.1.25",
"mongodb": "^3.7.3",
Expand Down
130 changes: 130 additions & 0 deletions src/utils/merge.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import mergeWith from 'lodash.mergewith';
import cloneDeep from 'lodash.clonedeep';
import { patch } from '@n1ru4l/json-patch-plus';
import { GroupedEventDBScheme, RepetitionDBScheme } from '@hawk.so/types';

/**
* One of the features of the events is that their repetition is the difference
* between the original, which greatly optimizes storage. So we need to restore
* the original repetition payload using the very first event and its difference
* between its repetition
*
* @deprecated remove after 6 september 2025
* @param originalEvent - the very first event we received
* @param repetition - the difference with its repetition, for the repetition we want to display
* @returns fully assembled payload of the current repetition
*/
export function repetitionAssembler(originalEvent: GroupedEventDBScheme['payload'], repetition: GroupedEventDBScheme['payload']): GroupedEventDBScheme['payload'] {
const customizer = (originalParam: any, repetitionParam: any): any => {
if (repetitionParam === null) {
return originalParam;
}

if (typeof repetitionParam === 'object' && typeof originalParam === 'object') {
/**
* If original event has null but repetition has some value, we need to return repetition value
*/
if (originalParam === null) {
return repetitionParam;
/**
* Otherwise, we need to recursively merge original and repetition values
*/
} else {
return repetitionAssembler(originalParam, repetitionParam);
}
}

return repetitionParam;
};

return mergeWith(cloneDeep(originalEvent), cloneDeep(repetition), customizer);
}

/**
* Parse addons and context fields from string to object, in db it stores as string
*
* @param payload - the payload of the event
* @param field - the field to parse, can be 'addons' or 'context'
* @returns the payload with parsed field
*/
function parsePayloadField(payload: GroupedEventDBScheme['payload'], field: 'addons' | 'context') {
if (payload && payload[field] && typeof payload[field] === 'string') {
payload[field] = JSON.parse(payload[field] as string);
}

return payload;
}

/**
* Stringify addons and context fields from object to string, in db it stores as string
*
* @param payload - the payload of the event
* @param field - the field to stringify, can be 'addons' or 'context'
* @returns the payload with stringified field
*/
function stringifyPayloadField(payload: GroupedEventDBScheme['payload'], field: 'addons' | 'context') {
if (payload && payload[field]) {
payload[field] = JSON.stringify(payload[field]);
}

return payload;
}

/**
* Helps to merge original event and repetition due to delta format,
* in case of old delta format, we need to patch the payload
* in case of new delta format, we need to assemble the payload
*
* @param originalEvent {HawkEvent} - The original event
* @param repetition {HawkEventRepetition} - The repetition to process
* @returns {HawkEvent} Updated event with processed repetition payload
*/
export function composeFullRepetitionEvent(originalEvent: GroupedEventDBScheme, repetition: RepetitionDBScheme | undefined): GroupedEventDBScheme {
/**
* Make a deep copy of the original event, because we need to avoid mutating the original event
*/
const event = cloneDeep(originalEvent);

if (!repetition) {
return event;
}

/**
* New delta format (repetition.delta is not null)
*/
if (repetition.delta) {
/**
* Parse addons and context fields from string to object before patching
*/
event.payload = parsePayloadField(event.payload, 'addons');
event.payload = parsePayloadField(event.payload, 'context');

event.payload = patch({
left: event.payload,
delta: JSON.parse(repetition.delta),
});

/**
* Stringify addons and context fields from object to string after patching
*/
event.payload = stringifyPayloadField(event.payload, 'addons');
event.payload = stringifyPayloadField(event.payload, 'context');

return event;
}

/**
* New delta format (repetition.payload is null) and repetition.delta is null (there is no delta between original and repetition)
*/
if (!repetition.payload) {
return event;
}

/**
* Old delta format (repetition.payload is not null)
* @todo remove after 6 september 2025
*/
event.payload = repetitionAssembler(event.payload, repetition.payload);

return event;
}
Loading
Loading