() → ObjectPartially populated options object
Returns an object containing default options for enhanceLROStatus and enhanceLROStatusEntry.
Note that you must still add a currentTime property to the result before use.
const defaultOptions = require('@eluvio/elv-lro-status/defaultOptions')
// Output when system is set to US / Pacific timezone
console.log(JSON.stringify(defaultOptions(), null, 2))
`{
"locale": "en-US",
"stallThreshold": 900,
"timezone": "America/Los_Angeles"
}`(Object, Object) → Objectoptionsoptions:currentTimeThe value to use for current time (this is used to compute eta_local)
options:localeThe Javascript Intl.Locale string to use to format eta_local
options:stallThresholdHow many seconds to wait since LRO info last updated before warning that LRO may have terminated
options:timezoneThe time zone to use to format eta_local
lroStatusThe data from ElvClient.LROStatus() call
An object containing an ok attribute that is true if enhancement succeeded or false if
enhancement failed (generally due to bad options being passed in).
If enhancement was successful, a result attribute will contain the enhanced status object.
If enhancement failed, an errors attribute will contain an array of error message strings, and an errorDetails
attribute will contain an array with any available additional information.
Processes a response from an ElvClient.LROStatus() API call, checking for stalled LROs and bad percentages, adding ETA fields if applicable, and adding a summary.
const enhanceLROStatus = require('@eluvio/elv-lro-status/enhanceLROStatus')
const defaultOptions = require('@eluvio/elv-lro-status/defaultOptions')
const options = Object.assign(defaultOptions(), {currentTime: new Date})
// assumes `client` contains a successfully prepared ElvClient instance
const statusResponse = await client.LROStatus({
libraryId: 'ilib_YOUR_LIBRARY_ID',
objectId: 'iq__YOUR_OBJECT_ID'
});
// check to make sure we received data back
if (statusResponse === undefined) {
throw Error("Received no job status information from server - no LROs started, or object already finalized?");
}
// EXAMPLE: SUCCESSFUL CALL
const enhancedStatus = enhanceLROStatus(options, statusResponse)
console.log(JSON.stringify(enhancedStatus, null, 2)
`{
"ok": true,
"result": {
"LROs": {
"tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSv3L57AV3CPYHbLmk3E9SF1b": {
"duration": 0,
"duration_ms": 0,
"progress": {
"percentage": 0
},
"estimated_time_left_h_m_s": "unknown (not enough progress yet)",
"run_state": "running",
"start": "2022-04-09T05:09:00Z",
"seconds_since_last_update": 84
},
"tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSvGJyKKJpmqtmJ2fzo5Fb3Be": {
"duration": 12747000000,
"duration_ms": 12747,
"end": "2022-04-09T05:09:13Z",
"progress": {
"percentage": 100
},
"run_state": "finished",
"start": "2022-04-09T05:09:00Z"
}
},
"summary": {
"run_state": "running",
"estimated_time_left_h_m_s": "unknown (not enough progress yet)"
}
}
}`
// EXAMPLE: FAILED CALL
const badOptions = defaultOptions() // failed to set currentTime
const failedEnhancement = enhanceLROStatus(badOptions, statusResponse)
console.log(JSON.stringify(failedEnhancement, null, 2)
`{
ok: false,
errors: [
"options: expecting currentTime to be Date, got undefined"
],
errorDetails: [
{
"path": "currentTime",
"message": "expecting currentTime to be Date, got undefined"
}
]
}`Pair String Object → Ok Pair String Object | Err Arrayoptionsoptions.currentTimeThe value to use for current time when calculating ETA fields
options.localeThe locale to use when formatting the eta_local field
options.stallThresholdHow many seconds to allow to pass between LRO info updates before warning that LRO may have terminated
options.timezoneThe timezone to use when formatting the eta_local field
lroA Crocks Pair with LRO ID as first element and LRO Status Entry as second element
Either Err([errors]) or Ok(Pair(lroId, enhancedStatusObject))
Returns an enhanced copy of the status entry for a single LRO, wrapped in a Crocks Result.
Bad options will cause an Err to be returned
const Pair = require('@eluvio/elv-js-helpers/Pair')
const defaultOptions = require('@eluvio/elv-lro-status/defaultOptions')
const options = Object.assign(defaultOptions(), {currentTime: new Date})
const singleLRO = Pair(
'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjJ2kukiD5NmnEgCP7iFFBjU',
{
"duration": 335613000000,
"duration_ms": 335613,
"end": "2022-04-08T21:10:34Z",
"progress": {
"percentage": 100
},
"run_state": "finished",
"start": "2022-04-08T21:05:00Z"
}
)
// EXAMPLE: Successful call
enhanceLROStatusEntry(options, singleLRO) //=> Ok Pair('tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjJ2kukiD5NmnEgCP7iFFBjU', {...})
// EXAMPLE: Failed call
enhanceLROStatusEntry(defaultOptions(), singleLRO) //=> Err [error objects]* → String | THROW(unnamed)The input to validate
The validated input, proxied by ObjectModel
An ObjectModel which validates that an input is:
If input passes validation, will return the input (proxied by ObjectModel)
Throws an exception if passed in an invalid value.
const LROIdModel = require('@eluvio/elv-lro-status/LROIdModel')
LROIdModel('tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh') //=> 'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh' (proxied by ObjectModel)
LROIdModel(42) //=> EXCEPTION: 'expecting String, got Number 42'
LROIdModel('foo') //=> EXCEPTION: 'Value is not a valid LRO ID string 'tlro...' (got: foo)'* → Object | THROW(unnamed)The input to validate
The validated input, proxied by ObjectModel
An ObjectModel to validate the progress attribute of an LRO Status entry
Throws an exception if passed in an invalid value.
const LROProgressModel = require('@eluvio/elv-lro-status/LROProgressModel')
LROProgressModel({}) //=> EXCEPTION: 'expecting percentage to be Number, got undefined'
LROProgressModel({percentage: -2}) //=> EXCEPTION: 'percentage must be >= 0 (got: -2)'
LROProgressModel({percentage: 10}) //=> {percentage: 10} // proxied by ObjectModel
LROProgressModel({percentage: 10, files: 7}) //=> {percentage: 10, files: 7} // proxied by ObjectModel
// NOTE: Object.keys and JSON.stringify will drop attributes not declared in model:
JSON.stringify(
LROProgressModel({percentage: 10, files: 7})
) //=> '{"percentage": 10}'
LROProgressModel('foo') //=> EXCEPTION: `expecting Object, got String "foo"`* → Object | THROW(unnamed)The input to validate
The validated input, proxied by ObjectModel
An ObjectModel to validate the an LRO Status entry
Throws an exception if passed in an invalid value.
const LROStatusEntryModel = require('@eluvio/elv-lro-status/LROStatusEntryModel')
LROStatusEntryModel({}) //=> EXCEPTION: 'expecting duration to be Number, got undefined...'
LROStatusEntryModel({
duration: 0,
duration_ms: 0,
progress: {
percentage: 0
},
run_state: 'running',
start: '2022-04-08T21:05:00Z'
}) //=> (copy of input object) // proxied by ObjectModel
LROStatusEntryModel({
duration: -1,
duration_ms: 0,
progress: {
percentage: 0
},
run_state: 'running',
start: '2022-04-08T21:05:00Z'
}) //=> EXCEPTION: 'duration must be >= 0 (got: -1)'
LROStatusEntryModel('foo') //=> EXCEPTION: `expecting Object, got String "foo"`* → Object | THROW(unnamed)The input to validate
The validated input, proxied by ObjectModel
An ObjectModel to validate the response from an LROStatus() API call
Throws an exception if passed in an invalid value or an empty object.
const LROStatusModel = require('@eluvio/elv-lro-status/LROStatusModel')
LROStatusModel({}) //=> Exception: "Value must not be empty (got: {})"
LROStatusModel({
tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh: {
duration: 0,
duration_ms: 0,
progress: {
percentage: 0
},
run_state: 'running',
start: '2022-04-08T21:05:00Z'
}
}) //=> (copy of input object) // proxied by ObjectModel
LROStatusModel({
foo: {
duration: 0,
duration_ms: 0,
progress: {
percentage: 0
},
run_state: 'running',
start: '2022-04-08T21:05:00Z'
}
}) //=> EXCEPTION: 'invalid property name 'foo' (is not a valid LROId)'
LROStatusModel({
tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh: {}
}) //=> EXCEPTION: 'TypeError: key 'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh' points to a value that is an invalid LROStatusEntry (LROStatusEntry: expecting duration to be Number, got undefined...'
LROStatusModel(3) //=> EXCEPTION: 'expecting Object, got Number 3'Type:String"cancelled by node shutdown"
run_state string returned by server for LROs
const {LRO_RS_CANCELLED_SHUTDOWN} = require('@eluvio/elv-lro-status/lroRunState') //=> 'cancelled by node shutdown'Type:String"cancelled by timeout"
run_state string returned by server for LROs
const {LRO_RS_CANCELLED_TIMEOUT} = require('@eluvio/elv-lro-status/lroRunState') //=> 'cancelled by timeout'Type:String"cancelled by user"
run_state string returned by server for LROs
const {LRO_RS_CANCELLED_USER} = require('@eluvio/elv-lro-status/lroRunState') //=> 'cancelled by user'Type:String"failed"
run_state string returned by server for LROs
const {LRO_RS_FAILED} = require('@eluvio/elv-lro-status/lroRunState') //=> 'failed'Type:String"finished"
run_state string returned by server for LROs
const {LRO_RS_FINISHED} = require('@eluvio/elv-lro-status/lroRunState') //=> 'finished'Type:String"not started"
run_state string returned by server for LROs
const {LRO_RS_NOT_STARTED} = require('@eluvio/elv-lro-status/lroRunState') //=> 'not started'Type:String"running"
run_state string returned by server for LROs
const {LRO_RS_RUNNING} = require('@eluvio/elv-lro-status/lroRunState') //=> 'running'Type:Array.<String>
A list of all the possible values returned by server for LRO run_state
const {LRO_RUN_STATES} = require('@eluvio/elv-lro-status/lroRunState') //=> ['not started', ..., 'cancelled by node shutdown']* → ResultoptionsThe options to validate
Fills in missing options for enhanceLROStatus() with default values, validates after merging, then returns a
Crocks Result, either an Ok wrapping the final options or an Err
wrapping an array of errors.
const mergeDefaultOptions = require('@eluvio/elv-lro-status/mergeDefaultOptions')
mergeDefaultOptions({
currentTime: new Date,
locale: 'en-US',
timezone: 'America/Los_Angeles'
}) //=> Ok() wrapping a copy of input object with {stallThreshold: 900} merged in
OptionsModel({
locale: 'en-US',
stallThreshold: 900,
timezone: 'America/Los_Angeles'
}) //=> Err([error object])* → Object → Object | THROW(unnamed)The options to validate
The validated options object
An ObjectModel which validates that an input is:
options argument used by enhanceLROStatus and enhanceLROStatusEntry.const OptionsModel = require('@eluvio/elv-lro-status/OptionsModel')
OptionsModel({
currentTime: new Date,
locale: 'en-US',
stallThreshold: 900,
timezone: 'America/Los_Angeles'
}) //=> Object (proxied by ObjectModel)
OptionsModel({
locale: 'en-US',
stallThreshold: 900,
timezone: 'America/Los_Angeles'
}) //=> EXCEPTION: 'expecting currentTime to be Date, got undefined'Type:Array.<String>
An array of all the enhanced run_state constants, ordered by increasing precedence. When creating a status summary,
states later in list will take precedence, e.g if one LRO is 'finished' and one LRO is 'running', the summary
run_state will be 'running'. Generally error/cancel states take precedence over STATE_RUNNING, which takes
precedence over the remaining states.
The order is:
const {STATES} = require('@eluvio/elv-lro-status/enhancedRunState') //=> ['unknown', ..., 'cancelled by user']Type:String"bad percentage"
An enhanced run_state (NOT used by Eluvio Content Fabric servers) to indicate that an LRO has reported a bad number
for progress percentage (i.e. either > 100 for any run_state, or < 100 for run_state 'finished')
const {STATE_BAD_PCT} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'bad percentage'Type:String"cancelled by node shutdown"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has been cancelled due
to a server shutdown.
Note that in practice this run_state is almost never encountered. (TODO: check implementation details)
const {STATE_CANCELLED_SHUTDOWN} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'cancelled by node shutdown'Type:String"cancelled by timeout"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has been cancelled due
to a timeout.
Note that in practice this run_state is almost never encountered. (TODO: check implementation details)
const {STATE_CANCELLED_TIMEOUT} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'cancelled by timeout'Type:String"cancelled by user"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has been cancelled by
the user.
Note that in practice this run_state is almost never encountered. (TODO: check implementation details)
const {STATE_CANCELLED_USER} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'cancelled by user'Type:String"failed"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has encountered an error
and has terminated.
const {STATE_FAILED} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'failed'Type:String"finished"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has finished.
Note that if the LRO reports a progress percentage that is not 100, the run_state will be set to STATE_BAD_PCT
instead.
const {STATE_FINISHED} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'finished'Type:String"not started"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO has not started yet.
Note that in practice this run_state is almost never encountered.
const {STATE_NOT_STARTED} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'not started'Type:String"running"
An enhanced run_state (also used by Eluvio Content Fabric servers) to indicate that an LRO is running.
Note that this does not guarantee that the LRO is actually running.
const {STATE_RUNNING} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'running'Type:String"stalled"
An enhanced run_state (NOT used by Eluvio Content Fabric servers) to indicate that an LRO has not updated its
information in a while and may have terminated.
const {STATE_STALLED} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'stalled'Type:String"unknown"
An enhanced run_state (NOT used by Eluvio Content Fabric servers) to indicate that run_state cannot be determined.
This is an internal initializer value used as a starting point for summarizing a list of states, and should never
get returned to caller.
const {STATE_UNKNOWN} = require('@eluvio/elv-lro-status/enhancedRunState') //=> 'unknown'Object → Object → ResultoptionsAn object containing options for enhanceLROStatus call
lroStatusData from an ElvClient.LROStatus() call - a key/value map with LRO ID key and LRO status value
A Crocks Result wrapping either the enhanced lroStatus object or an array of errors
Enhance lroResult data obtained via ElvClient.LROStatus().
Returns a Crocks Result, either Ok(enhancedLROStatus) or Err(array of errors)
Internal function, does not validate inputs or unwrap return value to supply a plain object to caller. See source code for public counterpart enhanceLROStatus for example usage.
Pair String Object → Pair String ObjectlroA Crocks Pair with LRO ID as first element and LRO Status Entry as second element
A Crocks Pair with LRO ID as first element and the enhanced copy of LRO Status Entry object with additional fields as second element
Returns an enhanced copy of an LRO Status Entry for an LRO that does not have a run_state of running.
Checks for bad reported progress percentage.
const Pair = require('@eluvio/elv-js-helpers/Pair')
const _enhanceNonRunningEntry = require('@eluvio/elv-lro-status/internal/_enhanceNonRunningEntry')
const lro = Pair(
'tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSvGJyKKJpmqtmJ2fzo5Fb3Be',
{
"duration": 12747000000,
"duration_ms": 12747,
"end": "2022-04-09T05:09:13Z",
"progress": {
"percentage": 100
},
"run_state": "finished",
"start": "2022-04-09T05:09:00Z"
}
)
const enhanced = _enhanceNonRunningEntry(lro)
console.log(Pair.fst())
'tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSvGJyKKJpmqtmJ2fzo5Fb3Be'
console.log(JSON.stringify(enhanced.snd(), null, 2)
`{
"duration": 12747000000,
"duration_ms": 12747,
"end": "2022-04-09T05:09:13Z",
"progress": {
"percentage": 100
},
"run_state": "finished",
"start": "2022-04-09T05:09:00Z"
}`
// change lro.progress.percentage to a bad value
lro.map(x => x.progress.percentage = 120)
const enhancedBadData = _enhanceNonRunningEntry(lro)
console.log(JSON.stringify(enhancedBadData.snd(), null, 2))
`{
"duration": 12747000000,
"duration_ms": 12747,
"end": "2022-04-09T05:09:13Z",
"progress": {
"percentage": 120
},
"run_state": "bad percentage",
"start": "2022-04-09T05:09:00Z",
"reported_run_state": "finished",
"warning": "Job tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSvGJyKKJpmqtmJ2fzo5Fb3Be has run_state 'finished', but progress pct is 120"
}`Object → Pair String Object → Pair String Objectoptionsoptions.currentTimeThe value to use for current time when calculating ETA fields
options.localeThe locale to use when formatting the eta_local field
options.stallThresholdHow many seconds to allow to pass between LRO info updates before warning that LRO may have terminated
options.timezoneThe timezone to use when formatting the eta_local field
lroA Crocks Pair with LRO ID as first element and LRO Status Entry as second element
A Crocks Pair with LRO ID as first element and the enhanced copy of LRO Status Entry object with additional fields as second element
Returns an enhanced copy of an LRO Status Entry for a running LRO. Checks for bad reported progress percentage and stalled LROs Adds ETA fields if possible
const Pair = require('@eluvio/elv-js-helpers/Pair')
const defaultOptions = require('@eluvio/elv-lro-status/defaultOptions')
const _enhanceRunningEntry = require('@eluvio/elv-lro-status/internal/_enhanceRunningEntry')
// for this example, current time is 2022-04-08T21:34:10Z
const options = Object.assign(defaultOptions(), {currentTime: new Date})
const lro = Pair(
'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh',
{
"duration": 1390740000000,
"duration_ms": 1390740,
"progress": {
"percentage": 76.66666666666667
},
"run_state": "running",
"start": "2022-04-08T21:05:00Z"
}
)
const enhanced = _enhanceRunningEntry(options, lro)
console.log(Pair.fst())
'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh'
console.log(JSON.stringify(Pair.snd(), null, 2)
`{
"duration": 1390740000000,
"duration_ms": 1390740,
"progress": {
"percentage": 76.66666666666667
},
"run_state": "running",
"start": "2022-04-08T21:05:00Z",
"seconds_since_last_update": 359,
"estimated_time_left_seconds": 533,
"estimated_time_left_h_m_s": "8m 53s",
"eta_local": "2:43:03 PM PDT"
}`
// if current time were instead 1 hour later (2022-04-08T22:34:10Z) then output would be:
`{
"duration": 1390740000000,
"duration_ms": 1390740,
"progress": {
"percentage": 76.66666666666667
},
"run_state": "stalled",
"start": "2022-04-08T21:05:00Z",
"seconds_since_last_update": 3959,
"estimated_time_left_seconds": 1628,
"estimated_time_left_h_m_s": "27m 08s",
"eta_local": "4:01:18 PM PDT",
"reported_run_state": "running",
"warning": "status has not been updated in 3959 seconds, process may have terminated"
}`[String] → StringlistAn array of enhanced run states
The state with the highest precedence, or value or STATE_UNKNOWN if list is empty.
Returns the enhanced run state with maximum precedence from a list, or STATE_UNKNOWN if an empty list passed in
const _maxEnhancedState = require('@eluvio/elv-lro-status/internal/_maxEnhancedState')
_maxEnhancedState(['running', 'stalled']) //=> 'stalled'
_maxEnhancedState([]) //=> 'unknown'Object → Number | undefined(unnamed)An LRO Status Entry object
The value or undefined if not found
Retrieves progress percentage from LRO Status entry, protecting against missing keys
const _progressPct = require('@eluvio/elv-lro-status/internal/_progressPct')
_progressPct({progress: {percentage: 10}}) //=> 10
_progressPct({}) //=> undefinedType:Object
A lookup table for enhanced run state precedence {unknown: 0, finished: 1, ...}
Object → ObjectenhancedLROStatusAn object where the keys are LRO IDs and the values are enhanced LRO Status Entries
The summary object
Returns a summary for an object containing enhanced LRO Status Entries
Returns an overall run_state and (if applicable) ETA fields.
const _sumEnhancedEntries = require('@eluvio/elv-lro-status/internal/_sumEnhancedEntries')
const enhancedEntriesNoProgress = {
"tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSv3L57AV3CPYHbLmk3E9SF1b": {
"duration": 0,
"duration_ms": 0,
"progress": {
"percentage": 0
},
"estimated_time_left_h_m_s": "unknown (not enough progress yet)",
"run_state": "running",
"start": "2022-04-09T05:09:00Z",
"seconds_since_last_update": 84
},
"tlro1Ejh9gNWAaTmfWJKeuu99V2MZ17XXkSvGJyKKJpmqtmJ2fzo5Fb3Be": {
"duration": 12747000000,
"duration_ms": 12747,
"end": "2022-04-09T05:09:13Z",
"progress": {
"percentage": 100
},
"run_state": "finished",
"start": "2022-04-09T05:09:00Z"
}
}
console.log(JSON.stringify(_sumEnhancedEntries(enhancedEntriesNoProgress), null, 2))
`{
"run_state": "running",
"estimated_time_left_h_m_s": "unknown (not enough progress yet)"
}`
const enhancedEntriesWithProgress = {
'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjJ2kukiD5NmnEgCP7iFFBjU': {
'duration': 335613000000,
'duration_ms': 335613,
'end': '2022-04-08T21:10:34Z',
'progress': {
'percentage': 100
},
'run_state': 'finished',
'start': '2022-04-08T21:05:00Z'
},
'tlro1EjdMMAvWb5iJn2isHdgESes1dq12kpjXCExCepbpWfwMpo2haCxnh': {
'duration': 1390740000000,
'duration_ms': 1390740,
'progress': {
'percentage': 76.66666666666667
},
'run_state': 'running',
'start': '2022-04-08T21:05:00Z',
'seconds_since_last_update': 359,
'estimated_time_left_seconds': 423,
'estimated_time_left_h_m_s': '7m 03s',
'eta_local': '2:41:13 PM PDT'
}
}
console.log(JSON.stringify(_sumEnhancedEntries(enhancedEntriesWithProgress), null, 2))
`{
"run_state": "running",
"estimated_time_left_seconds": 423,
"estimated_time_left_h_m_s": "7m 03s",
"eta_local": "2:41:13 PM PDT"
}`
`