Browse Source

Initial commit

master
Sven Slootweg 6 months ago
commit
cc302b3247
  1. 2
      .gitignore
  2. 12
      README.md
  3. 43
      example.js
  4. 165
      index.js
  5. 10
      numbers.txt
  6. 21
      package.json
  7. 16
      src/assert-error-type.js
  8. 19
      src/create-defer.js
  9. 11
      src/destroy-stream.js
  10. 5
      src/is-stdio-stream.js
  11. 29
      src/readable/attach-handlers.js
  12. 58
      src/readable/index.js
  13. 121
      src/readable/push-buffer.js
  14. 5
      src/warn.js
  15. 27
      src/writable/attach-handlers.js
  16. 40
      src/writable/index.js
  17. 25
      src/writable/write-to-stream.js
  18. 353
      yarn.lock

2
.gitignore

@ -0,0 +1,2 @@
node_modules
example-piped.js

12
README.md

@ -0,0 +1,12 @@
TODO
# Special case: Duplex streams
In Node.js streams, there is a thing called a Duplex stream. This is basically a stream that has an independent readable and writable part, that are not connected together. Like a TCP socket, for example; you send data through the writable half and receive data through the readable half - but the data you receive is not the same data as you sent.
Duplex streams are really just two independent streams that are stuffed into a single object, representing a single resource, like a network socket. And this is how ppstreams treats those - as two independent streams. In ppstreams, there are no Duplex streams, so when converting a Node.js Duplex stream, you will have to do the conversion twice - once for the readable part, and once for the writable part. This can be done with the `duplexRead` and `duplexWrite` methods.
__Why does ppstreams not have Duplex streams?__ Because they're surprisingly tricky to implement. They would make the ppstreams specification a lot more complex, and make certain things impossible; for example, a sink stream (writable stream) in ppstreams can return a Promise to signify when it's completely done receiving data, but a source stream (readable stream) needs to return a Promise with the next read value! You can only return one or the other, so supporting both would involve a lot of special logic for detecting what kind of thing is being returned.
Aside from that, they are also just difficult to work with. Developers routinely get tripped up by the difference between a Duplex and a Transform stream, and it's much simpler to reason about your streams code when a separate readable and writable stream don't pretend to be a single stream when they're really not.

43
example.js

@ -0,0 +1,43 @@
"use strict";
const Promise = require("bluebird");
const fs = require("fs");
const through2 = require("through2");
const fromNodeStream = require("./");
const pipe = require("@ppstreams/pipe");
const collect = require("@ppstreams/collect");
const rangeNumbers = require("@ppstreams/range-numbers");
const bufferedMap = require("@ppstreams/buffered-map");
const map = require("@ppstreams/map");
let transform = through2.obj((chunk, encoding, callback) => {
callback(null, chunk + 5);
});
return Promise.try(() => {
return pipe([
rangeNumbers(0, 10),
fromNodeStream(transform),
collect()
]).read();
}).then((result) => {
console.log("transform result:", result);
return pipe([
fromNodeStream(fs.createReadStream("example.js", { encoding: "utf8" })),
bufferedMap((line) => line.split("\n")),
collect()
]).read();
}).then((result) => {
console.log("read result:", result);
return pipe([
rangeNumbers(0, 10),
map((line) => String(line) + "\n"),
fromNodeStream(fs.createWriteStream("numbers.txt", { encoding: "utf8" }))
]).read();
}).then((result) => {
console.log("write result:", result);
console.log("numbers.txt contents:");
console.log(fs.readFileSync("numbers.txt", { encoding: "utf8" }));
});

165
index.js

