{"id":15706124,"url":"https://github.com/tillathehun0/tilla","last_synced_at":"2025-05-12T21:06:53.400Z","repository":{"id":46821398,"uuid":"118527437","full_name":"TillaTheHun0/tilla","owner":"TillaTheHun0","description":"Permission based object transformation made easy.","archived":false,"fork":false,"pushed_at":"2023-12-18T17:13:09.000Z","size":1478,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-05T13:57:51.133Z","etag":null,"topics":["asynchronous","data","datatransferobject","dto","mapper","mapping","node","permissions","promise-api","transform","transformer"],"latest_commit_sha":null,"homepage":"https://tillathehun0.github.io/tilla/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/TillaTheHun0.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-01-22T23:06:31.000Z","updated_at":"2020-10-28T01:44:09.000Z","dependencies_parsed_at":"2024-10-09T13:46:48.164Z","dependency_job_id":null,"html_url":"https://github.com/TillaTheHun0/tilla","commit_stats":{"total_commits":130,"total_committers":4,"mean_commits":32.5,"dds":"0.15384615384615385","last_synced_commit":"f317df40a122868daa0fc01b5fdaace0967496bb"},"previous_names":[],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TillaTheHun0%2Ftilla","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TillaTheHun0%2Ftilla/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TillaTheHun0%2Ftilla/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/TillaTheHun0%2Ftilla/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/TillaTheHun0","download_url":"https://codeload.github.com/TillaTheHun0/tilla/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243191611,"owners_count":20251083,"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":["asynchronous","data","datatransferobject","dto","mapper","mapping","node","permissions","promise-api","transform","transformer"],"created_at":"2024-10-03T20:21:36.981Z","updated_at":"2025-03-12T09:32:34.233Z","avatar_url":"https://github.com/TillaTheHun0.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tilla\n\n## This is Version 2 Docs\n\nIf you're looking for version 1 docs. Check out the [v1](https://github.com/TillaTheHun0/tilla/tree/v1) branch\n\n[![Coverage Status](https://coveralls.io/repos/github/TillaTheHun0/tilla/badge.svg?branch=development)](https://coveralls.io/github/TillaTheHun0/tilla?branch=development) [![Build Status](https://travis-ci.org/TillaTheHun0/tilla.svg?branch=development)](https://travis-ci.org/TillaTheHun0/tilla?branch=development) [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) [![TypeScript](https://camo.githubusercontent.com/21132e0838961fbecb75077042aa9b15bc0bf6f9/68747470733a2f2f62616467656e2e6e65742f62616467652f4275696c74253230576974682f547970655363726970742f626c7565)](https://www.typescriptlang.org/) [![npm version](https://img.shields.io/npm/v/tilla.svg)](https://www.npmjs.com/package/tilla) [![License](https://img.shields.io/npm/l/tilla.svg?maxAge=2592000?style=plastic)](https://github.com/TillaTheHun0/tilla/blob/master/LICENSE) [![Greenkeeper badge](https://badges.greenkeeper.io/TillaTheHun0/tilla.svg)](https://greenkeeper.io/)\n\n\nTilla transforms objects, based on the rules you specify. It has a fluid, composable API, and non-blocking transformations. It also comes\npackaged with sensible default permission levels, and a registry to keep track of all of your Transformers that can easily be tied into other parts of your app.\n\n## Table of Contents\n\n- [Installation](#installation)\n- [Documentation](#docs)\n- [Features](#features)\n- [Usage](#usage)\n- [Contribute](#Contribute)\n- [License](#License)\n- [Whats The Name Mean?](#Name)\n\n## Installation\n\n```bash\n$ npm install --save tilla\n```\n\n## Goals\n\nI wanted to have a fluid, easy to read, chainable API to build Transformers with sensible defaults. I wanted permissions to be incorporated in the Transformer API itself and allow the user to provide their own domain specific permissions and permission ranking that would cascade down to sub-transformations. All transformations should be completely asynchronous, down to the field level. Looking at the Transformer code should give an idea as to the shape of the resultant object produced by that Transformer. It should also be easy to transform fields on an object using other Transformers, in other words Sub-transformations, and these would be lazy loaded at runtime.\n\n## Features\n\n- Chainable, fluid Transformer API\n- Asynchronous transformations at the field level\n- Built-in Transformer registry\n- Field Permission-Masking\n- Cascading permissions (for sub-transformations)\n\n## Usage\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, atOrAbove, passthrough, buildWith, subTransform\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nconst addressTransformer = new Transformer({\n  street: fd('street', always(passthrough())),\n  city: fd('city', always(passthrough())),\n  state: fd('state', always(passthrough()))\n  otherThing: fd('otherThing', atOrAbove(Permissions.PRIVATE, passthrough()))\n})\n\nconst personTransformer = new Transformer({\n  // always directly map src.firstName -\u003e dest.firstName\n  firstName: fd('firstName',\n    always(passthrough())\n  ),\n  // computed field using a custom builder\n  name: fd(always(\n    buildWith(src =\u003e `${src.firstName} ${src.lastName}`\n  )),\n  // multiple mapping strategies, based on permission\n  age: fd('age',\n    when(Permissions.PRIVATE, passthrough()),\n    when(Permissions.PUBLIC, buildWith(\n      (src, key) =\u003e src[key] - 10\n    ))\n  ),\n  // Use another Transformer to map the field\n  address: fd('address', always(\n    subTransform(addressTransformer)\n  ),\n  // only mapped if permission level is \u003e=PRIVATE\n  ssn: fd('ssn', atOrAbove(Permission.PRIVATE, passthrough())),\n  // only mapped if permission level === PRIVATE\n  phoneNumber: fd('phoneNumber', restrictTo(Permissions.PRIVATE, passthrough()))\n})\n\nlet person = {\n  age: 32,\n  firstName: 'John',\n  lastName: 'Doe',\n  height: 60,\n  address: {\n    state: 'IL'\n  }\n  ssn: '123-45-6789',\n  phoneNumber: '867-5309'\n}\n\n// Transformers.transform() always returns a Promise\npersonTransformer.transform(Permissions.PUBLIC, person).then((personDto) =\u003e {\n  /*\n  {\n    firstName: 'John'\n    age: 22,\n    name: 'John Doe'\n    address: {\n      state: 'IL'\n    }\n  }\n  */\n})\n\n```\n\nTilla is used to transform objects. It's great for building DTOs and controlling access to certain fields on those DTOs. The core of Tilla is `Transformers`, `Rules`, and `FieldMappers`. `Transformers` describe the shape of the result object, `Rules` tell the `Transformer` _when_ to map each field, and `FieldMappers` tell the `Rule` _how_ to map each field.\n\n**`FieldMappers` are grouped into `Rules` which are grouped together in a `FieldMapperDelegate` which are further grouped together in a `Transformer`.**\n\n```bash\nTransformer {\n  [\n    fieldMapperDelegate(\n      [\n        rule(fieldMapper?)\n      ]\n    )\n  ]\n}\n```\n\nCall `transform()` on a `Transformer` and provide the permission lvl and object to transform. This will return a `Promise` that will resolve with the transformed object.\n\n### Field Masking \u0026 Permissions\n\nBy default `Tilla` ships with 4 permission levels: `PUBLIC`, `PRIVILEGED`, `PRIVATE`, and `ADMIN` and the ranking of these fields, from least sensitive to most sensitive is [`PUBLIC`, `PRIVILEGED`, `PRIVATE`, `ADMIN`]\n\n`FieldMapperDelegate`s can set multiple masking levels for each field, based on permissions, and their chainable API makes it easy to set up complex mappings for each field on a `Transformer`. In the example above, `always()` was used for each field, which indicates a single builder for all permission levels -- \"'Always' use this method to transform the value provided\". We can specify multiple methods like so:\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  when, atOrAbove, passthrough, buildWith\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet oldPersonTransformer = new Transformer({\n  // different transformations for PUBLIC and PRIVATE permission levels.\n  age: fd('age',\n    when(Permissions.PRIVATE, passthrough())\n    when(Perissions.PUBLIC, buildWith(\n      (src, key) =\u003e {\n        let age = src[key]\n        return age ? age - 10 : null\n      }\n    ))\n  ),\n  name: fd(always(buildWith(\n    src =\u003e `${src.firstName} ${src.lastName}`\n  ))),\n  // only transformations at PRIVATE and above permission lvls will have this field\n  ssn: fd('ssn', atOrAbove(Permissions.PRIVATE, passthrough()))\n})\n\n```\n\nYou can specify your own permission ranking, when instantiating the `fieldDelegate`. and `Tilla` will ensure that ranking is enforced throughout the entire `FieldDelegate` chain.\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  when, atOrAbove, passthrough, buildWith\n} from 'tilla'\n\nlet ranking = ['USER', 'EMPLOYEE', 'MANAGER']\n\nconst fd = fieldDelegate(ranking) // pass your ranking to the util wrapper\n\nlet oldPersonTransformer = new Transformer({\n  // different transformations for PUBLIC and PRIVATE permission levels.\n  age: fd('age',\n    when('EMPLOYEE', passthrough()),\n    when('USER', buildWith(\n      (src, key) =\u003e {\n        let age = src[key]\n        return age ? age - 10 : null\n      }\n    ))\n  ),\n  name: fd(always(buildWith(\n    src =\u003e `${src.firstName} ${src.lastName}`\n  ))),\n  // only transformations at PRIVATE and above permission lvls will have this field\n  ssn: fd('ssn', atOrAbove('MANAGER', passthrough())),\n  // Will throw an ERROR because this permission lvl does not exist in the provided ranking\n  broken: fd('broken', atOrAbove('BOGUS_LEVEL', passthrough()))\n})\n\n```\n\n### Can I Use a Transformer to Map a field?\n\nYes! This is called a 'subTransform'. You may want to do this for an attached association. For example, a `Person` may have an eagerly loaded `Address`. With `Tilla` you can specify each of these `Transformer`s and then specify a `SubTransformation` in the `Person` `Transformer` for the key, `address`. You can specify a string which will\nsearch the built in Transformer registry, a `Transformer`, or a function that returns a Promise that resolves to a `Transformer`.\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, atOrAbove, passthrough, subTransform\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet addressTransformer = new Transformer({\n  street: fd('street', always(passthrough())),\n  city: fd('city', always(passthrough())),\n  state: fd('state', always(passthrough())),\n  otherThing: fd('otherThing', atOrAbove(Permissions.PRIVATE, passthrough()))\n})\n\nlet personTransformer = new Transformer({\n  age: fd('age', always(passthrough())),\n  /*...*/\n  // Subtransform from the registry\n  address: fd('address', always(subTransform('address')))\n  // OR directly provide the transformer\n  address: fd('address', always(subTransform(addressTransformer)))\n  // OR a Thunk that returns a Transformer\n  address: fd('address', always(subTransform(async () =\u003e addressTransformer)))\n})\n\nlet person = {\n  age: 22,\n  firstName: 'John',\n  lastName: 'Doe',\n  ssn: '123-45-6789',\n  address: {\n    street: '123 Street',\n    city: 'Chicago',\n    state: 'IL',\n    otherThing: 'other'\n  }\n}\n\npersonTransformer.transform(Permissions.PUBLIC, person).then((publicPersonDto) =\u003e { // public permission lvl\n  /*\n  {\n    age: 22,\n    name: 'John Doe',\n    address: {\n      street: '123 Street',\n      city: 'Chicago',\n      state: 'IL'\n    }\n  }\n  */\n})\n\npersonTransformer.transform(Permissions.PRIVATE, person).then((privatePersonDto) =\u003e { // private permission lvl\n  /*\n  {\n    age: 22,\n    name: 'John Doe',\n    ssn: '123-45-6789',\n    address: {\n      street: '123 Street',\n      city: 'Chicago',\n      state: 'IL'\n      otherThing: 'other'\n    }\n  }\n  */\n})\n\n```\n\n`Transformer` has another constructor that accepts a string, the registry string, and an object, the field mapping. This will automatically add that `Transformer` instance to the internal registry at the key. However, you can also use your own registry system separate from `tilla`.\n\nAll the permission APIs work the same with `SubTransform`. **The permission provided to the parent propogates down to the `subTransform`**. This is the default behavior. To override this, you can specify a permission lvl to use for the SubTransformation when defining the transformer.\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, atOrAbove, passthrough, subTransform\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet addressTransformer = new Transformer({\n  street: fd('street', always(passthrough())),\n  city: fd('city', always(passthrough())),\n  state: fd('state', always(passthrough())),\n  otherThing: fd('otherThing', atOrAbove(Permissions.PRIVATE, passthrough()))\n})\n\nlet personTransformer = new Transformer({\n  age: fd('age', always(passthrough())),\n  /*...*/\n  // transform with PUBLIC permission lvl, regardless of the parents permission lvl\n  address: fd('address', always(subTransform(addressTransformer, Permissions.PUBLIC)))\n})\n\nlet person = {\n  age: 22,\n  firstName: 'John',\n  lastName: 'Doe',\n  ssn: '123-45-6789',\n  address: {\n    street: '123 Street',\n    city: 'Chicago',\n    state: 'IL',\n    otherThing: 'other'\n  }\n}\n\n// public permission lvl\npersonTransformer.transform(Permissions.PUBLIC, person).then((publicPersonDto) =\u003e {\n  /*\n  {\n    age: 22,\n    name: 'John Doe',\n    address: {\n      street: '123 Street',\n      city: 'Chicago',\n      state: 'IL'\n    }\n  }\n  */\n})\n\n// private permission lvl\npersonTransformer.transform(Permissions.PRIVATE, person).then((privatePersonDto) =\u003e {\n  /*\n  {\n    age: 22,\n    name: 'John Doe',\n    ssn: '123-45-6789',\n    address: {\n      street: '123 Street',\n      city: 'Chicago',\n      state: 'IL'\n      // Still no otherThing value because address was transformed using the PUBLIC permission lvl\n    }\n  }\n  */\n})\n\n```\n\n### Can a Transformer be used to transform a list of object?\n\nYes! It is common to have a list of objects to transform using a specified Transformer. For example, a `Person` could have multiple `Car`s that are eagerly loaded. To specify a list of objects to transform with a common `Transformer`, simply add `asList()` Rule on the chain provided to the `FieldMapperDelegate`.\n\n``` javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, passthrough, subTransform, asList\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet personTransformer = new Transformer({\n  age: fd('age', always(passthrough())),\n  /*...*/\n  // will transform each object in the list with the Transformer registerd at 'car' in the registry\n  cars: fd('cars', always(\n    subTransform('car'),\n    asList()\n  ))\n})\n\n```\n\n### Can I specify a default for a set of fields?\n\nYes! `Transformers` have a method `byDefault()` that will accept an Array of string attributes. You can then specify how all of those attributes will be transformed. A common case is just mark all those fields as `passthrough`.\n\n```javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, passthrough, buildWith\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet personTransformer = new Transformer({\n  // Special transformation cases here\n  name: fd(always(buildWith(\n    src =\u003e `${src.firstName} ${src.lastName}`\n  ))),\n  city: fd('homeCity', always(passthrough())),\n  state: fd('address', always(buildWith(\n    (src, key) =\u003e {\n      let address = src[key]\n      return address ? address.state : address\n    }\n  )))\n  // .BUILD_WITH() can also be used and follows the same builder API as customer field builders\n}).byDefault(['age', 'height']).PASSTHROUGH()\n\n```\n\n### Can I build a Transformer based off of another?\n\nYes! You can extend an exisiting `Transformer` by calling `extend()` and passing a map just like you would a normal `Transformer`. This will merge the two mappings and return a new `Transformer` instance.\n\n``` javascript\n\nimport {\n  fieldDelegate, Transformer, Permissions,\n  always, passthrough, buildWith\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nlet personTransformer = new Transformer({\n  // Special transformation cases here\n  name: fd(always(buildWith(\n    src =\u003e `${src.firstName} ${src.lastName}`\n  ))),\n  city: fd('homeCity', always(passthrough()))\n}).byDefault(['age', 'height']).PASSTHROUGH()\n\n// childTransformer will have all attributes of personTransformer, add a favoriteToy fieldDelegate, and override the name transformer\nlet childTransformer = personTransformer.extend({\n  favoriteToy: fd('favoriteToy', always(passthrough())),\n  name: fd('name', always(buildWith(\n    src =\u003e `Lil' ${src.firstName}`\n  )))\n})\n```\n\n### Can I write my own custom rules and mappers?\n\nYes you can! Most use cases are covered by the Rules and FieldMappers provided by `tilla`, but you might want to write your own. A `Rule` and a `FieldMapper` are nothing more than functions. Here are their APIs:\n\n```typescript\ntype Rule = (fieldMapperDelegate: FieldMapperDelegate) =\u003e FieldMapperDelegate\n\ntype FieldMapper = (fieldMapperDelegate: FieldMapperDelegate) =\u003e\n  (instance: any, key: string, isList: boolean, permission: string) =\u003e\n    Promise\u003creturnType\u003e\n```\n\nNotice that both `Rule` and `FieldMapper` eventually receive the `FieldMapperDelegate` instance. This enables both `Rule` or `FieldMapper` to access and/or mutate the state maintained by the delegate that is used later on when `tranform` is called.\n\nAny function that implements either of those APIs can be used as a `Rule` or a `FieldMapper`, respectively! Let's show an example.\n\n#### EitherOr Custom Rule Example\n\nSay I want a rule that will only transform a field _only if_ the permission lvl is `PUBLIC` or `ADMIN`. You could of course implement this as a list of rules that `tilla` already provides:\n\n```javascript\nimport {\n  fieldDelegate, Permissions, when, passthrough\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nfd('name',\n  when(Permissions.PUBLIC, passthrough()),\n  when(Permissions.ADMIN, passthrough())\n)\n```\n\nBut, let's write a _single_ Rule that accomplishes this:\n\n```javascript\nimport {\n  fieldDelegate, Permissions, passthrough\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\n// Our custom Rule\nconst eitherOr = (eitherPermission, orPermission, fieldMapper) =\u003e fieldMapperDelegate =\u003e {\n  const { delegateMap } = fieldMapperDelegate\n\n  delegateMap[eitherPermission] = fieldMapper(fieldDelegateMapper)\n  delegateMap[orPermission] = fieldMapper(fieldDelegateMapper)\n\n  return fieldDelegateMapper\n}\n\n// using our custom Rule\nfd('age', eitherOr(Permissions.PUBLIC, Permissions.ADMIN, passthrough()))\n```\n\nNow whenever we call `transform`, the fieldDelegate will only map the field, as a `passthrough()`, only if the provided permission is either `PUBLIC` _or_ `ADMIN`\n\n#### addMapper Custom FieldMapper Example\n\nLet's extend out last example. Say we wanted a `FieldMapper` that simply adds a provided number to the value that it was mapping. Again, you could implement this using the `buildWith` mapper `tilla` already provides:\n\n```javascript\nimport {\n  fieldDelegate, Permissions, buildWith\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\nconst addMapper = n =\u003e buildWith(async (instance, key) =\u003e instance[key] + n)\n\nfd('name',\n  when(Permissions.PUBLIC, addMapper(1)),\n  when(Permissions.ADMIN, addMapper(2))\n)\n```\n\nBut let's write our own mapper that accomplishes this:\n\n```javascript\nimport {\n  fieldDelegate, Permissions\n} from 'tilla'\n\nconst fd = fieldDelegate() // use the built permission levels\n\n// Our custom Rule\nconst eitherOr = (eitherPermission, orPermission, fieldMapper) =\u003e fieldMapperDelegate =\u003e {\n  const { delegateMap } = fieldMapperDelegate\n\n  delegateMap[eitherPermission] = fieldMapper(fieldDelegateMapper)\n  delegateMap[orPermission] = fieldMapper(fieldDelegateMapper)\n\n  return fieldDelegateMapper\n}\n\nconst addMapper = n =\u003e () =\u003e async (instance, key) =\u003e instance[key] + n\n\n// using our custom Rule AND custom FieldMapper\nfd('age', eitherOr(Permissions.PUBLIC, Permissions.ADMIN, addMapper(1)))\n```\n\nNow whenever we call `transform`, the fieldDelegate will only map the field, adding 1 to it's value, only if the provided permission is either `PUBLIC` _or_ `ADMIN`\n\n### Transformer Registry\n\n`Tilla` exposes an instantiated instance of the `TransformRegistry`. The `registry` is a great way to manage all of `Transformers` and then pass them around your app as needed. For example, you can easily incorporate in `Express` middleware.\n\n``` javascript\n\n// add some transformers to the registry somewhere\nregistry.register('person', personTransformer)\nregistry.register('address', addressTransformer)\n\nimport { registry } from 'tilla'\n\nconst attachTransformer = (transformerKey) =\u003e {\n  return (req, res, next) =\u003e {\n    let transformer = registry.transformer(transformerKey)\n    req.transformer = transformer // then use the transformer later on\n    next()\n  }\n}\n```\n\nThe registry also provides a `subscribe(observer)` api that allows you to listen for changes to the registry. The registry emits events on `register` and `clear`\n\n```javascript\nimport { registry } from 'tilla'\n\nconst unsubscribe = registry.subscribe(({ message, registry}) =\u003e {\n  console.log(message)\n})\n\nregistry.register('person', personTransformer)\n\n// produces 'Registered transform at key: person' in the logs\n\n// later on\nunsubscribe() // unsubscribes the observer from the registry\n```\n\nthe observer can be a function with single arity or an object that conforms to the or an object that satisfies this interface:\n\n```typescript\ninterface Observer\u003cT\u003e {\n  closed?: boolean;\n  next: (value: T) =\u003e void;\n  error: (err: any) =\u003e void;\n  complete: () =\u003e void;\n}\n```\n\n## TODO\n\n- Better document API\n\n## Contribute\n\nSubmit an issue or a PR\n\n## License\n\nMIT\n\n## Name\n\nI couldn't find any open npm module names that I liked that weren't already taken. As a result, I used a shotened version of my name :p. If you have a better idea, please make a suggestion!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftillathehun0%2Ftilla","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftillathehun0%2Ftilla","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftillathehun0%2Ftilla/lists"}