@resin/pinejs
Version:
Pine.js is a sophisticated rules-driven API engine that enables you to define rules in a structured subset of English. Those rules are used in order for Pine.js to generate a database schema and the associated [OData](http://www.odata.org/) API. This make
69 lines (61 loc) • 2.27 kB
text/typescript
import * as _ from 'lodash'
import * as Promise from 'bluebird'
// TODO: We force the return type here to merge `Promise<U> | Promise<U[]>` into `Promise<U | U[]>,
// which is equivalent for our uses but fixes issues where typing were being rejected
export const liftP = <T, U>(fn: (v: T) => U | Promise<U>): (a: T | T[]) => Promise<U | U[]> => {
return (a: T | T[]) => {
if (_.isArray(a)) {
// This must not be a settle as if any operation fails in a changeset
// we want to discard the whole
return Promise.mapSeries(a, fn)
} else {
return Promise.resolve(a).then(fn)
}
}
}
export interface MappingFunction {
<T, U>(a: T[], fn: (v: T) => U | Promise<U>): Promise<Array<U | Error>>
}
// The settle version of `Promise.mapSeries`
export const settleMapSeries: MappingFunction = (a, fn) => {
const runF = Promise.method(fn)
return Promise.mapSeries(a, _.flow(runF, wrap))
}
// This is used to guarantee that we convert a `.catch` result into an error, so that later code checking `_.isError` will work as expected
const ensureError = (err: any): Error => {
if (_.isError(err)) {
return err
}
return new Error(err)
}
// Wrap a promise with reflection. This promise will always succeed, either
// with the value or the error of the promise it is wrapping
const wrap = <T>(p: Promise<T>) => {
return p.then(_.identity as (value: T) => T, ensureError)
}
// Maps fn over collection and returns an array of Promises. If any promise in the
// collection is rejected it returns an array with the error, along with all the
// promises that were fulfilled up to that point
const mapTill: MappingFunction = <T, U>(a: T[], fn: (v: T) => U) => {
const runF = Promise.method(fn)
const results: Array<U | any> = []
return Promise.each(a, (p) => {
return runF(p)
.then((result) => {
results.push(result)
}).tapCatch((err) => {
results.push(ensureError(err))
})
})
.return(results)
.catchReturn(results)
}
// Used to obtain the appropriate mapping function depending on the
// semantics specified by the Prefer: header.
export const getMappingFn = (headers?: { prefer: string}): MappingFunction => {
if (headers != null && headers.prefer == 'odata.continue-on-error') {
return settleMapSeries
} else {
return mapTill
}
}