Security News
Oracle Drags Its Feet in the JavaScript Trademark Dispute
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
@bjmrq/pipe-flow
Advanced tools
THIS IS A WORK IN PROGRESS BREAKING CHANGED CAN HAPPEN ANY TIME BEFORE v1 release
This is a little utility to process data in a pipe of functions using node.js runtime.
Your pipeline will flow a "context" containing the data you'll need and want to return thought your functions.
Your pipe will receive some data that will be attached to the "context" as an input, this data will be immutable.
The "context" also contain a mutable state entry. Anything you return from any of your function will be attached to this state.
In the "context" their is also a return entry so you can control what you wish to return at the end of your pipe.
You will also find some little helpers to help you with error handling.
And some utilities like: debugFlow / flowIf / flowIfElse /addToState / addToStateOn / addToReturn / addToReturnOn / returnWith
With npm:
npm install @bjmrq/pipe-flow
With Yarn:
yarn add @bjmrq/pipe-flow
Javascript:
const flowWith = pipeFlow(
(context) => {
const message = context.input.message // access the context
context.return = message //control what you want to return
}
)();// This is for optional error handling
const result = flowWith({ message: "Hello world" }) // create context from data
console.log(result) // "Hello world"
Arity:
pipeFlow(...functions, errorHandler, dataToTransformAsContext)
You can create a flow made of multiple functions that will execute one after an other from left to right, similar to a pipe function. Those function can either be sync or async functions.
const flowWith = pipeFlow(
(context) => {
console.log(context.input) // {id: 9}
return { status: "ok" } // Attach data to the state
},
async (context) => {
console.log(context.state.status) // "ok"
}
)();
flowWith({ id: 9 });
If you want to move some data from one function to an other simply return it in an object. Any object you return from one of your function will be merge in the state key of the "context".
const flowWith = pipeFlow(
async (context) => {
const product = await database("products").where(
"id",
context.input.productId // Access data from context
);
return { product }; // Attach data to the state
},
async (context) => {
const productName = context.state.product.name // Access data from tate
console.log(productName) // "A great product indeed"
}
)();
flowWith({ productId: 9 });
You need to attach any data you wish to return to the return key of the "context", other keys will never be returned. If an error has happened during the flow it will be returned instead.
const flowWith = pipeFlow(
async (context) => {
const product = await database("products").where(
"id",
context.input.productId // Access data from context
);
return { product }; // Attach data to the state
},
async (context) => {
context.return = context.state.product // Control data to return
}
)();
const result = flowWith({ productId: 9 })
console.log(result) // { id: 9, name: "A great product indeed", type: "product",... }
You can use subFlow to branch different flow together subFlow receive an already built context and will be able to attache data to it's return and state property.
const flowWith = pipeFlow(
(context) => {
console.log(context.input) // {id: 9}
return { status: "ok" } // Attach data to the state
},
subFlow(
async (context) => {
return { subFlow: true } // "ok"
},
(context) => {
context.return = context.state
}
)
)();
const result = flowWith({ id: 9 });
console.log(result) // { status: "ok", subFlow: true }
If you want to control what is return from your flowPipe depending of different errors that might happen you need to attach the error to the error key of the "context". This will skip the execution of all other functions in your flow. The error should be attach to the "context" and not throw, to control the flow in your application. But if you forget to catch an error it will be attach to the "context" as well and returned as
const flowWith = pipeFlow(
async (context) => {
const product = await database("products").where(
"id",
context.input.productId // Access data from context
);
if(!product) {
context.error = { // Attache the error on the "context"
code: 404,
message: "Product Not Found"
};
}
return { product }; // Attach data to the state
},
// If an error has been attach in the previous function this one will not run
async (context) => {
const updatedProduct = await database("product").where(
"id",
context.state.product.id
).update(
{ sold: true}
);
context.return = updatedProduct;
}
)();
This will result the following error
{
code: 404,
message: 'Product Not found',
error: Error: Not found
at ...
}
type FlowError = {
statusCode: number;
message: string;
error?: Error;
};
(to help you with formatting errors see the error helpers section)
{
"code": 1,
"message": "Internal Error",
"error": Error: something wrong happened
at..
}
You can use little error helper to format the errors attached to the "context".
example 1:
context.error = errorBuilder()()
Will return
{
code: 1,
message: "Internal Error"
error: new Error()
}
example 2:
context.error = errorBuilder(9)(new Error("Could not process arguments"))
// Same as
context.error = errorBuilder(9)("Could not process arguments")
Will return
{
code: 9,
message: "Could not process arguments",
error: new Error("Could not process arguments")
}
Some predefined ones are derived from the builder but you can easily create yours
code=1
providedcode=9
providedcode=404
providedcode=403
providedcode=422
providedError builder in action
const notAuthorizedError = errorBuilder(403); // Import
const { notAuthorizedError } = require("@bjmrq/pipe-flow")
exports.handler = pipeFlow(
(context) => {
const authorizationToken = context.input.token;
if (!authorizationToken) {
context.error = notAuthorizedError("You can't do that");
}
return { isAuthenticated: true }; // Only taken in consideration if no error was attached to the "context"
},
)();
If you wish to have extra logic triggered when an error occurres (send log to remote place, call a cloud service..) you can provide pipeFlow
with an extra function.
exports.handler = pipeFlow(
async (context) => {
const product = await database("product").where(
"id",
context.state.productId
);
if (!product) {
context.error = notFoundError(new Error("Could not find this product"));
}
context.return = product
// Extra error handling
)((context) => {
sendLogs(context.error)
});
const flowWith = pipeFlow(
(context) => {
return { status: "ok" } // Attach data to the state
},
debugFlow() // { input: { id: 9 }, state: { status: "ok" }, error: undefined, return undefined}
)();
flowWith({ id: 9 });
const flowWith = pipeFlow(
(context) => {
return { status: "ok" } // Attach data to the state
},
debugFlow(["state", "status"]) // "ok"
)();
flowWith({ id: 9 });
const flowWith = pipeFlow(
addToReturn((context) => { userId: context.input.id }),
)();
const result = flowWith({ id: 9 });
console.log(result) // { userId: 9 }
const flowWith = pipeFlow(
addToState((context) => { userId: context.input.id }),
(context) => {
console.log(context.state) // { userId: 9 }
}
)();
const result = flowWith({ id: 9 });
const flowWith = pipeFlow(
addToReturnOn("userId", (context) => context.input.id ),
)();
const result = flowWith({ id: 9 });
console.log(result) // { userId: 9 }
const flowWith = pipeFlow(
addToStateOn("user", (context) => { userId: context.input.id }),
(context) => {
console.log(context.state) // { user: { userId: 9 }}
}
)();
const result = flowWith({ id: 9 });
addToStateImmutableOn: will add the return value of a given function to the specified key to the state property of the context, and make it read only
returnWith: usually used at the end of the pipeFlow or subFlow, will return the given path from the context
const flowWith = pipeFlow(
addToStateOn("user", (context) => { userId: context.input.id }),
(context) => {
console.log(context.state) // { user: { userId: 9 }}
},
returnWith(["state", "object", "userId"])
)();
const result = flowWith({ id: 9 });
console.log(result) // 9
const flowWith = pipeFlow(
addToStateOn("shouldExecute", () => true),
flowIf(
(context) => context.state.shouldExecute,
(context) => {
context.return = { hasExecuted: true };
}
)
)();
const result = flowWith({ id: 9 });
console.log(result) // { hasExecuted: true }
const flowWith = pipeFlow(
addToStateOn("shouldExecute", false}),
flowIf(
(context) => context.state.shouldExecute,
(context) => {
context.return = { hasExecuted: true };
}
)
)();
const result = flowWith({ id: 9 });
console.log(result) // undefined
const flowWith = pipeFlow(
addToStateOn("number", () => 1),
flowOn(["state", "number"],
(number) => ({
addedNumber: number + 1,
})
),
returnWith(["state", "addedNumber"])
)();
const result = flowWith({ initialData: "woo" });
console.log(result) // 2
const flowWith = pipeFlow(
addToStateOn("number", () => 1),
flowOnTo(
["state", "number"],
"addedNumber",
(number) => number + 1,
),
returnWith(["state", "addedNumber"])
)();
const result = flowWith({ initialData: "woo" });
console.log(result) // 2
A flow is similar to a pipe function in functional programming, you can combine your functions from left to right, and the "context" will flow thought them, what you return from those functions will be attach to the state of the "context" so it can be passed on to the next function of the flow. Anything you wish to return can be attached to the return entry of the "context".
Those are the keys accessible inside the "context"
If you want to pass data from one function to an other you can use the state key
You will find different types available
type FlowContext
type FlowMiddleware
FAQs
A little utility to flow data in a pipe
We found that @bjmrq/pipe-flow demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Oracle seeks to dismiss fraud claims in the JavaScript trademark dispute, delaying the case and avoiding questions about its right to the name.
Security News
The Linux Foundation is warning open source developers that compliance with global sanctions is mandatory, highlighting legal risks and restrictions on contributions.
Security News
Maven Central now validates Sigstore signatures, making it easier for developers to verify the provenance of Java packages.