@ -0,0 +1,165 @@
"use strict";
const Promise = require("bluebird");
const simpleSource = require("@ppstreams/simple-source");
const simpleSink = require("@ppstreams/simple-sink");
const buffer = require("@ppstreams/buffer");
const propagatePeek = require("@ppstreams/propagate-peek");
const propagateAbort = require("@ppstreams/propagate-abort");
const pipe = require("@ppstreams/pipe");
const isEndOfStream = require("@ppstreams/is-end-of-stream");
const createDefer = require("./src/create-defer");
const wireUpReadableInterface = require("./src/readable");
const wireUpWritableInterface = require("./src/writable");
// FIXME: Maybe also an abstraction for 'handle queue of requests', as this is used in multiple stream implementations
// TODO: Improve robustness of stream-end handling using https://nodejs.org/dist/latest-v14.x/docs/api/stream.html#stream_stream_finished_stream_options_callback?
// FIXME: Sequentialize all of these?
// readable
// writable
// transform
// duplex
module.exports = function convert(stream) {
// FIXME: Proper validation and tagging
// FIXME: Wrap v1 streams
// NOTE: Standard I/O streams are specialcased here because they may be Duplex streams; even though the other half is never actually used. We're only interested in the interface that *is* being used.
if (stream === process.stdin) {
return fromReadable(stream);
} else if (stream === process.stdout || stream === process.stderr) {
return fromWritable(stream);
} else if (stream.writable != null) {
if (stream.readable != null) {
if (stream._transform != null) {
// transform
return fromTransform(stream);
} else {
throw new Error(`Duplex streams cannot be converted with the auto-detection API. Instead, use 'fromReadable' and/or 'fromWritable' manually, depending on which parts of the Duplex stream you are interested in.`);
}
} else {
return fromWritable(stream);
}
} else if (stream.readable != null) {
return fromReadable(stream);
} else {
throw new Error(`Not a Node stream`);
}
};
// FIXME: Duplex APIs
function fromReadable(stream) {
let readable = wireUpReadableInterface(stream);
return simpleSource({
onRequest: () => {
return readable.request();
},
onAbort: () => {
return readable.destroy();
}
});
}
function fromWritable(stream) {
let upstreamHasEnded = false;
let mostRecentSource = { abort: function() {} }; // FIXME: Replace with a proper spec-compliant dummy stream
let convertedStream = simpleSink({
onResult: (result) => {
return writable.write(result);
},
onEnd: () => {
upstreamHasEnded = true;
return writable.end();
},
onAbort: (_reason) => {
return writable.destroy();
},
onSourceChanged: (source) => {
mostRecentSource = source;
}
});
// NOTE: The use of `var` is intentional, to make hoisting possible here; otherwise we'd have a broken cyclical reference
var writable = wireUpWritableInterface(stream, {
onClose: () => {
if (!upstreamHasEnded) {
convertedStream.abort(true);
}
},
onError: (error) => {
// Make sure we notify the pipeline, if any, by passing in the most recent source stream that we've seen.
convertedStream.abort(mostRecentSource, error);
}
});
return convertedStream;
}
function fromTransform(stream) {
let completionDefer;
let endHandled = false;
// FIXME: we need to specifically watch for the `error` and `end` events on the readable interface, to know when the transform stream has fully completed processing
// Respond to the EndOfStream produced by the pushbuffer in this case
// request, destroy
let readable = wireUpReadableInterface(stream, {
onEnd: () => {
if (completionDefer != null) {
completionDefer.resolve();
}
},
onError: (error) => {
if (completionDefer != null) {
completionDefer.reject(error);
}
}
});
// write, end, destroy
var writable = wireUpWritableInterface(stream);
let convertedStream = {
_promistreamVersion: 0,
description: `converted Node.js transform stream`,
abort: propagateAbort,
peek: propagatePeek,
read: function produceValue_nodeTransformStream(source) {
return Promise.try(() => {
return source.read();
}).then((value) => {
writable.write(value);
// This will quite possibly return an empty buffer, but that is fine; the `buffer` stream downstream from us will just keep reading (and therefore queueing up new items to be transformed) until it gets some results.
return readable.consumeImmediateBuffer();
}).catch(isEndOfStream, (marker) => {
// Wait for transform stream to drain fully, `error`/`end` event, and then return whatever buffer remains.
// FIXME: Error propagation logic is pretty shaky here. Verify that we don't end up with double error reports.
if (endHandled === false) {
endHandled = true;
writable.end();
return Promise.try(() => {
let { promise, defer } = createDefer();
completionDefer = defer;
return promise;
}).then(() => {
return readable.consumeImmediateBuffer();
});
} else {
throw marker;
}
});
}
};
return pipe([
convertedStream,
buffer()
]);
}

10
numbers.txt

@ -0,0 +1,10 @@
0
1
2
3
4
5
6
7
8
9

21
package.json

@ -0,0 +1,21 @@
{
"name": "@ppstreams/from-node-stream",
"version": "0.1.0",
"main": "index.js",
"repository": "http://git.cryto.net/ppstreams/from-node-stream.git",
"author": "Sven Slootweg <admin@cryto.net>",
"license": "WTFPL OR CC0-1.0",
"dependencies": {
"@joepie91/unreachable": "^1.0.0",
"@ppstreams/buffer": "^0.1.0",
"@ppstreams/end-of-stream": "^0.1.0",
"@ppstreams/is-end-of-stream": "^0.1.0",
"@ppstreams/pipe": "^0.1.1",
"@ppstreams/propagate-abort": "^0.1.3",
"@ppstreams/propagate-peek": "^0.1.0",
"@ppstreams/simple-sink": "^0.1.0",
"@ppstreams/simple-source": "^0.1.1",
"bluebird": "^3.7.2",
"split-filter": "^1.1.3"
}
}

16
src/assert-error-type.js

@ -0,0 +1,16 @@
"use strict";
const util = require("util");
module.exports = function assertErrorType(error) {
if (!(error instanceof Error)) {
let stack = (new Error).stack
.split("\n")
.filter((line, n) => (n > 0 || !line.startsWith("Error")))
.join("\n");
console.warn(`Converted Node stream produced an error value that isn't an Error instance: ${util.inspect(error)}`);
console.warn(`Stack:`);
console.warn(stack);
}
};

