@georges-tech/tardis
Advanced tools
Comparing version 1.0.2 to 1.1.0
@@ -20,3 +20,3 @@ "use strict"; | ||
} | ||
function filterEffectiveDocuments({ filteredDocuments = [], orderedDocuments, }) { | ||
function _filterEffectiveDocuments({ filteredDocuments = [], orderedDocuments, }) { | ||
if (lodash_1.default.isEmpty(orderedDocuments)) { | ||
@@ -26,3 +26,3 @@ return filteredDocuments; | ||
const [firstDocument, ...restDocuments] = orderedDocuments; | ||
return filterEffectiveDocuments({ | ||
return _filterEffectiveDocuments({ | ||
filteredDocuments: [...filteredDocuments, firstDocument], | ||
@@ -32,2 +32,17 @@ orderedDocuments: lodash_1.default.filter(restDocuments, (doc) => moment_1.default(doc.known_at).isAfter(firstDocument.known_at)), | ||
} | ||
function _splitDocumentsAtDate({ documents, splitDate }) { | ||
if (!splitDate) { | ||
return { | ||
left: documents, | ||
right: [], | ||
}; | ||
} | ||
return { | ||
left: lodash_1.default.filter(documents, document => moment_1.default(document.effective_date).isBefore(splitDate)), | ||
right: lodash_1.default.chain(documents) | ||
.filter(document => !document.end_date || moment_1.default(document.end_date).isAfter(splitDate)) | ||
.map(document => (Object.assign(Object.assign({}, document), { effective_date: moment_1.default.max(moment_1.default(splitDate), moment_1.default(document.effective_date)).toDate() }))) | ||
.value(), | ||
}; | ||
} | ||
/** | ||
@@ -38,10 +53,40 @@ * Allows you to transform the documents into a configuration | ||
function _getConfigurationTimeline({ documents }) { | ||
if (lodash_1.default.isEmpty(documents)) { | ||
return []; | ||
} | ||
const orderedDocumentsByStartDate = lodash_1.default.orderBy(documents, ['effective_date', 'known_at'], ['asc', 'desc']); | ||
const effectiveDocuments = filterEffectiveDocuments({ | ||
orderedDocuments: orderedDocumentsByStartDate, | ||
const firstEndDate = lodash_1.default.chain(orderedDocumentsByStartDate) | ||
.filter(document => Boolean(document.end_date)) | ||
.map(({ end_date }) => end_date) | ||
.min() | ||
.value(); | ||
const { left, right } = _splitDocumentsAtDate({ documents: orderedDocumentsByStartDate, splitDate: firstEndDate }); | ||
const effectiveDocuments = _filterEffectiveDocuments({ | ||
orderedDocuments: left, | ||
}); | ||
return lodash_1.default.zipWith(effectiveDocuments, lodash_1.default.drop(effectiveDocuments, 1), (document, nextDocument) => (Object.assign(Object.assign({}, document), { end_date: lodash_1.default.get(nextDocument, 'effective_date') }))); | ||
const joinedArray = _joinTimelines(effectiveDocuments, _getConfigurationTimeline({ documents: right })); | ||
return lodash_1.default.zipWith(joinedArray, lodash_1.default.drop(joinedArray, 1), (document, nextDocument) => (Object.assign(Object.assign({}, document), { end_date: lodash_1.default.get(nextDocument, 'effective_date') }))); | ||
} | ||
/** | ||
* Join two timelines by merging same temporal | ||
* documents (by tardis id), split because | ||
* of recursive `_getConfigurationTimeline` calls | ||
*/ | ||
function _joinTimelines(left, right) { | ||
const lastLeft = lodash_1.default.last(left); | ||
const firstRight = lodash_1.default.first(right); | ||
if (lastLeft && firstRight && (lastLeft === null || lastLeft === void 0 ? void 0 : lastLeft.__tardis_id) === (firstRight === null || firstRight === void 0 ? void 0 : firstRight.__tardis_id)) { | ||
const mergedItem = Object.assign(Object.assign({}, firstRight), { effective_date: lastLeft.effective_date, end_date: firstRight.end_date }); | ||
return [ | ||
...lodash_1.default.dropRight(left, 1), | ||
mergedItem, | ||
...lodash_1.default.drop(right, 1), | ||
]; | ||
} | ||
return [...left, ...right]; | ||
} | ||
function createHistoryService({ documents }) { | ||
const configurationTimeline = _getConfigurationTimeline({ documents }); | ||
const formattedDocuments = lodash_1.default.map(documents, (document, index) => (Object.assign(Object.assign({}, document), { __tardis_id: index + 1 }))); | ||
const configurationTimeline = _getConfigurationTimeline({ documents: formattedDocuments }) | ||
.map(item => lodash_1.default.omit(item, '__tardis_id')); | ||
return { | ||
@@ -48,0 +93,0 @@ getCurrentConfiguration() { |
@@ -6,3 +6,3 @@ export interface HistoryService<T extends TemporalDocument> { | ||
*/ | ||
getCurrentConfiguration(): ConfigurationTimelineItem<T> | undefined; | ||
getCurrentConfiguration(): TimelineItem<T> | undefined; | ||
/** | ||
@@ -14,3 +14,3 @@ * Effective configuration at provided date. | ||
date: Date; | ||
}): ConfigurationTimelineItem<T> | undefined; | ||
}): TimelineItem<T> | undefined; | ||
/** | ||
@@ -23,11 +23,20 @@ * An array of effective documents on given date range. | ||
endDate: Date; | ||
}): ConfigurationTimeline<T>; | ||
}): TimelineItem<T>[]; | ||
} | ||
/** | ||
* Necessary properties for Tardis documents | ||
*/ | ||
export interface TemporalDocument { | ||
effective_date: Date; | ||
end_date?: Date; | ||
known_at: Date; | ||
} | ||
export declare type ConfigurationTimelineItem<T extends TemporalDocument> = T & { | ||
export declare type TimelineItem<T extends TemporalDocument> = T & { | ||
end_date?: Date; | ||
}; | ||
export declare type ConfigurationTimeline<T extends TemporalDocument> = ConfigurationTimelineItem<T>[]; | ||
/** | ||
* Internal Tardis properties | ||
*/ | ||
export interface TardisInternal { | ||
__tardis_id: number; | ||
} |
{ | ||
"name": "@georges-tech/tardis", | ||
"version": "1.0.2", | ||
"version": "1.1.0", | ||
"description": "Temporal property implementation for reading historized data", | ||
@@ -5,0 +5,0 @@ "main": "dist/src/index.js", |
@@ -22,6 +22,16 @@ # Tardis | ||
```ts | ||
{ | ||
effective_date: Date // Date at which the configuration starts to be valid | ||
known_at: Date // Date at which the new configuration has been created | ||
} | ||
/** | ||
* Date at which the configuration starts to be valid | ||
*/ | ||
effective_date: Date; | ||
/** | ||
* Date at which the new configuration has been created | ||
*/ | ||
known_at: Date; | ||
/** | ||
* Optional end date for limited time configurations | ||
*/ | ||
end_date?: Date; | ||
``` | ||
@@ -28,0 +38,0 @@ |
@@ -384,3 +384,299 @@ import chai from 'chai'; | ||
}) | ||
// Graphical representation of these test cases available in tests/specs | ||
describe('with documents having an end date', () => { | ||
it('1. should "resume" passed document after newer document at end date', () => { | ||
const historyService = createHistoryService({ | ||
documents: [ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
] | ||
}); | ||
expect( | ||
historyService.getConfigurationsOnDateRange({ | ||
startDate: new Date('2019-01-01'), | ||
endDate: new Date('2019-12-31'), | ||
}) | ||
).to.deep.equal([ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-03-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-04-01"), | ||
end_date: undefined, | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
]) | ||
}); | ||
it('2. should use older document configuration and become effective at current document explicit end date', () => { | ||
const historyService = createHistoryService({ | ||
documents: [ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-03-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
] | ||
}); | ||
expect( | ||
historyService.getConfigurationsOnDateRange({ | ||
startDate: new Date('2019-01-01'), | ||
endDate: new Date('2019-12-31'), | ||
}) | ||
).to.deep.equal([ | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-04-01"), | ||
end_date: undefined, | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
]) | ||
}); | ||
it('3. should replace document explicit end date by newer document effective date', () => { | ||
const historyService = createHistoryService({ | ||
documents: [ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-02-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-05-01"), | ||
data: { | ||
number: 3, | ||
}, | ||
} | ||
] | ||
}); | ||
expect( | ||
historyService.getConfigurationsOnDateRange({ | ||
startDate: new Date('2019-01-01'), | ||
endDate: new Date('2019-12-31'), | ||
}) | ||
).to.deep.equal([ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-02-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-02-01"), | ||
end_date: new Date("2019-03-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-05-01"), | ||
data: { | ||
number: 3, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-05-01"), | ||
end_date: undefined, | ||
data: { | ||
number: 1, | ||
}, | ||
} | ||
]) | ||
}); | ||
it('4. should replace document explicit end date by newer document effective date', () => { | ||
const historyService = createHistoryService({ | ||
documents: [ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-05-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-02-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 3, | ||
}, | ||
} | ||
] | ||
}); | ||
expect( | ||
historyService.getConfigurationsOnDateRange({ | ||
startDate: new Date('2019-01-01'), | ||
endDate: new Date('2019-12-31'), | ||
}) | ||
).to.deep.equal([ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-02-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-02-01"), | ||
end_date: new Date("2019-04-01"), | ||
data: { | ||
number: 3, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-04-01"), | ||
end_date: new Date("2019-05-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-05-01"), | ||
end_date: undefined, | ||
data: { | ||
number: 1, | ||
}, | ||
} | ||
]) | ||
}); | ||
it('5. should ignore documents completely overwritten by newer documents', () => { | ||
const historyService = createHistoryService({ | ||
documents: [ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-02-01"), | ||
effective_date: new Date("2019-03-01"), | ||
end_date: new Date("2019-05-01"), | ||
data: { | ||
number: 2, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-02-01"), | ||
data: { | ||
number: 3, | ||
}, | ||
} | ||
] | ||
}); | ||
expect( | ||
historyService.getConfigurationsOnDateRange({ | ||
startDate: new Date('2019-01-01'), | ||
endDate: new Date('2019-12-31'), | ||
}) | ||
).to.deep.equal([ | ||
{ | ||
known_at: new Date("2020-01-01"), | ||
effective_date: new Date("2019-01-01"), | ||
end_date: new Date("2019-02-01"), | ||
data: { | ||
number: 1, | ||
}, | ||
}, | ||
{ | ||
known_at: new Date("2020-03-01"), | ||
effective_date: new Date("2019-02-01"), | ||
end_date: undefined, | ||
data: { | ||
number: 3, | ||
}, | ||
}, | ||
]) | ||
}); | ||
}); | ||
}); | ||
}); |
31381
816
71