{"id":25707712,"url":"https://github.com/jhsware/component-registry","last_synced_at":"2025-04-30T14:43:55.135Z","repository":{"id":29111525,"uuid":"32641110","full_name":"jhsware/component-registry","owner":"jhsware","description":"Register and look up your Javascript components by interface to create beautifully decoupled code. Simple and elegant overriding mechanism gives you awesome reusability of any components you write.","archived":false,"fork":false,"pushed_at":"2024-03-23T15:06:36.000Z","size":921,"stargazers_count":7,"open_issues_count":1,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-04-24T13:27:09.875Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","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/jhsware.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"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}},"created_at":"2015-03-21T16:59:16.000Z","updated_at":"2022-12-04T18:14:14.000Z","dependencies_parsed_at":"2024-01-09T12:29:37.029Z","dependency_job_id":"ed8002fe-abd9-44e8-94ae-120ed243f6ac","html_url":"https://github.com/jhsware/component-registry","commit_stats":{"total_commits":215,"total_committers":2,"mean_commits":107.5,"dds":"0.0046511627906976605","last_synced_commit":"39778c1652c8ee97b2968b4caf6efbb7f3155d0b"},"previous_names":[],"tags_count":33,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhsware%2Fcomponent-registry","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhsware%2Fcomponent-registry/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhsware%2Fcomponent-registry/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jhsware%2Fcomponent-registry/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jhsware","download_url":"https://codeload.github.com/jhsware/component-registry/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251446166,"owners_count":21590668,"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":[],"created_at":"2025-02-25T08:38:37.831Z","updated_at":"2025-04-30T14:43:55.112Z","avatar_url":"https://github.com/jhsware.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# component-registry #\n[![Build Status](https://travis-ci.org/jhsware/component-registry.svg?branch=master)](https://travis-ci.org/jhsware/component-registry)\n[![gzip size](http://img.badgesize.io/https://unpkg.com/component-registry/dist/index.cjs.js?compression=gzip)](https://unpkg.com/component-registry/dist/index.cjs.js)\n\nThe purpose of component-registry is to help you create reusable components that are easy to extend and customise. It is heavily inspired by battle proven concepts that have been available for many years in the Python community through the Zope Toolkit(ZTK).\n\nThink of it as decoupled imports and elegant composition.\n\n### v3 Rewrite for Typescript\nThis is a rewrite of component-registry for Typescript. All the typechecking can now be done by Typescript which shrinks this package from ~20KB to ~6KB. As you would expect, typing incurs an overhead when defining you object, adapter and utility classes. The end result is excellent coding hints and type safety.\n\nFeatures that have been removed in the Typescript version:\n- multiple inheritance -- although very useful it brings magic which makes application code harder to understand\n- type checking -- this now done by Typescript\n\n### v2 for Javascript\nThe Javascript version of component-registry is [available on the v2-branch](https://github.com/jhsware/component-registry/tree/v2-javascript).\n\n### Sample Code ###\n```typescript\nimport {\n  Adapter,\n  AdapterInterface,\n  createIdFactory,\n  ObjectInterface,\n  ObjectPrototype,\n  TAdapter,\n } from 'component-registry'\n// We need an id factory for the interfaces\nconst id = createIdFactory('test');\n\n// Entity object interface and class\nclass IUser extends ObjectInterface {\n  get interfaceId() { return id('IUser') };\n  name: string;\n}\n\ntype TUser = Omit\u003cIUser, 'interfaceId' | 'providedBy'\u003e;\nclass User extends ObjectPrototype\u003cTUser\u003e implements TUser {\n  readonly __implements__ = [IUser];\n  name: string;\n  constructor({ name }: TUser) {\n      super({ name });\n  }\n}\n\n// Adapter interface and class\nclass IDisplayWidget extends AdapterInterface {\n  get interfaceId() { return id('IDisplayWidget') };\n  render(): void { return };\n}\n\nclass DisplayWidget extends Adapter {\n  get __implements__() { return IDisplayWidget };\n  constructor({ adapts, render, registry }: Omit\u003cIDisplayWidget, 'interfaceId'\u003e \u0026 TAdapter) {\n    super({ adapts, render, registry });\n  }\n}\n\n// Adapter instance that can operate on objects implementing IUser\nnew DisplayWidget({\n  adapts: IUser,\n  render () {\n  console.log(`My name is ${this.context.name}`)\n  }\n})\n\n\n// Create our entity object instance\nconst user = new User({ name: 'Julia' })\n\n// Look up the DisplayWidget adapter instance and invoke the render method\nnew IDisplayWidget(user).render()\n// [console]$ I am a User\n```\n\n### The Global Registry ##\nThe brain of the component-registry is the `globalRegistry` which keeps track of all the components you have available in your application. These are normally registered at startup, but can be added at any time during your application lifecycle.\n\n### Adapters, Utilities and ObjectPrototypes ##\nThere are three main object types that are available in component-registry. Adapters, Utilities and ObjectPrototypes.\n\n**ObjectPrototypes** are basially entity objects. They contain data and often nothing more. ObjectPrototypes will normally look a lot like the JSON you would send between subsystems. \n\nAn **Adapter** most of the time works in concert with an ObjectPrototype. You would ask the registry to find an adapter that has certain capabilities, perhaps methods that can convert the ObjectPrototype to JSON or HTML markup. The most obvious use of Adapters is to implement UI-widgets, but you can also use it for business logic that operates on an entity object. Basically you can move any methods you would otherwise place on an entity object to the adapter. This keeps the ObjectPrototypes lean and data centric.\n\n**Utilities** are stateless components. They provide you with utility methods or services. You could get DB-credentials through a Utility, or you could provide methods for i18n-translations. Similar to Adapters you would ask the registry to find a Utility with the capabilities you require.\n\n### Interfaces ##\nThe capabilities of your Adapters and Utilities are specified by **Interfaces**. Interfaces are what some would call developer contracts, literally a promise to implement a given set of methods and properties. When doing a lookup of adapters or utilities, the `globalRegistry` uses interfaces to find what you are looking for.\n\nBy using Interfaces you decouple your code. When asking the globalRegistry for an Adapter or Utility that implements a specific Interface you have no idea where or how it has been implemented. All you know is that it should be registered with the globalRegistry. In your application the benefit might not be obvious. However, if you create an NPM package that requires your application to perform certain tasks, such as providing application specific i18n-translations, all that package needs to know is that it should ask for a Utility that implements a certain interface, say ITranslationUtil. Your application can provide that utility through the globalRegistry so the package can access it at runtime.\n\nThis is a two way street. If you want to change the package that consume ITranslationUtil, you won't have to worry about ripping out initialisation code etc. All you have to do is make sure the new package you have created asks for the same Interface and it is automatically hooked up with your existing Utility.\n\nThis becomes even more powerful when you have several NPM-packages that need to consume the same ITranslationUtil. You could create a meta-package that literally only contains Interfaces and then have both consuming packages and your application use those common Interfaces as glue.\n\nWhy is this good? Well it forces you to think about the architecture of your application. And it will automatically makes you write reusable code with next to no added effort.\n\nAnother brilliant side-effect is that you can move your implementation code around, and also split it into it's own NPM-packages at any time. As long as you don't move the Interface, all the components can still do their lookups without changing a single line of code. This makes refactoring simple, fun and helps in agile development.  \n\n### More on ObjectPrototypes and Adapters ##\nObjectPrototypes contain a bit more than just data. They provide a property `__implements__` that contains an array of Interfaces that describe the capabilities of that object. The order is important, the first Interface in this array tells us what this ObjectPrototype is. The rest tell us what other things this ObjectPrototype contains or can be used for. This could be a list of implemented Interfaces:\n\n```typescript\nimport { ObjectPrototype } from 'component-registry';\ntype TEmployee = Omit\u003cIEmployee \u0026 IUser \u0026 IHasAvatar, 'interfaceId' | 'providedBy'\u003e;\nclass Employee extends ObjectPrototype\u003cTEmployee\u003e implements TEmployee {\n  readonly __implements__ = [IEmployee, IUser, IHasAvatar];\n  ...\n}\n```\nThe ObjectPrototype should be called Employee, which corresponds to the most significant Interface it implements. When you instantiate an object of type Employee you will find the Interfaces as the property `__implements__`.\n\nThe `globalRegistry` uses these interfaces in order to find Adapters for you that can be used with your Employee objects. Say that you want to render a directory listing of your employees. You decide you want each row to be rendered by using an Adapter that you find with the interface `IDirectoryListEntryWidget`, you can call the Interface what ever you want. Now you need to implement that Adapter so it can render your Employee object. It would look something like this:\n\n```typescript\nimport { Adapter, TAdapter } from 'component-registry'\n\nclass DirectoryListEntryWidget extends Adapter {\n  get __implements__() { return IDirectoryListEntryWidget };\n  constructor({ adapts, render, registry }: Omit\u003cIDirectoryListEntryWidget, 'interfaceId'\u003e \u0026 TAdapter) {\n    super({ adapts, render, registry });\n  }\n}\nnew DirectoryListEntryWidget({\n  adapts: IEmployee,\n  render () { ... }\n})\n```\nThe parameter **__implements__** tells the globalRegistry what the Adapter can do. The parameter **adapts** tells the globalRegistry what kind of object it can do this with.\n\nSo when you want to render the list you would write something like this:\n\n```typescript\nimport { IDirectoryListEntryWidget } from './myAppInterfaces'\nfunction renderList (entries) {\n  const outp = entries.map((entry) =\u003e new IDirectoryListEntryWidget(entry).render())\n  return outp.join('\\n')\n}\n```\nYour list rendering code has no idea how the individual widgets are rendered, it is all done by the widget. All it knows is that it should get the `IDirectoryListEntryWidget` adapter that adapts the `entry` object.\n\nIn a simple case with only a single object type this still makes the code compact and readable. But the power becomes more apparent if we have more object types in our entries list. There could be ten different object types, each with their own registered Adapter that implements IDirectoryListEntryWidget. These list items can all be rendered by the same code which is blissfully unaware of how different the implementations are.\n\n## Public API ##\n\n```javascript\nimport { globalRegistry } from 'component-registry'\n```\nUse the global registry to register you adapters and utilities.\n\nWARNING! The registry is also available as a global variable. You should not make\nyour code dependent on using the global variable, it is mainly intended for\ndebugging purposes. Always require the registry for proper use. The global variable\nmight be removed when run in production mode.\n\nYou will also use these extensively:\n\n```javascript\n// To ceate interfaces\nimport { createIdFactory } from 'component-registry'\nconst id = createIdFactory('test');\n\n// To create adapters\nimport { Adapter } from 'component-registry'\n\n// To create utilities\nimport { Utility } from 'component-registry'\n\n// To create object prototypes\nimport { createObjectPrototype } from 'component-registry'\n```\n\nFor advanced use, you can create your own adapter and/or utility registry. The use\ncase could be to create a sub system that can't be accessed by the rest of your app.\n```javascript\nimport { Registry, AdapterRegistry, UtilityRegistry } from 'component-registry'\n```\n\n## Object Prototypes ##\n\nObject Prototypes can implement interfaces. This declares what capabilities they support. Interfaces are used for looking up Adapters among other things. You can use the `.providedBy(obj)` method on interfaces to check if it is implemented by an object.\n\n```typescript\nINews.providedBy(obj) === true;\nINotImplemented.providedBy(obj) === false;\n```\n\nInterfaces also provide a convenient way of looking up adapters and utilities.\n\n## Adapters ##\n\nAdapters provide functionality for objects. It literally adapts an object for use in a specific context, such as rendering UI-widgets. When you ask for an adapter from the adapter registry it finds an adapter that implements the interface you are asking for the specific object you are working with. A look can be done in two ways:\n\n```javascript\nimport { IPermissions } from './interfaces'\nimport { globalRegistry } from 'component-registry'\n\n// The pretext here is that we have registered adapters somewhere\n// wich adapt userObj that was also created somehow\n\nconst userObj = new User(...);\n\nconst permissionsByShorthand =  new IPermissions(userObj).getPermissions();\n```\n\nSo this is what happens during a lookup:\n\n    1 Find a set of registered adapters that claim to implement ICoolAdapter\n     \n    2a Check if any of these adapt the given object type\n    \n    2b If not, check if any of these adapt any of the interfaces that\n      the object states that it implements\n      \n    3 Return an instance of the adapter with this.context set to obj\n    \nNow that you have the adapter you can start using it for a variety of scenarios:\n\n    - Render the object to HTML\n    \n    - Manipulate the properties of the object\n    \n    - Persist the object to a backend\n    \nAdapters are basically a nice way of creating reusable business logic and render components that are loosly coupled (by interface) to the objects they manipulate. \n\n## Utilities ##\n\nA utilitiy is a stateless object that provides a set of functions in your code. You create a utility and register it in the utility registry. To identify the capabilities of the utility you define an interface. This can optionally declare what methods attributes you can call on the utility or just be a marker interface. Declaring the interface is a good way to architect your api before implementation.\n\n\n```javascript\nimport { IDatabaseService } from './interfaces'\nimport { globalRegistry } from 'component-registry'\n\n// The pretext here is that we have registered utilities somewhere\n\nconst connection =  new IDatabaseService().connect()\nconst otherConnection =  new IDatabaseService('mongodb').connect()\n```\n\nThe point of using utilities is that you can define the interface in a general component but leave the implementation up to the application that uses the component. An example would be a database connection. The component needs a database connection but doesn't know what authorisation credentials to use, so it asks for these by calling the utility registry and requesting say a IDatabaseCredentials utility. It is then up to the application developer to create this utility and register it as an implementation of IDatabaseCredentials.\n\nThis is a nice way to decouple and organise your code.\n\n### About Named Utilities ###\n\nAnother example of how to use a utility could be if you want to provide internationalisation features. In which case you could give each utility a name that corresponds to the region it implements. So basically you would ask for `new ILocalization('us')` for the United States and `new ILocalization('se')` for Sweden. You can also query for all named utilities that implement ILocalization and get them as a list `new ILocalization('*')`.\n\n# API Docs #\n\n### Object Prototypes ###\n\n```typescript\nimport { ObjectPrototype } from 'component-registry'\n\ntype TUser = Omit\u003cIUser, 'interfaceId' | 'providedBy'\u003e;\nclass User extends ObjectPrototype\u003cTUser\u003e implements TUser {\n    readonly __implements__ = [IUser];\n    name: string;\n    constructor({ name }: TUser) {\n        super({ name });\n    }\n    sayHi() {\n        return \"Hi!\"\n    }\n}\n```\nThe object implements the provided list of interfaces and the method sayHi will be added to the object.prototype and available to instantiated objects.\n\nThe first interface in the list is significant. It should be a unique interface describing the object. The name of this interface is used for inheritance.\n\n```typescript\nconst obj = new User();\n```\nCreates an instance of the object prototype you created above.\n\n### Interfaces ###\nFirst you need an id factory. Ids are in fact GUID style strings created with the UUID-package. The id is memoised by the returned id function to reduce the overhead of id generation.\n\n```typescript\nimport { createIdFactory } from 'component-registry'\nconst id = createIdFactory('my-namespace'); // Use the name of your module as namespace\n```\n\nCreate the Interface class with a getter method to set the interface. The id of the interface is a UUID built from namespace and name. The id will be the same regardless of when you create it.\n\n```typescript\n// We need the Interface created above\nclass IUser extends ObjectInterface {\n  get interfaceId() { return id('IUser') };\n}\n```\nCreates a simple object interface. You have four different kinds of interfaces:\n\n- MarkerInterface -- when all you want is to use `.providedBy(obj)` method in your application code\n- ObjectInterface -- define your entity objects. Allows adding an `.init(...)` property that you can call from your object constructor. This allows sharing functionality through composition.\n- AdapterInterface -- define your adapters. Allows looking up the adapter using shorthand `new IMyAdapter(obj)`\n- UtilityInterface -- define you utilities. Allows looking up the utility using shorthand `new IMyUtility()`\n\nUse the convention of prefixing interfaces with \"I\" to improve readability.\n\nYou can add dummy functions to your interface prototype to show what methods are required for an adapter, utility or object prototype that implements that interface. Note: object prototypes will in most cases be simple data objects with no or few methods.\n\n```typescript\nclass IUser extends ObjectInterface {\n  get interfaceId() { return id('IUser') };\n  sayHi(): string { return };\n}\n```\n\nDon't forget to omit function members from your constructor props type.\n\nTODO: Add example\n\n### Adapters ###\n\n**new Adapter(params)**\n\nCreate and adapter that adapts an interface or an object prototype. It is automatically registered with the `globalRegistry` available in component-registry.\n\n```typescript\nimport { Adapter, TAdapter } from 'component-registry'\n\nconst MyAdapter = new Adapter({\n    __implements__ IInterface,\n    adapts: IInterface || ObjectPrototype\n})\n\nclass MyAdapter extends Adapter {\n  get __implements__() { return IMyAdapter };\n  constructor({ adapts, registry }: Omit\u003cIMyAdapter, 'interfaceId'\u003e \u0026 TAdapter) {\n    super({ adapts, registry });\n  }\n}\n\n// Adapter instance that can operate on objects implementing IUser\nnew MyAdapter({\n  adapts: IUser,\n})\n```\n\nIf you want to register the created adapter with a scoped registry instead of `globalRegistry` you pass it as a parameter. This is useful in tests to make sure you have a known set adapters registered.\n\n```typescript\nnew MyAdapter({\n  adapts: IUser,\n  registry: myOwnRegistry\n})\n```\n\n### Utilities ###\n\nCreate an unamed utility that implements a given interface. It is automatically registered with the `globalRegistry` available in component-registry.\n\n```typescript\nconst utility = new Utility({\n    __implements__ IInterface\n});\n```\n\nCreate a named utility that implements a given interface and has a variation name. It is automatically registered with the `globalRegistry` available in component-registry.\n\n```typescript\nimport { Utility, TUtility } from 'component-registry';\n// The pretext is that we already have created the UtilityInterface ITranslateUtil\nclass TranslateUtil extends Utility implements Omit\u003cITranslateUtil, 'interfaceId'\u003e {\n  get __implements__() { return ITranslateUtil };\n  constructor({ name, translate, registry }: Omit\u003cITranslateUtil, 'interfaceId'\u003e \u0026 TUtility) {\n    super({ name, translate, registry });\n  }\n  translate(inp: string): string { return };\n}\n\nconst util = new TranslateUtil({\n  name: \"sv\",\n  translate(inp: string) {\n    return inp;\n  }\n})\n```\n\nJust like an adapter you can pass a scoped registry to register the utility there.\n\n```typescript\nconst util = new TranslateUtil({\n  name: \"sv\",\n  translate(inp: string) {\n    return inp;\n  },\n  registry: myRegistry\n})\n```\n\nFind all registered utilities (named and unnamed) that implement the given interface.\n\n```javascript\nconst utils = new IMyInterface('*');\nconst utilsAltSyntax = registry.getUtilities(IMyInterface);\nconst utilsFromScopedRegistry = new IMyInterface('*', myRegistry);\n```\n\n## Creating a scoped registry ##\n\nYou can create a scoped registry if you want to have an alternative set of utilities or adapters available for a task. This feature is useful for tests.\n\n```javascript\nimport { AdapterRegistry, UtilityRegistry, LocalRegistry } from 'comonent-registry'\nconst myAdapterRegistry = new AdapterRegistry();\nconst myUtilityRegistry = new UtilityRegistry();\nconst myRegistry = new LocalRegistry(); // Combines an adapter and utility registry\n```\n\nIf you have created a scoped registry in application code you might want to register some of your existing adapters. In this case you would use the registration API:\n\n```typescript\n  myRegistry.registerAdapter(MyAdapter);\n  myRegistry.registerUtility(utility);\n```\n\nGood luck!\n\n## Migrating to 2.0 ##\nThe Typescript version of component-registry is a complete rewrite to leverage the typing features of Typescript. The main hurdle is to restructure your code to remove inheritance, and especially multiple inheritance. In v3 you need to be explicit, but this also makes the code more readable since inheritance can feel like magic and magic can be hard to understand.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhsware%2Fcomponent-registry","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjhsware%2Fcomponent-registry","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjhsware%2Fcomponent-registry/lists"}