19
src/create-defer.js

@ -0,0 +1,19 @@
"use strict";
module.exports = function createDefer() {
let resolveFunc, rejectFunc;
// NOTE: This works because the `new Promise` callback gets executed synchronously.
let promise = new Promise((resolve, reject) => {
resolveFunc = resolve;
rejectFunc = reject;
});
return {
promise: promise,
defer: {
resolve: resolveFunc,
reject: rejectFunc
}
};
};

11
src/destroy-stream.js

@ -0,0 +1,11 @@
"use strict";
const warn = require("./warn");
module.exports = function destroyStream(stream) {
if (typeof stream.destroy === "function") {
stream.destroy();
} else {
warn("The stream you are converting does not have a 'destroy' method, and could therefore not be destroyed. This may cause resource leaks!");
}
};

5
src/is-stdio-stream.js

@ -0,0 +1,5 @@
"use strict";
module.exports = function isStdioStream(stream) {
return (stream._isStdio === true);
};

29
src/readable/attach-handlers.js

@ -0,0 +1,29 @@
"use strict";
module.exports = function attachReadableStreamHandlers({ stream, onClose, onError, onData }) {
function detachEventHandlers() {
stream.removeListener("end", onCloseWrapper);
stream.removeListener("close", onCloseWrapper);
stream.removeListener("error", onErrorWrapper);
stream.removeListener("data", onData);
}
function attachEventHandlers() {
stream.on("end", onCloseWrapper);
stream.on("close", onCloseWrapper);
stream.on("error", onErrorWrapper);
stream.on("data", onData);
}
function onCloseWrapper() {
onClose();
detachEventHandlers();
}
function onErrorWrapper(error) {
onError(error);
detachEventHandlers();
}
attachEventHandlers();
};

58
src/readable/index.js

@ -0,0 +1,58 @@
"use strict";
const attachHandlers = require("./attach-handlers");
const createPushBuffer = require("./push-buffer");
const destroyStream = require("../destroy-stream");
const assertErrorType = require("../assert-error-type");
module.exports = function wireUpReadableInterface(stream, { onEnd, onError } = {}) {
let pushBuffer = createPushBuffer({
onPause: function () {
if (stream.pause != null) {
stream.pause();
return true; // FIXME: Can we verify whether the pausing was successful, somehow? Eg. to deal with streams with `readable` event handlers attached.
} else {
return false;
}
},
onResume: function () {
if (stream.resume != null) {
stream.resume();
return true;
} else {
throw new Error(`Stream was successfully paused but does not have a resume method. This should never happen!`);
}
}
});
// TODO: Verify that auto-detaching all event handlers upon end/error is actually the correct thing to do!
attachHandlers({
stream: stream,
onData: (data) => {
pushBuffer.queueValue(data);
},
onError: (error) => {
assertErrorType(error);
pushBuffer.queueError(error);
if (onError != null) {
onError(error);
}
},
onClose: () => {
pushBuffer.markEnded();
if (onEnd != null) {
onEnd();
}
}
});
return {
request: pushBuffer.queueRequest,
consumeImmediateBuffer: pushBuffer.consumeImmediateBuffer,
destroy: () => {
return destroyStream(stream);
}
};
};

121
src/readable/push-buffer.js

@ -0,0 +1,121 @@
"use strict";
// FIXME: Separate this out into its own package
const splitFilter = require("split-filter");
const unreachable = require("@joepie91/unreachable")("@ppstreams/from-node-stream");
const EndOfStream = require("@ppstreams/end-of-stream");
const warn = require("../warn");
const createDefer = require("../create-defer");
module.exports = function createPushBuffer(options) {
let onPause = options.onPause || function pauseNotImplemented() {
return false;
};
let onResume = options.onResume || function resumeNotImplemented() {
return false;
};
let itemBuffer = [];
let requestQueue = [];
let isPaused = false;
let hasEnded = false;
function resumeIfEmpty() {
let bufferIsEmpty = (itemBuffer.length === 0);
if (bufferIsEmpty && isPaused) {
if (onResume() === true) {
isPaused = false;
}
}
}
function attemptDrain() {
// NOTE: This must remain fully synchronous, if we want to avoid unnecessary pauses in the `data` handler
while (requestQueue.length > 0) {
let hasItems = (itemBuffer.length > 0);
let hasResponse = (hasEnded || hasItems);
if (hasResponse) {
let defer = requestQueue.shift();
if (hasItems) {
// FIXME: Does this correctly deal with an error event produced as a result of an abort?
let item = itemBuffer.shift();
if (item.type === "value") {
defer.resolve(item.value);
} else if (item.type === "error") {
defer.reject(item.error);
} else {
unreachable(`Unexpected item type '${item.type}'`);
}
} else if (hasEnded) {
defer.reject(new EndOfStream());
} else {
unreachable("Invalid response state, neither has items in queue nor ended");
}
} else {
break;
}
}
resumeIfEmpty();
}
return {
queueValue: function (value) {
itemBuffer.push({ type: "value", value: value });
attemptDrain();
let stillHasItemsBuffered = (itemBuffer.length > 0);
if (stillHasItemsBuffered && !isPaused) {
if (onPause() === true) {
isPaused = true;
} else {
// FIXME: Only show this warning once?
warn("The stream you are converting does not support pausing. This may lead to unexpectedly high memory usage!");
}
}
},
queueError: function (error) {
itemBuffer.push({ type: "error", error: error });
attemptDrain();
},
queueRequest: function () {
let { defer, promise } = createDefer();
requestQueue.push(defer);
attemptDrain();
return promise;
},
markEnded: function () {
hasEnded = true;
attemptDrain();
},
consumeImmediateBuffer: function () {
attemptDrain();
// FIXME: Only return successful items here?
if (requestQueue.length > 0) {
// We won't ever serve up the buffer until any individual-item requests have been fulfilled.
return [];
} else {
let [ values, errors ] = splitFilter(itemBuffer, (item) => item.type === "value");
if (errors.length > 0) {
itemBuffer = values; // In case we ever write code that will do something with the remaining values in the buffer
throw errors[0].error;
} else {
itemBuffer = [];
resumeIfEmpty(); // Ensure that we haven't left the source stream in a paused state, because that would deadlock the pipeline
return values.map((item) => item.value);
}
}
}
};
};

