{"id":20473520,"url":"https://github.com/xpl/pipez","last_synced_at":"2025-07-20T22:07:33.237Z","repository":{"id":57324835,"uuid":"89600694","full_name":"xpl/pipez","owner":"xpl","description":"Function sequencing reloaded","archived":false,"fork":false,"pushed_at":"2018-07-16T22:07:00.000Z","size":55,"stargazers_count":14,"open_issues_count":0,"forks_count":2,"subscribers_count":6,"default_branch":"master","last_synced_at":"2025-06-20T10:03:21.465Z","etag":null,"topics":["aspect-oriented","aspect-oriented-framework","aspect-oriented-programming","functional-programming","javascript","javascript-framework","javascript-library","npm-package","pipeline-framework","sequencing"],"latest_commit_sha":null,"homepage":"http://npmjs.com/package/pipez","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/xpl.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-04-27T13:29:18.000Z","updated_at":"2023-11-03T17:12:50.000Z","dependencies_parsed_at":"2022-09-05T11:32:11.886Z","dependency_job_id":null,"html_url":"https://github.com/xpl/pipez","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/xpl/pipez","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpl%2Fpipez","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpl%2Fpipez/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpl%2Fpipez/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpl%2Fpipez/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/xpl","download_url":"https://codeload.github.com/xpl/pipez/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/xpl%2Fpipez/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266205669,"owners_count":23892497,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aspect-oriented","aspect-oriented-framework","aspect-oriented-programming","functional-programming","javascript","javascript-framework","javascript-library","npm-package","pipeline-framework","sequencing"],"created_at":"2024-11-15T14:25:44.424Z","updated_at":"2025-07-20T22:07:33.219Z","avatar_url":"https://github.com/xpl.png","language":"JavaScript","readme":"# Pipez\n\n[![Build Status](https://travis-ci.org/xpl/pipez.svg?branch=master)](https://travis-ci.org/xpl/pipez) [![Coverage Status](https://coveralls.io/repos/github/xpl/pipez/badge.svg)](https://coveralls.io/github/xpl/pipez) [![npm](https://img.shields.io/npm/v/pipez.svg)](https://npmjs.com/package/pipez) [![dependencies Status](https://david-dm.org/xpl/pipez/status.svg)](https://david-dm.org/xpl/pipez)\n\nPipez stands for _[purely functional](https://en.wikipedia.org/wiki/Purely_functional) pipelines_. A pipeline is a function composed of other functions, like a sequence. It takes some data as input and pushes it down through, transforming it on each stage until the final result is achieved. Each function's output is an input for the next function in a sequence, and so on.\n\nThis tiny [(~100 lines of code)](https://github.com/xpl/pipez/blob/master/pipez.js) library implements a novel way for describing it in modern JavaScript, proposing a framework that focuses on easy ad-hoc parameterization of a constructed utility, so you can build [incredibly configurable tools](https://github.com/xpl/ololog) with less pain.\n\n## A case study (toy logging)\n\nTake a logging function as an example, that behaves like `console.log` in general, but has also some fancy additional features, like timestamping and indentation. As the proof of concept, one may come up with the following code (omiting the screen output part):\n\n```javascript\nindent = 0                                          // Configuration\ntimestamp = false\n\nlog = (...args) =\u003e                                  // Implementation\n\n        (timestamp ? [new Date (), ...args] : args) // Insert timestamp (if needed)\n        .map (arg =\u003e String (arg))                  // Stringify arguments \n        .join (' ')                                 // Concatenate results\n        .split ('\\n')                               // Split with linebreaks\n        .map (line =\u003e '\\t'.repeat (indent) + line), // Apply indentation\n        ...                                         // ...\n```\n\nIt kinda \"works\", but these global configuration params are not looking good. One common way is to modify a function signature, introducing a special configuration parameter to it:\n\n```javascript\nlog = ({ indent = 0, timestamp = false }, ...args) =\u003e\n```\n\nBut that's somewhat intrusive, invading the original call semantics. With closures and first-class functions, JS offers a better way of separating these concerns:\n\n```javascript\nconfigure = ({ indent = 0, timestamp = false }) =\u003e   // Configuration\n            (...args) =\u003e                             // Implementation\n            ...\n```\n```javascript\nlog = configure ({ indent: 2, timestamp: true })\nlog ('hello', 'world')\n```\n\nSome better languages even got this feature implemented on the syntatic level (currying)! But speaking of JS, it is also very convenient to have that `configure` as a regular method of a constructed function instance. This way you can stack up multiple `configure` calls, thus being able to incrementally update an existing configuration, in an _ad hoc_ way:\n\n```javascript\nmylog = log.configure ({ timestamp: true })\n\nmylog ('hello')\nmylog.configure ({ indent: 2 }) ('world') // ad-hoc configuration\n```\n\nI recently had coded a couple of tiny libraries ([as-table](https://github.com/xpl/as-table), [String.ify](https://github.com/xpl/string.ify)) embracing that API design principle, and found it immensely useful in practice.\n\nBut as of now, we had only scratched the surface of the hidden complexity landscape that _Pipez_ successfully tackles. When you start thinking about _configuration_ — i.e. what and how can be parameterized externally — once-simple things can quickly start getting really complicated...\n\n```javascript\nlog = ({\n\n        indentLevel      = 0,\n        indentCharacters = '\\t'                          // Many prefer spaces over tabs\n        timestamp        = false,\n        stringify        = x =\u003e String (x)               // Custom argument stringifiers are more than useful\n        stringifyDate    = date =\u003e date.toISOString (),  // So that are custom date formatters, too\n        when             = new Date (),                  // Sometimes you need to set a date other than the current date\n        linebreak        = '\\n',                         // Think about outputting HTML (may want \u003cbr\u003e's instead)\n        wordSeparator    = ' ',                          // In HTML we may want use the \u0026nbsp; instead...\n    \n    }) =\u003e (...args) =\u003e\n\n        timestamp ? [stringifyDate (when), ...args] : args\n        .map (arg =\u003e stringify (arg))\n        .join (wordSeparator)\n        .split (linebreak)\n        .map (line =\u003e indentCharacters.repeat (indentLevel) + line),\n        ...\n```\n\nAnd your beautiful tiny several-lines-of-code-proof-of-concept-thing start turning into 500-pound nightmare in production! The new ES6 destructuring/defaults syntax is amazing and helping alot, though.\n\nAgain, there exist better ways to deal with such a high degree extensibility. Can you, by the way, tell the biggest problem with the code above? To me, paradoxically, this is the very thing that we considered good until quite recently — the separation of concerns. The actual logic now is starting to split between the externalized and the intrinsic part, and it's becoming harder to grasp the full thing, as you need to constantly switch your attention back and forth while trying to understand what's the code does. As the codebase grows and you extract more features into configurable parameters, the problem arise.\n\nThis is not really better as the global parameters... Wouldn't it be nice, if we could somehow modularize the thing, finding a way of specifying the external parameters and their default values just along with the code that uses it?\n\n## Function sequencing\n\nThink of it as a sequence of functions. Each step is essentially a function, taking input from the previous step and outputting result to the next one in the chain:\n\n```javascript\nargs → timestamp → stringify → concat → linebreaks → indent\n```\n\nOr (in terms of function application):\n\n```javascript\nindent (linebreaks (concat (stringify (timestamp (args)))))\n```\n\nAs for somewhat unexpected feature, we can specify the sequence using the object initializer syntax, thus giving each step a meaningful name. Order matters, so it's really an ordered list, not a random dictonary — and with the new `Reflect.ownKeys` API we can consistently capture the order declared:\n\n```javascript\nlog = pipez ({\n    \n    timestamp:  args  =\u003e ...,\n    stringify:  args  =\u003e ...,\n    concat:     args  =\u003e ...,\n    linebreaks: text  =\u003e ...,\n    indent:     lines =\u003e ...,\n    ...\n})\n\nlog ('hello', 'world')\n```\n\nEach routine can receive the externally configurable parameters (coming as the second formal parameter of a routine). These parameters are local, so no name conflicts with other steps' stuff — both routines can declare a `print` thing here, as an example:\n\n\n```javascript\nlog = pipez ({\n    \n    timestamp: (args, { print = x =\u003e x.toISOString (), when = new Date () }) =\u003e [print (when), ...args],\n    stringify: (args, { print = x =\u003e String (x) }) =\u003e args.map (print),\n   \n    ...\n    \n    indent: (lines, { level = 0, characters = '\\t' }) =\u003e lines.map (line =\u003e characters.repeat (level) + line),\n    \n    ...\n})\n```\n\n## Binding to parameters\n\nThese parameters can be bound via the `configure` calls.\n\n### Pre-configuring\n\nGiven a `log` function, this creates a derived `mylog` function configured in some special way:\n\n```javascript\nmylog = log.configure ({ indent: { characters: '  ' }, timestamp: { print: x =\u003e x.getDate () } })\n```\n\n### Ad-hoc configuration\n\nGiven the previously defined `mylog` function, it prints `hello world` message with the indentation level set to `2`:\n\n```javascript\nmylog.configure ({ indent: { level: 2 } }) ('hello world')\n```\n\n## Turning arbitrary steps on and off\n\nInstead of manually coding an on/off switch:\n\n```javascript\nlog = pipez ({\n\n    timestamp: (args, { yes = true, when = new Date () }) =\u003e yes ? [when, ...args] : args,\n    ...\n})\n```\n\nYou can just use this semantics, as it's already recognized by the framework as built-in:\n\n```javascript\nmylog = log.configure ({ timestamp: { yes: false } })\n```\n\nA shortcut notation:\n\n```javascript\nmylog = log.configure ({ timestamp: false }) // timestamp step will be skipped from evaluation\n```\n\n## Replacing the code\n\nYou may override a step behavior completely, rather just changing it parameters. Pass a function instead of an object, and it will become a new step implementation. You can also declare and use the new external params as well.\n\nCreates a derived `mylog` function that draws ANSI-colored timestamps in the end of messages (using the [ansicolor](https://github.com/xpl/ansicolor) library):\n\n```javascript\nmylog = log.configure ({ timestamp: (args, { color = 'red' }) =\u003e [...args, ansicolor[color] (new Date ())]\n```\n\nPrints 'hello world' followed with a cyan timestamp:\n\n```javascript\nmylog.configure ({ timestamp: { color: 'cyan' } }) ('hello world')\n```\n\n## Injecting code before and after steps\n\nIf you don't want to replace the original behavior, you may bind to the _before_ and the _after_ execution of steps, giving your function a special name, with `+` symbol placed before or after the target step name, respectively. Following code will be chained in just after the `concat` step:\n\n```javascript\nlog.magenta = log.configure ({ 'concat+': text =\u003e ansicolor.magenta (text) })\n```\n\nAnd this schedules to execute just before the `linebreaks` step:\n\n```javascript\nlog.magenta = log.configure ({ '+linebreaks': text =\u003e ansicolor.magenta (text) })\n```\n\n## Executing just a part of a sequence:\n\nExecuting all steps before a step (_not including_ it):\n\n```javascript\nlet concatenated = log.before ('linebreaks') (...)\n```\n\nExecuting all steps after a step, _including_ it:\n\n```javascript\nlog.from ('linebreaks') (concatenated)\n```\n\n## Adding inherited methods\n\nThis adds `magenta` property accessor to the `log`:\n\n```javascript\nlog = pipez ({ ... })\n\nlog.methods ({\n\n    get magenta () { return this.configure ({ 'concat+': text =\u003e ansicolor.magenta (text) }) }\n})\n\nlog.magenta ('this is magenta colored')\n```\n\n...and to it's every derived object:\n\n```javascript\nmylog = log.configure ({ ... })\nmylog.magenta ('this is magenta colored too')\n```\n\n## Accessing initial arguments\n\nEvery step can access it from its configuration parameters, as the `initialArguments` property:\n\n```javascript\nconst logThatReturnsFirstArgument = log.configure ({\n\n    'output+': (_, { initialArguments: [first] }) =\u003e first // adds a step after the 'output' step\n})\n\nlogThatReturnsFirstArgument ('foo', 'bar', 42) // returns 'foo'\n```\n\n## Applications\n\n- [Ololog!](https://github.com/xpl/ololog) — a platform-agnostic logging with blackjack and hookers\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxpl%2Fpipez","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fxpl%2Fpipez","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fxpl%2Fpipez/lists"}