@jahed/sparql-engine
Version:
SPARQL query engine for servers and web browsers.
179 lines • 7.15 kB
JavaScript
// SPDX-License-Identifier: MIT
import { identity, isUndefined, uniqBy } from "lodash-es";
export function createSubscription() {
return {
[Symbol.dispose]() { },
};
}
export function createObserver(observerOrNext) {
return observerOrNext
? typeof observerOrNext === "object"
? observerOrNext
: { next: observerOrNext }
: {};
}
/**
* Abstract representation used to apply transformations on a pipeline of iterators.
* Concrete subclasses are used by the framework to build the query execution pipeline.
* @abstract
*/
export class PipelineEngine {
/**
* Maps each source value to an array of values which is merged in the output PipelineStage.
* @param input - Input PipelineStage
* @param mapper - Transformation function
* @return Output PipelineStage
*/
flatMap(input, mapper) {
return this.mergeMap(input, (value) => this.of(...mapper(value)));
}
/**
* Flatten the output of a pipeline stage that emits array of values into single values.
* @param input - Input PipelineStage
* @return Output PipelineStage
*/
flatten(input) {
return this.flatMap(input, (v) => v);
}
/**
* Returns a PipelineStage that emits all items emitted by the source PipelineStage that are distinct by comparison from previous items.
* @param input - Input PipelineStage
* @param selector - Optional function to select which value you want to check as distinct.
* @return A PipelineStage that emits items from the source PipelineStage with distinct values.
*/
distinct(input, selector) {
if (isUndefined(selector)) {
selector = identity;
}
return this.flatMap(this.collect(input), (values) => uniqBy(values, selector));
}
/**
* Emits only the first value (or the first value that meets some condition) emitted by the source PipelineStage.
* @param input - Input PipelineStage
* @return A PipelineStage of the first item that matches the condition.
*/
first(input) {
return this.limit(input, 1);
}
/**
* Returns a PipelineStage that emits the items you specify as arguments after it finishes emitting items emitted by the source PipelineStage.
* @param input - Input PipelineStage
* @param values - Values to append
* @return A PipelineStage that emits the items emitted by the source PipelineStage and then emits the additional values.
*/
endWith(input, values) {
return this.merge(input, this.from(values));
}
/**
* Perform a side effect for every emission on the source PipelineStage, but return a PipelineStage that is identical to the source.
* @param input - Input PipelineStage
* @param cb - Callback invoked on each item
* @return A PipelineStage identical to the source, but runs the specified PipelineStage or callback(s) for each item.
*/
tap(input, cb) {
return this.map(input, (value) => {
cb(value);
return value;
});
}
/**
* Find the smallest value produced by a pipeline of iterators.
* It takes a ranking function as input, which is invoked with (x, y)
* and must returns True if x < y and False otherwise.
* Warning: this function needs to materialize all values of the pipeline.
* @param input - Input PipelineStage
* @param comparator - (optional) Ranking function
* @return A pipeline stage that emits the lowest value found
*/
min(input, ranking) {
if (isUndefined(ranking)) {
ranking = (x, y) => x < y;
}
return this.map(this.collect(input), (values) => {
let minValue = values[0];
for (let i = 1; i < values.length - 1; i++) {
if (ranking(values[i], minValue)) {
minValue = values[i];
}
}
return minValue;
});
}
/**
* Find the smallest value produced by a pipeline of iterators.
* It takes a ranking function as input, which is invoked with (x, y)
* and must returns True if x > y and False otherwise.
* Warning: this function needs to materialize all values of the pipeline.
* @param input - Input PipelineStage
* @param comparator - (optional) Ranking function
* @return A pipeline stage that emits the highest value found
*/
max(input, ranking) {
if (isUndefined(ranking)) {
ranking = (x, y) => x > y;
}
return this.map(this.collect(input), (values) => {
let maxValue = values[0];
for (let i = 1; i < values.length - 1; i++) {
if (ranking(values[i], maxValue)) {
maxValue = values[i];
}
}
return maxValue;
});
}
/**
* Groups the items produced by a pipeline according to a specified criterion,
* and emits the resulting groups
* @param input - Input PipelineStage
* @param keySelector - A function that extracts the grouping key for each item
* @param elementSelector - (optional) A function that transforms items before inserting them in a group
*/
groupBy(input, keySelector, elementSelector) {
if (isUndefined(elementSelector)) {
elementSelector = identity;
}
const groups = new Map();
let stage = this.map(input, (value) => {
return {
key: keySelector(value),
value: elementSelector(value),
};
});
return this.mergeMap(this.collect(stage), (subgroups) => {
// build groups
subgroups.forEach((g) => {
if (!groups.has(g.key)) {
groups.set(g.key, [g.value]);
}
else {
groups.set(g.key, groups.get(g.key).concat([g.value]));
}
});
// inject groups into the pipeline
return this.fromAsync((input) => {
groups.forEach((value, key) => input.next([key, value]));
});
});
}
/**
* Peek values from the input pipeline stage, and use them to decide
* between two candidate pipeline stages to continue the pipeline.
* @param input - Input pipeline stage
* @param count - How many items to peek from the input?
* @param predicate - Predicate function invoked with the values
* @param ifCase - Callback invoked if the predicate function evaluates to True
* @param elseCase - Callback invoked if the predicate function evaluates to False
* @return A pipeline stage
*/
peekIf(input, count, predicate, ifCase, elseCase) {
const peekable = this.limit(this.clone(input), count);
return this.mergeMapAsync(this.collect(peekable), (values) => {
if (predicate(values)) {
return ifCase(values);
}
return elseCase(values);
});
}
}
//# sourceMappingURL=pipeline-engine.js.map