5
src/warn.js

@ -0,0 +1,5 @@
"use strict";
module.exports = function warn(message) {
console.error(`[@ppstreams/from-node-stream] Warning: ${message}`);
};

27
src/writable/attach-handlers.js

@ -0,0 +1,27 @@
"use strict";
module.exports = function attachWritableStreamHandlers({ stream, onClose, onError }) {
function detachEventHandlers() {
stream.removeListener("finish", onCloseWrapper);
stream.removeListener("close", onCloseWrapper);
stream.removeListener("error", onErrorWrapper);
}
function attachEventHandlers() {
stream.on("finish", onCloseWrapper);
stream.on("close", onCloseWrapper);
stream.on("error", onErrorWrapper);
}
function onCloseWrapper() {
onClose();
detachEventHandlers();
}
function onErrorWrapper(error) {
onError(error);
detachEventHandlers();
}
attachEventHandlers();
};

40
src/writable/index.js

@ -0,0 +1,40 @@
"use strict";
const attachHandlers = require("./attach-handlers");
const writeToStream = require("./write-to-stream");
const isStdioStream = require("../is-stdio-stream");
const destroyStream = require("../destroy-stream");
const assertErrorType = require("../assert-error-type");
module.exports = function wireUpWritableInterface(stream, { onEnd, onError } = {}) {
attachHandlers({
stream: stream,
onClose: () => {
if (onEnd != null) {
onEnd();
}
},
onError: (error) => {
assertErrorType(error);
if (onError != null) {
onError(error);
}
}
});
return {
write: function (value) {
return writeToStream(stream, value);
},
end: function () {
// stdout/stderr cannot be ended like other streams
if (!isStdioStream(stream)) {
stream.end();
}
},
destroy: function () {
return destroyStream(stream);
}
};
};

25
src/writable/write-to-stream.js

@ -0,0 +1,25 @@
"use strict";
const isStdioStream = require("../is-stdio-stream");
module.exports = function writeToStream(stream, value) {
if (!isStdioStream(stream)) {
let canWriteMore = stream.write(value);
if (canWriteMore) {
return;
} else {
return new Promise((resolve, _reject) => {
stream.once("drain", () => resolve());
});
}
} else {
// NOTE: According to the `stream-to-pull-stream` code, stdout/stderr behave differently from normal streams, and the `drain` event doesn't work correctly there. So instead, we use the flush callback to know when to write the next bit of data.
return new Promise((resolve, _reject) => {
stream.write(value, (_error) => {
// NOTE: We ignore any errors here, and wait for them to be thrown in the `error` event, to simplify the logic.
resolve();
});
});
}
};

353
yarn.lock

