{"id":21581463,"url":"https://github.com/zheksoon/context-tree","last_synced_at":"2025-03-18T08:14:27.019Z","repository":{"id":209156687,"uuid":"723359986","full_name":"zheksoon/context-tree","owner":"zheksoon","description":"Simple and flexible hierarchical dependency injection (DI) library for TypeScript and vanilla JS","archived":false,"fork":false,"pushed_at":"2024-01-07T19:45:19.000Z","size":322,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-03-17T19:49:27.372Z","etag":null,"topics":["awilix","context","dependency-injection","di","di-container","di-framework","fractal","fractal-architecture","hierarchical","inversifyjs","ioc","ioc-container","react-context","tsyringe","typescript","vanilla-js"],"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/zheksoon.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}},"created_at":"2023-11-25T12:23:47.000Z","updated_at":"2023-11-28T14:45:10.000Z","dependencies_parsed_at":"2024-01-07T20:56:25.133Z","dependency_job_id":"20462db0-a033-4ea9-92be-379690990139","html_url":"https://github.com/zheksoon/context-tree","commit_stats":null,"previous_names":["zheksoon/context-tree"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fcontext-tree","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fcontext-tree/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fcontext-tree/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/zheksoon%2Fcontext-tree/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/zheksoon","download_url":"https://codeload.github.com/zheksoon/context-tree/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244181391,"owners_count":20411605,"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":["awilix","context","dependency-injection","di","di-container","di-framework","fractal","fractal-architecture","hierarchical","inversifyjs","ioc","ioc-container","react-context","tsyringe","typescript","vanilla-js"],"created_at":"2024-11-24T14:12:37.926Z","updated_at":"2025-03-18T08:14:26.996Z","avatar_url":"https://github.com/zheksoon.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/zheksoon/context-tree/master/assets/logo.png\" alt=\"context-tree logo\" width=\"100\" /\u003e\n  \u003cbr /\u003e\n  context-tree\n  \u003cbr /\u003e\n  \u003cimg src=\"https://img.shields.io/npm/v/context-tree?color=16b887\" alt=\"npm version\" /\u003e\n  \u003cimg src=\"https://img.shields.io/bundlephobia/minzip/context-tree?label=gzip\u0026color=16b887\" alt=\"bundle size\" /\u003e\n  \u003cimg src=\"https://img.shields.io/github/license/zheksoon/context-tree?color=16b887\" alt=\"license\" /\u003e\n\u003c/h1\u003e\n\n**context-tree** is a simple implementation of a hierarchical dependency injection (DI) pattern for building scalable web applications.\nIt implements the same concept as React's `Context`, but without relying on React.\nThe pattern allows you to create arbitrary nested applications and sub-applications, simplifying the architecture and maintaining code clarity.\n\nUnlike other DI frameworks, **context-tree** does not require you to define a dependency graph in advance, offering a more flexible approach.\nThe difference from the other DI frameworks is an inherent hierarchy of injectable entities, called **contexts**.\nThey align with the hierarchy of the application itself and form a tree like React contexts do.\nLike React, you can redefine the context value at any point of the tree, and also add and remove context resolvers dynamically.\n\n**context-tree** is framework-agnostic and can be used with any framework or without a framework at all.\nIt also does not require any decorators or other magic, so it's easy to understand and debug, and can be used in pure JS projects.\n**context-tree** has no dependencies and is very lightweight, and because of the pattern simplicity, it's very fast.\n\n## Defining models\n\n**Context-tree** requires your data models to implement a simple `IContext` interface with only one field required: `context` of type `IContext | IContext[] | null | undefined`.\nThis field should point to a parent or multiple parent models, or be `null` in case there is no parent model.\n\nHere's a simple example:\n\n```ts\nimport { IContext } from \"context-tree\";\n\nclass RootModel implements IContext {\n  // root model does not have context, so it's null\n  public context = null;\n\n  // by convention, all models with context take a parent as the first argument\n  childModel = new ChildModel(this);\n}\n\nclass ChildModel implements IContext {\n  // use TS shortcut to define a field from the constructor arg\n  // now it points to the parent model\n  constructor(public context: IContext) {}\n}\n```\n\nAt this point, we have a model tree. Let's define a context and add the resolver to it.\n\n```ts\nimport { Context, IContext } from \"context-tree\";\n\n// define some interface for config object\ninterface IConfigModel {\n  baseUrl: string;\n  apiKey: string;\n}\n\n// an implementation of the interface\nclass ConfigModel implements IConfigModel {\n  baseUrl = \"https://.../\";\n  apiKey = \"abcdef123\";\n}\n\n// define a context that carries the type of the config\nconst ConfigContext = new Context\u003cIConfig\u003e(\"ConfigContext\");\n\nclass RootModel implements IContext {\n  public context = null;\n\n  // define resolvers - functions that are called when the context resolves\n  public contextResolvers = Context.resolvers([\n    ConfigContext.resolvesTo(() =\u003e this.config),\n  ]);\n\n  private config = new ConfigModel();\n\n  // pass this as the context of the child model\n  private childModel = new ChildModel(this);\n}\n\nclass ChildModel implements IContext {\n  constructor(public context: IContext) {}\n\n  async getData() {\n    // to get the config instance, call `resolve` on the context\n    // and pass the current model as the first argument\n    const config = ConfigContext.resolve(this);\n\n    // use the config instance\n    const data = await fetch(`${config.baseUrl}/endpoint`);\n  }\n}\n```\n\nIn case you want a model itself to be a context, you can use `contextType` field:\n\n```ts\nconst RootContext = new Context\u003cRootModel\u003e('RootContext');\n\nclass RootModel implements IContext {\n  // no parent context\n  context = null;\n\n  // now RootContext resolves to the model instance\n  contextType = RootContext;\n\n  // define some extra resolvers\n  contextResolvers = Context.resolvers([\n    ...\n  ]);\n}\n```\n\n## Partial contexts\n\nNot always full contexts are needed. For example, the config model and its interface can contain dozens of fields, and our model may need only a few of them. When we are writing unit tests for a model, we have to supply a full config context that implements every field from its interface, and that can be cumbersome.\n\nPartial contexts solve this problem by allowing you to define a partial interface from your original context. If the partial context resolves, it will resolve to the closest parent model that implements the partial interface.\n\nHere's an example:\n\n```ts\ninterface IConfigModel {\n  baseUrl: string;\n  option1: string;\n  option2: string;\n  option3: string;\n}\n\nconst ConfigContext = new Context\u003cIConfigModel\u003e(\"ConfigContext\");\n\n// pick only option1 and option2 from IConfigModel\ntype IPartialConfigModel = Pick\u003cIConfigModel, \"option1\" | \"option2\"\u003e;\n\n// define a partial context derived from the ConfigContext\nconst PartialConfigContext = ConfigContext.partial\u003cIPartialConfigModel\u003e(\n  \"PartialConfigContext\"\n);\n\n// Finds a closest instance of IPartialConfigModel or IConfigModel\nPartialConfigContext.resolve(this);\n```\n\n### Dynamic context manipulation\n\nContext resolvers can be dynamically added or removed from a model. This might be useful in complex scenarios when contexts are not known in advance.\n\n```ts\nconst Context1 = new Context\u003cnumber\u003e(\"Context1\");\nconst Context2 = new Context\u003cstring\u003e(\"Context2\");\n\nclass RootModel implements IContext {\n  // no parent context\n  context = null;\n\n  // define static resolvers\n  contextResolvers = Context.resolvers([Context1.resolvesTo(() =\u003e 1 + 2)]);\n\n  // add dynamic resolvers\n  doSomething() {\n    this.contextResolvers.addResolver(Context2.resolvesTo(() =\u003e \"hello\"));\n  }\n\n  // remove dynamic resolvers\n  doSomethingElse() {\n    this.contextResolvers.removeResolver(Context2);\n  }\n}\n```\n\n## Required contexts\n\nSometimes you want to make sure that a model has all required contexts resolved. For example, you may want to make sure that a model has a config context resolved before it can be used. To do that, you can define a static field `requiredContexts` on a class or class instance:\n\n```ts\nconst Context1 = new Context\u003cnumber\u003e(\"Context1\");\nconst Context2 = new Context\u003cstring\u003e(\"Context2\");\n\nclass RootModel implements IContext {\n  // no parent context\n  context = null;\n\n  // define resolvers\n  contextResolvers = Context.resolvers([Context1.resolvesTo(() =\u003e 1 + 2)]);\n\n  // define required contexts\n  // RootModel has no Context2 resolver, so it will throw an error\n  static requiredContexts = [Context2];\n}\n\n// throws an error\nContext.checkRequired(new RootModel());\n```\n\n# API\n\n## Models\n\nEach model should implement the `IContext` interface:\n\n```ts\ninterface IContext {\n  context: IContext | IContext[] | null | undefined;\n  contextType?: Context\u003cany\u003e;\n  contextResolvers?: ContextResolvers;\n}\n```\n\nThe usual way to pass the required `context` field is the first argument of the constructor:\n\n```ts\nclass Model implements IContext {\n  constructor(public context: IContext) {}\n}\n```\n\n## Context\n\n### `new Context\u003cT\u003e(name: string): Context\u003cT\u003e`\n\nCreates a new context with the given name. The name is used for debugging purposes.\n\n### `contextInstance.partial\u003cT\u003e(name: string): Context\u003cT\u003e`\n\nCreates a partial context derived from the current context. The partial context can be resolved to the closest parent model that implements the partial interface.\n\n### `Context.resolvesTo\u003cT\u003e(resolver: () =\u003e T): ContextResolver\u003cT\u003e`\n\nCreate a resolver for the context. The resolver is a function that returns a value of type `T`. The resolver is called when the context is resolved.\n\n### `Context.resolvers(resolvers: Array\u003cContextResolver\u003cany\u003e\u003e): ContextResolvers`\n\nDefine a list of resolvers for a model.\n\n### `contextInstance.resolve\u003cT\u003e(model: IContext): T`\n\nFinds the closest context resolver of the type and calls it to resolve the value. If no resolver is found, throws an error.\n\n### `contextInstance.resolveMaybe\u003cT\u003e(model: IContext): T | undefined`\n\nFinds the closest context resolver of the type and calls it to resolve the value. If no resolver is found, returns `undefined`.\n\n### `contextInstance.findResolver(model: IContext): ContextResolver\u003cany\u003e | undefined`\n\nFinds the closest context resolver of the type. If no resolver is found, returns `undefined`.\n\n### `Context.checkRequired(model: IContext): void`\n\nChecks if all required resolvers are defined for the model. If not, throws an error. Required contexts are defined by `requiredContexts` field on a class or class instance.\n\n# Author\n\nEugene Daragan\n\n# License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzheksoon%2Fcontext-tree","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fzheksoon%2Fcontext-tree","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fzheksoon%2Fcontext-tree/lists"}