@ -0,0 +1,353 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@joepie91/unreachable@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@joepie91/unreachable/-/unreachable-1.0.0.tgz#8032bb8a5813e81bbbe516cb3031d60818526687"
integrity sha512-vZRJ5UDq4mqP1vgSrcOLD3aIfS/nzwsvGFOOHv5sj5fa1Ss0dT1xnIzrXKLD9pu5EcUvF3K6n6jdaMW8uXpNEQ==
"@ppstreams/aborted@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/aborted/-/aborted-0.1.0.tgz#0e7d89be4234403412dda522600ad991abd0c9b6"
integrity sha512-KNrfltZBVZqcTML2ix6Pg5KjKCnD0pA4hNKEYo+PX4iNfk3oaQa+ma54lvgPEhTSOB9Vq2PKdq1ekxD/UAaMMQ==
dependencies:
create-error "^0.3.1"
"@ppstreams/buffer@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/buffer/-/buffer-0.1.0.tgz#22247ae19b84494cd916e702fae0568c2811e350"
integrity sha512-6dZoCZH9z969kzLhTLEHEbyHUhYVFtVXZA0t9vPMoTSuQFqlJJZzSO/7p3VyVpxW9okJmE5h4DxZoN8BJudVlg==
dependencies:
"@joepie91/unreachable" "^1.0.0"
"@ppstreams/propagate-abort" "^0.1.2"
bluebird "^3.5.4"
"@ppstreams/end-of-stream@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/end-of-stream/-/end-of-stream-0.1.0.tgz#fa1d3c7cbef9c2cdfdd5132a9467221826eef0b1"
integrity sha512-Fo3ljetPOuy0cV8bDtYc9vFxtRnePr4+1hy3vVmD8uxR2/oZWTgL64R91RkJSxh2FpI33X6hstnP7qEbbSQfhQ==
dependencies:
create-error "^0.3.1"
"@ppstreams/is-aborted@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/is-aborted/-/is-aborted-0.1.0.tgz#0beb590a663a09683898de9f335320e07cc2afd3"
integrity sha512-Y/th7e/VtLjnUUuYo/URkN+BEF9GYmUPo91kd2YS//L3wmmeeXwCy+cMuFLf1v4T3szLQqUbdX1YM6vyfXpiIg==
"@ppstreams/is-end-of-stream@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/is-end-of-stream/-/is-end-of-stream-0.1.0.tgz#703b0530698dc920a8fbdeea9812ef63a6fdb42c"
integrity sha512-F7J7ey5oApuH/+QD/CCotePi9ID1b5Wl1h7/IAArjL7ETDOEyHuQevsi/KXIq8kY+0YArW3F0c4QqgQujjvSnQ==
"@ppstreams/pipe@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@ppstreams/pipe/-/pipe-0.1.1.tgz#453079d2ed8074e8a105f572967e50b0bcb7ca53"
integrity sha512-Wn/uBJArde8UwB5NrZbS4kYvUHdJtloRaobAwUAUmVKqPYTSs5zcAOy3AmxRw0k+aB4w6UtlZ47lM57K/DV/QQ==
"@ppstreams/propagate-abort@^0.1.2", "@ppstreams/propagate-abort@^0.1.3":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@ppstreams/propagate-abort/-/propagate-abort-0.1.3.tgz#ddb6f63e9891d642e225802d690186db6a5e2245"
integrity sha512-mFG6okJF+l/3Ct6SmUAIxG75tXmQlK3P/O7w4IO9rjjciww8fa5srd66c/XWUk30qLh0gaPgR38TmvynsHS6bA==
"@ppstreams/propagate-peek@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/propagate-peek/-/propagate-peek-0.1.0.tgz#b8d24aa611845f64cf56b8cfd84266ce443b5f24"
integrity sha512-rX3ECnMxdD70fkEB6gQPr4aFg5bgOQ2Cks1LgFZv7Fj7qbj2SIjj1jHnhlFD1Wm6OZlcGXWuH2nbQp1hKoutkg==
"@ppstreams/simple-sink@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@ppstreams/simple-sink/-/simple-sink-0.1.0.tgz#add7aae3068b9130a9b74d0e840a767c8b22bd0f"
integrity sha512-JjfPW9NNZmddzX724bXeht+9COVLlmq1LqL/DmLa9r3JQ5JxhvUCHVYLEjE7ER7ab4psCwxVxkmslUPpa/7+fQ==
dependencies:
"@ppstreams/is-aborted" "^0.1.0"
"@ppstreams/is-end-of-stream" "^0.1.0"
"@ppstreams/propagate-abort" "^0.1.2"
"@ppstreams/propagate-peek" "^0.1.0"
"@validatem/core" "^0.3.11"
"@validatem/default-to" "^0.1.0"
"@validatem/is-function" "^0.1.0"
"@validatem/required" "^0.1.1"
"@validatem/wrap-value-as-option" "^0.1.0"
bluebird "^3.5.4"
"@ppstreams/simple-source@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@ppstreams/simple-source/-/simple-source-0.1.1.tgz#a8f276f2b6a27632edc0f123dc64c183414c4f9e"
integrity sha512-q0jpPZSGmld9VIhM+GmMRqwE9PXRiEJSXf3jhypf5VqiEs1JfrSNENvFeLYL/OxaF5PV+Jh0Up1hRfBWVK4+Sw==
dependencies:
"@ppstreams/aborted" "^0.1.0"
"@validatem/core" "^0.3.12"
"@validatem/is-function" "^0.1.0"
"@validatem/required" "^0.1.1"
"@validatem/wrap-value-as-option" "^0.1.0"
bluebird "^3.7.2"
"@validatem/annotate-errors@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@validatem/annotate-errors/-/annotate-errors-0.1.2.tgz#fa9152bb30f4f42b69496b527e38f0c31ff605a9"
integrity sha512-EuX7pzdYI/YpTmZcgdPG481Oi3elAg8JWh/LYXuE1h6MaZk3A8eP5DD33/l7EoKzrysn6y8nCsqNa1ngei562w==
dependencies:
"@validatem/match-validation-error" "^0.1.0"
"@validatem/any-property@^0.1.0":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@validatem/any-property/-/any-property-0.1.3.tgz#fc7768c1922a8bacff9369ae48913672e5350f52"
integrity sha512-jYWxif5ff9pccu7566LIQ/4+snlApXEJUimBywzAriBgS3r4eDBbz3oZFHuiPmhxNK/NNof5YUS+L6Sk3zaMfg==
dependencies:
"@validatem/annotate-errors" "^0.1.2"
"@validatem/combinator" "^0.1.0"
"@validatem/error" "^1.0.0"
"@validatem/validation-result" "^0.1.1"
"@validatem/virtual-property" "^0.1.0"
default-value "^1.0.0"
"@validatem/combinator@^0.1.0", "@validatem/combinator@^0.1.1":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@validatem/combinator/-/combinator-0.1.2.tgz#eab893d55f1643b9c6857eaf6ff7ed2a728e89ff"
integrity sha512-vE8t1tNXknmN62FlN6LxQmA2c6TwVKZ+fl/Wit3H2unFdOhu7SZj2kRPGjAXdK/ARh/3svYfUBeD75pea0j1Sw==
"@validatem/core@^0.3.11", "@validatem/core@^0.3.12":
version "0.3.12"
resolved "https://registry.yarnpkg.com/@validatem/core/-/core-0.3.12.tgz#e4e8a566850571bf55412862e88a3b06e75c8072"
integrity sha512-ngrFk6PT/pPZntpleG6q55SByongNxRk7wJhUiCihyv4yqIqqG+bNGH4wb6yW33IHefreWxkkJ53yM1Yj9srNA==
dependencies:
"@validatem/annotate-errors" "^0.1.2"
"@validatem/any-property" "^0.1.0"
"@validatem/error" "^1.0.0"
"@validatem/match-validation-error" "^0.1.0"
"@validatem/match-versioned-special" "^0.1.0"
"@validatem/match-virtual-property" "^0.1.0"
"@validatem/normalize-rules" "^0.1.0"
"@validatem/required" "^0.1.0"
"@validatem/validation-result" "^0.1.1"
"@validatem/virtual-property" "^0.1.0"
as-expression "^1.0.0"
assure-array "^1.0.0"
create-error "^0.3.1"
default-value "^1.0.0"
execall "^2.0.0"
flatten "^1.0.3"
indent-string "^4.0.0"
is-arguments "^1.0.4"
supports-color "^7.1.0"
syncpipe "^1.0.0"
"@validatem/default-to@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/default-to/-/default-to-0.1.0.tgz#62766a3ca24d2f61a96c713bcb629a5b3c6427c5"
integrity sha512-UE/mJ6ZcHFlBLUhX75PQHDRYf80GFFhB+vZfIcsEWduh7Nm6lTMDnCPj4MI+jd9E/A7HV5D1yCZhaRSwoWo4vg==
dependencies:
is-callable "^1.1.5"
"@validatem/either@^0.1.9":
version "0.1.9"
resolved "https://registry.yarnpkg.com/@validatem/either/-/either-0.1.9.tgz#0d753ef8fe04486d2b7122de3dd3ac51b3acaacf"
integrity sha512-cUqlRjy02qDcZ166/D6duk8lrtqrHynHuSakU0TvMGMBiLzjWpMJ+3beAWHe+kILB5/dlXVyc68ZIjSNhBi8Kw==
dependencies:
"@validatem/combinator" "^0.1.1"
"@validatem/error" "^1.0.0"
"@validatem/match-validation-error" "^0.1.0"
"@validatem/validation-result" "^0.1.2"
flatten "^1.0.3"
"@validatem/error@^1.0.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@validatem/error/-/error-1.1.0.tgz#bef46e7066c39761b494ebe3eec2ecdc7348f4ed"
integrity sha512-gZJEoZq1COi/8/5v0fVKQ9uX54x5lb5HbV7mzIOhY6dqjmLNfxdQmpECZPQrCAOpcRkRMJ7zaFhq4UTslpY9yA==
"@validatem/has-shape@^0.1.0":
version "0.1.8"
resolved "https://registry.yarnpkg.com/@validatem/has-shape/-/has-shape-0.1.8.tgz#dff0f0449c12b96d150091b7a980154d810ae63d"
integrity sha512-x2i8toW1uraFF2Vl6WBl4CScbBeg5alrtoCKMyXbJkHf2B5QxL/ftUh2RQRcBzx6U0i7KUb8vdShcWAa+fehRQ==
dependencies:
"@validatem/annotate-errors" "^0.1.2"
"@validatem/combinator" "^0.1.0"
"@validatem/error" "^1.0.0"
"@validatem/validation-result" "^0.1.1"
array-union "^2.1.0"
as-expression "^1.0.0"
assure-array "^1.0.0"
default-value "^1.0.0"
flatten "^1.0.3"
"@validatem/is-function@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/is-function/-/is-function-0.1.0.tgz#15a2e95259dc5e32256e8c21872455661437d069"
integrity sha512-UtVrwTGhaIdIJ0mPG5XkAmYZUeWgRoMP1G9ZEHbKvAZJ4+SXf/prC0jPgE0pw+sPjdQG4hblsXSfo/9Bf3PGdQ==
dependencies:
"@validatem/error" "^1.0.0"
is-callable "^1.1.5"
"@validatem/is-plain-object@^0.1.0", "@validatem/is-plain-object@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@validatem/is-plain-object/-/is-plain-object-0.1.1.tgz#b7a3ef8ef960882c7c41e84ed709fa0bfb932e93"
integrity sha512-aNGbNIbKRpYI0lRBczlTBbiA+nqN52ADAASdySKg2/QeSCVtYS4uOIeCNIJRAgXe/5sUnLTuL4pgq628uAl7Kw==
dependencies:
"@validatem/error" "^1.0.0"
is-plain-obj "^2.1.0"
"@validatem/match-special@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/match-special/-/match-special-0.1.0.tgz#4e0c28f1aee5bf53c1ef30bbf8c755d4946ae0ff"
integrity sha512-TFiq9Wk/1Hoja4PK85WwNYnwBXk3+Lgoj59ZIMxm2an1qmNYp8j+BnSvkKBflba451yIn6V1laU9NJf+/NYZgw==
"@validatem/match-validation-error@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/match-validation-error/-/match-validation-error-0.1.0.tgz#fa87f5f1836e7c1d9bf6b75b2addf0a5b21e4c1e"
integrity sha512-6akGTk7DdulOreyqDiGdikwRSixQz/AlvARSX18dcWaTFc79KxCLouL2hyoFcor9IIUhu5RTY4/i756y4T1yxA==
dependencies:
"@validatem/match-versioned-special" "^0.1.0"
"@validatem/match-versioned-special@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/match-versioned-special/-/match-versioned-special-0.1.0.tgz#2eacc48debecdbbe7e3d02f0c0a665afaea9bedf"
integrity sha512-xoOTY0bdA2ELj+ntcDVJ8YyMEFIJpjZ4HNPL9lGcbnRFwJBhQcHUAhUpZwkMxu02zH9wkNM1FvYGHxPz40745Q==
"@validatem/match-virtual-property@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/match-virtual-property/-/match-virtual-property-0.1.0.tgz#4de2de1075987b5f3b356d3f2bcf6c0be5b5fb83"
integrity sha512-ssd3coFgwbLuqvZftLZTy3eHN0TFST8oTS2XTViQdXJPXVoJmwEKBpFhXgwnb5Ly1CE037R/KWpjhd1TP/56kQ==
"@validatem/normalize-rules@^0.1.0":
version "0.1.3"
resolved "https://registry.yarnpkg.com/@validatem/normalize-rules/-/normalize-rules-0.1.3.tgz#59fd6193b1091ff97b5c723b32c9bb1fe2a9dc9c"
integrity sha512-HHPceAP2ce9NWymIZrgLCTzpdwXNRBCCB5H6ZPc5ggOrbmh4STpT83fLazleHtvYNlqgXZ4GjQOvCwrjaM+qEA==
dependencies:
"@validatem/has-shape" "^0.1.0"
"@validatem/is-plain-object" "^0.1.0"
"@validatem/match-special" "^0.1.0"
assure-array "^1.0.0"
default-value "^1.0.0"
flatten "^1.0.3"
is-plain-obj "^2.1.0"
"@validatem/required@^0.1.0", "@validatem/required@^0.1.1":
version "0.1.1"
resolved "https://registry.yarnpkg.com/@validatem/required/-/required-0.1.1.tgz#64f4a87333fc5955511634036b7f8948ed269170"
integrity sha512-vI4NzLfay4RFAzp7xyU34PHb8sAo6w/3frrNh1EY9Xjnw2zxjY5oaxwmbFP1jVevBE6QQEnKogtzUHz/Zuvh6g==
"@validatem/validation-result@^0.1.1", "@validatem/validation-result@^0.1.2":
version "0.1.2"
resolved "https://registry.yarnpkg.com/@validatem/validation-result/-/validation-result-0.1.2.tgz#4e75cfd87305fc78f8d05ac84921a2c99a0348e0"
integrity sha512-okmP8JarIwIgfpaVcvZGuQ1yOsLKT3Egt49Ynz6h1MAeGsP/bGHXkkXtbiWOVsk5Tzku5vDVFSrFnF+5IEHKxw==
dependencies:
default-value "^1.0.0"
"@validatem/virtual-property@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/virtual-property/-/virtual-property-0.1.0.tgz#880540dfd149f98ecf1095d93912e34443381fe4"
integrity sha512-JUUvWtdqoSkOwlsl20oB3qFHYIL05a/TAfdY4AJcs55QeVTiX5iI1b8IoQW644sIWWooBuLv+XwoxjRsQFczlQ==
"@validatem/wrap-value-as-option@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@validatem/wrap-value-as-option/-/wrap-value-as-option-0.1.0.tgz#57fa8d535f6cdf40cf8c8846ad45f4dd68f44568"
integrity sha512-gWDkfyU0DOsbinE9iqvRSJ+NxuynChyueJsC+AFm3EYbe8+s7V2gRs3qkJ4mq7hOlUbEh8tgCWQfZZvr+IdVFw==
dependencies:
"@validatem/either" "^0.1.9"
"@validatem/is-plain-object" "^0.1.1"
array-union@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
as-expression@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/as-expression/-/as-expression-1.0.0.tgz#7bc620ca4cb2fe0ee90d86729bd6add33b8fd831"
integrity sha512-Iqh4GxNUfxbJdGn6b7/XMzc8m1Dz2ZHouBQ9DDTzyMRO3VPPIAXeoY/sucRxxxXKbUtzwzWZSN6jPR3zfpYHHA==
assure-array@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/assure-array/-/assure-array-1.0.0.tgz#4f4ad16a87659d6200a4fb7103462033d216ec1f"
integrity sha1-T0rRaodlnWIApPtxA0YgM9IW7B8=
bluebird@^3.5.4, bluebird@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f"
integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==
clone-regexp@^2.1.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/clone-regexp/-/clone-regexp-2.2.0.tgz#7d65e00885cd8796405c35a737e7a86b7429e36f"
integrity sha512-beMpP7BOtTipFuW8hrJvREQ2DrRu3BE7by0ZpibtfBA+qfHYvMGTc2Yb1JMYPKg/JUw0CHYvpg796aNTSW9z7Q==
dependencies:
is-regexp "^2.0.0"
create-error@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/create-error/-/create-error-0.3.1.tgz#69810245a629e654432bf04377360003a5351a23"
integrity sha1-aYECRaYp5lRDK/BDdzYAA6U1GiM=
default-value@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/default-value/-/default-value-1.0.0.tgz#8c6f52a5a1193fe78fdc9f86eb71d16c9757c83a"
integrity sha1-jG9SpaEZP+eP3J+G63HRbJdXyDo=
dependencies:
es6-promise-try "0.0.1"
es6-promise-try@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/es6-promise-try/-/es6-promise-try-0.0.1.tgz#10f140dad27459cef949973e5d21a087f7274b20"
integrity sha1-EPFA2tJ0Wc75SZc+XSGgh/cnSyA=
execall@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/execall/-/execall-2.0.0.tgz#16a06b5fe5099df7d00be5d9c06eecded1663b45"
integrity sha512-0FU2hZ5Hh6iQnarpRtQurM/aAvp3RIbfvgLHrcqJYzhXyV2KFruhuChf9NC6waAhiUR7FFtlugkI4p7f2Fqlow==
dependencies:
clone-regexp "^2.1.0"
flatten@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
has-flag@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
indent-string@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==
is-arguments@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.0.4.tgz#3faf966c7cba0ff437fb31f6250082fcf0448cf3"
integrity sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==
is-callable@^1.1.5:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb"
integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==
is-plain-obj@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287"
integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==
is-regexp@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-2.1.0.tgz#cd734a56864e23b956bf4e7c66c396a4c0b22c2d"
integrity sha512-OZ4IlER3zmRIoB9AqNhEggVxqIH4ofDns5nRrPS6yQxXE1TPCUpFznBfRQmQa8uC+pXqjMnukiJBxCisIxiLGA==
split-filter@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/split-filter/-/split-filter-1.1.3.tgz#c68cc598783d88f60d16e7b452dacfe95ba60539"
integrity sha512-2xXwhWeJUFrYE8CL+qoy9mCohu5/E+uglvpqL1FVXz1XbvTwivafVC6oTDeg/9ksOAxg6DvyCF44Dvf5crFU0w==
supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
dependencies:
has-flag "^4.0.0"
syncpipe@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/syncpipe/-/syncpipe-1.0.0.tgz#170340f813150bc8fcb8878b1b9c71ea0ccd3727"
integrity sha512-cdiAFTnFJRvUaNPDc2n9CqoFvtIL3+JUMJZrC3kA3FzpugHOqu0TvkgNwmnxPZ5/WjAzMcfMS3xm+AO7rg/j/w==
dependencies:
assure-array "^1.0.0"
Loading…
Cancel
Save